From f40bbcba1874d00629e8f7742a09021f8b81ff5f Mon Sep 17 00:00:00 2001 From: Jen Jones Arnesen Date: Mon, 11 Sep 2023 13:07:40 +0200 Subject: [PATCH 1/3] fix: open as chart should open in new tab (DHIS2-15794) (#2956) By opening in a new tab, the user will not lose their work Fixes https://dhis2.atlassian.net/browse/DHIS2-15794 --- src/components/layers/overlays/OverlayCard.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/layers/overlays/OverlayCard.js b/src/components/layers/overlays/OverlayCard.js index 7c1405dff..cfd1e86e3 100644 --- a/src/components/layers/overlays/OverlayCard.js +++ b/src/components/layers/overlays/OverlayCard.js @@ -105,7 +105,10 @@ const OverlayCard = ({ await set(currentAO) // Open it in another app - window.location.href = `${baseUrl}/${APP_URLS[type]}/#/currentAnalyticalObject` + window.open( + `${baseUrl}/${APP_URLS[type]}/#/currentAnalyticalObject`, + '_blank' + ) } : undefined } From b7577709a1aa0fcd7e0e45a84771bf79a9de47eb Mon Sep 17 00:00:00 2001 From: Bjorn Sandvik Date: Tue, 12 Sep 2023 10:51:02 +0200 Subject: [PATCH 2/3] feat: custom calculations for thematic layer (DHIS2-15474) (#2745) --- .../integration/layers/thematiclayer.cy.js | 81 ++++++++++++++++++- i18n/en.pot | 15 ++++ .../calculations/CalculationSelect.js | 62 ++++++++++++++ .../styles/CalculationSelect.module.css | 3 + src/components/core/SelectField.js | 6 ++ .../edit/thematic/ThematicDialog.js | 18 +++++ .../edit/thematic/ValueTypeSelect.js | 40 ++++----- src/constants/dimension.js | 6 ++ 8 files changed, 212 insertions(+), 19 deletions(-) create mode 100644 src/components/calculations/CalculationSelect.js create mode 100644 src/components/calculations/styles/CalculationSelect.module.css diff --git a/cypress/integration/layers/thematiclayer.cy.js b/cypress/integration/layers/thematiclayer.cy.js index d9966fc7d..4dacacc0b 100644 --- a/cypress/integration/layers/thematiclayer.cy.js +++ b/cypress/integration/layers/thematiclayer.cy.js @@ -6,7 +6,7 @@ import { expectContextMenuOptions, } from '../../elements/map_context_menu.js' import { ThematicLayer } from '../../elements/thematic_layer.js' -import { CURRENT_YEAR } from '../../support/util.js' +import { CURRENT_YEAR, getApiBaseUrl } from '../../support/util.js' const INDICATOR_NAME = 'VCCT post-test counselling rate' @@ -136,4 +136,83 @@ context('Thematic Layers', () => { { name: VIEW_PROFILE }, ]) }) + + // TODO - update demo database with calculations instead of creating on the fly + it('adds a thematic layer with a calculation', () => { + const timestamp = new Date().toUTCString().slice(-24, -4) + const calculationName = `map calc ${timestamp}` + + // add a calculation + cy.request('POST', `${getApiBaseUrl()}/api/expressionDimensionItems`, { + name: calculationName, + shortName: calculationName, + expression: '#{fbfJHSPpUQD}/2', + }).then((response) => { + expect(response.status).to.eq(201) + + const calculationUid = response.body.response.uid + + // open thematic dialog + cy.getByDataTest('add-layer-button').click() + cy.getByDataTest('addlayeritem-thematic').click() + + // choose "Calculation" in item type + cy.getByDataTest('thematic-layer-value-type-select').click() + cy.contains('Calculations').click() + + // assert that the label on the Calculation select is "Calculation" + cy.getByDataTest('calculationselect-label').contains('Calculation') + + // click to open the calculation select + cy.getByDataTest('calculationselect').click() + + // check search box exists "Type to filter options" + cy.getByDataTest('dhis2-uicore-popper') + .find('input[type="text"]') + .should('have.attr', 'placeholder', 'Type to filter options') + + // search for something that doesn't exist + cy.getByDataTest('dhis2-uicore-popper') + .find('input[type="text"]') + .type('foo') + + cy.getByDataTest('dhis2-uicore-select-menu-menuwrapper') + .contains('No options found') + .should('be.visible') + + // try search for something that exists + cy.getByDataTest('dhis2-uicore-popper') + .find('input[type="text"]') + .clear() + + cy.getByDataTest('dhis2-uicore-popper') + .find('input[type="text"]') + .type(calculationName) + + cy.getByDataTest('dhis2-uicore-select-menu-menuwrapper') + .contains(calculationName) + .should('be.visible') + + // select the calculation and close dialog + cy.contains(calculationName).click() + + cy.getByDataTest('dhis2-uicore-modalactions') + .contains('Add layer') + .click() + + // check the layer card title + cy.getByDataTest('layercard') + .contains(calculationName, { timeout: 50000 }) + .should('be.visible') + + // check the map canvas is displayed + cy.get('canvas.maplibregl-canvas').should('be.visible') + + // delete the calculation + cy.request( + 'DELETE', + `${getApiBaseUrl()}/api/expressionDimensionItems/${calculationUid}` + ) + }) + }) }) diff --git a/i18n/en.pot b/i18n/en.pot index 2ff7557e9..34bbf28dd 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -17,6 +17,15 @@ msgstr "Map \"{{- name}}\" is saved." msgid "Failed to save map: {{message}}" msgstr "Failed to save map: {{message}}" +msgid "Calculation" +msgstr "Calculation" + +msgid "No calculations found" +msgstr "No calculations found" + +msgid "Calculations can be created in the Data Visualizer app." +msgstr "Calculations can be created in the Data Visualizer app." + msgid "Classification" msgstr "Classification" @@ -421,6 +430,9 @@ msgstr "Event data item is required" msgid "Program indicator is required" msgstr "Program indicator is required" +msgid "Calculation is required" +msgstr "Calculation is required" + msgid "Period is required" msgstr "Period is required" @@ -436,6 +448,9 @@ msgstr "Event data items" msgid "Program indicators" msgstr "Program indicators" +msgid "Calculations" +msgstr "Calculations" + msgid "Item type" msgstr "Item type" diff --git a/src/components/calculations/CalculationSelect.js b/src/components/calculations/CalculationSelect.js new file mode 100644 index 000000000..8bdc94ad6 --- /dev/null +++ b/src/components/calculations/CalculationSelect.js @@ -0,0 +1,62 @@ +import { useDataQuery } from '@dhis2/app-runtime' +import i18n from '@dhis2/d2-i18n' +import PropTypes from 'prop-types' +import React from 'react' +import { SelectField, Help } from '../core/index.js' +import { useUserSettings } from '../UserSettingsProvider.js' +import styles from './styles/CalculationSelect.module.css' + +// Load all calculations +const CALCULATIONS_QUERY = { + calculations: { + resource: 'expressionDimensionItems', + params: ({ nameProperty }) => ({ + fields: ['id', `${nameProperty}~rename(name)`], + paging: false, + }), + }, +} + +const CalculationSelect = ({ calculation, className, errorText, onChange }) => { + const { nameProperty } = useUserSettings() + const { loading, error, data } = useDataQuery(CALCULATIONS_QUERY, { + variables: { nameProperty }, + }) + + const items = data?.calculations.expressionDimensionItems + const value = calculation?.id + + return ( +
+ onChange(dataItem, 'calculation')} + className={className} + emptyText={i18n.t('No calculations found')} + errorText={ + error?.message || + (!calculation && errorText ? errorText : null) + } + filterable={true} + dataTest="calculationselect" + /> + + {i18n.t( + 'Calculations can be created in the Data Visualizer app.' + )} + +
+ ) +} + +CalculationSelect.propTypes = { + onChange: PropTypes.func.isRequired, + calculation: PropTypes.object, + className: PropTypes.string, + errorText: PropTypes.string, +} + +export default CalculationSelect diff --git a/src/components/calculations/styles/CalculationSelect.module.css b/src/components/calculations/styles/CalculationSelect.module.css new file mode 100644 index 000000000..d8cf160d2 --- /dev/null +++ b/src/components/calculations/styles/CalculationSelect.module.css @@ -0,0 +1,3 @@ +.calculationSelect { + margin-bottom: var(--spacers-dp16); +} diff --git a/src/components/core/SelectField.js b/src/components/core/SelectField.js index ecfed175d..fa21e24c9 100644 --- a/src/components/core/SelectField.js +++ b/src/components/core/SelectField.js @@ -17,6 +17,7 @@ import styles from './styles/InputField.module.css' const SelectField = (props) => { const { dense = true, + emptyText, errorText, helpText, warning, @@ -25,6 +26,7 @@ const SelectField = (props) => { prefix, loading, multiple, + filterable, disabled, onChange, className, @@ -65,12 +67,14 @@ const SelectField = (props) => { label={label} prefix={prefix} selected={!isLoading ? selected : undefined} + filterable={filterable} disabled={disabled} loading={isLoading} error={!!errorText} warning={!!warning} validationText={warning ? warning : errorText} helpText={helpText} + empty={emptyText} onChange={onSelectChange} dataTest={dataTest} > @@ -88,7 +92,9 @@ SelectField.propTypes = { dataTest: PropTypes.string, dense: PropTypes.bool, disabled: PropTypes.bool, + emptyText: PropTypes.string, // If set, shows empty text when no options errorText: PropTypes.string, // If set, shows the error message below the SelectField + filterable: PropTypes.bool, helpText: PropTypes.string, // If set, shows the help text below the SelectField items: PropTypes.arrayOf( PropTypes.shape({ diff --git a/src/components/edit/thematic/ThematicDialog.js b/src/components/edit/thematic/ThematicDialog.js index 263c4120f..7470902a9 100644 --- a/src/components/edit/thematic/ThematicDialog.js +++ b/src/components/edit/thematic/ThematicDialog.js @@ -36,6 +36,7 @@ import { } from '../../../util/analytics.js' import { isPeriodAvailable } from '../../../util/periods.js' import { getStartEndDateError } from '../../../util/time.js' +import CalculationSelect from '../../calculations/CalculationSelect.js' import NumericLegendStyle from '../../classification/NumericLegendStyle.js' import { Tab, Tabs } from '../../core/index.js' import DataElementGroupSelect from '../../dataElement/DataElementGroupSelect.js' @@ -255,6 +256,7 @@ class ThematicDialog extends Component { dataElementError, dataSetError, programError, + calculationError, eventDataItemError, programIndicatorError, periodTypeError, @@ -408,6 +410,14 @@ class ThematicDialog extends Component { /> ), ]} + {valueType === dimConf.calculation.objectName && ( + + )} @@ -609,6 +619,14 @@ class ThematicDialog extends Component { } } + if (valueType === dimConf.calculation.objectName && !dataItem) { + return this.setErrorState( + 'calculationError', + i18n.t('Calculation is required'), + 'data' + ) + } + if (!period && periodType !== START_END_DATES) { return this.setErrorState( 'periodError', diff --git a/src/components/edit/thematic/ValueTypeSelect.js b/src/components/edit/thematic/ValueTypeSelect.js index 018b87ab8..0726f9b86 100644 --- a/src/components/edit/thematic/ValueTypeSelect.js +++ b/src/components/edit/thematic/ValueTypeSelect.js @@ -1,11 +1,29 @@ import i18n from '@dhis2/d2-i18n' import PropTypes from 'prop-types' -import React from 'react' +import React, { useMemo } from 'react' import { dimConf } from '../../../constants/dimension.js' import { SelectField } from '../../core/index.js' -const ValueTypeSelect = (props) => { - const { value, onChange, className } = props +const getValueTypes = () => [ + { id: dimConf.indicator.objectName, name: i18n.t('Indicator') }, + { id: dimConf.dataElement.objectName, name: i18n.t('Data element') }, + { id: dimConf.dataSet.objectName, name: i18n.t('Reporting rates') }, + { + id: dimConf.eventDataItem.objectName, + name: i18n.t('Event data items'), + }, + { + id: dimConf.programIndicator.objectName, + name: i18n.t('Program indicators'), + }, + { + id: dimConf.calculation.objectName, + name: i18n.t('Calculations'), + }, +] + +const ValueTypeSelect = ({ value, onChange, className }) => { + const items = useMemo(() => getValueTypes(), []) // If value type is data element operand, make it data element const type = @@ -13,21 +31,6 @@ const ValueTypeSelect = (props) => { ? dimConf.dataElement.objectName : value - // TODO: Avoid creating on each render (needs to be created after i18next contains translations - const items = [ - { id: dimConf.indicator.objectName, name: i18n.t('Indicator') }, - { id: dimConf.dataElement.objectName, name: i18n.t('Data element') }, - { id: dimConf.dataSet.objectName, name: i18n.t('Reporting rates') }, - { - id: dimConf.eventDataItem.objectName, - name: i18n.t('Event data items'), - }, - { - id: dimConf.programIndicator.objectName, - name: i18n.t('Program indicators'), - }, - ] - return ( { value={type} onChange={(valueType) => onChange(valueType.id)} className={className} + dataTest="thematic-layer-value-type-select" /> ) } diff --git a/src/constants/dimension.js b/src/constants/dimension.js index 16b6d48ae..3834de2b0 100644 --- a/src/constants/dimension.js +++ b/src/constants/dimension.js @@ -64,6 +64,12 @@ export const dimConf = { objectName: 'pi', itemType: 'PROGRAM_INDICATOR', }, + calculation: { + value: 'expressionDimensionItems', + dimensionName: 'dx', + objectName: 'ed', // Created by Bjorn, don't seem to be in use when the map is saved + itemType: 'EXPRESSION_DIMENSION_ITEM', + }, period: { id: 'period', value: 'period', From 3b5b3861dfe053919022a3c118ddbc9f692d80b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Sep 2023 07:30:24 +0000 Subject: [PATCH 3/3] chore(deps-dev): bump cypress from 12.16.0 to 12.17.4 Bumps [cypress](https://github.com/cypress-io/cypress) from 12.16.0 to 12.17.4. - [Release notes](https://github.com/cypress-io/cypress/releases) - [Changelog](https://github.com/cypress-io/cypress/blob/develop/CHANGELOG.md) - [Commits](https://github.com/cypress-io/cypress/compare/v12.16.0...v12.17.4) --- updated-dependencies: - dependency-name: cypress dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 53 +++++++++++++++++++++++++++++++--------------------- 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index 90d10a274..b256755ea 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "@dhis2/cypress-commands": "^10.0.3", "@dhis2/cypress-plugins": "^10.0.3", "@testing-library/react": "^12.1.5", - "cypress": "^12.16.0", + "cypress": "^12.17.4", "cypress-tags": "^1.1.2", "cypress-wait-until": "^1.7.2", "d2-i18n-extract": "^1.0.5", diff --git a/yarn.lock b/yarn.lock index b886043cb..934bbb425 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1403,10 +1403,10 @@ through2 "^2.0.0" watchify "^4.0.0" -"@cypress/request@^2.88.10": - version "2.88.11" - resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.11.tgz#5a4c7399bc2d7e7ed56e92ce5acb620c8b187047" - integrity sha512-M83/wfQ1EkspjkE2lNWNV5ui2Cv7UCv1swW1DqljahbzLVWltcsexQh8jYtuS/vzFXP+HySntGM83ZXA9fn17w== +"@cypress/request@2.88.12": + version "2.88.12" + resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.12.tgz#ba4911431738494a85e93fb04498cb38bc55d590" + integrity sha512-tOn+0mDZxASFM+cuAP9szGUGPI1HwWVSvdzm7V4cCsPdFTx6qMj29CwaQmRAMIEhORIUBFBsYROYJcveK4uOjA== dependencies: aws-sign2 "~0.7.0" aws4 "^1.8.0" @@ -1423,7 +1423,7 @@ performance-now "^2.1.0" qs "~6.10.3" safe-buffer "^5.1.2" - tough-cookie "~2.5.0" + tough-cookie "^4.1.3" tunnel-agent "^0.6.0" uuid "^8.3.2" @@ -3498,10 +3498,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.14.2.tgz#c076ed1d7b6095078ad3cf21dfeea951842778b1" integrity sha512-1uEQxww3DaghA0RxqHx0O0ppVlo43pJhepY51OxuQIKHpjbnYLA7vcdwioNPzIqmC2u3I/dmylcqjlh0e7AyUA== -"@types/node@^14.14.31": - version "14.18.36" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.36.tgz#c414052cb9d43fab67d679d5f3c641be911f5835" - integrity sha512-FXKWbsJ6a1hIrRxv+FoukuHnGTgEzKYGi7kilfMae96AL9UNkPFNWJEEYWzdRI9ooIkbr4AKldyuSTLql06vLQ== +"@types/node@^16.18.39": + version "16.18.50" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.50.tgz#93003cf0251a2ecd26dad6dc757168d648519805" + integrity sha512-OiDU5xRgYTJ203v4cprTs0RwOCd5c5Zjv+K5P8KSqfiCsB1W3LcamTUMcnQarpq5kOYbhHfSOgIEJvdPyb5xyw== "@types/normalize-package-data@^2.4.0": version "2.4.1" @@ -6238,14 +6238,14 @@ cypress-wait-until@^1.7.2: resolved "https://registry.yarnpkg.com/cypress-wait-until/-/cypress-wait-until-1.7.2.tgz#7f534dd5a11c89b65359e7a0210f20d3dfc22107" integrity sha512-uZ+M8/MqRcpf+FII/UZrU7g1qYZ4aVlHcgyVopnladyoBrpoaMJ4PKZDrdOJ05H5RHbr7s9Tid635X3E+ZLU/Q== -cypress@^12.16.0: - version "12.16.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-12.16.0.tgz#d0dcd0725a96497f4c60cf54742242259847924c" - integrity sha512-mwv1YNe48hm0LVaPgofEhGCtLwNIQEjmj2dJXnAkY1b4n/NE9OtgPph4TyS+tOtYp5CKtRmDvBzWseUXQTjbTg== +cypress@^12.17.4: + version "12.17.4" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-12.17.4.tgz#b4dadf41673058493fa0d2362faa3da1f6ae2e6c" + integrity sha512-gAN8Pmns9MA5eCDFSDJXWKUpaL3IDd89N9TtIupjYnzLSmlpVr+ZR+vb4U/qaMp+lB6tBvAmt7504c3Z4RU5KQ== dependencies: - "@cypress/request" "^2.88.10" + "@cypress/request" "2.88.12" "@cypress/xvfb" "^1.2.4" - "@types/node" "^14.14.31" + "@types/node" "^16.18.39" "@types/sinonjs__fake-timers" "8.1.1" "@types/sizzle" "^2.3.2" arch "^2.2.0" @@ -6278,9 +6278,10 @@ cypress@^12.16.0: minimist "^1.2.8" ospath "^1.2.2" pretty-bytes "^5.6.0" + process "^0.11.10" proxy-from-env "1.0.0" request-progress "^3.0.0" - semver "^7.3.2" + semver "^7.5.3" supports-color "^8.1.1" tmp "~0.2.1" untildify "^4.0.0" @@ -12692,7 +12693,7 @@ process-nextick-args@^2.0.0, process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -process@~0.11.0: +process@^0.11.10, process@~0.11.0: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== @@ -13878,10 +13879,10 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8: - version "7.3.8" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" - integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== +semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.3: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== dependencies: lru-cache "^6.0.0" @@ -15077,6 +15078,16 @@ tough-cookie@^4.0.0: universalify "^0.2.0" url-parse "^1.5.3" +tough-cookie@^4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf" + integrity sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw== + dependencies: + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.2.0" + url-parse "^1.5.3" + tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"