@dhis2/analytics 26.1.0 → 26.1.2

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 (29) hide show
  1. package/build/cjs/__demo__/InterpretationsUnit.stories.js +41 -0
  2. package/build/cjs/components/AboutAOUnit/AboutAOUnit.js +6 -3
  3. package/build/cjs/components/Interpretations/InterpretationModal/InterpretationModal.js +15 -15
  4. package/build/cjs/components/Interpretations/InterpretationModal/InterpretationThread.js +14 -12
  5. package/build/cjs/components/Interpretations/InterpretationsUnit/InterpretationForm.js +4 -1
  6. package/build/cjs/components/Interpretations/InterpretationsUnit/InterpretationList.js +5 -1
  7. package/build/cjs/components/Interpretations/InterpretationsUnit/InterpretationsUnit.js +7 -2
  8. package/build/cjs/components/Interpretations/common/Message/Message.js +12 -8
  9. package/build/cjs/components/Interpretations/common/RichTextEditor/RichTextEditor.js +5 -2
  10. package/build/cjs/components/Interpretations/common/__tests__/getInterpretationAccess.spec.js +172 -3
  11. package/build/cjs/components/Interpretations/common/getInterpretationAccess.js +15 -3
  12. package/build/cjs/locales/en/translations.json +1 -0
  13. package/build/cjs/locales/nb/translations.json +1 -1
  14. package/build/es/__demo__/InterpretationsUnit.stories.js +38 -0
  15. package/build/es/components/AboutAOUnit/AboutAOUnit.js +7 -4
  16. package/build/es/components/Interpretations/InterpretationModal/InterpretationModal.js +15 -15
  17. package/build/es/components/Interpretations/InterpretationModal/InterpretationThread.js +14 -12
  18. package/build/es/components/Interpretations/InterpretationsUnit/InterpretationForm.js +4 -1
  19. package/build/es/components/Interpretations/InterpretationsUnit/InterpretationList.js +5 -1
  20. package/build/es/components/Interpretations/InterpretationsUnit/InterpretationsUnit.js +7 -2
  21. package/build/es/components/Interpretations/common/Message/Message.js +12 -8
  22. package/build/es/components/Interpretations/common/RichTextEditor/RichTextEditor.js +5 -2
  23. package/build/es/components/Interpretations/common/__tests__/getInterpretationAccess.spec.js +172 -3
  24. package/build/es/components/Interpretations/common/getInterpretationAccess.js +15 -3
  25. package/build/es/locales/en/translations.json +1 -0
  26. package/build/es/locales/nb/translations.json +1 -1
  27. package/package.json +1 -1
  28. /package/build/cjs/modules/pivotTable/__tests__/{addToTotalIfNumber.js → addToTotalIfNumber.spec.js} +0 -0
  29. /package/build/es/modules/pivotTable/__tests__/{addToTotalIfNumber.js → addToTotalIfNumber.spec.js} +0 -0
@@ -140,7 +140,7 @@
140
140
  "Search for a user": "",
141
141
  "Searching for \"{{- searchText}}\"": "",
142
142
  "No results found": "Ingen resultater funnet",
143
- "Not available offline": "",
143
+ "Not available offline": "Ikke tilgjengelig i frakoblet modus",
144
144
  "Created by": "Opprettet av",
145
145
  "Anyone": "Hvem som helst",
146
146
  "Only you": "Bare deg",
@@ -0,0 +1,38 @@
1
+ import { CustomDataProvider } from '@dhis2/app-runtime';
2
+ import { storiesOf } from '@storybook/react';
3
+ import React from 'react';
4
+ import { InterpretationsUnit } from '../components/Interpretations/InterpretationsUnit/index.js';
5
+ storiesOf('IntepretationsUnit', module).add('Default', () => {
6
+ return /*#__PURE__*/React.createElement(CustomDataProvider, {
7
+ data: {
8
+ interpretations: {
9
+ interpretations: []
10
+ }
11
+ }
12
+ }, /*#__PURE__*/React.createElement(InterpretationsUnit, {
13
+ currentUser: {
14
+ name: 'Tom Wakiki'
15
+ },
16
+ id: "abcd",
17
+ onReplyIconClick: Function.prototype,
18
+ type: "eventVisualization",
19
+ visualizationHasTimeDimension: true
20
+ }));
21
+ });
22
+ storiesOf('IntepretationsUnit', module).add('With no time dimensions warning', () => {
23
+ return /*#__PURE__*/React.createElement(CustomDataProvider, {
24
+ data: {
25
+ interpretations: {
26
+ interpretations: []
27
+ }
28
+ }
29
+ }, /*#__PURE__*/React.createElement(InterpretationsUnit, {
30
+ currentUser: {
31
+ name: 'Tom Wakiki'
32
+ },
33
+ id: "abcd",
34
+ onReplyIconClick: Function.prototype,
35
+ type: "eventVisualization",
36
+ visualizationHasTimeDimension: false
37
+ }));
38
+ });
@@ -1,5 +1,5 @@
1
1
  import _JSXStyle from "styled-jsx/style";
2
- import { useDataQuery, useDataMutation } from '@dhis2/app-runtime';
2
+ import { useDataQuery, useDataMutation, useTimeZoneConversion } from '@dhis2/app-runtime';
3
3
  import i18n from '@dhis2/d2-i18n';
4
4
  import { Parser as RichTextParser } from '@dhis2/d2-ui-rich-text';
5
5
  import { Button, CircularLoader, IconChevronDown24, IconChevronUp24, IconClock16, IconShare16, IconSubscribe24, IconSubscribeOff24, IconUser16, IconView16, colors } from '@dhis2/ui';
@@ -51,6 +51,9 @@ const AboutAOUnit = /*#__PURE__*/forwardRef((_ref3, ref) => {
51
51
  renderId
52
52
  } = _ref3;
53
53
  const [isExpanded, setIsExpanded] = useState(true);
54
+ const {
55
+ fromServerDate
56
+ } = useTimeZoneConversion();
54
57
  const queries = useMemo(() => getQueries(type), [type]);
55
58
  const {
56
59
  data,
@@ -157,16 +160,16 @@ const AboutAOUnit = /*#__PURE__*/forwardRef((_ref3, ref) => {
157
160
  }, /*#__PURE__*/React.createElement(IconClock16, {
158
161
  color: colors.grey700
159
162
  }), i18n.t('Last updated {{time}}', {
160
- time: moment(data.ao.lastUpdated).fromNow()
163
+ time: moment(fromServerDate(data.ao.lastUpdated)).fromNow()
161
164
  })), /*#__PURE__*/React.createElement("p", {
162
165
  className: `jsx-${styles.__hash}` + " " + "detailLine"
163
166
  }, /*#__PURE__*/React.createElement(IconUser16, {
164
167
  color: colors.grey700
165
168
  }), (_data$ao$createdBy = data.ao.createdBy) !== null && _data$ao$createdBy !== void 0 && _data$ao$createdBy.displayName ? i18n.t('Created {{time}} by {{author}}', {
166
- time: moment(data.ao.created).fromNow(),
169
+ time: moment(fromServerDate(data.ao.created)).fromNow(),
167
170
  author: data.ao.createdBy.displayName
168
171
  }) : i18n.t('Created {{time}}', {
169
- time: moment(data.ao.created).fromNow()
172
+ time: moment(fromServerDate(data.ao.created)).fromNow()
170
173
  })), /*#__PURE__*/React.createElement("p", {
171
174
  className: `jsx-${styles.__hash}` + " " + "detailLine"
172
175
  }, /*#__PURE__*/React.createElement(IconView16, {
@@ -9,17 +9,17 @@ import { InterpretationThread } from './InterpretationThread.js';
9
9
  import { useModalContentWidth } from './useModalContentWidth.js';
10
10
  const modalCSS = {
11
11
  styles: /*#__PURE__*/React.createElement(_JSXStyle, {
12
- id: "422732072"
13
- }, ["aside.jsx-422732072{max-width:calc(100vw - 128px) !important;max-height:calc(100vh - 128px) !important;width:auto !important;height:auto !important;overflow-y:hidden;}", "aside.hidden.jsx-422732072{display:none;}", "aside.jsx-422732072>div>div{max-height:none;}"]),
14
- className: "jsx-422732072"
12
+ id: "2559940363"
13
+ }, ["aside.jsx-2559940363{max-width:calc(100vw - 128px) !important;max-height:calc(100vh - 128px) !important;width:auto !important;height:calc(100vh - 128px) !important;overflow-y:hidden;}", "aside.hidden.jsx-2559940363{display:none;}", "aside.jsx-2559940363>div>div{height:100%;}"]),
14
+ className: "jsx-2559940363"
15
15
  };
16
16
  function getModalContentCSS(width) {
17
17
  return {
18
18
  styles: /*#__PURE__*/React.createElement(_JSXStyle, {
19
- id: "1689110567",
19
+ id: "2099285089",
20
20
  dynamic: [width]
21
- }, [`div.__jsx-style-dynamic-selector{width:${width}px;}`]),
22
- className: _JSXStyle.dynamic([["1689110567", [width]]])
21
+ }, [`div.__jsx-style-dynamic-selector{width:${width}px;overflow-y:visible;}`]),
22
+ className: _JSXStyle.dynamic([["2099285089", [width]]])
23
23
  };
24
24
  }
25
25
  const query = {
@@ -100,23 +100,23 @@ const InterpretationModal = _ref2 => {
100
100
  }),
101
101
  dataTest: "interpretation-modal"
102
102
  }, /*#__PURE__*/React.createElement("h1", {
103
- className: _JSXStyle.dynamic([["3175860552", [colors.grey900, spacers.dp24, spacers.dp4, spacers.dp4]]]) + " " + "title"
103
+ className: _JSXStyle.dynamic([["2014146191", [colors.grey900, spacers.dp24, spacers.dp4, spacers.dp4]]]) + " " + "title"
104
104
  }, /*#__PURE__*/React.createElement("span", {
105
- className: _JSXStyle.dynamic([["3175860552", [colors.grey900, spacers.dp24, spacers.dp4, spacers.dp4]]]) + " " + "ellipsis"
105
+ className: _JSXStyle.dynamic([["2014146191", [colors.grey900, spacers.dp24, spacers.dp4, spacers.dp4]]]) + " " + "ellipsis"
106
106
  }, i18n.t('Viewing interpretation: {{- visualisationName}}', {
107
107
  visualisationName: visualization.displayName || visualization.name,
108
108
  nsSeparator: '^^'
109
109
  }))), /*#__PURE__*/React.createElement(ModalContent, {
110
110
  className: modalContentCSS.className
111
111
  }, /*#__PURE__*/React.createElement("div", {
112
- className: _JSXStyle.dynamic([["3175860552", [colors.grey900, spacers.dp24, spacers.dp4, spacers.dp4]]]) + " " + "container"
112
+ className: _JSXStyle.dynamic([["2014146191", [colors.grey900, spacers.dp24, spacers.dp4, spacers.dp4]]]) + " " + "container"
113
113
  }, error && /*#__PURE__*/React.createElement(NoticeBox, {
114
114
  error: true,
115
115
  title: i18n.t('Could not load interpretation')
116
116
  }, error.message || i18n.t('The interpretation couldn’t be displayed. Try again or contact your system administrator.')), shouldRenderModalContent && /*#__PURE__*/React.createElement("div", {
117
- className: _JSXStyle.dynamic([["3175860552", [colors.grey900, spacers.dp24, spacers.dp4, spacers.dp4]]]) + " " + "row"
117
+ className: _JSXStyle.dynamic([["2014146191", [colors.grey900, spacers.dp24, spacers.dp4, spacers.dp4]]]) + " " + "row"
118
118
  }, /*#__PURE__*/React.createElement("div", {
119
- className: _JSXStyle.dynamic([["3175860552", [colors.grey900, spacers.dp24, spacers.dp4, spacers.dp4]]]) + " " + "visualisation-wrap"
119
+ className: _JSXStyle.dynamic([["2014146191", [colors.grey900, spacers.dp24, spacers.dp4, spacers.dp4]]]) + " " + "visualisation-wrap"
120
120
  }, /*#__PURE__*/React.createElement(VisualizationPlugin, {
121
121
  filters: {
122
122
  relativePeriodDate: interpretation.created
@@ -124,9 +124,9 @@ const InterpretationModal = _ref2 => {
124
124
  visualization: visualization,
125
125
  onResponsesReceived: onResponsesReceived,
126
126
  displayProperty: (_currentUser$settings = currentUser.settings) === null || _currentUser$settings === void 0 ? void 0 : _currentUser$settings.keyAnalysisDisplayProperty,
127
- className: _JSXStyle.dynamic([["3175860552", [colors.grey900, spacers.dp24, spacers.dp4, spacers.dp4]]])
127
+ className: _JSXStyle.dynamic([["2014146191", [colors.grey900, spacers.dp24, spacers.dp4, spacers.dp4]]])
128
128
  })), /*#__PURE__*/React.createElement("div", {
129
- className: _JSXStyle.dynamic([["3175860552", [colors.grey900, spacers.dp24, spacers.dp4, spacers.dp4]]]) + " " + "thread-wrap"
129
+ className: _JSXStyle.dynamic([["2014146191", [colors.grey900, spacers.dp24, spacers.dp4, spacers.dp4]]]) + " " + "thread-wrap"
130
130
  }, /*#__PURE__*/React.createElement(InterpretationThread, {
131
131
  currentUser: currentUser,
132
132
  fetching: fetching,
@@ -139,9 +139,9 @@ const InterpretationModal = _ref2 => {
139
139
  disabled: fetching,
140
140
  onClick: handleClose
141
141
  }, i18n.t('Hide interpretation'))), modalCSS.styles, modalContentCSS.styles, /*#__PURE__*/React.createElement(_JSXStyle, {
142
- id: "3175860552",
142
+ id: "2014146191",
143
143
  dynamic: [colors.grey900, spacers.dp24, spacers.dp4, spacers.dp4]
144
- }, [`.title.__jsx-style-dynamic-selector{color:${colors.grey900};margin:0px;padding:${spacers.dp24} 0 ${spacers.dp4};}`, ".ellipsis.__jsx-style-dynamic-selector{display:inline-block;font-size:20px;font-weight:500;line-height:24px;white-space:nowrap;width:100%;overflow:hidden;text-overflow:ellipsis;}", ".container.__jsx-style-dynamic-selector{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;}", ".row.__jsx-style-dynamic-selector{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;gap:16px;}", ".visualisation-wrap.__jsx-style-dynamic-selector{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;min-width:0;}", `.thread-wrap.__jsx-style-dynamic-selector{padding-right:${spacers.dp4};-webkit-flex-basis:300px;-ms-flex-preferred-size:300px;flex-basis:300px;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;overflow-y:auto;}`])));
144
+ }, [`.title.__jsx-style-dynamic-selector{color:${colors.grey900};margin:0px;padding:${spacers.dp24} 0 ${spacers.dp4};}`, ".ellipsis.__jsx-style-dynamic-selector{display:inline-block;font-size:20px;font-weight:500;line-height:24px;white-space:nowrap;width:100%;overflow:hidden;text-overflow:ellipsis;}", ".container.__jsx-style-dynamic-selector{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;height:100%;}", ".row.__jsx-style-dynamic-selector{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;gap:16px;height:100%;}", ".visualisation-wrap.__jsx-style-dynamic-selector{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;min-width:0;}", `.thread-wrap.__jsx-style-dynamic-selector{padding-right:${spacers.dp4};-webkit-flex-basis:300px;-ms-flex-preferred-size:300px;flex-basis:300px;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;}`])));
145
145
  };
146
146
  InterpretationModal.propTypes = {
147
147
  currentUser: PropTypes.object.isRequired,
@@ -1,4 +1,5 @@
1
1
  import _JSXStyle from "styled-jsx/style";
2
+ import { useTimeZoneConversion } from '@dhis2/app-runtime';
2
3
  import { IconClock16, colors } from '@dhis2/ui';
3
4
  import cx from 'classnames';
4
5
  import moment from 'moment';
@@ -17,6 +18,9 @@ const InterpretationThread = _ref => {
17
18
  onThreadUpdated,
18
19
  downloadMenuComponent: DownloadMenu
19
20
  } = _ref;
21
+ const {
22
+ fromServerDate
23
+ } = useTimeZoneConversion();
20
24
  const focusRef = useRef();
21
25
  useEffect(() => {
22
26
  if (initialFocus && focusRef.current) {
@@ -27,20 +31,18 @@ const InterpretationThread = _ref => {
27
31
  }, [initialFocus]);
28
32
  const interpretationAccess = getInterpretationAccess(interpretation, currentUser);
29
33
  return /*#__PURE__*/React.createElement("div", {
30
- className: "jsx-615306698" + " " + (cx('container', {
34
+ className: "jsx-1846557365" + " " + (cx('container', {
31
35
  fetching
32
36
  }) || "")
33
37
  }, /*#__PURE__*/React.createElement("div", {
34
- className: "jsx-615306698" + " " + 'scrollbox'
35
- }, /*#__PURE__*/React.createElement("div", {
36
- className: "jsx-615306698" + " " + 'title'
38
+ className: "jsx-1846557365" + " " + 'title'
37
39
  }, /*#__PURE__*/React.createElement(IconClock16, {
38
40
  color: colors.grey700
39
- }), moment(interpretation.created).format('LLL')), DownloadMenu && /*#__PURE__*/React.createElement(DownloadMenu, {
41
+ }), moment(fromServerDate(interpretation.created)).format('LLL')), DownloadMenu && /*#__PURE__*/React.createElement(DownloadMenu, {
40
42
  relativePeriodDate: interpretation.created,
41
- className: "jsx-615306698"
43
+ className: "jsx-1846557365"
42
44
  }), /*#__PURE__*/React.createElement("div", {
43
- className: "jsx-615306698" + " " + 'thread'
45
+ className: "jsx-1846557365" + " " + 'thread'
44
46
  }, /*#__PURE__*/React.createElement(Interpretation, {
45
47
  currentUser: currentUser,
46
48
  interpretation: interpretation,
@@ -52,7 +54,7 @@ const InterpretationThread = _ref => {
52
54
  onDeleted: onInterpretationDeleted,
53
55
  isInThread: true
54
56
  }), /*#__PURE__*/React.createElement("div", {
55
- className: "jsx-615306698" + " " + 'comments'
57
+ className: "jsx-1846557365" + " " + 'comments'
56
58
  }, interpretation.comments.map(comment => /*#__PURE__*/React.createElement(Comment, {
57
59
  key: comment.id,
58
60
  comment: comment,
@@ -60,14 +62,14 @@ const InterpretationThread = _ref => {
60
62
  interpretationId: interpretation.id,
61
63
  onThreadUpdated: onThreadUpdated,
62
64
  canComment: interpretationAccess.comment
63
- }))), interpretationAccess.comment && /*#__PURE__*/React.createElement(CommentAddForm, {
65
+ })))), interpretationAccess.comment && /*#__PURE__*/React.createElement(CommentAddForm, {
64
66
  currentUser: currentUser,
65
67
  interpretationId: interpretation.id,
66
68
  onSave: () => onThreadUpdated(true),
67
69
  focusRef: focusRef
68
- }))), /*#__PURE__*/React.createElement(_JSXStyle, {
69
- id: "615306698"
70
- }, [".thread.jsx-615306698{margin-top:var(--spacers-dp16);}", ".container.jsx-615306698{position:relative;overflow:hidden;max-height:calc(100vh - 285px);display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;}", ".container.fetching.jsx-615306698::before{content:'';position:absolute;inset:0px;background-color:rgba(255,255,255,0.8);}", ".container.fetching.jsx-615306698::after{content:'';position:absolute;top:calc(50% - 12px);left:calc(50% - 12px);width:24px;height:24px;border-width:4px;border-style:solid;border-color:rgba(110,122,138,0.15) rgba(110,122,138,0.15) rgb(20,124,215);border-image:initial;border-radius:50%;-webkit-animation:1s linear 0s infinite normal none running rotation-jsx-615306698;animation:1s linear 0s infinite normal none running rotation-jsx-615306698;}", ".scrollbox.jsx-615306698{overflow-y:auto;-webkit-scroll-behavior:smooth;-moz-scroll-behavior:smooth;-ms-scroll-behavior:smooth;scroll-behavior:smooth;}", ".title.jsx-615306698{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;gap:var(--spacers-dp8);color:var(--colors-grey900);font-size:14px;line-height:18px;}", ".comments.jsx-615306698{padding-left:16px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;padding-top:var(--spacers-dp4);gap:var(--spacers-dp4);}", "@-webkit-keyframes rotation-jsx-615306698{0%{-webkit-transform:rotate(0);-ms-transform:rotate(0);transform:rotate(0);}100%{-webkit-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg);}}", "@keyframes rotation-jsx-615306698{0%{-webkit-transform:rotate(0);-ms-transform:rotate(0);transform:rotate(0);}100%{-webkit-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg);}}"]));
70
+ }), /*#__PURE__*/React.createElement(_JSXStyle, {
71
+ id: "1846557365"
72
+ }, [".thread.jsx-1846557365{margin-top:var(--spacers-dp16);overflow-y:auto;-webkit-scroll-behavior:smooth;-moz-scroll-behavior:smooth;-ms-scroll-behavior:smooth;scroll-behavior:smooth;}", ".container.jsx-1846557365{position:relative;overflow:auto;max-height:calc(100vh - 285px);display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;}", ".container.fetching.jsx-1846557365::before{content:'';position:absolute;inset:0px;background-color:rgba(255,255,255,0.8);}", ".container.fetching.jsx-1846557365::after{content:'';position:absolute;top:calc(50% - 12px);left:calc(50% - 12px);width:24px;height:24px;border-width:4px;border-style:solid;border-color:rgba(110,122,138,0.15) rgba(110,122,138,0.15) rgb(20,124,215);border-image:initial;border-radius:50%;-webkit-animation:1s linear 0s infinite normal none running rotation-jsx-1846557365;animation:1s linear 0s infinite normal none running rotation-jsx-1846557365;}", ".title.jsx-1846557365{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;gap:var(--spacers-dp8);color:var(--colors-grey900);font-size:14px;line-height:18px;}", ".comments.jsx-1846557365{padding-left:16px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;padding-top:var(--spacers-dp4);gap:var(--spacers-dp4);}", "@-webkit-keyframes rotation-jsx-1846557365{0%{-webkit-transform:rotate(0);-ms-transform:rotate(0);transform:rotate(0);}100%{-webkit-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg);}}", "@keyframes rotation-jsx-1846557365{0%{-webkit-transform:rotate(0);-ms-transform:rotate(0);transform:rotate(0);}100%{-webkit-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg);}}"]));
71
73
  };
72
74
  InterpretationThread.propTypes = {
73
75
  currentUser: PropTypes.object.isRequired,
@@ -10,6 +10,7 @@ export const InterpretationForm = _ref => {
10
10
  id,
11
11
  currentUser,
12
12
  disabled,
13
+ showNoTimeDimensionHelpText,
13
14
  onSave
14
15
  } = _ref;
15
16
  const [showRichTextEditor, setShowRichTextEditor] = useState(false);
@@ -41,7 +42,8 @@ export const InterpretationForm = _ref => {
41
42
  disabled: saveMutationInProgress,
42
43
  inputPlaceholder: inputPlaceholder,
43
44
  onChange: setInterpretationText,
44
- value: interpretationText
45
+ value: interpretationText,
46
+ helpText: showNoTimeDimensionHelpText ? i18n.t('Other people viewing this interpretation in the future may see more data.') : undefined
45
47
  }), /*#__PURE__*/React.createElement(MessageButtonStrip, null, /*#__PURE__*/React.createElement(Button, {
46
48
  primary: true,
47
49
  small: true,
@@ -67,6 +69,7 @@ InterpretationForm.propTypes = {
67
69
  currentUser: PropTypes.object,
68
70
  disabled: PropTypes.bool,
69
71
  id: PropTypes.string,
72
+ showNoTimeDimensionHelpText: PropTypes.bool,
70
73
  type: PropTypes.string,
71
74
  onSave: PropTypes.func
72
75
  };
@@ -1,4 +1,5 @@
1
1
  import _JSXStyle from "styled-jsx/style";
2
+ import { useTimeZoneConversion } from '@dhis2/app-runtime';
2
3
  import { IconCalendar24, colors, spacers } from '@dhis2/ui';
3
4
  import moment from 'moment';
4
5
  import PropTypes from 'prop-types';
@@ -24,6 +25,9 @@ export const InterpretationList = _ref => {
24
25
  refresh,
25
26
  disabled
26
27
  } = _ref;
28
+ const {
29
+ fromServerDate
30
+ } = useTimeZoneConversion();
27
31
  const interpretationsByDate = interpretations.reduce((groupedInterpretations, interpretation) => {
28
32
  const date = interpretation.created.split('T')[0];
29
33
  if (date in groupedInterpretations) {
@@ -46,7 +50,7 @@ export const InterpretationList = _ref => {
46
50
  }), /*#__PURE__*/React.createElement("time", {
47
51
  dateTime: date,
48
52
  className: _JSXStyle.dynamic([["4058400613", [spacers.dp8, spacers.dp8, spacers.dp16, colors.grey800, spacers.dp12, spacers.dp12, spacers.dp32, spacers.dp4]]]) + " " + "date-header"
49
- }, moment(date).format('ll'))), /*#__PURE__*/React.createElement("ol", {
53
+ }, moment(fromServerDate(date)).format('ll'))), /*#__PURE__*/React.createElement("ol", {
50
54
  className: _JSXStyle.dynamic([["4058400613", [spacers.dp8, spacers.dp8, spacers.dp16, colors.grey800, spacers.dp12, spacers.dp12, spacers.dp32, spacers.dp4]]]) + " " + "interpretation-list"
51
55
  }, interpretationsByDate[date].sort(sortByCreatedDateDesc).map(interpretation => /*#__PURE__*/React.createElement(Interpretation, {
52
56
  key: interpretation.id,
@@ -27,12 +27,14 @@ export const InterpretationsUnit = /*#__PURE__*/forwardRef((_ref2, ref) => {
27
27
  currentUser,
28
28
  type,
29
29
  id,
30
+ visualizationHasTimeDimension,
30
31
  onInterpretationClick,
31
32
  onReplyIconClick,
32
33
  disabled,
33
34
  renderId
34
35
  } = _ref2;
35
36
  const [isExpanded, setIsExpanded] = useState(true);
37
+ const showNoTimeDimensionHelpText = type === 'eventVisualization' && !visualizationHasTimeDimension;
36
38
  const {
37
39
  data,
38
40
  loading,
@@ -84,7 +86,8 @@ export const InterpretationsUnit = /*#__PURE__*/forwardRef((_ref2, ref) => {
84
86
  type: type,
85
87
  id: id,
86
88
  onSave: onCompleteAction,
87
- disabled: disabled
89
+ disabled: disabled,
90
+ showNoTimeDimensionHelpText: showNoTimeDimensionHelpText
88
91
  }), /*#__PURE__*/React.createElement(InterpretationList, {
89
92
  currentUser: currentUser,
90
93
  interpretations: data.interpretations.interpretations,
@@ -99,7 +102,8 @@ export const InterpretationsUnit = /*#__PURE__*/forwardRef((_ref2, ref) => {
99
102
  });
100
103
  InterpretationsUnit.displayName = 'InterpretationsUnit';
101
104
  InterpretationsUnit.defaultProps = {
102
- onInterpretationClick: Function.prototype
105
+ onInterpretationClick: Function.prototype,
106
+ visualizationHasTimeDimension: true
103
107
  };
104
108
  InterpretationsUnit.propTypes = {
105
109
  currentUser: PropTypes.object.isRequired,
@@ -107,6 +111,7 @@ InterpretationsUnit.propTypes = {
107
111
  type: PropTypes.string.isRequired,
108
112
  disabled: PropTypes.bool,
109
113
  renderId: PropTypes.number,
114
+ visualizationHasTimeDimension: PropTypes.bool,
110
115
  onInterpretationClick: PropTypes.func,
111
116
  onReplyIconClick: PropTypes.func
112
117
  };
@@ -1,4 +1,5 @@
1
1
  import _JSXStyle from "styled-jsx/style";
2
+ import { useTimeZoneConversion } from '@dhis2/app-runtime';
2
3
  import { Parser as RichTextParser } from '@dhis2/d2-ui-rich-text';
3
4
  import { UserAvatar, spacers, colors } from '@dhis2/ui';
4
5
  import moment from 'moment';
@@ -11,24 +12,27 @@ const Message = _ref => {
11
12
  created,
12
13
  username
13
14
  } = _ref;
15
+ const {
16
+ fromServerDate
17
+ } = useTimeZoneConversion();
14
18
  return /*#__PURE__*/React.createElement("li", {
15
- className: _JSXStyle.dynamic([["2436588813", [spacers.dp8, colors.grey100, spacers.dp8, colors.grey900, colors.grey600, colors.grey900, spacers.dp8]]]) + " " + "container"
19
+ className: _JSXStyle.dynamic([["3500389795", [spacers.dp8, colors.grey100, spacers.dp8, colors.grey900, colors.grey600, colors.grey900, spacers.dp8]]]) + " " + "container"
16
20
  }, /*#__PURE__*/React.createElement("div", {
17
- className: _JSXStyle.dynamic([["2436588813", [spacers.dp8, colors.grey100, spacers.dp8, colors.grey900, colors.grey600, colors.grey900, spacers.dp8]]]) + " " + "header"
21
+ className: _JSXStyle.dynamic([["3500389795", [spacers.dp8, colors.grey100, spacers.dp8, colors.grey900, colors.grey600, colors.grey900, spacers.dp8]]]) + " " + "header"
18
22
  }, /*#__PURE__*/React.createElement(UserAvatar, {
19
23
  name: username,
20
24
  extrasmall: true
21
25
  }), username, /*#__PURE__*/React.createElement("time", {
22
26
  dateTime: created,
23
- className: _JSXStyle.dynamic([["2436588813", [spacers.dp8, colors.grey100, spacers.dp8, colors.grey900, colors.grey600, colors.grey900, spacers.dp8]]])
24
- }, moment(created).format('lll'))), /*#__PURE__*/React.createElement("div", {
25
- className: _JSXStyle.dynamic([["2436588813", [spacers.dp8, colors.grey100, spacers.dp8, colors.grey900, colors.grey600, colors.grey900, spacers.dp8]]]) + " " + "content"
27
+ className: _JSXStyle.dynamic([["3500389795", [spacers.dp8, colors.grey100, spacers.dp8, colors.grey900, colors.grey600, colors.grey900, spacers.dp8]]])
28
+ }, moment(fromServerDate(created)).format('lll'))), /*#__PURE__*/React.createElement("div", {
29
+ className: _JSXStyle.dynamic([["3500389795", [spacers.dp8, colors.grey100, spacers.dp8, colors.grey900, colors.grey600, colors.grey900, spacers.dp8]]]) + " " + "content"
26
30
  }, /*#__PURE__*/React.createElement(RichTextParser, null, text)), /*#__PURE__*/React.createElement("div", {
27
- className: _JSXStyle.dynamic([["2436588813", [spacers.dp8, colors.grey100, spacers.dp8, colors.grey900, colors.grey600, colors.grey900, spacers.dp8]]]) + " " + "footer"
31
+ className: _JSXStyle.dynamic([["3500389795", [spacers.dp8, colors.grey100, spacers.dp8, colors.grey900, colors.grey600, colors.grey900, spacers.dp8]]]) + " " + "footer"
28
32
  }, children), /*#__PURE__*/React.createElement(_JSXStyle, {
29
- id: "2436588813",
33
+ id: "3500389795",
30
34
  dynamic: [spacers.dp8, colors.grey100, spacers.dp8, colors.grey900, colors.grey600, colors.grey900, spacers.dp8]
31
- }, [`.container.__jsx-style-dynamic-selector{padding:${spacers.dp8};background-color:${colors.grey100};border-radius:5px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;gap:${spacers.dp8};}`, `.header.__jsx-style-dynamic-selector{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;gap:6px;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;font-size:13px;line-height:16px;color:${colors.grey900};}`, `.header.__jsx-style-dynamic-selector time.__jsx-style-dynamic-selector{font-size:12px;color:${colors.grey600};}`, `.content.__jsx-style-dynamic-selector{font-size:14px;line-height:19px;color:${colors.grey900};}`, ".content.__jsx-style-dynamic-selector p:first-child{margin:0;}", `.footer.__jsx-style-dynamic-selector{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-align-items:flex-start;-webkit-box-align:flex-start;-ms-flex-align:flex-start;align-items:flex-start;gap:${spacers.dp8};}`]));
35
+ }, [`.container.__jsx-style-dynamic-selector{padding:${spacers.dp8};background-color:${colors.grey100};border-radius:5px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;gap:${spacers.dp8};}`, `.header.__jsx-style-dynamic-selector{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;gap:6px;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;font-size:13px;line-height:16px;color:${colors.grey900};}`, `.header.__jsx-style-dynamic-selector time.__jsx-style-dynamic-selector{font-size:12px;color:${colors.grey600};}`, `.content.__jsx-style-dynamic-selector{font-size:14px;line-height:19px;color:${colors.grey900};word-break:break-word;}`, ".content.__jsx-style-dynamic-selector p:first-child{margin:0;}", `.footer.__jsx-style-dynamic-selector{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-align-items:flex-start;-webkit-box-align:flex-start;-ms-flex-align:flex-start;align-items:flex-start;gap:${spacers.dp8};}`]));
32
36
  };
33
37
  Message.propTypes = {
34
38
  children: PropTypes.node.isRequired,
@@ -173,7 +173,8 @@ export const RichTextEditor = /*#__PURE__*/forwardRef((_ref5, externalRef) => {
173
173
  disabled,
174
174
  inputPlaceholder,
175
175
  onChange,
176
- errorText
176
+ errorText,
177
+ helpText
177
178
  } = _ref5;
178
179
  const [previewMode, setPreviewMode] = useState(false);
179
180
  const internalRef = useRef();
@@ -206,7 +207,8 @@ export const RichTextEditor = /*#__PURE__*/forwardRef((_ref5, externalRef) => {
206
207
  className: `jsx-${mainClasses.__hash}` + " " + "preview"
207
208
  }, /*#__PURE__*/React.createElement(RichTextParser, null, value)) : /*#__PURE__*/React.createElement(Field, {
208
209
  error: !!errorText,
209
- validationText: errorText
210
+ validationText: errorText,
211
+ helpText: helpText
210
212
  }, /*#__PURE__*/React.createElement(UserMentionWrapper, {
211
213
  onUserSelect: onChange,
212
214
  inputReference: textareaRef
@@ -228,5 +230,6 @@ RichTextEditor.propTypes = {
228
230
  onChange: PropTypes.func.isRequired,
229
231
  disabled: PropTypes.bool,
230
232
  errorText: PropTypes.string,
233
+ helpText: PropTypes.string,
231
234
  inputPlaceholder: PropTypes.string
232
235
  };
@@ -1,16 +1,28 @@
1
1
  import { getInterpretationAccess, getCommentAccess } from '../getInterpretationAccess.js';
2
- const superuser = {
2
+ const superuserD2CurrentUser = {
3
3
  id: 'iamsuper',
4
4
  authorities: new Set(['ALL'])
5
5
  };
6
- const userJoe = {
6
+ const userJoeD2CurrentUser = {
7
7
  id: 'johndoe',
8
8
  authorities: new Set(['Some'])
9
9
  };
10
- const userJane = {
10
+ const userJaneD2CurrentUser = {
11
11
  id: 'jane',
12
12
  authorities: new Set(['Some'])
13
13
  };
14
+ const superuser = {
15
+ id: 'iamsuper',
16
+ authorities: ['ALL']
17
+ };
18
+ const userJoe = {
19
+ id: 'johndoe',
20
+ authorities: ['Some']
21
+ };
22
+ const userJane = {
23
+ id: 'jane',
24
+ authorities: ['Some']
25
+ };
14
26
  describe('interpretation and comment access', () => {
15
27
  describe('getInterpretationAccess', () => {
16
28
  it('returns true for all accesses for superuser', () => {
@@ -88,6 +100,163 @@ describe('interpretation and comment access', () => {
88
100
  delete: false
89
101
  });
90
102
  });
103
+ it('throws an error for all accesses when no currentUser provided', () => {
104
+ const interpretation = {
105
+ access: {
106
+ write: false,
107
+ manage: false
108
+ },
109
+ createdBy: userJane
110
+ };
111
+ expect(() => getInterpretationAccess(interpretation)).toThrow('"hasAuthority" requires "authorities" to be an array or set of authorities (strings)');
112
+ });
113
+ it('throws an error when currentUser is missing authorities', () => {
114
+ const interpretation = {
115
+ access: {
116
+ write: false,
117
+ manage: false
118
+ },
119
+ createdBy: userJane
120
+ };
121
+ expect(() => getInterpretationAccess(interpretation, {
122
+ id: 'usernoauthorties'
123
+ })).toThrow('"hasAuthority" requires "authorities" to be an array or set of authorities (strings)');
124
+ });
125
+ });
126
+ describe('getInterpretationAccess using D2.currentUser', () => {
127
+ it('returns true for all accesses for superuser', () => {
128
+ const interpretation = {
129
+ access: {
130
+ write: true,
131
+ manage: true
132
+ },
133
+ createdBy: userJoeD2CurrentUser
134
+ };
135
+ expect(getInterpretationAccess(interpretation, superuserD2CurrentUser)).toMatchObject({
136
+ share: true,
137
+ comment: true,
138
+ edit: true,
139
+ delete: true
140
+ });
141
+ });
142
+ it('returns true for all accesses for creator', () => {
143
+ const interpretation = {
144
+ access: {
145
+ write: true,
146
+ manage: true
147
+ },
148
+ createdBy: userJoeD2CurrentUser
149
+ };
150
+ expect(getInterpretationAccess(interpretation, userJoeD2CurrentUser)).toMatchObject({
151
+ share: true,
152
+ comment: true,
153
+ edit: true,
154
+ delete: true
155
+ });
156
+ });
157
+ it('returns false for edit/delete if user is not creator/superuser', () => {
158
+ const interpretation = {
159
+ access: {
160
+ write: true,
161
+ manage: true
162
+ },
163
+ createdBy: userJaneD2CurrentUser
164
+ };
165
+ expect(getInterpretationAccess(interpretation, userJoeD2CurrentUser)).toMatchObject({
166
+ share: true,
167
+ comment: true,
168
+ edit: false,
169
+ delete: false
170
+ });
171
+ });
172
+ it('returns false for comment/edit/delete if user is not creator/superuser and no write access', () => {
173
+ const interpretation = {
174
+ access: {
175
+ write: false,
176
+ manage: true
177
+ },
178
+ createdBy: userJaneD2CurrentUser
179
+ };
180
+ expect(getInterpretationAccess(interpretation, userJoeD2CurrentUser)).toMatchObject({
181
+ share: true,
182
+ comment: false,
183
+ edit: false,
184
+ delete: false
185
+ });
186
+ });
187
+ it('returns false for share/comment/edit/delete if user is not creator/superuser and no write or manage access', () => {
188
+ const interpretation = {
189
+ access: {
190
+ write: false,
191
+ manage: false
192
+ },
193
+ createdBy: userJaneD2CurrentUser
194
+ };
195
+ expect(getInterpretationAccess(interpretation, userJoeD2CurrentUser)).toMatchObject({
196
+ share: false,
197
+ comment: false,
198
+ edit: false,
199
+ delete: false
200
+ });
201
+ });
202
+ });
203
+ describe('getCommentAccess using D2.currentUser', () => {
204
+ it('returns true for all accesses for superuser', () => {
205
+ const interpretation = {
206
+ access: {
207
+ write: true
208
+ }
209
+ };
210
+ const comment = {
211
+ createdBy: userJoeD2CurrentUser
212
+ };
213
+ expect(getCommentAccess(comment, interpretation.access.write, superuserD2CurrentUser)).toMatchObject({
214
+ edit: true,
215
+ delete: true
216
+ });
217
+ });
218
+ it('returns true for all accesses for creator when interpretation has write access', () => {
219
+ const interpretation = {
220
+ access: {
221
+ write: true
222
+ }
223
+ };
224
+ const comment = {
225
+ createdBy: userJoeD2CurrentUser
226
+ };
227
+ expect(getCommentAccess(comment, interpretation.access.write, userJoeD2CurrentUser)).toMatchObject({
228
+ edit: true,
229
+ delete: true
230
+ });
231
+ });
232
+ it('returns true for edit and false for delete for creator when interpretation does not have write access', () => {
233
+ const interpretation = {
234
+ access: {
235
+ write: false
236
+ }
237
+ };
238
+ const comment = {
239
+ createdBy: userJoeD2CurrentUser
240
+ };
241
+ expect(getCommentAccess(comment, interpretation.access.write, userJoeD2CurrentUser)).toMatchObject({
242
+ edit: true,
243
+ delete: false
244
+ });
245
+ });
246
+ it('returns false for edit/delete for user who is not creator or superuser', () => {
247
+ const interpretation = {
248
+ access: {
249
+ write: true
250
+ }
251
+ };
252
+ const comment = {
253
+ createdBy: userJaneD2CurrentUser
254
+ };
255
+ expect(getCommentAccess(comment, interpretation.access.write, userJoeD2CurrentUser)).toMatchObject({
256
+ edit: false,
257
+ delete: false
258
+ });
259
+ });
91
260
  });
92
261
  describe('getCommentAccess', () => {
93
262
  it('returns true for all accesses for superuser', () => {
@@ -1,6 +1,18 @@
1
- const isCreatorOrSuperuser = (object, currentUser) => (object === null || object === void 0 ? void 0 : object.createdBy.id) === (currentUser === null || currentUser === void 0 ? void 0 : currentUser.id) || (currentUser === null || currentUser === void 0 ? void 0 : currentUser.authorities.has('ALL'));
1
+ // For backwards compatibility
2
+ // accept both Set (from the old d2.currentUser object) and array
3
+ const hasAuthority = (authorities, authority) => {
4
+ if (!authority || typeof authority !== 'string') {
5
+ throw new Error(`"hasAuthority" requires "authority" to be a populated string but received ${authority}`);
6
+ }
7
+ if (!(Array.isArray(authorities) || typeof (authorities === null || authorities === void 0 ? void 0 : authorities.has) === 'function')) {
8
+ throw new Error(`"hasAuthority" requires "authorities" to be an array or set of authorities (strings)`);
9
+ }
10
+ return Array.isArray(authorities) ? authorities.includes(authority) : authorities.has(authority);
11
+ };
12
+ const isSuperuser = authorities => hasAuthority(authorities, 'ALL');
13
+ const isCreator = (object, currentUser) => (object === null || object === void 0 ? void 0 : object.createdBy.id) === (currentUser === null || currentUser === void 0 ? void 0 : currentUser.id);
2
14
  export const getInterpretationAccess = (interpretation, currentUser) => {
3
- const canEditDelete = isCreatorOrSuperuser(interpretation, currentUser);
15
+ const canEditDelete = isCreator(interpretation, currentUser) || isSuperuser(currentUser === null || currentUser === void 0 ? void 0 : currentUser.authorities);
4
16
  return {
5
17
  share: interpretation.access.manage,
6
18
  comment: interpretation.access.write,
@@ -9,7 +21,7 @@ export const getInterpretationAccess = (interpretation, currentUser) => {
9
21
  };
10
22
  };
11
23
  export const getCommentAccess = (comment, hasInterpretationReplyAccess, currentUser) => {
12
- const canEditDelete = isCreatorOrSuperuser(comment, currentUser);
24
+ const canEditDelete = isCreator(comment, currentUser) || isSuperuser(currentUser === null || currentUser === void 0 ? void 0 : currentUser.authorities);
13
25
  return {
14
26
  edit: canEditDelete,
15
27
  delete: canEditDelete && hasInterpretationReplyAccess
@@ -116,6 +116,7 @@
116
116
  "The interpretation couldn’t be displayed. Try again or contact your system administrator.": "The interpretation couldn’t be displayed. Try again or contact your system administrator.",
117
117
  "Hide interpretation": "Hide interpretation",
118
118
  "Write an interpretation": "Write an interpretation",
119
+ "Other people viewing this interpretation in the future may see more data.": "Other people viewing this interpretation in the future may see more data.",
119
120
  "Post interpretation": "Post interpretation",
120
121
  "Interpretations": "Interpretations",
121
122
  "Reply": "Reply",