@dhis2/analytics 24.10.0 → 25.0.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 (86) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/build/cjs/__demo__/CalculationModal.stories.js +448 -0
  3. package/build/cjs/api/analytics/AnalyticsRequest.js +12 -1
  4. package/build/cjs/api/dimensions.js +1 -1
  5. package/build/cjs/api/expression.js +67 -0
  6. package/build/cjs/assets/DimensionItemIcons/CalculationIcon.js +25 -0
  7. package/build/cjs/assets/FormulaIcon.js +40 -0
  8. package/build/cjs/components/DataDimension/Calculation/CalculationModal.js +447 -0
  9. package/build/cjs/components/DataDimension/Calculation/DataElementOption.js +77 -0
  10. package/build/cjs/components/DataDimension/Calculation/DataElementSelector.js +306 -0
  11. package/build/cjs/components/DataDimension/Calculation/DndContext.js +213 -0
  12. package/build/cjs/components/DataDimension/Calculation/DragHandleIcon.js +23 -0
  13. package/build/cjs/components/DataDimension/Calculation/DraggingItem.js +58 -0
  14. package/build/cjs/components/DataDimension/Calculation/DropZone.js +58 -0
  15. package/build/cjs/components/DataDimension/Calculation/FormulaField.js +121 -0
  16. package/build/cjs/components/DataDimension/Calculation/FormulaItem.js +232 -0
  17. package/build/cjs/components/DataDimension/Calculation/MathOperatorSelector.js +57 -0
  18. package/build/cjs/components/DataDimension/Calculation/Operator.js +81 -0
  19. package/build/cjs/components/DataDimension/Calculation/styles/CalculationModal.style.js +13 -0
  20. package/build/cjs/components/DataDimension/Calculation/styles/DataElementOption.style.js +13 -0
  21. package/build/cjs/components/DataDimension/Calculation/styles/DataElementSelector.style.js +13 -0
  22. package/build/cjs/components/DataDimension/Calculation/styles/DraggingItem.style.js +13 -0
  23. package/build/cjs/components/DataDimension/Calculation/styles/DropZone.style.js +13 -0
  24. package/build/cjs/components/DataDimension/Calculation/styles/FormulaField.style.js +13 -0
  25. package/build/cjs/components/DataDimension/Calculation/styles/FormulaItem.style.js +13 -0
  26. package/build/cjs/components/DataDimension/Calculation/styles/MathOperatorSelector.style.js +13 -0
  27. package/build/cjs/components/DataDimension/Calculation/styles/Operator.style.js +13 -0
  28. package/build/cjs/components/DataDimension/DataDimension.js +22 -6
  29. package/build/cjs/components/DataDimension/DataTypeSelector.js +5 -3
  30. package/build/cjs/components/DataDimension/ItemSelector.js +111 -73
  31. package/build/cjs/components/TransferOption.js +13 -4
  32. package/build/cjs/components/styles/DimensionSelector.style.js +2 -2
  33. package/build/cjs/components/styles/TransferOption.style.js +2 -2
  34. package/build/cjs/index.js +6 -0
  35. package/build/cjs/locales/en/translations.json +32 -7
  36. package/build/cjs/modules/__tests__/expressions.spec.js +139 -0
  37. package/build/cjs/modules/__tests__/hash.spec.js +92 -0
  38. package/build/cjs/modules/__tests__/parseExpression.spec.js +46 -0
  39. package/build/cjs/modules/dataTypes.js +8 -1
  40. package/build/cjs/modules/dimensionListItem.js +82 -0
  41. package/build/cjs/modules/expressions.js +164 -0
  42. package/build/cjs/modules/hash.js +28 -0
  43. package/build/cjs/visualizations/config/generators/dhis/singleValue.js +2 -2
  44. package/build/es/__demo__/CalculationModal.stories.js +440 -0
  45. package/build/es/api/analytics/AnalyticsRequest.js +11 -1
  46. package/build/es/api/dimensions.js +1 -1
  47. package/build/es/api/expression.js +57 -0
  48. package/build/es/assets/DimensionItemIcons/CalculationIcon.js +13 -0
  49. package/build/es/assets/FormulaIcon.js +30 -0
  50. package/build/es/components/DataDimension/Calculation/CalculationModal.js +418 -0
  51. package/build/es/components/DataDimension/Calculation/DataElementOption.js +60 -0
  52. package/build/es/components/DataDimension/Calculation/DataElementSelector.js +280 -0
  53. package/build/es/components/DataDimension/Calculation/DndContext.js +194 -0
  54. package/build/es/components/DataDimension/Calculation/DragHandleIcon.js +11 -0
  55. package/build/es/components/DataDimension/Calculation/DraggingItem.js +40 -0
  56. package/build/es/components/DataDimension/Calculation/DropZone.js +43 -0
  57. package/build/es/components/DataDimension/Calculation/FormulaField.js +98 -0
  58. package/build/es/components/DataDimension/Calculation/FormulaItem.js +207 -0
  59. package/build/es/components/DataDimension/Calculation/MathOperatorSelector.js +41 -0
  60. package/build/es/components/DataDimension/Calculation/Operator.js +64 -0
  61. package/build/es/components/DataDimension/Calculation/styles/CalculationModal.style.js +4 -0
  62. package/build/es/components/DataDimension/Calculation/styles/DataElementOption.style.js +4 -0
  63. package/build/es/components/DataDimension/Calculation/styles/DataElementSelector.style.js +4 -0
  64. package/build/es/components/DataDimension/Calculation/styles/DraggingItem.style.js +4 -0
  65. package/build/es/components/DataDimension/Calculation/styles/DropZone.style.js +4 -0
  66. package/build/es/components/DataDimension/Calculation/styles/FormulaField.style.js +4 -0
  67. package/build/es/components/DataDimension/Calculation/styles/FormulaItem.style.js +4 -0
  68. package/build/es/components/DataDimension/Calculation/styles/MathOperatorSelector.style.js +4 -0
  69. package/build/es/components/DataDimension/Calculation/styles/Operator.style.js +4 -0
  70. package/build/es/components/DataDimension/DataDimension.js +21 -6
  71. package/build/es/components/DataDimension/DataTypeSelector.js +6 -4
  72. package/build/es/components/DataDimension/ItemSelector.js +111 -73
  73. package/build/es/components/TransferOption.js +14 -5
  74. package/build/es/components/styles/DimensionSelector.style.js +2 -2
  75. package/build/es/components/styles/TransferOption.style.js +2 -2
  76. package/build/es/index.js +1 -1
  77. package/build/es/locales/en/translations.json +32 -7
  78. package/build/es/modules/__tests__/expressions.spec.js +136 -0
  79. package/build/es/modules/__tests__/hash.spec.js +88 -0
  80. package/build/es/modules/__tests__/parseExpression.spec.js +43 -0
  81. package/build/es/modules/dataTypes.js +6 -0
  82. package/build/es/modules/dimensionListItem.js +61 -0
  83. package/build/es/modules/expressions.js +131 -0
  84. package/build/es/modules/hash.js +12 -0
  85. package/build/es/visualizations/config/generators/dhis/singleValue.js +2 -2
  86. package/package.json +6 -1
@@ -3,19 +3,19 @@ import _JSXStyle from "styled-jsx/style";
3
3
  function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
4
4
 
5
5
  import { useDataEngine } from '@dhis2/app-runtime';
6
- import { Transfer, InputField, IconInfo16, IconDimensionDataSet16, IconDimensionIndicator16, IconDimensionEventDataItem16, IconDimensionProgramIndicator16 } from '@dhis2/ui';
6
+ import { Transfer, InputField, IconInfo16, Button, IconAdd24 } from '@dhis2/ui';
7
7
  import PropTypes from 'prop-types';
8
8
  import React, { useState } from 'react';
9
9
  import { apiFetchOptions } from '../../api/dimensions.js';
10
- import DataElementIcon from '../../assets/DimensionItemIcons/DataElementIcon.js';
11
- import GenericIcon from '../../assets/DimensionItemIcons/GenericIcon.js';
12
10
  import i18n from '../../locales/index.js';
13
11
  import { DATA_SETS_CONSTANTS, REPORTING_RATE } from '../../modules/dataSets.js';
14
- import { dataTypeMap as dataTypes, DIMENSION_TYPE_ALL, DIMENSION_TYPE_DATA_ELEMENT, DIMENSION_TYPE_DATA_ELEMENT_OPERAND, DIMENSION_TYPE_DATA_SET, DIMENSION_TYPE_EVENT_DATA_ITEM, DIMENSION_TYPE_PROGRAM_INDICATOR, DIMENSION_TYPE_INDICATOR, TOTALS, DIMENSION_TYPE_PROGRAM_DATA_ELEMENT, DIMENSION_TYPE_PROGRAM_ATTRIBUTE } from '../../modules/dataTypes.js';
12
+ import { dataTypeMap as dataTypes, DIMENSION_TYPE_ALL, DIMENSION_TYPE_DATA_ELEMENT, DIMENSION_TYPE_DATA_SET, DIMENSION_TYPE_EVENT_DATA_ITEM, DIMENSION_TYPE_PROGRAM_INDICATOR, DIMENSION_TYPE_INDICATOR, TOTALS, DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM } from '../../modules/dataTypes.js';
13
+ import { getIcon, getTooltipText } from '../../modules/dimensionListItem.js';
15
14
  import { TRANSFER_HEIGHT, TRANSFER_OPTIONS_WIDTH, TRANSFER_SELECTED_WIDTH } from '../../modules/dimensionSelectorHelper.js';
16
15
  import { useDebounce, useDidUpdateEffect } from '../../modules/utils.js';
17
16
  import styles from '../styles/DimensionSelector.style.js';
18
17
  import { TransferOption } from '../TransferOption.js';
18
+ import CalculationModal from './Calculation/CalculationModal.js';
19
19
  import DataTypeSelector from './DataTypeSelector.js';
20
20
  import GroupSelector from './GroupSelector.js';
21
21
 
@@ -30,7 +30,8 @@ const LeftHeader = _ref => {
30
30
  subGroup,
31
31
  setSubGroup,
32
32
  displayNameProp,
33
- dataTest
33
+ dataTest,
34
+ supportsEDI
34
35
  } = _ref;
35
36
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
36
37
  className: "jsx-".concat(styles.__hash) + " " + "leftHeader"
@@ -50,8 +51,9 @@ const LeftHeader = _ref => {
50
51
  }), /*#__PURE__*/React.createElement(DataTypeSelector, {
51
52
  currentDataType: dataType,
52
53
  onChange: setDataType,
53
- dataTest: "".concat(dataTest, "-data-types-select-field")
54
- }), dataTypes[dataType] && /*#__PURE__*/React.createElement(GroupSelector, {
54
+ dataTest: "".concat(dataTest, "-data-types-select-field"),
55
+ includeCalculations: supportsEDI
56
+ }), dataTypes[dataType] && dataType !== DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM && /*#__PURE__*/React.createElement(GroupSelector, {
55
57
  dataType: dataType,
56
58
  displayNameProp: displayNameProp,
57
59
  currentGroup: group,
@@ -74,7 +76,8 @@ LeftHeader.propTypes = {
74
76
  setGroup: PropTypes.func,
75
77
  setSearchTerm: PropTypes.func,
76
78
  setSubGroup: PropTypes.func,
77
- subGroup: PropTypes.string
79
+ subGroup: PropTypes.string,
80
+ supportsEDI: PropTypes.bool
78
81
  };
79
82
 
80
83
  const EmptySelection = () => /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("p", {
@@ -210,7 +213,9 @@ const ItemSelector = _ref5 => {
210
213
  rightFooter,
211
214
  displayNameProp,
212
215
  infoBoxMessage,
213
- dataTest
216
+ dataTest,
217
+ supportsEDI,
218
+ onEDISave
214
219
  } = _ref5;
215
220
  const [state, setState] = useState({
216
221
  searchTerm: '',
@@ -221,6 +226,7 @@ const ItemSelector = _ref5 => {
221
226
  loading: true,
222
227
  nextPage: 1
223
228
  });
229
+ const [currentCalculation, setCurrentCalculation] = useState();
224
230
  const dataEngine = useDataEngine();
225
231
 
226
232
  const setSearchTerm = searchTerm => setState(state => ({ ...state,
@@ -231,7 +237,7 @@ const ItemSelector = _ref5 => {
231
237
  filter
232
238
  }));
233
239
 
234
- const debouncedSearchTerm = useDebounce(state.searchTerm, 200);
240
+ const debouncedSearchTerm = useDebounce(state.searchTerm, 500);
235
241
 
236
242
  const fetchItems = async page => {
237
243
  var _result$dimensionItem;
@@ -255,7 +261,8 @@ const ItemSelector = _ref5 => {
255
261
  label: "".concat(item.name, " - ").concat(metric.getName()),
256
262
  value: "".concat(item.id, ".").concat(metric.id),
257
263
  disabled: item.disabled,
258
- type: item.dimensionItemType
264
+ type: item.dimensionItemType,
265
+ expression: item.expression
259
266
  });
260
267
  } else {
261
268
  DATA_SETS_CONSTANTS.forEach(metric => {
@@ -263,7 +270,8 @@ const ItemSelector = _ref5 => {
263
270
  label: "".concat(item.name, " - ").concat(metric.getName()),
264
271
  value: "".concat(item.id, ".").concat(metric.id),
265
272
  disabled: item.disabled,
266
- type: item.dimensionItemType
273
+ type: item.dimensionItemType,
274
+ expression: item.expression
267
275
  });
268
276
  });
269
277
  }
@@ -272,7 +280,8 @@ const ItemSelector = _ref5 => {
272
280
  label: item.name,
273
281
  value: item.id,
274
282
  disabled: item.disabled,
275
- type: item.dimensionItemType
283
+ type: item.dimensionItemType,
284
+ expression: item.expression
276
285
  });
277
286
  }
278
287
  });
@@ -281,11 +290,11 @@ const ItemSelector = _ref5 => {
281
290
  options: page > 1 ? [...state.options, ...newOptions] : newOptions,
282
291
  nextPage: result.nextPage
283
292
  }));
284
- /* The following handles a very specific edge-case where the user can select all items from a
285
- page and then reopen the modal. Usually Transfer triggers the onEndReached when the end of
286
- the page is reached (scrolling down) or if too few items are on the left side (e.g. selecting
287
- 49 items from page 1, leaving only 1 item on the left side). However, due to the way Transfer
288
- works, if 0 items are available, more items are fetched, but all items are already selected
293
+ /* The following handles a very specific edge-case where the user can select all items from a
294
+ page and then reopen the modal. Usually Transfer triggers the onEndReached when the end of
295
+ the page is reached (scrolling down) or if too few items are on the left side (e.g. selecting
296
+ 49 items from page 1, leaving only 1 item on the left side). However, due to the way Transfer
297
+ works, if 0 items are available, more items are fetched, but all items are already selected
289
298
  (leaving 0 items on the left side still), the onReachedEnd won't trigger. Hence the code below:
290
299
  IF there is a next page AND some options were just fetched AND you have the same or more
291
300
  selected items than fetched items AND all fetched items are already selected -> fetch more!
@@ -310,7 +319,10 @@ const ItemSelector = _ref5 => {
310
319
  return {
311
320
  value,
312
321
  label: matchingItem.label,
313
- type: matchingItem.type
322
+ type: matchingItem.type,
323
+ ...(matchingItem.expression ? {
324
+ expression: matchingItem.expression
325
+ } : {})
314
326
  };
315
327
  }));
316
328
  };
@@ -332,55 +344,52 @@ const ItemSelector = _ref5 => {
332
344
  return (_find = [...state.options, ...selectedItems].find(item => item.value === value)) === null || _find === void 0 ? void 0 : _find.type;
333
345
  };
334
346
 
335
- const getTooltipText = itemType => {
336
- var _dataTypes$itemType;
337
-
338
- switch (itemType) {
339
- case DIMENSION_TYPE_DATA_ELEMENT_OPERAND:
340
- return dataTypes[DIMENSION_TYPE_DATA_ELEMENT].getItemName();
347
+ const onSaveCalculation = async _ref6 => {
348
+ let {
349
+ id,
350
+ name,
351
+ expression,
352
+ isNew
353
+ } = _ref6;
354
+ onEDISave({
355
+ id,
356
+ name,
357
+ expression,
358
+ type: DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM
359
+ }); // close the modal
360
+
361
+ setCurrentCalculation(); // reload the list of options
341
362
 
342
- case REPORTING_RATE:
343
- return dataTypes[DIMENSION_TYPE_DATA_SET].getItemName();
344
-
345
- case DIMENSION_TYPE_PROGRAM_DATA_ELEMENT:
346
- case DIMENSION_TYPE_PROGRAM_ATTRIBUTE:
347
- return dataTypes[DIMENSION_TYPE_EVENT_DATA_ITEM].getItemName();
363
+ fetchItems(1);
348
364
 
349
- default:
350
- return (_dataTypes$itemType = dataTypes[itemType]) === null || _dataTypes$itemType === void 0 ? void 0 : _dataTypes$itemType.getItemName();
365
+ if (isNew) {
366
+ // select the new calculation
367
+ onSelect([...selectedItems, {
368
+ value: id,
369
+ label: name,
370
+ expression,
371
+ type: DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM
372
+ }]);
351
373
  }
352
374
  };
353
375
 
354
- const getIcon = itemType => {
355
- switch (itemType) {
356
- case DIMENSION_TYPE_INDICATOR:
357
- return /*#__PURE__*/React.createElement(IconDimensionIndicator16, null);
376
+ const onDeleteCalculation = _ref7 => {
377
+ let {
378
+ id
379
+ } = _ref7;
380
+ // close the modal
381
+ setCurrentCalculation(); // reload the list of options
358
382
 
359
- case DIMENSION_TYPE_DATA_ELEMENT_OPERAND:
360
- case DIMENSION_TYPE_DATA_ELEMENT:
361
- return DataElementIcon;
383
+ fetchItems(1); // unselect the deleted calculation
362
384
 
363
- case REPORTING_RATE:
364
- return /*#__PURE__*/React.createElement(IconDimensionDataSet16, null);
365
-
366
- case DIMENSION_TYPE_EVENT_DATA_ITEM:
367
- case DIMENSION_TYPE_PROGRAM_DATA_ELEMENT:
368
- case DIMENSION_TYPE_PROGRAM_ATTRIBUTE:
369
- return /*#__PURE__*/React.createElement(IconDimensionEventDataItem16, null);
370
-
371
- case DIMENSION_TYPE_PROGRAM_INDICATOR:
372
- return /*#__PURE__*/React.createElement(IconDimensionProgramIndicator16, null);
373
-
374
- default:
375
- return GenericIcon;
376
- }
385
+ onSelect([...selectedItems.filter(item => item.value !== id)]);
377
386
  };
378
387
 
379
- return /*#__PURE__*/React.createElement(Transfer, {
380
- onChange: _ref6 => {
388
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Transfer, {
389
+ onChange: _ref8 => {
381
390
  let {
382
391
  selected
383
- } = _ref6;
392
+ } = _ref8;
384
393
  return onChange(selected);
385
394
  },
386
395
  selected: selectedItems.map(item => item.value),
@@ -420,8 +429,16 @@ const ItemSelector = _ref5 => {
420
429
  searchTerm: state.searchTerm,
421
430
  setSearchTerm: setSearchTerm,
422
431
  displayNameProp: displayNameProp,
423
- dataTest: "".concat(dataTest, "-left-header")
432
+ dataTest: "".concat(dataTest, "-left-header"),
433
+ supportsEDI: supportsEDI
424
434
  }),
435
+ leftFooter: supportsEDI ? /*#__PURE__*/React.createElement("div", {
436
+ className: "jsx-".concat(styles.__hash) + " " + "calculation-button"
437
+ }, /*#__PURE__*/React.createElement(Button, {
438
+ icon: /*#__PURE__*/React.createElement(IconAdd24, null),
439
+ onClick: () => setCurrentCalculation({}),
440
+ small: true
441
+ }, i18n.t('Calculation'))) : undefined,
425
442
  enableOrderChange: true,
426
443
  height: TRANSFER_HEIGHT,
427
444
  optionsWidth: TRANSFER_OPTIONS_WIDTH,
@@ -431,20 +448,38 @@ const ItemSelector = _ref5 => {
431
448
  infoText: infoBoxMessage
432
449
  }),
433
450
  rightFooter: rightFooter,
434
- renderOption: props => /*#__PURE__*/React.createElement(TransferOption, _extends({}, props, {
435
- active: isActive(props.value
436
- /* eslint-disable-line react/prop-types */
437
- ),
438
- icon: getIcon(getItemType(props.value
439
- /* eslint-disable-line react/prop-types */
440
- )),
441
- tooltipText: state.filter.dataType === DIMENSION_TYPE_ALL ? getTooltipText(getItemType(props.value
442
- /* eslint-disable-line react/prop-types */
443
- )) : undefined,
444
- dataTest: "".concat(dataTest, "-transfer-option")
445
- })),
451
+ renderOption: props => {
452
+ var _props$access;
453
+
454
+ return /*#__PURE__*/React.createElement(TransferOption
455
+ /* eslint-disable react/prop-types */
456
+ , _extends({}, props, {
457
+ active: isActive(props.value),
458
+ icon: getIcon(getItemType(props.value)),
459
+ tooltipText: getTooltipText({
460
+ type: getItemType(props.value),
461
+ expression: props.expression
462
+ }),
463
+ dataTest: "".concat(dataTest, "-transfer-option"),
464
+ onEditClick: getItemType(props.value) === DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM && !(((_props$access = props.access) === null || _props$access === void 0 ? void 0 : _props$access.write) === false) && supportsEDI ? () => setCurrentCalculation({
465
+ id: props.value,
466
+ name: props.label,
467
+ expression: props.expression
468
+ }) : undefined
469
+ /* eslint-enable react/prop-types */
470
+
471
+ }));
472
+ },
446
473
  dataTest: "".concat(dataTest, "-transfer")
447
- });
474
+ }), currentCalculation && supportsEDI && /*#__PURE__*/React.createElement(CalculationModal, {
475
+ calculation: currentCalculation,
476
+ onSave: onSaveCalculation,
477
+ onClose: () => setCurrentCalculation(),
478
+ onDelete: onDeleteCalculation,
479
+ displayNameProp: displayNameProp
480
+ }), /*#__PURE__*/React.createElement(_JSXStyle, {
481
+ id: styles.__hash
482
+ }, styles));
448
483
  };
449
484
 
450
485
  ItemSelector.propTypes = {
@@ -458,8 +493,11 @@ ItemSelector.propTypes = {
458
493
  label: PropTypes.string.isRequired,
459
494
  value: PropTypes.string.isRequired,
460
495
  isActive: PropTypes.bool,
461
- type: PropTypes.string
462
- }))
496
+ type: PropTypes.string,
497
+ expression: PropTypes.string
498
+ })),
499
+ supportsEDI: PropTypes.bool,
500
+ onEDISave: PropTypes.func
463
501
  };
464
502
  ItemSelector.defaultProps = {
465
503
  selectedItems: []
@@ -1,5 +1,5 @@
1
1
  import _JSXStyle from "styled-jsx/style";
2
- import { Tooltip } from '@dhis2/ui';
2
+ import { Tooltip, IconEdit16 } from '@dhis2/ui';
3
3
  import cx from 'classnames';
4
4
  import PropTypes from 'prop-types';
5
5
  import React from 'react';
@@ -16,10 +16,12 @@ export const TransferOption = _ref => {
16
16
  icon,
17
17
  active,
18
18
  tooltipText,
19
- dataTest
19
+ dataTest,
20
+ onEditClick
20
21
  } = _ref;
21
22
 
22
23
  const renderContent = () => /*#__PURE__*/React.createElement("div", {
24
+ "data-test": "".concat(dataTest, "-content"),
23
25
  onClick: event => {
24
26
  if (disabled) {
25
27
  return;
@@ -40,7 +42,6 @@ export const TransferOption = _ref => {
40
42
  value
41
43
  }, event);
42
44
  },
43
- "data-test": "".concat(dataTest, "-content"),
44
45
  className: "jsx-".concat(styles.__hash) + " " + (cx('chip', {
45
46
  highlighted,
46
47
  disabled,
@@ -51,7 +52,14 @@ export const TransferOption = _ref => {
51
52
  className: "jsx-".concat(styles.__hash) + " " + "icon"
52
53
  }, icon), /*#__PURE__*/React.createElement("span", {
53
54
  className: "jsx-".concat(styles.__hash) + " " + "label"
54
- }, label), /*#__PURE__*/React.createElement(_JSXStyle, {
55
+ }, label), onEditClick && /*#__PURE__*/React.createElement("span", {
56
+ onClick: e => {
57
+ e.stopPropagation();
58
+ onEditClick();
59
+ },
60
+ "data-test": "".concat(dataTest, "-edit-button"),
61
+ className: "jsx-".concat(styles.__hash) + " " + "edit"
62
+ }, /*#__PURE__*/React.createElement(IconEdit16, null)), /*#__PURE__*/React.createElement(_JSXStyle, {
55
63
  id: styles.__hash
56
64
  }, styles));
57
65
 
@@ -78,5 +86,6 @@ TransferOption.propTypes = {
78
86
  selected: PropTypes.bool,
79
87
  tooltipText: PropTypes.string,
80
88
  onClick: PropTypes.func,
81
- onDoubleClick: PropTypes.func
89
+ onDoubleClick: PropTypes.func,
90
+ onEditClick: PropTypes.func
82
91
  };
@@ -1,4 +1,4 @@
1
1
  import { spacers, colors } from '@dhis2/ui';
2
- const _defaultExport = [".filterContainer.jsx-2057111968{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;margin-bottom:".concat(spacers.dp12, ";margin-top:").concat(spacers.dp8, ";}"), ".emptyList.jsx-2057111968{text-align:center;font-size:14px;line-height:16px;margin:".concat(spacers.dp24, " 0 0;color:").concat(colors.grey700, ";}"), ".rightHeader.jsx-2057111968{font-size:14px;font-weight:400;}", ".leftHeader.jsx-2057111968{padding:".concat(spacers.dp12, " ").concat(spacers.dp4, ";}"), ".info-container.jsx-2057111968{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;margin-bottom:".concat(spacers.dp8, ";padding:").concat(spacers.dp8, ";background-color:").concat(colors.grey200, ";border-radius:3px;}"), ".info-text.jsx-2057111968{padding-left:".concat(spacers.dp8, ";color:").concat(colors.grey900, ";font-size:12px;line-height:16px;}")];
3
- _defaultExport.__hash = "2057111968";
2
+ const _defaultExport = [".filterContainer.jsx-336728173{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;margin-bottom:".concat(spacers.dp12, ";margin-top:").concat(spacers.dp8, ";}"), ".emptyList.jsx-336728173{text-align:center;font-size:14px;line-height:16px;margin:".concat(spacers.dp24, " 0 0;color:").concat(colors.grey700, ";}"), ".rightHeader.jsx-336728173{font-size:14px;font-weight:400;}", ".leftHeader.jsx-336728173{padding:".concat(spacers.dp12, " ").concat(spacers.dp4, ";}"), ".info-container.jsx-336728173{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;margin-bottom:".concat(spacers.dp8, ";padding:").concat(spacers.dp8, ";background-color:").concat(colors.grey200, ";border-radius:3px;}"), ".info-text.jsx-336728173{padding-left:".concat(spacers.dp8, ";color:").concat(colors.grey900, ";font-size:12px;line-height:16px;}"), ".calculation-button.jsx-336728173{margin:".concat(spacers.dp8, " 0;}")];
3
+ _defaultExport.__hash = "336728173";
4
4
  export default _defaultExport;
@@ -1,4 +1,4 @@
1
1
  import { colors, spacers, theme } from '@dhis2/ui';
2
- const _defaultExport = [".wrapper.jsx-3199850788:last-child{margin-bottom:".concat(spacers.dp4, ";}"), ".chip.jsx-3199850788{display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;background:".concat(colors.grey200, ";font-size:14px;line-height:16px;padding:2px ").concat(spacers.dp8, " 2px ").concat(spacers.dp4, ";margin:").concat(spacers.dp4, " ").concat(spacers.dp8, " 0 ").concat(spacers.dp8, ";border-radius:3px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;}"), ".chip.jsx-3199850788:hover{background:".concat(colors.grey300, ";}"), ".selected.jsx-3199850788{background:".concat(theme.secondary100, ";color:").concat(theme.secondary900, ";}"), ".selected.jsx-3199850788 .icon path{fill:".concat(theme.secondary700, ";}"), ".selected.jsx-3199850788:hover{background:#c9edeb;}", ".highlighted.jsx-3199850788,.highlighted.jsx-3199850788:hover{background:".concat(theme.secondary800, ";color:").concat(colors.white, ";}"), ".highlighted.jsx-3199850788 .icon path{fill:".concat(colors.white, ";}"), ".disabled.jsx-3199850788{opacity:0.3;cursor:not-allowed;}", ".inactive.jsx-3199850788{opacity:0.3;}", ".icon.jsx-3199850788,.label.jsx-3199850788{line-height:18px;}", ".icon.jsx-3199850788{margin-right:".concat(spacers.dp4, ";display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;vertical-align:text-bottom;padding-top:1px;}"), ".label.jsx-3199850788{font-size:14px;}"];
3
- _defaultExport.__hash = "3199850788";
2
+ const _defaultExport = [".wrapper.jsx-1302852599:last-child{margin-bottom:".concat(spacers.dp4, ";}"), ".chip.jsx-1302852599{display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;background:".concat(colors.grey200, ";font-size:14px;line-height:16px;padding:2px ").concat(spacers.dp8, " 2px ").concat(spacers.dp4, ";margin:").concat(spacers.dp4, " ").concat(spacers.dp8, " 0 ").concat(spacers.dp8, ";border-radius:3px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;}"), ".chip.jsx-1302852599:hover{background:".concat(colors.grey300, ";}"), ".selected.jsx-1302852599{background:".concat(theme.secondary100, ";color:").concat(theme.secondary900, ";}"), ".selected.jsx-1302852599 .icon path{fill:".concat(theme.secondary700, ";}"), ".selected.jsx-1302852599:hover{background:#c9edeb;}", ".highlighted.jsx-1302852599,.highlighted.jsx-1302852599:hover{background:".concat(theme.secondary800, ";color:").concat(colors.white, ";}"), ".highlighted.jsx-1302852599 .icon path{fill:".concat(colors.white, ";}"), ".disabled.jsx-1302852599{opacity:0.3;cursor:not-allowed;}", ".inactive.jsx-1302852599{opacity:0.3;}", ".icon.jsx-1302852599,.label.jsx-1302852599{line-height:18px;}", ".icon.jsx-1302852599{margin-right:".concat(spacers.dp4, ";display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;vertical-align:text-bottom;padding-top:1px;}"), ".label.jsx-1302852599{font-size:14px;}", ".edit.jsx-1302852599{height:16px;margin-top:1px;margin-left:".concat(spacers.dp8, ";cursor:pointer;}"), ".edit.jsx-1302852599:hover{background-color:rgba(0,0,0,0.12);outline:1px solid rgba(0,0,0,0.12);border-radius:3px;}", ".highlighted.jsx-1302852599 .edit.jsx-1302852599:hover{background-color:rgba(255,255,255,0.12);outline:1px solid rgba(255,255,255,0.12);}"];
3
+ _defaultExport.__hash = "1302852599";
4
4
  export default _defaultExport;
package/build/es/index.js CHANGED
@@ -95,4 +95,4 @@ export { DAILY, WEEKLY, WEEKLYWED, WEEKLYTHU, WEEKLYSAT, WEEKLYSUN, WEEKS_THIS_Y
95
95
  export { getRelativePeriodsOptionsById } from './components/PeriodDimension/utils/relativePeriods.js';
96
96
  export { getFixedPeriodsOptionsById } from './components/PeriodDimension/utils/fixedPeriods.js';
97
97
  export { default as VisualizationOptions } from './components/Options/VisualizationOptions.js';
98
- export { DIMENSION_TYPE_INDICATOR, DIMENSION_TYPE_DATA_ELEMENT, DIMENSION_TYPE_DATA_SET, DIMENSION_TYPE_EVENT_DATA_ITEM, DIMENSION_TYPE_PROGRAM_INDICATOR, DIMENSION_TYPE_PROGRAM_DATA_ELEMENT, DIMENSION_TYPE_PROGRAM_ATTRIBUTE, DIMENSION_TYPE_DATA_ELEMENT_OPERAND, DIMENSION_TYPE_CATEGORY, DIMENSION_TYPE_CATEGORY_OPTION_GROUP_SET, DIMENSION_TYPE_ALL, DIMENSION_TYPE_DATA, DIMENSION_TYPE_DATA_ELEMENT_GROUP_SET, DIMENSION_TYPE_ORGANISATION_UNIT, DIMENSION_TYPE_PERIOD, DIMENSION_TYPE_ORGANISATION_UNIT_GROUP_SET } from './modules/dataTypes.js';
98
+ export { DIMENSION_TYPE_INDICATOR, DIMENSION_TYPE_DATA_ELEMENT, DIMENSION_TYPE_DATA_SET, DIMENSION_TYPE_EVENT_DATA_ITEM, DIMENSION_TYPE_PROGRAM_INDICATOR, DIMENSION_TYPE_PROGRAM_DATA_ELEMENT, DIMENSION_TYPE_PROGRAM_ATTRIBUTE, DIMENSION_TYPE_DATA_ELEMENT_OPERAND, DIMENSION_TYPE_CATEGORY, DIMENSION_TYPE_CATEGORY_OPTION_GROUP_SET, DIMENSION_TYPE_ALL, DIMENSION_TYPE_DATA, DIMENSION_TYPE_DATA_ELEMENT_GROUP_SET, DIMENSION_TYPE_ORGANISATION_UNIT, DIMENSION_TYPE_PERIOD, DIMENSION_TYPE_ORGANISATION_UNIT_GROUP_SET, DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM } from './modules/dataTypes.js';
@@ -21,27 +21,45 @@
21
21
  "About this visualization": "About this visualization",
22
22
  "This app could not retrieve required data.": "This app could not retrieve required data.",
23
23
  "Network error": "Network error",
24
- "Data Type": "Data Type",
25
- "All types": "All types",
24
+ "Data / Edit calculation": "Data / Edit calculation",
25
+ "Data / New calculation": "Data / New calculation",
26
+ "Remove item": "Remove item",
27
+ "Check formula": "Check formula",
28
+ "Calculation name": "Calculation name",
29
+ "Shown in table headers and chart axes/legends": "Shown in table headers and chart axes/legends",
30
+ "Delete calculation": "Delete calculation",
31
+ "Cancel": "Cancel",
32
+ "The calculation can only be saved with a valid formula": "The calculation can only be saved with a valid formula",
33
+ "Add a name to save this calculation": "Add a name to save this calculation",
34
+ "Save calculation": "Save calculation",
35
+ "Are you sure you want to delete this calculation? It may be used by other visualizations.": "Are you sure you want to delete this calculation? It may be used by other visualizations.",
36
+ "Yes, delete": "Yes, delete",
26
37
  "Totals only": "Totals only",
27
38
  "Details only": "Details only",
39
+ "Loading": "Loading",
40
+ "Data elements": "Data elements",
41
+ "Search by data element name": "Search by data element name",
42
+ "No data elements found for \"{{- searchTerm}}\"": "No data elements found for \"{{- searchTerm}}\"",
43
+ "No data elements found": "No data elements found",
44
+ "Drag items here, or double click in the list, to start building a calculation formula": "Drag items here, or double click in the list, to start building a calculation formula",
45
+ "Math operators": "Math operators",
46
+ "Data Type": "Data Type",
47
+ "All types": "All types",
28
48
  "Disaggregation": "Disaggregation",
29
49
  "No data": "No data",
30
- "Loading": "Loading",
31
50
  "Search by data item name": "Search by data item name",
32
51
  "No items selected": "No items selected",
33
52
  "Selected Items": "Selected Items",
34
53
  "No indicators found": "No indicators found",
35
- "No data elements found": "No data elements found",
36
54
  "No data sets found": "No data sets found",
37
55
  "No event data items found": "No event data items found",
38
56
  "No program indicators found": "No program indicators found",
39
57
  "No indicators found for \"{{- searchTerm}}\"": "No indicators found for \"{{- searchTerm}}\"",
40
- "No data elements found for \"{{- searchTerm}}\"": "No data elements found for \"{{- searchTerm}}\"",
41
58
  "No data sets found for \"{{- searchTerm}}\"": "No data sets found for \"{{- searchTerm}}\"",
42
59
  "No event data items found for \"{{- searchTerm}}\"": "No event data items found for \"{{- searchTerm}}\"",
43
60
  "No program indicators found for \"{{- searchTerm}}\"": "No program indicators found for \"{{- searchTerm}}\"",
44
61
  "Nothing found for \"{{- searchTerm}}\"": "Nothing found for \"{{- searchTerm}}\"",
62
+ "Calculation": "Calculation",
45
63
  "Metric type": "Metric type",
46
64
  "All metrics": "All metrics",
47
65
  "Move to {{axisName}}": "Move to {{axisName}}",
@@ -63,7 +81,6 @@
63
81
  "Nothing found for {{- searchTerm}}": "Nothing found for {{- searchTerm}}",
64
82
  "Delete {{fileType}}": "Delete {{fileType}}",
65
83
  "This {{fileType}} and related interpretations will be deleted. Continue?": "This {{fileType}} and related interpretations will be deleted. Continue?",
66
- "Cancel": "Cancel",
67
84
  "Delete": "Delete",
68
85
  "File": "File",
69
86
  "New": "New",
@@ -278,7 +295,6 @@
278
295
  "Indicator": "Indicator",
279
296
  "No indicator groups found": "No indicator groups found",
280
297
  "Loading indicator groups": "Loading indicator groups",
281
- "Data elements": "Data elements",
282
298
  "Data element group": "Data element group",
283
299
  "Data element": "Data element",
284
300
  "No data element groups found": "No data element groups found",
@@ -294,6 +310,15 @@
294
310
  "Loading programs": "Loading programs",
295
311
  "Program indicators": "Program indicators",
296
312
  "Program indicator": "Program indicator",
313
+ "Calculations": "Calculations",
314
+ "Number": "Number",
315
+ "Formula is empty. Add items to the formula from the lists on the left.": "Formula is empty. Add items to the formula from the lists on the left.",
316
+ "Consecutive math operators": "Consecutive math operators",
317
+ "Consecutive data elements": "Consecutive data elements",
318
+ "Starts or ends with a math operator": "Starts or ends with a math operator",
319
+ "Empty parentheses": "Empty parentheses",
320
+ "Missing right parenthesis )": "Missing right parenthesis )",
321
+ "Missing left parenthesis (": "Missing left parenthesis (",
297
322
  "Extra Small": "Extra Small",
298
323
  "Small": "Small",
299
324
  "Regular": "Regular",
@@ -0,0 +1,136 @@
1
+ import { validateExpression, parseArrayToExpression, parseExpressionToArray, EXPRESSION_TYPE_DATA, EXPRESSION_TYPE_NUMBER, EXPRESSION_TYPE_OPERATOR, INVALID_EXPRESSION, getItemIdsFromExpression } from '../expressions.js';
2
+ const invalidTestExpressions = [{
3
+ message: 'Formula is empty. Add items to the formula from the lists on the left.',
4
+ expressions: ['']
5
+ }, // {
6
+ // message: 'Consecutive math operators',
7
+ // expressions: ['5+-', '5+++', '4+9-*', '5++9'],
8
+ // },
9
+ {
10
+ message: 'Consecutive data elements',
11
+ expressions: ['#{cYeuwXTCPkU}#{Jtf34kNZhzP}']
12
+ }, {
13
+ message: 'Starts or ends with a math operator',
14
+ expressions: ['+', '+1', '1-2/', '*((#{cYeuwXTCPkU}*#{Jtf34kNZhzP})))']
15
+ }, {
16
+ message: 'Empty parentheses',
17
+ expressions: ['#{cYeuwXTCPkU}*()']
18
+ }, {
19
+ message: 'Missing left parenthesis (',
20
+ expressions: [')', '5)', '((#{cYeuwXTCPkU}*#{P3jJH5Tu5VC.S34ULMcHMca})))']
21
+ }, {
22
+ message: 'Missing right parenthesis )',
23
+ expressions: ['(', '(5', '((#{cYeuwXTCPkU}*#{Jtf34kNZhzP})']
24
+ }];
25
+ const validTestExpressions = ['5+9', '((#{cYeuwXTCPkU}*#{Jtf34kNZhzP}))#{iKGjnOOaPlE}', '#{P3jJH5Tu5VC.S34ULMcHMca}*#{Jtf34kNZhzP}', '(5)+9', '(5+9)', '10/-5'];
26
+ describe('validateExpression', () => {
27
+ invalidTestExpressions.forEach(_ref => {
28
+ let {
29
+ expressions,
30
+ message
31
+ } = _ref;
32
+ expressions.forEach(exp => {
33
+ test("Fails: ".concat(message), () => {
34
+ expect(validateExpression(exp)).toEqual({
35
+ status: INVALID_EXPRESSION,
36
+ message
37
+ });
38
+ });
39
+ });
40
+ });
41
+ validTestExpressions.forEach(exp => {
42
+ test("Passes validation: ".concat(exp), () => {
43
+ expect(validateExpression(exp)).toEqual(undefined);
44
+ });
45
+ });
46
+ });
47
+ describe('parseArrayToExpression', () => {
48
+ test('exp 1', () => {
49
+ const expressionArray = [{
50
+ label: 'abc123',
51
+ value: '#{abc123}',
52
+ type: EXPRESSION_TYPE_DATA
53
+ }, {
54
+ label: '+',
55
+ value: '+',
56
+ type: EXPRESSION_TYPE_OPERATOR
57
+ }, {
58
+ label: 'def456.xyz999',
59
+ value: '#{def456.xyz999}',
60
+ type: EXPRESSION_TYPE_DATA
61
+ }, {
62
+ label: '/',
63
+ value: '/',
64
+ type: EXPRESSION_TYPE_OPERATOR
65
+ }, {
66
+ label: '10',
67
+ value: '10',
68
+ type: EXPRESSION_TYPE_NUMBER
69
+ }];
70
+ const expected = '#{abc123}+#{def456.xyz999}/10';
71
+ expect(parseArrayToExpression(expressionArray)).toEqual(expected);
72
+ });
73
+ });
74
+ describe('parseExpressionToArray', () => {
75
+ test('exp 1', () => {
76
+ const expression = '#{abc123}/10*99';
77
+ const expected = [{
78
+ label: 'abc123',
79
+ value: '#{abc123}',
80
+ type: EXPRESSION_TYPE_DATA
81
+ }, {
82
+ label: '/',
83
+ value: '/',
84
+ type: EXPRESSION_TYPE_OPERATOR
85
+ }, {
86
+ label: '10',
87
+ value: '10',
88
+ type: EXPRESSION_TYPE_NUMBER
89
+ }, {
90
+ label: '×',
91
+ value: '*',
92
+ type: EXPRESSION_TYPE_OPERATOR
93
+ }, {
94
+ label: '99',
95
+ value: '99',
96
+ type: EXPRESSION_TYPE_NUMBER
97
+ }];
98
+ expect(parseExpressionToArray(expression)).toEqual(expected);
99
+ });
100
+ });
101
+ describe('getItemIdsFromExpression', () => {
102
+ test('exp 1', () => {
103
+ const expression = '#{abc123}/10*99';
104
+ const expected = ['abc123'];
105
+ expect(getItemIdsFromExpression(expression)).toEqual(expected);
106
+ });
107
+ test('exp 2', () => {
108
+ const expression = '#{abc123}/10*#{def456}';
109
+ const expected = ['abc123', 'def456'];
110
+ expect(getItemIdsFromExpression(expression)).toEqual(expected);
111
+ });
112
+ test('exp 3', () => {
113
+ const expression = '#{abc123}/10*#{def456.xyz999}';
114
+ const expected = ['abc123', 'def456.xyz999'];
115
+ expect(getItemIdsFromExpression(expression)).toEqual(expected);
116
+ });
117
+ test('exp 4', () => {
118
+ const expression = '#{abc123}/10*#{def456.xyz999}+#{ghi789}';
119
+ const expected = ['abc123', 'def456.xyz999', 'ghi789'];
120
+ expect(getItemIdsFromExpression(expression)).toEqual(expected);
121
+ });
122
+ test('exp 5', () => {
123
+ const expression = '#{abc123}/10*#{def456.xyz999}+#{ghi789}+#{jkl000}';
124
+ const expected = ['abc123', 'def456.xyz999', 'ghi789', 'jkl000'];
125
+ expect(getItemIdsFromExpression(expression)).toEqual(expected);
126
+ });
127
+ test('exp 6', () => {
128
+ const expression = '5/10';
129
+ const expected = [];
130
+ expect(getItemIdsFromExpression(expression)).toEqual(expected);
131
+ });
132
+ test('exp 6', () => {
133
+ const expected = [];
134
+ expect(getItemIdsFromExpression()).toEqual(expected);
135
+ });
136
+ });