diff --git a/.github/workflows/dhis2-preview-pr.yml b/.github/workflows/dhis2-preview-pr.yml index 27a52daf87..e5bf971cce 100644 --- a/.github/workflows/dhis2-preview-pr.yml +++ b/.github/workflows/dhis2-preview-pr.yml @@ -24,7 +24,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: - node-version: 14.x + node-version: 18.x - uses: c-hive/gha-yarn-cache@v1 - run: yarn install --frozen-lockfile diff --git a/.github/workflows/dhis2-verify-app.yml b/.github/workflows/dhis2-verify-app.yml index 20a4e735fe..c87c7cb631 100644 --- a/.github/workflows/dhis2-verify-app.yml +++ b/.github/workflows/dhis2-verify-app.yml @@ -82,6 +82,7 @@ jobs: REPORTPORTAL_PROJECT: ${{ vars.REPORTPORTAL_PROJECT }} call-workflow-e2e-prod: + if: "!contains(github.event.head_commit.message, '[skip ci]')" needs: [build, lint, test] uses: dhis2/workflows/.github/workflows/analytics-e2e-tests-prod.yml@master secrets: @@ -98,7 +99,7 @@ jobs: if: | !github.event.push.repository.fork && github.actor != 'dependabot[bot]' && - github.event_name != 'pull_request' + github.ref == 'refs/heads/master' steps: - uses: actions/checkout@v3 with: @@ -133,13 +134,98 @@ jobs: if: | failure() && !cancelled() && - github.ref == 'refs/heads/master' + github.ref == 'refs/heads/master' && + contains(github.event.head_commit.message, 'chore(release)') steps: + - name: Checkout code + uses: actions/checkout@master + + - name: Extract version + id: extract_version + uses: Saionaro/extract-package-version@v1.2.1 + - name: Send failure message to analytics-internal-bot slack channel id: slack uses: slackapi/slack-github-action@v1.23.0 with: channel-id: ${{ secrets.SLACK_CHANNEL_ID }} - slack-message: ':small_red_triangle_down: Data-visualizer-app release ' + payload: | + { + "text": ":small_red_triangle_down: :data-visualizer-app: Data Visualizer release ", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": ":small_red_triangle_down: :data-visualizer-app: Data Visualizer release " + } + } + ] + } + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + + report-release-success: + runs-on: ubuntu-latest + needs: release + if: | + success() && + !cancelled() && + github.ref == 'refs/heads/master' && + contains(github.event.head_commit.message, 'chore(release)') + steps: + - name: Checkout code + uses: actions/checkout@master + + - name: Extract version + id: extract_version + uses: Saionaro/extract-package-version@v1.2.1 + + - name: Send success message to analytics-internal-bot slack channel + id: slack + uses: slackapi/slack-github-action@v1.23.0 + with: + channel-id: ${{ secrets.SLACK_CHANNEL_ID }} + payload: | + { + "text": "Data Visualizer app release ${{ steps.extract_version.outputs.version }} succeeded", + "blocks": [ + { + "type": "header", + "text": { + "type": "plain_text", + "text": ":large_green_circle: :data-visualizer-app: Data Visualizer version ${{ steps.extract_version.outputs.version }} released :tada:", + "emoji": true + } + }, + { + "type": "divider" + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Release Notes*" + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": ${{ toJSON(github.event.head_commit.message) }} + } + }, + { + "type": "divider" + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "Link to " + } + } + ] + } env: SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index cecf7e271a..e90033b62c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +# [100.4.0](https://github.com/dhis2/data-visualizer-app/compare/v100.3.1...v100.4.0) (2023-12-14) + + +### Bug Fixes + +* **translations:** sync translations from transifex (dev) ([65441f2](https://github.com/dhis2/data-visualizer-app/commit/65441f2b454a6e0f74567b8b7107cad63d594a04)) +* **translations:** sync translations from transifex (dev) ([005be59](https://github.com/dhis2/data-visualizer-app/commit/005be599f7dd4382f0086861d74064aedc49ac29)) + + +### Features + +* cumulative values in PT (DHIS2-5497) ([#2746](https://github.com/dhis2/data-visualizer-app/issues/2746)) ([bff69ab](https://github.com/dhis2/data-visualizer-app/commit/bff69ab9cec7685adad823197de8508e9ae83636)), closes [#1946](https://github.com/dhis2/data-visualizer-app/issues/1946) + ## [100.3.1](https://github.com/dhis2/data-visualizer-app/compare/v100.3.0...v100.3.1) (2023-11-08) diff --git a/cypress.config.js b/cypress.config.js index 6ce2dc5d61..12810c2f08 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -1,3 +1,4 @@ +const fs = require('fs') const { chromeAllowXSiteCookies } = require('@dhis2/cypress-plugins') const { defineConfig } = require('cypress') const { @@ -8,6 +9,28 @@ async function setupNodeEvents(on, config) { chromeAllowXSiteCookies(on, config) excludeByVersionTags(on, config) + // Delete videos for passing tests + on('after:spec', (spec, results) => { + try { + if (results && results.video) { + // Do we have failures for any retry attempts? + const failures = results.tests.some((test) => + test.attempts.some((attempt) => attempt.state === 'failed') + ) + if (!failures) { + // delete the video if the spec passed and no tests retried + fs.unlinkSync(results.video) + } + } + } catch (error) { + if (error.code === 'ENOENT') { + console.log('Video already deleted') + } else { + throw error + } + } + }) + if (!config.env.dhis2InstanceVersion) { throw new Error( 'dhis2InstanceVersion is missing. Check the README for more information.' @@ -59,10 +82,6 @@ module.exports = defineConfig({ testIsolation: false, // Record video video: true, - /* Only compress and upload videos for failures. - * This will save execution time and reduce the risk - * out-of-memory issues on the CI machine */ - videoUploadOnPasses: false, // Enabled to reduce the risk of out-of-memory issues experimentalMemoryManagement: true, // Set to a low number to reduce the risk of out-of-memory issues diff --git a/cypress/elements/common.js b/cypress/elements/common.js index 7b245a202f..2ff7ba0562 100644 --- a/cypress/elements/common.js +++ b/cypress/elements/common.js @@ -12,9 +12,16 @@ const loadingEl = 'dhis2-uicore-circularloader' export const expectAppToNotBeLoading = () => cy.getBySel(loadingEl, { timeout: 15000 }).should('not.exist') -export const clickCheckbox = (target) => +export const checkCheckbox = (target) => cy.getBySel(target).click().find('[type="checkbox"]').should('be.checked') +export const uncheckCheckbox = (target) => + cy + .getBySel(target) + .click() + .find('[type="checkbox"]') + .should('not.be.checked') + export const typeInput = (target, text) => cy.getBySel(target).find('input').type(text) diff --git a/cypress/elements/dimensionsPanel.js b/cypress/elements/dimensionsPanel.js index e795e99d11..73b2e09d01 100644 --- a/cypress/elements/dimensionsPanel.js +++ b/cypress/elements/dimensionsPanel.js @@ -75,6 +75,7 @@ export const expectDimensionToNotHaveSelectedStyle = (dimensionId) => export const expectRecommendedIconToBeVisible = (dimensionId) => cy .getBySel(getDimensionButtonById(dimensionId)) + .scrollIntoView() .findBySel(recommendedIconEl) .should('have.length', 1) .and('be.visible') diff --git a/cypress/elements/optionsModal/index.js b/cypress/elements/optionsModal/index.js index 1f8f7f6bdd..b1a74c55fa 100644 --- a/cypress/elements/optionsModal/index.js +++ b/cypress/elements/optionsModal/index.js @@ -51,19 +51,19 @@ export { } from './axes.js' export { - clickTrendLineCheckbox, + checkTrendLineCheckbox, selectTrendLineType, - clickTargetLineCheckbox, + checkTargetLineCheckbox, setTargetLineValue, setTargetLineLabel, - clickBaseLineCheckbox, + checkBaseLineCheckbox, setBaseLineLabel, setBaseLineValue, } from './lines.js' export { setCustomSubtitle } from './subtitle.js' -export { clickOutliersCheckbox } from './outliers.js' +export { checkOutliersCheckbox } from './outliers.js' export { setItemToAxis, setItemToType } from './series.js' diff --git a/cypress/elements/optionsModal/lines.js b/cypress/elements/optionsModal/lines.js index d7368fa505..6402fa1534 100644 --- a/cypress/elements/optionsModal/lines.js +++ b/cypress/elements/optionsModal/lines.js @@ -1,4 +1,4 @@ -import { clickCheckbox, typeInput } from '../common.js' +import { checkCheckbox, typeInput } from '../common.js' const trendLineCheckboxEl = 'option-trend-line-checkbox' const trendLineSelectEl = 'option-trend-line-select' @@ -10,14 +10,14 @@ const baseLineCheckboxEl = 'option-base-line-checkbox' const baseLineValueInputEl = 'option-base-line-value-input' const baseLineLabelInputEl = 'option-base-line-label-input' -export const clickTrendLineCheckbox = () => clickCheckbox(trendLineCheckboxEl) +export const checkTrendLineCheckbox = () => checkCheckbox(trendLineCheckboxEl) export const selectTrendLineType = (optionName) => { cy.getBySel(trendLineSelectEl).findBySel('dhis2-uicore-select').click() cy.getBySel(trendLineSelectOptionEl).contains(optionName).click() } -export const clickTargetLineCheckbox = () => clickCheckbox(targetLineCheckboxEl) +export const checkTargetLineCheckbox = () => checkCheckbox(targetLineCheckboxEl) export const setTargetLineValue = (text) => typeInput(targetLineValueInputEl, text) @@ -25,7 +25,7 @@ export const setTargetLineValue = (text) => export const setTargetLineLabel = (text) => typeInput(targetLineLabelInputEl, text) -export const clickBaseLineCheckbox = () => clickCheckbox(baseLineCheckboxEl) +export const checkBaseLineCheckbox = () => checkCheckbox(baseLineCheckboxEl) export const setBaseLineValue = (text) => typeInput(baseLineValueInputEl, text) diff --git a/cypress/elements/optionsModal/outliers.js b/cypress/elements/optionsModal/outliers.js index 93db0ea56b..678a4e2e18 100644 --- a/cypress/elements/optionsModal/outliers.js +++ b/cypress/elements/optionsModal/outliers.js @@ -1,5 +1,5 @@ -import { clickCheckbox } from '../common.js' +import { checkCheckbox } from '../common.js' const outliersCheckboxEl = 'option-outliers-enabled-checkbox' -export const clickOutliersCheckbox = () => clickCheckbox(outliersCheckboxEl) +export const checkOutliersCheckbox = () => checkCheckbox(outliersCheckboxEl) diff --git a/cypress/elements/optionsModal/totals.js b/cypress/elements/optionsModal/totals.js new file mode 100644 index 0000000000..bbe931be43 --- /dev/null +++ b/cypress/elements/optionsModal/totals.js @@ -0,0 +1,100 @@ +export const colTotalsOptionEl = 'option-col-totals' +const colSubTotalsOptionEl = 'option-col-subtotals' +const rowTotalsOptionEl = 'option-row-totals' +const rowSubTotalsOptionEl = 'option-row-subtotals' + +export const expectColumnsTotalsToBeDisabled = () => + cy + .getBySel(colTotalsOptionEl) + .find('[type="checkbox"]') + .should('be.disabled') + +export const expectColumnsTotalsToBeEnabled = () => + cy + .getBySel(colTotalsOptionEl) + .find('[type="checkbox"]') + .should('not.be.disabled') + +export const expectColumnsTotalsToBeChecked = () => + cy + .getBySel(colTotalsOptionEl) + .find('[type="checkbox"]') + .should('be.checked') + +export const expectColumnsTotalsToBeUnchecked = () => + cy + .getBySel(colTotalsOptionEl) + .find('[type="checkbox"]') + .should('not.be.checked') + +export const expectColumnsSubTotalsToBeDisabled = () => + cy + .getBySel(colSubTotalsOptionEl) + .find('[type="checkbox"]') + .should('be.disabled') + +export const expectColumnsSubTotalsToBeEnabled = () => + cy + .getBySel(colSubTotalsOptionEl) + .find('[type="checkbox"]') + .should('be.enabled') + +export const expectColumnsSubTotalsToBeChecked = () => + cy + .getBySel(colSubTotalsOptionEl) + .find('[type="checkbox"]') + .should('be.checked') + +export const expectColumnsSubTotalsToBeUnchecked = () => + cy + .getBySel(colSubTotalsOptionEl) + .find('[type="checkbox"]') + .should('not.be.checked') + +export const expectRowsTotalsToBeDisabled = () => + cy + .getBySel(rowTotalsOptionEl) + .find('[type="checkbox"]') + .should('be.disabled') + +export const expectRowsTotalsToBeEnabled = () => + cy + .getBySel(rowTotalsOptionEl) + .find('[type="checkbox"]') + .should('be.enabled') + +export const expectRowsTotalsToBeChecked = () => + cy + .getBySel(rowTotalsOptionEl) + .find('[type="checkbox"]') + .should('be.checked') + +export const expectRowsTotalsToBeUnchecked = () => + cy + .getBySel(rowTotalsOptionEl) + .find('[type="checkbox"]') + .should('not.be.checked') + +export const expectRowsSubTotalsToBeDisabled = () => + cy + .getBySel(rowSubTotalsOptionEl) + .find('[type="checkbox"]') + .should('be.disabled') + +export const expectRowsSubTotalsToBeEnabled = () => + cy + .getBySel(rowSubTotalsOptionEl) + .find('[type="checkbox"]') + .should('be.enabled') + +export const expectRowsSubTotalsToBeChecked = () => + cy + .getBySel(rowSubTotalsOptionEl) + .find('[type="checkbox"]') + .should('be.checked') + +export const expectRowsSubTotalsToBeUnchecked = () => + cy + .getBySel(rowSubTotalsOptionEl) + .find('[type="checkbox"]') + .should('not.be.checked') diff --git a/cypress/elements/pivotTable.js b/cypress/elements/pivotTable.js index e92d64a714..3b41d3982d 100644 --- a/cypress/elements/pivotTable.js +++ b/cypress/elements/pivotTable.js @@ -1,7 +1,14 @@ const valueCellEl = 'visualization-value-cell' +const headerCellEl = 'visualization-column-header' export const clickTableValueCell = (index) => cy.getBySel(valueCellEl).eq(index).click() export const expectTableValueCellsToHaveLength = (length) => cy.getBySel(valueCellEl).should('have.length', length) + +export const expectTableValueCellToContainValue = (index, value) => + cy.getBySel(valueCellEl).eq(index).contains(value) + +export const clickTableHeaderCell = (name) => + cy.getBySel(headerCellEl).contains(name).click() diff --git a/cypress/integration/dimensions/data.cy.js b/cypress/integration/dimensions/data.cy.js index 90c245e4c9..8b8244eba8 100644 --- a/cypress/integration/dimensions/data.cy.js +++ b/cypress/integration/dimensions/data.cy.js @@ -48,159 +48,162 @@ const PAGE_SIZE = 50 const DATA_ITEMS_URL = '**/dataItems*' describe('Data dimension', () => { - describe('initial state', () => { - it('navigates to the start page', () => { - goToStartPage() - }) - it('opens the data dimension modal', () => { - cy.intercept('GET', DATA_ITEMS_URL).as('request') - openDimension(DIMENSION_ID_DATA) - cy.wait('@request').then(({ request, response }) => { - expect(request.url).to.contain('page=1') - expect(response.statusCode).to.eq(200) - expect(response.body.dataItems.length).to.eq(PAGE_SIZE) - }) - expectDataDimensionModalToBeVisible() - }) - it('modal has a title', () => { - expectDimensionModalToContain('Data') - }) - it('no items are selected', () => { - expectNoDataItemsToBeSelected() - }) - it("data type is 'All'", () => { - expectDataTypeToBe('All') - }) - it('group select is not visible', () => { - expectGroupSelectToNotBeVisible() + it('has correct initial state', () => { + // navigates to the start page + goToStartPage() + + // opens the data dimension modal + cy.intercept('GET', DATA_ITEMS_URL).as('request') + openDimension(DIMENSION_ID_DATA) + cy.wait('@request').then(({ request, response }) => { + expect(request.url).to.contain('page=1') + expect(response.statusCode).to.eq(200) + expect(response.body.dataItems.length).to.eq(PAGE_SIZE) }) + expectDataDimensionModalToBeVisible() + + // modal has a title + expectDimensionModalToContain('Data') + + // no items are selected + expectNoDataItemsToBeSelected() + + // data type is 'All' + expectDataTypeToBe('All') + + // group select is not visible + expectGroupSelectToNotBeVisible() + + // an item can be selected by double click const firstPageItemName = TEST_INDICATORS[0].name - it('an item can be selected by double click', () => { - selectItemByDoubleClick(firstPageItemName) - expectItemToBeSelected(firstPageItemName) - }) - it('an item can be unselected by double click', () => { - unselectItemByDoubleClick(firstPageItemName) - expectNoDataItemsToBeSelected() - }) - it('an item can be selected by button', () => { - selectItemByButton(firstPageItemName) - expectItemToBeSelected(firstPageItemName) - }) - it('an item can be unselected by button', () => { - unselectItemByButton(firstPageItemName) - expectNoDataItemsToBeSelected() - }) + selectItemByDoubleClick(firstPageItemName) + expectItemToBeSelected(firstPageItemName) + + // an item can be unselected by double click + unselectItemByDoubleClick(firstPageItemName) + expectNoDataItemsToBeSelected() + + // an item can be selected by button + selectItemByButton(firstPageItemName) + expectItemToBeSelected(firstPageItemName) + + // an item can be unselected by button + unselectItemByButton(firstPageItemName) + expectNoDataItemsToBeSelected() }) - describe('selecting all and fetching more', () => { + it('can select all and fetch more', () => { + goToStartPage() + openDimension(DIMENSION_ID_DATA) + expectDataDimensionModalToBeVisible() + expectSelectableDataItemsAmountToBeLeast(PAGE_SIZE) + const secondPageItemName = 'BCG doses' - it('all items can be selected', () => { - cy.intercept('GET', DATA_ITEMS_URL).as('request') - selectAllItemsByButton() - expectSelectedItemsAmountToBeLeast(PAGE_SIZE) - cy.wait('@request').then(({ request, response }) => { - expect(request.url).to.contain('page=2') - expect(response.statusCode).to.eq(200) - expect(response.body.dataItems.length).to.eq(PAGE_SIZE) - }) - expectSourceToNotBeLoading() + // all items can be selected + cy.intercept('GET', DATA_ITEMS_URL).as('request') + selectAllItemsByButton() + expectSelectedItemsAmountToBeLeast(PAGE_SIZE) + cy.wait('@request').then(({ request, response }) => { + expect(request.url).to.contain('page=2') + expect(response.statusCode).to.eq(200) + expect(response.body.dataItems.length).to.eq(PAGE_SIZE) }) - it('more items are fetched', () => { - expectSelectableDataItemsAmountToBeLeast(PAGE_SIZE) - expectItemToBeSelectable(secondPageItemName) - }) - it('all items can be unselected', () => { - unselectAllItemsByButton() - expectNoDataItemsToBeSelected() - }) - it('more items are fetched when scrolling down', () => { - cy.intercept('GET', DATA_ITEMS_URL).as('request') - scrollSourceToBottom() - cy.wait('@request').then(({ request, response }) => { - expect(request.url).to.contain('page=3') - expect(response.statusCode).to.eq(200) - expect(response.body.dataItems.length).to.eq(PAGE_SIZE) - }) - expectSourceToNotBeLoading() - expectSelectableDataItemsAmountToBeLeast(PAGE_SIZE * 3) + expectSourceToNotBeLoading() + + // more items are fetched + expectSelectableDataItemsAmountToBeLeast(PAGE_SIZE) + expectItemToBeSelectable(secondPageItemName) + + // all items can be unselected + unselectAllItemsByButton() + expectNoDataItemsToBeSelected() + + // more items are fetched when scrolling down + cy.intercept('GET', DATA_ITEMS_URL).as('request') + scrollSourceToBottom() + cy.wait('@request').then(({ request, response }) => { + expect(request.url).to.contain('page=3') + expect(response.statusCode).to.eq(200) + expect(response.body.dataItems.length).to.eq(PAGE_SIZE) }) + expectSourceToNotBeLoading() + expectSelectableDataItemsAmountToBeLeast(PAGE_SIZE * 3) }) - describe('global search', () => { + it('can use global search', () => { + goToStartPage() + openDimension(DIMENSION_ID_DATA) + expectDataDimensionModalToBeVisible() + expectSelectableDataItemsAmountToBeLeast(PAGE_SIZE) + const testSearchTerm = 'Dispenser' // Use a data element for the third step to work - it('receives a search term', () => { - cy.intercept('GET', DATA_ITEMS_URL).as('request') - inputSearchTerm(testSearchTerm) - cy.wait('@request').then(({ request, response }) => { - expect(request.url).to.contain('page=1') - expect(request.url).to.contain(testSearchTerm) - expect(response.statusCode).to.eq(200) - expect(response.body.dataItems.length).to.eq(1) - }) - expectSourceToNotBeLoading() + // receives a search term + cy.intercept('GET', DATA_ITEMS_URL).as('request') + inputSearchTerm(testSearchTerm) + cy.wait('@request').then(({ request, response }) => { + expect(request.url).to.contain('page=1') + expect(request.url).to.contain(testSearchTerm) + expect(response.statusCode).to.eq(200) + expect(response.body.dataItems.length).to.eq(1) }) + expectSourceToNotBeLoading() + // TODO: Test that the search is only called once, i.e. debounce works - it('search result is displayed', () => { - expectSelectableDataItemsAmountToBe(1) - expectItemToBeSelectable(testSearchTerm) + // search result is displayed + expectSelectableDataItemsAmountToBe(1) + expectItemToBeSelectable(testSearchTerm) + + // search result is maintained when switching data type + cy.intercept('GET', '**/dataElements*').as('dataElements') + switchDataTypeTo('Data elements') + cy.wait('@dataElements').then(({ request, response }) => { + expect(request.url).to.contain('page=1') + expect(response.statusCode).to.eq(200) + expect(response.body.dataElements.length).to.be.eq(1) }) - it('search result is maintained when switching data type', () => { - cy.intercept('GET', '**/dataElements*').as('dataElements') - switchDataTypeTo('Data elements') - cy.wait('@dataElements').then(({ request, response }) => { - expect(request.url).to.contain('page=1') - expect(response.statusCode).to.eq(200) - expect(response.body.dataElements.length).to.be.eq(1) - }) - expectSourceToNotBeLoading() - expectSelectableDataItemsAmountToBe(1) - expectItemToBeSelectable(testSearchTerm) - clearSearchTerm() - cy.wait('@dataElements').then(({ request, response }) => { - expect(request.url).to.contain('page=1') - expect(response.statusCode).to.eq(200) - expect(response.body.dataElements.length).to.be.eq(PAGE_SIZE) - }) - expectSourceToNotBeLoading() - cy.intercept('GET', DATA_ITEMS_URL).as('dataItems') - switchDataTypeToAll() - cy.wait('@dataItems').then(({ request, response }) => { - expect(request.url).to.contain('page=1') - expect(response.statusCode).to.eq(200) - expect(response.body.dataItems.length).to.eq(PAGE_SIZE) - }) - expectSourceToNotBeLoading() - expectSelectableDataItemsAmountToBeLeast(PAGE_SIZE) + expectSourceToNotBeLoading() + expectSelectableDataItemsAmountToBe(1) + expectItemToBeSelectable(testSearchTerm) + clearSearchTerm() + cy.wait('@dataElements').then(({ request, response }) => { + expect(request.url).to.contain('page=1') + expect(response.statusCode).to.eq(200) + expect(response.body.dataElements.length).to.be.eq(PAGE_SIZE) }) - it('search displays a correct error message', () => { - const testSearchTermWithNoMatch = 'nomatch' - cy.intercept('GET', DATA_ITEMS_URL).as('request') - inputSearchTerm(testSearchTermWithNoMatch) - cy.wait('@request').then(({ request, response }) => { - expect(request.url).to.contain('page=1') - expect(request.url).to.contain(testSearchTermWithNoMatch) - expect(response.statusCode).to.eq(200) - expect(response.body.dataItems.length).to.eq(0) - }) - expectSourceToNotBeLoading() - expectEmptySourceMessageToBe( - `Nothing found for "${testSearchTermWithNoMatch}"` - ) + expectSourceToNotBeLoading() + cy.intercept('GET', DATA_ITEMS_URL).as('dataItems') + switchDataTypeToAll() + cy.wait('@dataItems').then(({ request, response }) => { + expect(request.url).to.contain('page=1') + expect(response.statusCode).to.eq(200) + expect(response.body.dataItems.length).to.eq(PAGE_SIZE) }) - it('search result can be cleared', () => { - cy.intercept('GET', DATA_ITEMS_URL).as('request') - clearSearchTerm() - cy.wait('@request').then(({ request, response }) => { - expect(request.url).to.contain('page=1') - expect(response.statusCode).to.eq(200) - expect(response.body.dataItems.length).to.be.eq(PAGE_SIZE) - }) - expectSourceToNotBeLoading() - expectSelectableDataItemsAmountToBeLeast(PAGE_SIZE) + expectSelectableDataItemsAmountToBeLeast(PAGE_SIZE) + expectSourceToNotBeLoading() + + // search displays a correct error message + const testSearchTermWithNoMatch = 'nomatch' + cy.intercept('GET', DATA_ITEMS_URL).as('requestNoMatch') + inputSearchTerm(testSearchTermWithNoMatch) + cy.wait('@requestNoMatch').then(({ request, response }) => { + expect(request.url).to.contain('page=1') + expect(request.url).to.contain(testSearchTermWithNoMatch) + expect(response.statusCode).to.eq(200) + expect(response.body.dataItems.length).to.eq(0) }) - it('modal is closed', () => { - clickDimensionModalHideButton() - expectDimensionModalToNotBeVisible() + expectSourceToNotBeLoading() + expectEmptySourceMessageToBe( + `Nothing found for "${testSearchTermWithNoMatch}"` + ) + + // search result can be cleared + cy.intercept('GET', DATA_ITEMS_URL).as('requestClear') + clearSearchTerm() + cy.wait('@requestClear').then(({ request, response }) => { + expect(request.url).to.contain('page=1') + expect(response.statusCode).to.eq(200) + expect(response.body.dataItems.length).to.be.eq(PAGE_SIZE) }) + expectSourceToNotBeLoading() + expectSelectableDataItemsAmountToBeLeast(PAGE_SIZE) }) const testDataTypes = [ { @@ -251,7 +254,7 @@ describe('Data dimension', () => { { name: 'Event data items', testGroup: { name: 'Information Campaign', itemAmount: 6 }, - testItem: { name: 'Diagnosis (ICD-10)' }, + testItem: { name: 'E2E TE program 1 First name' }, defaultGroup: { name: 'All programs' }, endpoint: { hasMultiplePages: true, @@ -272,89 +275,92 @@ describe('Data dimension', () => { }, ] testDataTypes.forEach((testDataType) => { - describe(`${testDataType.name}`, () => { - it('opens the data dimension modal', () => { - openDimension(DIMENSION_ID_DATA) - expectDataDimensionModalToBeVisible() + it(`displays ${testDataType.name} correctly`, () => { + // opens the data dimension modal + goToStartPage() + openDimension(DIMENSION_ID_DATA) + expectDataDimensionModalToBeVisible() + + // switches to type + cy.log(`type: ${testDataType.name}`) + cy.intercept('GET', testDataType.endpoint.requestUrl).as('request') + switchDataTypeTo(testDataType.name) + cy.wait('@request').then(({ response }) => { + expect(response.statusCode).to.eq(200) + expect( + response.body[testDataType.endpoint.responseBody].length + ).to.be.least(1) }) - it(`switches to ${testDataType.name}`, () => { + expectSourceToNotBeLoading() + expectSelectableDataItemsAmountToBeLeast(PAGE_SIZE) + + // group select is visible + expectGroupSelectToBeVisible() + expectGroupSelectToBe(testDataType.defaultGroup.name) + + if (testDataType.endpoint.hasMultiplePages) { + // more items are fetched when scrolling down cy.intercept('GET', testDataType.endpoint.requestUrl).as( 'request' ) - switchDataTypeTo(testDataType.name) - cy.wait('@request').then(({ response }) => { + scrollSourceToBottom() + cy.wait('@request').then(({ request, response }) => { + expect(request.url).to.contain('page=2') expect(response.statusCode).to.eq(200) expect( response.body[testDataType.endpoint.responseBody].length ).to.be.least(1) }) expectSourceToNotBeLoading() - expectSelectableDataItemsAmountToBeLeast(PAGE_SIZE) - }) - it('group select is visible', () => { - expectGroupSelectToBeVisible() - expectGroupSelectToBe(testDataType.defaultGroup.name) - }) - if (testDataType.endpoint.hasMultiplePages) { - it('more items are fetched when scrolling down', () => { - cy.intercept('GET', testDataType.endpoint.requestUrl).as( - 'request' - ) - scrollSourceToBottom() - cy.wait('@request').then(({ request, response }) => { - expect(request.url).to.contain('page=2') - expect(response.statusCode).to.eq(200) - expect( - response.body[testDataType.endpoint.responseBody] - .length - ).to.be.least(1) - }) - expectSourceToNotBeLoading() - expectSelectableDataItemsAmountToBeLeast(PAGE_SIZE + 1) - }) + expectSelectableDataItemsAmountToBeLeast(PAGE_SIZE + 1) } - it('an item can be selected', () => { - expectDataItemsToBeInSource([testDataType.testItem.name]) - selectItemByDoubleClick(testDataType.testItem.name) - expectItemToBeSelected(testDataType.testItem.name) - }) - it(`group can be changed to "${testDataType.testGroup.name}"`, () => { - cy.intercept('GET', testDataType.endpoint.requestUrl).as( - 'request' - ) - switchGroupTo(testDataType.testGroup.name) - cy.wait('@request').then(({ request, response }) => { - expect(request.url).to.contain('page=1') - expect(response.statusCode).to.eq(200) - }) - expectSourceToNotBeLoading() - expectGroupSelectToBe(testDataType.testGroup.name) - expectSelectableDataItemsAmountToBe( - testDataType.testGroup.itemAmount - ) - expectItemToBeSelected(testDataType.testItem.name) - }) - it('the first item can be selected', () => { - selectFirstDataItem() - expectSelectedItemsAmountToBe(2) - expectSelectableDataItemsAmountToBe( - testDataType.testGroup.itemAmount - 1 - ) + // an item can be selected + expectDataItemsToBeInSource([testDataType.testItem.name]) + selectItemByDoubleClick(testDataType.testItem.name) + expectItemToBeSelected(testDataType.testItem.name) + + // group can be changed + cy.log(`group: ${testDataType.testGroup.name}`) + cy.intercept('GET', testDataType.endpoint.requestUrl).as( + 'requestWithGroup' + ) + switchGroupTo(testDataType.testGroup.name) + cy.wait('@requestWithGroup').then(({ request, response }) => { + expect(request.url).to.contain('page=1') + expect(response.statusCode).to.eq(200) }) + expectSourceToNotBeLoading() + expectGroupSelectToBe(testDataType.testGroup.name) + cy.getBySel('data-dimension-transfer-sourceoptions').trigger( + 'mouseover' + ) // seems to trigger Cypress to refretch the list and update the result so it's no longer stale + expectSelectableDataItemsAmountToBe( + testDataType.testGroup.itemAmount + ) + expectItemToBeSelected(testDataType.testItem.name) + + // the first item can be selected + selectFirstDataItem() + expectSelectedItemsAmountToBe(2) + expectSelectableDataItemsAmountToBe( + testDataType.testGroup.itemAmount - 1 + ) + if (['Data elements', 'Data sets'].includes(testDataType.name)) { - it('sub group select is visible', () => { - expectSubGroupSelectToBeVisible() - expectSubGroupSelectToBe(testDataType.defaultSubGroup.name) - }) + // sub group select is visible + expectSubGroupSelectToBeVisible() + expectSubGroupSelectToBe(testDataType.defaultSubGroup.name) - it(`sub group can be changed to "${testDataType.testSubGroup.name}"`, () => { - cy.intercept( - 'GET', - testDataType.testSubGroup.endpoint?.requestUrl || - testDataType.endpoint.requestUrl - ).as('request') - switchSubGroupTo(testDataType.testSubGroup.name) - cy.wait('@request').then(({ request, response }) => { + // sub group can be changed + cy.log(`sub group: ${testDataType.testSubGroup.name}`) + cy.intercept( + 'GET', + testDataType.testSubGroup.endpoint?.requestUrl || + testDataType.endpoint.requestUrl + ).as('requestWithSubGroup') + switchSubGroupTo(testDataType.testSubGroup.name) + cy.wait('@requestWithSubGroup').then( + ({ request, response }) => { expect(request.url).to.contain('page=1') expect(response.statusCode).to.eq(200) expect( @@ -364,92 +370,94 @@ describe('Data dimension', () => { testDataType.endpoint.responseBody ].length ).to.be.eq(testDataType.testSubGroup.itemAmount) - }) - expectSourceToNotBeLoading() - expectSubGroupSelectToBe(testDataType.testSubGroup.name) - expectSelectableDataItemsAmountToBe( - testDataType.testSubGroup.itemAmount - ) - expectItemToBeSelected(testDataType.testItem.name) - }) - it(`sub group can be changed back to "${testDataType.defaultSubGroup.name}"`, () => { - cy.intercept('GET', testDataType.endpoint.requestUrl).as( - 'request' - ) - switchSubGroupTo(testDataType.defaultSubGroup.name) - cy.wait('@request').then(({ request, response }) => { - expect(request.url).to.contain('page=1') - expect(response.statusCode).to.eq(200) - }) - expectSourceToNotBeLoading() - expectSubGroupSelectToBe(testDataType.defaultSubGroup.name) - expectSelectableDataItemsAmountToBe( - testDataType.testGroup.itemAmount - 1 - ) - expectItemToBeSelected(testDataType.testItem.name) - }) - } - it('search displays a correct error message', () => { - const testSearchTermWithNoMatch = 'nomatch' + } + ) + expectSourceToNotBeLoading() + expectSubGroupSelectToBe(testDataType.testSubGroup.name) + expectSelectableDataItemsAmountToBe( + testDataType.testSubGroup.itemAmount + ) + expectItemToBeSelected(testDataType.testItem.name) + + // sub group can be changed back to default + cy.log( + `default sub group: ${testDataType.defaultSubGroup.name}` + ) cy.intercept('GET', testDataType.endpoint.requestUrl).as( 'request' ) - inputSearchTerm(testSearchTermWithNoMatch) + switchSubGroupTo(testDataType.defaultSubGroup.name) cy.wait('@request').then(({ request, response }) => { expect(request.url).to.contain('page=1') - expect(request.url).to.contain(testSearchTermWithNoMatch) expect(response.statusCode).to.eq(200) - expect( - response.body[testDataType.endpoint.responseBody].length - ).to.eq(0) }) expectSourceToNotBeLoading() - expectEmptySourceMessageToBe( - `No ${testDataType.name.toLowerCase()} found for "${testSearchTermWithNoMatch}"` - ) - }) - it('selection and filter can be reset', () => { - unselectAllItemsByButton() - expectNoDataItemsToBeSelected() - cy.intercept('GET', testDataType.endpoint.requestUrl).as( - testDataType.endpoint.requestUrl - ) - clearSearchTerm() - cy.wait(`@${testDataType.endpoint.requestUrl}`).then( - ({ request, response }) => { - expect(request.url).to.contain('page=1') - expect(response.statusCode).to.eq(200) - } - ) - expectSourceToNotBeLoading() + expectSubGroupSelectToBe(testDataType.defaultSubGroup.name) expectSelectableDataItemsAmountToBe( - testDataType.testGroup.itemAmount - ) - switchGroupToAll() - cy.wait(`@${testDataType.endpoint.requestUrl}`).then( - ({ request, response }) => { - expect(request.url).to.contain('page=1') - expect(response.statusCode).to.eq(200) - } + testDataType.testGroup.itemAmount - 1 ) - expectSourceToNotBeLoading() - expectSelectableDataItemsAmountToBeLeast(PAGE_SIZE) - if (testDataType.endpoint.requestUrl !== DATA_ITEMS_URL) { - cy.intercept('GET', DATA_ITEMS_URL).as('**/dataItems*') + expectItemToBeSelected(testDataType.testItem.name) + } + // search displays a correct error message + const testSearchTermWithNoMatch = 'nomatch' + cy.intercept('GET', testDataType.endpoint.requestUrl).as( + 'requestNoMatch' + ) + inputSearchTerm(testSearchTermWithNoMatch) + cy.wait('@requestNoMatch').then(({ request, response }) => { + expect(request.url).to.contain('page=1') + expect(request.url).to.contain(testSearchTermWithNoMatch) + expect(response.statusCode).to.eq(200) + expect( + response.body[testDataType.endpoint.responseBody].length + ).to.eq(0) + }) + expectSourceToNotBeLoading() + expectEmptySourceMessageToBe( + `No ${testDataType.name.toLowerCase()} found for "${testSearchTermWithNoMatch}"` + ) + + // selection and filter can be reset + unselectAllItemsByButton() + expectNoDataItemsToBeSelected() + cy.intercept('GET', testDataType.endpoint.requestUrl).as( + testDataType.endpoint.requestUrl + ) + clearSearchTerm() + cy.wait(`@${testDataType.endpoint.requestUrl}`).then( + ({ request, response }) => { + expect(request.url).to.contain('page=1') + expect(response.statusCode).to.eq(200) } - switchDataTypeToAll() - cy.wait('@**/dataItems*').then(({ request, response }) => { + ) + expectSourceToNotBeLoading() + expectSelectableDataItemsAmountToBe( + testDataType.testGroup.itemAmount + ) + switchGroupToAll() + cy.wait(`@${testDataType.endpoint.requestUrl}`).then( + ({ request, response }) => { expect(request.url).to.contain('page=1') expect(response.statusCode).to.eq(200) - expect(response.body.dataItems.length).to.eq(PAGE_SIZE) - }) - expectSourceToNotBeLoading() - expectSelectableDataItemsAmountToBeLeast(PAGE_SIZE) - }) - it('modal is closed', () => { - clickDimensionModalHideButton() - expectDimensionModalToNotBeVisible() + } + ) + expectSourceToNotBeLoading() + expectSelectableDataItemsAmountToBeLeast(PAGE_SIZE) + if (testDataType.endpoint.requestUrl !== DATA_ITEMS_URL) { + cy.intercept('GET', DATA_ITEMS_URL).as('**/dataItems*') + } + switchDataTypeToAll() + cy.wait('@**/dataItems*').then(({ request, response }) => { + expect(request.url).to.contain('page=1') + expect(response.statusCode).to.eq(200) + expect(response.body.dataItems.length).to.eq(PAGE_SIZE) }) + expectSourceToNotBeLoading() + expectSelectableDataItemsAmountToBeLeast(PAGE_SIZE) + + // modal is closed + clickDimensionModalHideButton() + expectDimensionModalToNotBeVisible() }) }) }) diff --git a/cypress/integration/dimensions/dynamic.cy.js b/cypress/integration/dimensions/dynamic.cy.js index 44e722fbc6..d632357538 100644 --- a/cypress/integration/dimensions/dynamic.cy.js +++ b/cypress/integration/dimensions/dynamic.cy.js @@ -20,11 +20,8 @@ import { expectDimensionToHaveItemAmount, } from '../../elements/layout.js' import { goToStartPage } from '../../elements/startScreen.js' -import { TEST_DATA_ELEMENTS } from '../../utils/data.js' -import { getRandomArrayItem } from '../../utils/random.js' import { expectWindowConfigSeriesToHaveLength } from '../../utils/window.js' -const TEST_DATA_ELEMENT_NAME = getRandomArrayItem(TEST_DATA_ELEMENTS).name const TEST_DYNAMIC_DIMENSION = { id: 'J5jldMd8OHv', name: 'Facility type', @@ -32,15 +29,16 @@ const TEST_DYNAMIC_DIMENSION = { } describe(`Dynamic dimension - ${TEST_DYNAMIC_DIMENSION.name}`, () => { - it('navigates to the start page and adds a data item', () => { + it('can add and remove items, which persist after saving', () => { + cy.log('navigates to the start page and adds a data item') goToStartPage() openDimension(DIMENSION_ID_DATA) - selectDataElements([TEST_DATA_ELEMENT_NAME]) + selectDataElements(['ANC 2nd visit']) clickDimensionModalUpdateButton() expectVisualizationToBeVisible(VIS_TYPE_COLUMN) - }) - const TEST_ITEM = 'Hospital' - it('adds an item manually', () => { + + const TEST_ITEM = 'Hospital' + cy.log('adds an item manually') openDimension(TEST_DYNAMIC_DIMENSION.id) expectDimensionModalToBeVisible(TEST_DYNAMIC_DIMENSION.id) expectManualSelectionToBeChecked() @@ -49,8 +47,8 @@ describe(`Dynamic dimension - ${TEST_DYNAMIC_DIMENSION.name}`, () => { expectVisualizationToBeVisible(VIS_TYPE_COLUMN) expectWindowConfigSeriesToHaveLength(1) expectDimensionToHaveItemAmount(TEST_DYNAMIC_DIMENSION.id, 1) - }) - it('adds all items automatically', () => { + + cy.log('adds all items automatically') openDimension(TEST_DYNAMIC_DIMENSION.id) expectDimensionModalToBeVisible(TEST_DYNAMIC_DIMENSION.id) expectManualSelectionToBeChecked() @@ -60,8 +58,8 @@ describe(`Dynamic dimension - ${TEST_DYNAMIC_DIMENSION.name}`, () => { expectVisualizationToBeVisible(VIS_TYPE_COLUMN) expectWindowConfigSeriesToHaveLength(TEST_DYNAMIC_DIMENSION.itemAmount) expectDimensionToHaveAllItemsSelected(TEST_DYNAMIC_DIMENSION.id) - }) - it('switches back to manual and previous item is presisted', () => { + + cy.log('switches back to manual and previous item is persisted') openDimension(TEST_DYNAMIC_DIMENSION.id) expectDimensionModalToBeVisible(TEST_DYNAMIC_DIMENSION.id) expectAutomaticSelectionToBeChecked() @@ -72,8 +70,8 @@ describe(`Dynamic dimension - ${TEST_DYNAMIC_DIMENSION.name}`, () => { expectVisualizationToBeVisible(VIS_TYPE_COLUMN) expectWindowConfigSeriesToHaveLength(1) expectDimensionToHaveItemAmount(TEST_DYNAMIC_DIMENSION.id, 1) - }) - it('switches back to automatic, saving, the selection is presisted', () => { + + cy.log('switches back to automatic, saving, the selection is persisted') openDimension(TEST_DYNAMIC_DIMENSION.id) expectDimensionModalToBeVisible(TEST_DYNAMIC_DIMENSION.id) expectManualSelectionToBeChecked() diff --git a/cypress/integration/options/cumulativeValues.cy.js b/cypress/integration/options/cumulativeValues.cy.js new file mode 100644 index 0000000000..0334f46e7e --- /dev/null +++ b/cypress/integration/options/cumulativeValues.cy.js @@ -0,0 +1,221 @@ +import { + AXIS_ID_COLUMNS, + AXIS_ID_ROWS, + DIMENSION_ID_DATA, + DIMENSION_ID_PERIOD, + VIS_TYPE_PIVOT_TABLE, + visTypeDisplayNames, +} from '@dhis2/analytics' +import { + clickNewCalculationButton, + clickSaveButton, + inputCalculationLabel, + selectOperatorFromListByDoubleClick, + typeInNumberField, +} from '../../elements/calculationsModal.js' +import { checkCheckbox, uncheckCheckbox } from '../../elements/common.js' +import { + clickDimensionModalHideButton, + clickDimensionModalUpdateButton, + selectDataElements, + selectFixedPeriods, + unselectAllItemsByButton, +} from '../../elements/dimensionModal/index.js' +import { openDimension } from '../../elements/dimensionsPanel.js' +import { clickContextMenuMove, openContextMenu } from '../../elements/layout.js' +import { openOptionsModal } from '../../elements/menuBar.js' +import { + OPTIONS_TAB_DATA, + OPTIONS_TAB_LEGEND, + clickOptionsModalHideButton, + clickOptionsModalUpdateButton, + clickOptionsTab, +} from '../../elements/optionsModal/index.js' +import { + colTotalsOptionEl, + expectColumnsTotalsToBeChecked, + expectColumnsTotalsToBeDisabled, + expectColumnsSubTotalsToBeDisabled, + expectRowsTotalsToBeDisabled, + expectRowsSubTotalsToBeDisabled, + expectColumnsTotalsToBeEnabled, + expectColumnsSubTotalsToBeEnabled, + expectRowsTotalsToBeEnabled, + expectRowsSubTotalsToBeEnabled, +} from '../../elements/optionsModal/totals.js' +import { + expectTableValueCellToContainValue, + clickTableHeaderCell, +} from '../../elements/pivotTable.js' +import { goToStartPage } from '../../elements/startScreen.js' +import { changeVisType } from '../../elements/visualizationTypeSelector.js' +import { TEST_DATA_ELEMENTS } from '../../utils/data.js' + +const cumulativeValuesOptionEl = 'option-cumulative-values' + +describe('Options - Cumulative values', () => { + describe('Interaction with other options (only for PT)', () => { + beforeEach(() => { + goToStartPage() + changeVisType(visTypeDisplayNames[VIS_TYPE_PIVOT_TABLE]) + }) + + it('disables/enables Totals, Number type and Legend options when cumulativeValues is checked/unchecked', () => { + openOptionsModal(OPTIONS_TAB_DATA) + checkCheckbox(cumulativeValuesOptionEl) + + // Totals + expectColumnsTotalsToBeDisabled() + expectColumnsSubTotalsToBeDisabled() + expectRowsTotalsToBeDisabled() + expectRowsSubTotalsToBeDisabled() + + // Number type + cy.getBySel('option-number-type-select') + .should('contain', 'Not supported when using cumulative values') + .find('[data-test="dhis2-uicore-select-input"]') + .should('have.class', 'disabled') + + // Legend + clickOptionsTab(OPTIONS_TAB_LEGEND) + cy.getBySel('option-legend') + .should('contain', 'Not supported when using cumulative values') + .find('[type="checkbox"]') + .should('be.disabled') + + clickOptionsTab(OPTIONS_TAB_DATA) + uncheckCheckbox(cumulativeValuesOptionEl) + + // Totals + expectColumnsTotalsToBeEnabled() + expectColumnsSubTotalsToBeEnabled() + expectRowsTotalsToBeEnabled() + expectRowsSubTotalsToBeEnabled() + + // Number type + cy.getBySel('option-number-type-select') + .should( + 'not.contain', + 'Not supported when using cumulative values' + ) + .find('[data-test="dhis2-uicore-select-input"]') + .should('not.have.class', 'disabled') + + // Legend + clickOptionsTab(OPTIONS_TAB_LEGEND) + cy.getBySel('option-legend') + .should( + 'not.contain', + 'Not supported when using cumulative values' + ) + .find('[type="checkbox"]') + .should('not.be.disabled') + + clickOptionsModalHideButton() + }) + + it('disables/enables a total option preserving its state', () => { + openOptionsModal(OPTIONS_TAB_DATA) + checkCheckbox(colTotalsOptionEl) + + expectColumnsTotalsToBeChecked() + + checkCheckbox(cumulativeValuesOptionEl) + + expectColumnsTotalsToBeDisabled() + expectColumnsTotalsToBeChecked() + + uncheckCheckbox(cumulativeValuesOptionEl) + + expectColumnsTotalsToBeEnabled() + expectColumnsTotalsToBeChecked() + + clickOptionsModalHideButton() + }) + }) + + describe('Applying cumulativeValues: Pivot table', () => { + beforeEach(() => { + goToStartPage() + changeVisType(visTypeDisplayNames[VIS_TYPE_PIVOT_TABLE]) + }) + + it('correctly shows the cumulative values', () => { + openContextMenu(DIMENSION_ID_DATA) + clickContextMenuMove(DIMENSION_ID_DATA, AXIS_ID_ROWS) + openContextMenu(DIMENSION_ID_PERIOD) + clickContextMenuMove(DIMENSION_ID_PERIOD, AXIS_ID_COLUMNS) + + // create a calculation to facilitate testing the cumulative values + openDimension(DIMENSION_ID_DATA) + clickNewCalculationButton() + selectOperatorFromListByDoubleClick('Number') + typeInNumberField(1, 1) + inputCalculationLabel('test data for cumulativeValues') + clickSaveButton() + + clickDimensionModalUpdateButton() + + openOptionsModal(OPTIONS_TAB_DATA) + checkCheckbox(cumulativeValuesOptionEl) + clickOptionsModalUpdateButton() + + Array.from({ length: 12 }, (_, i) => i).forEach((i) => + expectTableValueCellToContainValue(i, i + 1) + ) + }) + + it('correctly sort a column with cumulative values', () => { + openContextMenu(DIMENSION_ID_DATA) + clickContextMenuMove(DIMENSION_ID_DATA, AXIS_ID_ROWS) + openContextMenu(DIMENSION_ID_PERIOD) + clickContextMenuMove(DIMENSION_ID_PERIOD, AXIS_ID_COLUMNS) + + const year = new Date().getFullYear().toString() + + openDimension(DIMENSION_ID_PERIOD) + unselectAllItemsByButton() + selectFixedPeriods( + [`October ${year}`, `November ${year}`, `December ${year}`], + 'Monthly' + ) + clickDimensionModalHideButton() + + // create a calculation to facilitate testing the cumulative values + openDimension(DIMENSION_ID_DATA) + clickNewCalculationButton() + selectOperatorFromListByDoubleClick('Number') + typeInNumberField(1, 6000) + inputCalculationLabel( + 'test data for sorting cumulative values sorting' + ) + clickSaveButton() + + selectDataElements([TEST_DATA_ELEMENTS[4].name]) + + clickDimensionModalUpdateButton() + + // sort before cumulative + expectTableValueCellToContainValue(2, '6 000') + expectTableValueCellToContainValue(5, '5 266') + + clickTableHeaderCell(`December ${year}`) + + expectTableValueCellToContainValue(2, '5 266') + expectTableValueCellToContainValue(5, '6 000') + + // sort after cumulative + openOptionsModal(OPTIONS_TAB_DATA) + checkCheckbox(cumulativeValuesOptionEl) + clickOptionsModalUpdateButton() + + expectTableValueCellToContainValue(2, '18 000') + expectTableValueCellToContainValue(5, '18 488') + + clickTableHeaderCell(`December ${year}`) + + expectTableValueCellToContainValue(2, '18 000') + expectTableValueCellToContainValue(5, '18 488') + }) + }) +}) diff --git a/cypress/integration/options/fontStyles.cy.js b/cypress/integration/options/fontStyles.cy.js index 15408d334f..366f519265 100644 --- a/cypress/integration/options/fontStyles.cy.js +++ b/cypress/integration/options/fontStyles.cy.js @@ -20,10 +20,10 @@ import { OPTIONS_TAB_STYLE, OPTIONS_TAB_DATA, OPTIONS_TAB_AXES, - clickTargetLineCheckbox, + checkTargetLineCheckbox, setTargetLineValue, setTargetLineLabel, - clickBaseLineCheckbox, + checkBaseLineCheckbox, setBaseLineLabel, setBaseLineValue, setAxisTitleText, @@ -168,7 +168,6 @@ describe('Options - Font styles', () => { const TEST_SUBTITLE_TEXT = 'S' it('has default value', () => { - expectChartSubtitleToBeVisible() expectWindowConfigSubtitleToBeValue(CONFIG_DEFAULT_SUBTITLE) }) it('opens Options -> Style', () => { @@ -221,7 +220,7 @@ describe('Options - Font styles', () => { }) it('sets target line', () => { cy.log(`Test value: ${TEST_VALUE}`) - clickTargetLineCheckbox() + checkTargetLineCheckbox() setTargetLineLabel(TEST_LABEL) setTargetLineValue(TEST_VALUE) }) @@ -280,7 +279,7 @@ describe('Options - Font styles', () => { }) it('sets base line', () => { cy.log(`Test value: ${TEST_VALUE}`) - clickBaseLineCheckbox() + checkBaseLineCheckbox() setBaseLineLabel(TEST_LABEL) setBaseLineValue(TEST_VALUE) }) diff --git a/cypress/integration/options/icon.cy.js b/cypress/integration/options/icon.cy.js index 541e586eac..ea90015372 100644 --- a/cypress/integration/options/icon.cy.js +++ b/cypress/integration/options/icon.cy.js @@ -9,7 +9,7 @@ import { visTypeDisplayNames, } from '@dhis2/analytics' import { expectVisualizationToBeVisible } from '../../elements/chart.js' -import { clickCheckbox } from '../../elements/common.js' +import { checkCheckbox } from '../../elements/common.js' import { expectSelectableDataItemsAmountToBeLeast, switchDataTypeTo, @@ -98,7 +98,7 @@ describe('Icon', () => { it(`icon shows when option is enabled for ${type}`, () => { // enable the icon openOptionsModal(OPTIONS_TAB_STYLE) - clickCheckbox('option-show-data-item-icon') + checkCheckbox('option-show-data-item-icon') clickOptionsModalHideButton() // find the data item @@ -129,7 +129,7 @@ describe('Icon', () => { it.skip('icon gets correct color when a legend is in use', () => { // enable the icon openOptionsModal(OPTIONS_TAB_STYLE) - clickCheckbox('option-show-data-item-icon') + checkCheckbox('option-show-data-item-icon') // enable the legend clickOptionsTab(OPTIONS_TAB_LEGEND) diff --git a/cypress/integration/options/lines.cy.js b/cypress/integration/options/lines.cy.js index ea8fef0357..3a332aeb84 100644 --- a/cypress/integration/options/lines.cy.js +++ b/cypress/integration/options/lines.cy.js @@ -11,7 +11,7 @@ import { openDimension } from '../../elements/dimensionsPanel.js' import { openOptionsModal } from '../../elements/menuBar.js' import { clickOptionsModalUpdateButton, - clickTrendLineCheckbox, + checkTrendLineCheckbox, OPTIONS_TAB_DATA, selectTrendLineType, } from '../../elements/optionsModal/index.js' @@ -54,7 +54,7 @@ describe('Options - Lines', () => { }) if (index === 0) { it('enables trendline', () => { - clickTrendLineCheckbox() + checkTrendLineCheckbox() }) } it('selects trendline type', () => { diff --git a/cypress/integration/visTypes/scatter.cy.js b/cypress/integration/visTypes/scatter.cy.js index 6fba12f094..d35eba4e66 100644 --- a/cypress/integration/visTypes/scatter.cy.js +++ b/cypress/integration/visTypes/scatter.cy.js @@ -41,7 +41,7 @@ import { } from '../../elements/menuBar.js' import { clickOptionsModalUpdateButton, - clickOutliersCheckbox, + checkOutliersCheckbox, OPTIONS_TAB_AXES, OPTIONS_TAB_OUTLIERS, setAxisRangeMaxValue, @@ -180,7 +180,7 @@ describe('using a Scatter chart', () => { }) it('Options -> Outliers -> enables outliers', () => { openOptionsModal(OPTIONS_TAB_OUTLIERS) - clickOutliersCheckbox() + checkOutliersCheckbox() // TODO: Set more outlier options clickOptionsModalUpdateButton() expectVisualizationToBeVisible(VIS_TYPE_SCATTER) diff --git a/docs/data-visualizer.md b/docs/data-visualizer.md index 2978d41418..3dc516c7a4 100644 --- a/docs/data-visualizer.md +++ b/docs/data-visualizer.md @@ -165,7 +165,7 @@ The display of a visualization can be changed by enabling/disabling and configur | Base line | Displays a horizontal line at the given domain value. Useful for example when you want to visualize how your performance has evolved since the beginning of a process. | | Column sub-totals | Displays sub-totals in a Pivot table for each dimension.
If you only select one dimension, sub-totals will be hidden for those columns. This is because the values will be equal to the sub-totals. | | Column totals | Displays total values in a Pivot table for each column, as well as a total for all values in the table. | -| Cumulative values | Displays cumulative values in Column, Stacked column, Bar, Stacked bar, Line and Area visualizations | +| Cumulative values | Displays cumulative values in Column, Stacked column, Bar, Stacked bar, Line, Area and Pivot Table visualizations | | Custom sort order | Controls the sort order of the values. | | Dimension labels | Shows the dimension names as part of a Pivot table. | | Hide empty categories | Hides the category items with no data from the visualization.
**Before first**: hides missing values only before the first value
**After last**: hides missing values only after the last value
**Before first and after last**: hides missing values only before the first value and after the last value
**All**: hides all missing values
This is useful for example when you create Column and Bar visualizations. | diff --git a/i18n/ar.po b/i18n/ar.po index 16b77191f5..a22426cb11 100644 --- a/i18n/ar.po +++ b/i18n/ar.po @@ -1,16 +1,16 @@ # # Translators: # KRG HIS , 2020 -# Philip Larsen Donnelly, 2020 # Hamza Assada <7amza.it@gmail.com>, 2022 # Viktor Varland , 2022 +# Philip Larsen Donnelly, 2024 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-09-06T13:41:14.540Z\n" +"POT-Creation-Date: 2023-11-13T12:11:28.959Z\n" "PO-Revision-Date: 2019-06-25 18:46+0000\n" -"Last-Translator: Viktor Varland , 2022\n" +"Last-Translator: Philip Larsen Donnelly, 2024\n" "Language-Team: Arabic (https://app.transifex.com/hisp-uio/teams/100509/ar/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -194,7 +194,7 @@ msgid "Only '{{- name}}' in use" msgstr "قيد الإستخدام '{{- name}}' فقط" msgid "Only '{{number}}' in use" -msgstr "قيد الإستخدام '{{- name}}' فقط" +msgstr "قيد الإستخدام '{{number}}' فقط" msgid "All items are selected" msgstr "" @@ -386,6 +386,9 @@ msgstr "تضمين التراكم" msgid "Cumulative values" msgstr "قيم تراكمية" +msgid "Accumulate cell values along rows" +msgstr "" + msgid "Show data item icon" msgstr "" @@ -705,6 +708,9 @@ msgstr "" "رسم البيانات بصريًا على خريطة العالم. تستخدم عناصر البيانات طبقات خريطة " "منفصلة." +msgid "Not supported when using cumulative values" +msgstr "" + msgid "No data available" msgstr "لا توجد بيانات متاحة" @@ -835,6 +841,14 @@ msgstr "" msgid "Something went wrong" msgstr "هناك خطأ ما" +msgid "" +"There's a problem with the generated analytics. Contact a system " +"administrator." +msgstr "" + +msgid "There's a syntax problem with the analytics request." +msgstr "" + msgid "or" msgstr "أو" @@ -997,6 +1011,9 @@ msgstr "الخطوط" msgid "Totals" msgstr "الإجمالي" +msgid "Totals are not supported when using cumulative values" +msgstr "" + msgid "Vertical (y) axis {{axisId}}" msgstr "" diff --git a/i18n/cs.po b/i18n/cs.po index 00fb9f303f..ba438bd71a 100644 --- a/i18n/cs.po +++ b/i18n/cs.po @@ -1,13 +1,14 @@ # # Translators: # Jiří Podhorecký, 2022 +# Philip Larsen Donnelly, 2024 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-09-06T13:41:14.540Z\n" +"POT-Creation-Date: 2023-11-13T12:11:28.959Z\n" "PO-Revision-Date: 2019-06-25 18:46+0000\n" -"Last-Translator: Jiří Podhorecký, 2022\n" +"Last-Translator: Philip Larsen Donnelly, 2024\n" "Language-Team: Czech (https://app.transifex.com/hisp-uio/teams/100509/cs/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -387,6 +388,9 @@ msgstr "Zahrnout kumulativní" msgid "Cumulative values" msgstr "Kumulativní hodnoty" +msgid "Accumulate cell values along rows" +msgstr "" + msgid "Show data item icon" msgstr "" @@ -718,6 +722,9 @@ msgstr "" "Vizuálně vykreslete data na mapě světa. Datové prvky používají samostatné " "mapové vrstvy." +msgid "Not supported when using cumulative values" +msgstr "" + msgid "No data available" msgstr "Údaje nejsou k dispozici" @@ -735,14 +742,14 @@ msgid "Add at least one item to {{axisName}}." msgstr "Přidat alespoň jednu položku do {{axisName}}." msgid "{{columnsAxisName}} and {{rowsAxisName}} are empty" -msgstr "{{columnsAxisName}} a {{linesAxisName}} jsou prázdné" +msgstr "{{columnsAxisName}} a {{rowsAxisName}} jsou prázdné" msgid "" "Add at least one item to {{columnsAxisName}} or {{rowsAxisName}} to create a" " {{visualizationType}}." msgstr "" -"Přidejte alespoň jednu položku do {{columnsAxisName}} nebo {{linesAxisName}}" -" a vytvořte {{visualizationType}}." +"Přidejte alespoň jednu položku do {{columnsAxisName}} nebo {{rowsAxisName}} " +"a vytvořte {{visualizationType}}." msgid "No period selected" msgstr "Není vybráno žádné období" @@ -851,6 +858,14 @@ msgstr "" msgid "Something went wrong" msgstr "Něco se pokazilo" +msgid "" +"There's a problem with the generated analytics. Contact a system " +"administrator." +msgstr "" + +msgid "There's a syntax problem with the analytics request." +msgstr "" + msgid "or" msgstr "nebo" @@ -1017,6 +1032,9 @@ msgstr "Linie" msgid "Totals" msgstr "Součty" +msgid "Totals are not supported when using cumulative values" +msgstr "" + msgid "Vertical (y) axis {{axisId}}" msgstr "Svislá osa (y) {{axisId}}" diff --git a/i18n/en.pot b/i18n/en.pot index 8e76313004..10985d66d0 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2023-09-06T13:41:14.540Z\n" -"PO-Revision-Date: 2023-09-06T13:41:14.540Z\n" +"POT-Creation-Date: 2023-11-13T12:11:28.959Z\n" +"PO-Revision-Date: 2023-11-13T12:11:28.959Z\n" msgid "All items" msgstr "All items" @@ -381,6 +381,9 @@ msgstr "Include cumulative" msgid "Cumulative values" msgstr "Cumulative values" +msgid "Accumulate cell values along rows" +msgstr "Accumulate cell values along rows" + msgid "Show data item icon" msgstr "Show data item icon" @@ -709,6 +712,9 @@ msgstr "Open as Map" msgid "Visually plot data on a world map. Data elements use separate map layers." msgstr "Visually plot data on a world map. Data elements use separate map layers." +msgid "Not supported when using cumulative values" +msgstr "Not supported when using cumulative values" + msgid "No data available" msgstr "No data available" @@ -839,6 +845,16 @@ msgstr "" msgid "Something went wrong" msgstr "Something went wrong" +msgid "" +"There's a problem with the generated analytics. Contact a system " +"administrator." +msgstr "" +"There's a problem with the generated analytics. Contact a system " +"administrator." + +msgid "There's a syntax problem with the analytics request." +msgstr "There's a syntax problem with the analytics request." + msgid "or" msgstr "or" @@ -1002,6 +1018,9 @@ msgstr "Lines" msgid "Totals" msgstr "Totals" +msgid "Totals are not supported when using cumulative values" +msgstr "Totals are not supported when using cumulative values" + msgid "Vertical (y) axis {{axisId}}" msgstr "Vertical (y) axis {{axisId}}" diff --git a/i18n/es.po b/i18n/es.po index 484d4d4474..f79de3b039 100644 --- a/i18n/es.po +++ b/i18n/es.po @@ -1,21 +1,21 @@ # # Translators: # ericbp , 2020 -# Philip Larsen Donnelly, 2020 # Gabriela Rodriguez , 2020 # Pablo Pajuelo Cabezas , 2020 # Prabhjot Singh, 2021 # Viktor Varland , 2021 # Carlos Tejo Alonso, 2022 -# Enzo Nicolas Rossi , 2023 # Janeth Cruz, 2023 +# Enzo Nicolas Rossi , 2024 +# Philip Larsen Donnelly, 2024 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-09-06T13:41:14.540Z\n" +"POT-Creation-Date: 2023-11-13T12:11:28.959Z\n" "PO-Revision-Date: 2019-06-25 18:46+0000\n" -"Last-Translator: Janeth Cruz, 2023\n" +"Last-Translator: Philip Larsen Donnelly, 2024\n" "Language-Team: Spanish (https://app.transifex.com/hisp-uio/teams/100509/es/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -33,7 +33,7 @@ msgid "Untitled {{visualizationType}} visualization, {{date}}" msgstr "Visualización {{visualizationType}} sin título, {{date}}" msgid "\"{{- deletedObject}}\" successfully deleted." -msgstr "\"{{- deleteObject}}\" se eliminó con éxito." +msgstr "\"{{- deletedObject}}\" se eliminó con éxito." msgid "You have unsaved changes." msgstr "Tiene cambios sin guardar." @@ -55,7 +55,7 @@ msgid "Yes, leave" msgstr "Si, salir" msgid "Add to {{axisName}}" -msgstr "Añadir a {{Nombredeleje}}" +msgstr "Añadir a {{axisName}}" msgid "" "'{{visualizationType}}' is intended to show a single data item. Only the " @@ -328,6 +328,8 @@ msgid "" "Number of axis tick steps, including the min and max. A value of 2 or lower " "will be ignored." msgstr "" +"Número de pasos de las marcas de los ejes, incluyendo el mínimo y el máximo." +" Se ignorará un valor de 2 o menor." msgid "Steps" msgstr "Pasos" @@ -395,6 +397,9 @@ msgstr "Incluir acumulado" msgid "Cumulative values" msgstr "Valores acumulativos" +msgid "Accumulate cell values along rows" +msgstr "Acumular valores de celdas a lo largo de las filas" + msgid "Show data item icon" msgstr "Mostrar el icono de elemento de datos" @@ -497,10 +502,10 @@ msgid "Legend type" msgstr "Tipo de leyenda" msgid "Use pre-defined legend by data item" -msgstr "" +msgstr "Utilizar leyenda predefinida por elemento de datos" msgid "Select a legend" -msgstr "" +msgstr "Selecciona una leyenda" msgid "Legend changes background color" msgstr "La leyenda cambia el color de fondo" @@ -729,6 +734,9 @@ msgstr "" "Trazar visualmente los datos en un mapa mundial. Los elementos de datos usan" " capas de mapa separadas." +msgid "Not supported when using cumulative values" +msgstr "No es compatible cuando se utilizan valores acumulativos." + msgid "No data available" msgstr "No hay datos disponibles" @@ -870,6 +878,16 @@ msgstr "" msgid "Something went wrong" msgstr "Algo ha ido mal" +msgid "" +"There's a problem with the generated analytics. Contact a system " +"administrator." +msgstr "" +"Hay un problema con los análisis generados. Contacta a un administrador del " +"sistema." + +msgid "There's a syntax problem with the analytics request." +msgstr "Hay un problema de sintaxis con la solicitud de análisis." + msgid "or" msgstr "o" @@ -1041,6 +1059,10 @@ msgstr "Líneas" msgid "Totals" msgstr "Totales" +msgid "Totals are not supported when using cumulative values" +msgstr "" +"Los totales no son compatibles cuando se utilizan valores acumulativos." + msgid "Vertical (y) axis {{axisId}}" msgstr "Eje vertical (y) {{axisId}}" diff --git a/i18n/es_419.po b/i18n/es_419.po index 0647dbf5cf..7e5c6a669e 100644 --- a/i18n/es_419.po +++ b/i18n/es_419.po @@ -1,14 +1,14 @@ # # Translators: # Jaime Bosque , 2022 -# Enzo Nicolas Rossi , 2023 +# Enzo Nicolas Rossi , 2024 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-05-30T12:32:59.044Z\n" +"POT-Creation-Date: 2023-11-13T12:11:28.959Z\n" "PO-Revision-Date: 2019-06-25 18:46+0000\n" -"Last-Translator: Enzo Nicolas Rossi , 2023\n" +"Last-Translator: Enzo Nicolas Rossi , 2024\n" "Language-Team: Spanish (Latin America) (https://app.transifex.com/hisp-uio/teams/100509/es_419/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -46,7 +46,7 @@ msgid "Yes, leave" msgstr "" msgid "Add to {{axisName}}" -msgstr "" +msgstr "Añadir a {{axisName}}" msgid "" "'{{visualizationType}}' is intended to show a single data item. Only the " @@ -375,6 +375,9 @@ msgstr "" msgid "Cumulative values" msgstr "" +msgid "Accumulate cell values along rows" +msgstr "" + msgid "Show data item icon" msgstr "" @@ -474,10 +477,10 @@ msgstr "" msgid "Legend type" msgstr "" -msgid "Use pre-defined legend per data item" +msgid "Use pre-defined legend by data item" msgstr "" -msgid "Select a single legend for the entire visualization" +msgid "Select a legend" msgstr "" msgid "Legend changes background color" @@ -690,6 +693,9 @@ msgid "" "Visually plot data on a world map. Data elements use separate map layers." msgstr "" +msgid "Not supported when using cumulative values" +msgstr "" + msgid "No data available" msgstr "" @@ -808,6 +814,14 @@ msgstr "" msgid "Something went wrong" msgstr "Algo ha ido mal" +msgid "" +"There's a problem with the generated analytics. Contact a system " +"administrator." +msgstr "" + +msgid "There's a syntax problem with the analytics request." +msgstr "" + msgid "or" msgstr "" @@ -970,6 +984,9 @@ msgstr "" msgid "Totals" msgstr "" +msgid "Totals are not supported when using cumulative values" +msgstr "" + msgid "Vertical (y) axis {{axisId}}" msgstr "" diff --git a/i18n/fr.po b/i18n/fr.po index a75c7b3294..0a617a4f81 100644 --- a/i18n/fr.po +++ b/i18n/fr.po @@ -4,19 +4,19 @@ # Matthieu Pinard , 2019 # Karoline Tufte Lien , 2020 # Djibril Dione , 2020 -# Philip Larsen Donnelly, 2020 # Gabriela Rodriguez , 2020 # Viktor Varland , 2021 # Yayra Gomado , 2022 # Yao Selom SAKA (HISP WCA) , 2022 # Edem Kossi , 2023 +# Philip Larsen Donnelly, 2024 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-09-06T13:41:14.540Z\n" +"POT-Creation-Date: 2023-11-13T12:11:28.959Z\n" "PO-Revision-Date: 2019-06-25 18:46+0000\n" -"Last-Translator: Edem Kossi , 2023\n" +"Last-Translator: Philip Larsen Donnelly, 2024\n" "Language-Team: French (https://app.transifex.com/hisp-uio/teams/100509/fr/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -56,7 +56,7 @@ msgid "Yes, leave" msgstr "Oui, quitter" msgid "Add to {{axisName}}" -msgstr "Ajouter à {{nomdel'axe}} " +msgstr "Ajouter à {{axisName}}" msgid "" "'{{visualizationType}}' is intended to show a single data item. Only the " @@ -400,6 +400,9 @@ msgstr "Inclure cumulatif" msgid "Cumulative values" msgstr "Valeurs cumulées" +msgid "Accumulate cell values along rows" +msgstr "" + msgid "Show data item icon" msgstr "" @@ -725,7 +728,7 @@ msgid "Change org unit" msgstr "Changer d'unité d'organisation" msgid "{{level}} level in {{orgunit}}" -msgstr "{niveau}} niveau dans {{unité d'org.}}" +msgstr "{{level}} niveau dans {{orgunit}}" msgid "Open as Map" msgstr "Ouvrir comme carte" @@ -736,6 +739,9 @@ msgstr "" "Tracez graphiquement les données sur une carte du monde. Les éléments de " "données utilisent des couches de carte distinctes." +msgid "Not supported when using cumulative values" +msgstr "" + msgid "No data available" msgstr "Pas de données disponibles" @@ -877,6 +883,14 @@ msgstr "" msgid "Something went wrong" msgstr "Quelque chose a mal tourné" +msgid "" +"There's a problem with the generated analytics. Contact a system " +"administrator." +msgstr "" + +msgid "There's a syntax problem with the analytics request." +msgstr "" + msgid "or" msgstr "ou" @@ -1048,6 +1062,9 @@ msgstr "Lignes" msgid "Totals" msgstr "Totaux" +msgid "Totals are not supported when using cumulative values" +msgstr "" + msgid "Vertical (y) axis {{axisId}}" msgstr "Axe vertical (y) {{axisId}}" diff --git a/i18n/id.po b/i18n/id.po index 09b5352d52..431c590483 100644 --- a/i18n/id.po +++ b/i18n/id.po @@ -6,13 +6,14 @@ # Untoro Dwi Raharjo , 2021 # Aprisa Chrysantina , 2021 # Raja Fathurrahim, 2022 +# Philip Larsen Donnelly, 2024 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-09-06T13:41:14.540Z\n" +"POT-Creation-Date: 2023-11-13T12:11:28.959Z\n" "PO-Revision-Date: 2019-06-25 18:46+0000\n" -"Last-Translator: Raja Fathurrahim, 2022\n" +"Last-Translator: Philip Larsen Donnelly, 2024\n" "Language-Team: Indonesian (https://app.transifex.com/hisp-uio/teams/100509/id/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -65,7 +66,7 @@ msgid "" "'{{visualiationType}}' is intended to show maximum {{maxNumber}} number of " "items. Only the first {{maxNumber}} items will be used and saved." msgstr "" -"'{{visualizationType}}' digunakan untuk menunjukkan maksimum {{maxNumber}} " +"'{{visualiationType}}' digunakan untuk menunjukkan maksimum {{maxNumber}} " "item. Hanya {{maxNumber}} item pertama yang akan digunakan dan disimpan." msgid "" @@ -393,6 +394,9 @@ msgstr "Masukkan kumulatif" msgid "Cumulative values" msgstr "Nilai Kumulatif" +msgid "Accumulate cell values along rows" +msgstr "" + msgid "Show data item icon" msgstr "" @@ -712,7 +716,7 @@ msgid "Change org unit" msgstr "Ubah unit organisasi" msgid "{{level}} level in {{orgunit}}" -msgstr "{{level}} level pada {{unit organisasi}}" +msgstr "{{level}} level pada {{orgunit}}" msgid "Open as Map" msgstr "Buka sebagai Peta" @@ -723,6 +727,9 @@ msgstr "" "Plot data secara visual pada peta dunia. Elemen data menggunakan lapisan " "peta terpisah." +msgid "Not supported when using cumulative values" +msgstr "" + msgid "No data available" msgstr "Tidak ada data yang tersedia" @@ -860,6 +867,14 @@ msgstr "" msgid "Something went wrong" msgstr "Terjadi kesalahan" +msgid "" +"There's a problem with the generated analytics. Contact a system " +"administrator." +msgstr "" + +msgid "There's a syntax problem with the analytics request." +msgstr "" + msgid "or" msgstr "atau" @@ -1025,6 +1040,9 @@ msgstr "Garis" msgid "Totals" msgstr "Total" +msgid "Totals are not supported when using cumulative values" +msgstr "" + msgid "Vertical (y) axis {{axisId}}" msgstr "Sumbu vertikal (y) {{axisId}}" diff --git a/i18n/nb.po b/i18n/nb.po index a822c90e46..25ba6d4f77 100644 --- a/i18n/nb.po +++ b/i18n/nb.po @@ -1,14 +1,15 @@ # # Translators: # Caroline Hesthagen Holen , 2022 -# Karoline Tufte Lien , 2023 +# Karoline Tufte Lien , 2024 +# Jan Henrik Øverland, 2024 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-09-06T13:41:14.540Z\n" +"POT-Creation-Date: 2023-11-13T12:11:28.959Z\n" "PO-Revision-Date: 2019-06-25 18:46+0000\n" -"Last-Translator: Karoline Tufte Lien , 2023\n" +"Last-Translator: Jan Henrik Øverland, 2024\n" "Language-Team: Norwegian Bokmål (https://app.transifex.com/hisp-uio/teams/100509/nb/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -87,7 +88,7 @@ msgid "Your dimensions" msgstr "Dine dimensjoner" msgid "Filter dimensions" -msgstr "Filterdimensjoner" +msgstr "Filtrer dimensjoner" msgid "Data value set" msgstr "Dataverdisett" @@ -195,7 +196,7 @@ msgid "Only '{{- name}}' in use" msgstr "Bare '{{- name}}' er i bruk" msgid "Only '{{number}}' in use" -msgstr "Bare '{{nummer}}' er i bruk" +msgstr "Bare '{{number}}' er i bruk" msgid "All items are selected" msgstr "Alle elementer er valgt" @@ -389,6 +390,9 @@ msgstr "Inkluder kumulativ" msgid "Cumulative values" msgstr "Kumulative verdier" +msgid "Accumulate cell values along rows" +msgstr "" + msgid "Show data item icon" msgstr "" @@ -711,6 +715,9 @@ msgid "" msgstr "" "Vis data på et verdenskart visuelt. Dataelementer bruker separate kartlag." +msgid "Not supported when using cumulative values" +msgstr "" + msgid "No data available" msgstr "Ingen data tilgjengelig" @@ -842,6 +849,14 @@ msgstr "" msgid "Something went wrong" msgstr "Noe gikk galt" +msgid "" +"There's a problem with the generated analytics. Contact a system " +"administrator." +msgstr "" + +msgid "There's a syntax problem with the analytics request." +msgstr "" + msgid "or" msgstr "eller" @@ -1008,6 +1023,9 @@ msgstr "Linjer" msgid "Totals" msgstr "Totalsummer" +msgid "Totals are not supported when using cumulative values" +msgstr "" + msgid "Vertical (y) axis {{axisId}}" msgstr "" diff --git a/i18n/nl.po b/i18n/nl.po index 67ac49b507..6f36a47d9e 100644 --- a/i18n/nl.po +++ b/i18n/nl.po @@ -5,13 +5,14 @@ # Rica Zamora Duchateau, 2022 # Charel van den Elsen, 2023 # Enzo Nicolas Rossi , 2023 +# Philip Larsen Donnelly, 2024 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-09-06T13:41:14.540Z\n" +"POT-Creation-Date: 2023-11-13T12:11:28.959Z\n" "PO-Revision-Date: 2019-06-25 18:46+0000\n" -"Last-Translator: Enzo Nicolas Rossi , 2023\n" +"Last-Translator: Philip Larsen Donnelly, 2024\n" "Language-Team: Dutch (https://app.transifex.com/hisp-uio/teams/100509/nl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -29,7 +30,7 @@ msgid "Untitled {{visualizationType}} visualization, {{date}}" msgstr "Ongetitelde {{visualizationType}} visualisatie, {{date}}" msgid "\"{{- deletedObject}}\" successfully deleted." -msgstr "\"{{- verwijderObject}}\" succesvol verwijderd." +msgstr "\"{{- deletedObject}}\" succesvol verwijderd." msgid "You have unsaved changes." msgstr "U heeft niet opgeslagen veranderingen." @@ -64,8 +65,8 @@ msgid "" "'{{visualiationType}}' is intended to show maximum {{maxNumber}} number of " "items. Only the first {{maxNumber}} items will be used and saved." msgstr "" -"'{{visualizationType}}' is bedoeld om het maximum {{maxNumber}} aantal items" -" te tonen. Alleen de eerste {{maxNumber}} items worden gebruikt en " +"'{{visualiationType}}' is bedoeld om het maximum {{maxNumber}} aantal items " +"te tonen. Alleen de eerste {{maxNumber}} items worden gebruikt en " "opgeslagen." msgid "" @@ -390,6 +391,9 @@ msgstr "" msgid "Cumulative values" msgstr "" +msgid "Accumulate cell values along rows" +msgstr "" + msgid "Show data item icon" msgstr "" @@ -708,6 +712,9 @@ msgid "" "Visually plot data on a world map. Data elements use separate map layers." msgstr "" +msgid "Not supported when using cumulative values" +msgstr "" + msgid "No data available" msgstr "Geen gegevens beschikbaar" @@ -831,6 +838,14 @@ msgstr "" msgid "Something went wrong" msgstr "Er is iets fout gegaan." +msgid "" +"There's a problem with the generated analytics. Contact a system " +"administrator." +msgstr "" + +msgid "There's a syntax problem with the analytics request." +msgstr "" + msgid "or" msgstr "of" @@ -993,6 +1008,9 @@ msgstr "" msgid "Totals" msgstr "Totalen" +msgid "Totals are not supported when using cumulative values" +msgstr "" + msgid "Vertical (y) axis {{axisId}}" msgstr "" diff --git a/i18n/pt.po b/i18n/pt.po index b7f044eb84..8b1f95aec3 100644 --- a/i18n/pt.po +++ b/i18n/pt.po @@ -1,19 +1,19 @@ # # Translators: -# Philip Larsen Donnelly, 2020 # Gabriela Rodriguez , 2020 # Viktor Varland , 2021 # Sheila André , 2021 # Fernando Jorge Bade, 2021 # Ge Joao , 2022 # Emilio Mosse, 2022 +# Philip Larsen Donnelly, 2024 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-09-06T13:41:14.540Z\n" +"POT-Creation-Date: 2023-11-13T12:11:28.959Z\n" "PO-Revision-Date: 2019-06-25 18:46+0000\n" -"Last-Translator: Emilio Mosse, 2022\n" +"Last-Translator: Philip Larsen Donnelly, 2024\n" "Language-Team: Portuguese (https://app.transifex.com/hisp-uio/teams/100509/pt/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -28,7 +28,7 @@ msgid "Rename successful" msgstr "Renomeado com sucesso" msgid "Untitled {{visualizationType}} visualization, {{date}}" -msgstr "Sem título {{visualizaçãoTipo}} visualização, {{date}}" +msgstr "Sem título {{visualizationType}} visualização, {{date}}" msgid "\"{{- deletedObject}}\" successfully deleted." msgstr "" @@ -53,7 +53,7 @@ msgid "Yes, leave" msgstr "Sim, sair" msgid "Add to {{axisName}}" -msgstr "Adicionar à {{Nomedoeixo}}" +msgstr "Adicionar à {{axisName}}" msgid "" "'{{visualizationType}}' is intended to show a single data item. Only the " @@ -173,11 +173,11 @@ msgid "{{total}} of {{axisMaxNumberOfItems}} selected" msgstr "{{total}} of {{axisMaxNumberOfItems}} selected" msgid "{{total}} selected" -msgstr "{{Total}} selecionados " +msgstr "{{total}} selecionados " msgid "{{dimensionName}} is locked to {{axisName}} for {{visTypeName}}" msgstr "" -"{{dimension Name}} está bloqueado para {{axisName}} para {{visTypeName}}" +"{{dimensionName}} está bloqueado para {{axisName}} para {{visTypeName}}" msgid "Not available for Scatter" msgstr "Não disponível para Dispersão" @@ -198,10 +198,10 @@ msgid "None selected" msgstr "Nenhum selecionado" msgid "Only '{{- name}}' in use" -msgstr "Apenas '{{- nome}}' em uso" +msgstr "Apenas '{{- name}}' em uso" msgid "Only '{{number}}' in use" -msgstr "Somente '{{numero}}' em uso" +msgstr "Somente '{{number}}' em uso" msgid "All items are selected" msgstr "Todos os itens são selecionados" @@ -216,7 +216,7 @@ msgid "And 1 other..." msgstr "E mais 1 ..." msgid "And {{numberOfItems}} others..." -msgstr "E mais {{númerodeítens}} outros ..." +msgstr "E mais {{numberOfItems}} outros ..." msgid "Select a period" msgstr "Selecionar periodos " @@ -395,6 +395,9 @@ msgstr "Incluir cumulativos" msgid "Cumulative values" msgstr "Valores cumulativos " +msgid "Accumulate cell values along rows" +msgstr "" + msgid "Show data item icon" msgstr "" @@ -636,7 +639,7 @@ msgid "Visualization type" msgstr "Tipo de visualização" msgid "Axis {{axisId}}" -msgstr "Eixo {{eixoId}}" +msgstr "Eixo {{axisId}}" msgid "Series is empty" msgstr "A série está vazia." @@ -727,6 +730,9 @@ msgstr "" "Traçar visualmente dados num mapa do mundo. Os elementos de dados usam " "camadas de mapas separadas." +msgid "Not supported when using cumulative values" +msgstr "" + msgid "No data available" msgstr "Sem dados disponiveis " @@ -744,13 +750,13 @@ msgid "Add at least one item to {{axisName}}." msgstr "Adicione pelo menos um item ao {{axisName}}." msgid "{{columnsAxisName}} and {{rowsAxisName}} are empty" -msgstr "{{columnAxisName}} e {{linesAxisName}} estão vazias" +msgstr "{{columnsAxisName}} e {{rowsAxisName}} estão vazias" msgid "" "Add at least one item to {{columnsAxisName}} or {{rowsAxisName}} to create a" " {{visualizationType}}." msgstr "" -"Adicione pelo menos um item a {{columnAxisName}} ou {{linesAxisName}} para " +"Adicione pelo menos um item a {{columnsAxisName}} ou {{rowsAxisName}} para " "criar um {{visualizationType}}." msgid "No period selected" @@ -763,7 +769,7 @@ msgstr "" "{{axes}}. " msgid "Add organisation units to {{axisName}}." -msgstr "Adicione unidades de organização a {{Nome Eixo}}}" +msgstr "Adicione unidades de organização a {{axisName}}}" msgid "Vertical is empty" msgstr "Vertical está vazio" @@ -861,6 +867,14 @@ msgstr "" msgid "Something went wrong" msgstr "Ocorreu algo de errado" +msgid "" +"There's a problem with the generated analytics. Contact a system " +"administrator." +msgstr "" + +msgid "There's a syntax problem with the analytics request." +msgstr "" + msgid "or" msgstr "ou" @@ -1020,7 +1034,7 @@ msgid "Empty data" msgstr "Dados vazios" msgid "Horizontal (x) axis {{axisId}}" -msgstr "Horizontal (x) eixo {{eixoId}}" +msgstr "Horizontal (x) eixo {{axisId}}" msgid "Horizontal (x) axis" msgstr "Eixo Horizontal (x) " @@ -1031,8 +1045,11 @@ msgstr "Linhas " msgid "Totals" msgstr "Totais" +msgid "Totals are not supported when using cumulative values" +msgstr "" + msgid "Vertical (y) axis {{axisId}}" -msgstr "Eixo vertical (y) {{eixoId}}" +msgstr "Eixo vertical (y) {{axisId}}" msgid "Vertical (y) axis" msgstr "Eixo vertical (y)" diff --git a/i18n/pt_BR.po b/i18n/pt_BR.po index e8a673119f..94c9df5f98 100644 --- a/i18n/pt_BR.po +++ b/i18n/pt_BR.po @@ -2,13 +2,14 @@ # Translators: # Philip Larsen Donnelly, 2020 # Viktor Varland , 2020 +# Thiago Rocha, 2024 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-05-30T12:32:59.044Z\n" +"POT-Creation-Date: 2023-11-13T12:11:28.959Z\n" "PO-Revision-Date: 2019-06-25 18:46+0000\n" -"Last-Translator: Viktor Varland , 2020\n" +"Last-Translator: Thiago Rocha, 2024\n" "Language-Team: Portuguese (Brazil) (https://app.transifex.com/hisp-uio/teams/100509/pt_BR/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -40,10 +41,10 @@ msgid "" msgstr "" msgid "No, cancel" -msgstr "" +msgstr "Não, cancelar" msgid "Yes, leave" -msgstr "" +msgstr "Sim, sair" msgid "Add to {{axisName}}" msgstr "" @@ -85,10 +86,10 @@ msgid "Data value set" msgstr "" msgid "JSON" -msgstr "" +msgstr "JSON" msgid "XML" -msgstr "" +msgstr "XML" msgid "Other formats" msgstr "" @@ -106,7 +107,7 @@ msgid "Microsoft Excel" msgstr "" msgid "CSV" -msgstr "" +msgstr "CSV" msgid "Advanced" msgstr "" @@ -242,7 +243,7 @@ msgid "Your most viewed charts and tables" msgstr "" msgid "Aggregation type" -msgstr "" +msgstr "Tipo de agregacão" msgid "Overrides aggregation type for all data values." msgstr "" @@ -257,7 +258,7 @@ msgid "Average" msgstr "Média" msgid "Average (sum in org unit hierarchy)" -msgstr "" +msgstr "Média (soma na hierarquia da unidade organizacional)" msgid "Sum" msgstr "Soma" @@ -375,6 +376,9 @@ msgstr "Incluir cumulativa" msgid "Cumulative values" msgstr "" +msgid "Accumulate cell values along rows" +msgstr "" + msgid "Show data item icon" msgstr "" @@ -474,10 +478,10 @@ msgstr "" msgid "Legend type" msgstr "Tipo de legenda" -msgid "Use pre-defined legend per data item" +msgid "Use pre-defined legend by data item" msgstr "" -msgid "Select a single legend for the entire visualization" +msgid "Select a legend" msgstr "" msgid "Legend changes background color" @@ -690,6 +694,9 @@ msgid "" "Visually plot data on a world map. Data elements use separate map layers." msgstr "" +msgid "Not supported when using cumulative values" +msgstr "" + msgid "No data available" msgstr "" @@ -808,6 +815,14 @@ msgstr "" msgid "Something went wrong" msgstr "" +msgid "" +"There's a problem with the generated analytics. Contact a system " +"administrator." +msgstr "" + +msgid "There's a syntax problem with the analytics request." +msgstr "" + msgid "or" msgstr "ou" @@ -970,6 +985,9 @@ msgstr "" msgid "Totals" msgstr "Totais" +msgid "Totals are not supported when using cumulative values" +msgstr "" + msgid "Vertical (y) axis {{axisId}}" msgstr "" diff --git a/i18n/sv.po b/i18n/sv.po index da42086d6e..fa9ba47fb5 100644 --- a/i18n/sv.po +++ b/i18n/sv.po @@ -2,13 +2,14 @@ # Translators: # Philip Larsen Donnelly, 2020 # Viktor Varland , 2021 +# Jason Pickering , 2024 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-05-30T12:32:59.044Z\n" +"POT-Creation-Date: 2023-11-13T12:11:28.959Z\n" "PO-Revision-Date: 2019-06-25 18:46+0000\n" -"Last-Translator: Viktor Varland , 2021\n" +"Last-Translator: Jason Pickering , 2024\n" "Language-Team: Swedish (https://app.transifex.com/hisp-uio/teams/100509/sv/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -375,6 +376,9 @@ msgstr "" msgid "Cumulative values" msgstr "" +msgid "Accumulate cell values along rows" +msgstr "" + msgid "Show data item icon" msgstr "" @@ -474,10 +478,10 @@ msgstr "" msgid "Legend type" msgstr "Typ av symbolförklaring" -msgid "Use pre-defined legend per data item" +msgid "Use pre-defined legend by data item" msgstr "" -msgid "Select a single legend for the entire visualization" +msgid "Select a legend" msgstr "" msgid "Legend changes background color" @@ -690,6 +694,9 @@ msgid "" "Visually plot data on a world map. Data elements use separate map layers." msgstr "" +msgid "Not supported when using cumulative values" +msgstr "" + msgid "No data available" msgstr "" @@ -743,7 +750,7 @@ msgid "" msgstr "" msgid "No data selected" -msgstr "" +msgstr "Ingen data har valts" msgid "" "{{visualizationType}} must have at least one data item or data element group" @@ -808,6 +815,14 @@ msgstr "" msgid "Something went wrong" msgstr "" +msgid "" +"There's a problem with the generated analytics. Contact a system " +"administrator." +msgstr "" + +msgid "There's a syntax problem with the analytics request." +msgstr "" + msgid "or" msgstr "" @@ -970,6 +985,9 @@ msgstr "" msgid "Totals" msgstr "Totals" +msgid "Totals are not supported when using cumulative values" +msgstr "" + msgid "Vertical (y) axis {{axisId}}" msgstr "" diff --git a/i18n/uz_UZ_Cyrl.po b/i18n/uz_UZ_Cyrl.po index 193d29b0d6..bf4b89e5d4 100644 --- a/i18n/uz_UZ_Cyrl.po +++ b/i18n/uz_UZ_Cyrl.po @@ -1,13 +1,14 @@ # # Translators: -# Ibatov , 2023 +# Ibatov , 2024 +# Philip Larsen Donnelly, 2024 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-09-06T13:41:14.540Z\n" +"POT-Creation-Date: 2023-11-13T12:11:28.959Z\n" "PO-Revision-Date: 2019-06-25 18:46+0000\n" -"Last-Translator: Ibatov , 2023\n" +"Last-Translator: Philip Larsen Donnelly, 2024\n" "Language-Team: Uzbek (Cyrillic) (https://app.transifex.com/hisp-uio/teams/100509/uz@Cyrl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -180,7 +181,7 @@ msgid "Add Assigned Categories" msgstr "Белгиланган категориялар қўшилсин" msgid "Swap with {{axisName}} axis" -msgstr "{{AxisName}} ўқлари жойини алмаштириш" +msgstr "{{axisName}} ўқлари жойини алмаштириш" msgid "vertical" msgstr "вертикал" @@ -390,6 +391,9 @@ msgstr "Кумуляцияни ўз ичига олади" msgid "Cumulative values" msgstr "Кумулятив қийматлар" +msgid "Accumulate cell values along rows" +msgstr "Йиғма қиймат катак ҳажмидан катта" + msgid "Show data item icon" msgstr "Маълумотлар элементи белгисини кўрсатиш" @@ -723,6 +727,9 @@ msgstr "" "Жаҳон харитасига маълумотларни визуал киритинг. Маълумотлар элементлари " "хаританинг алоҳида қаватларидан фойдаланади." +msgid "Not supported when using cumulative values" +msgstr "Йиғма қиймат қўлланилиши қўллаб қувватланмади" + msgid "No data available" msgstr "Маълумотлар мавжуд эмас" @@ -759,7 +766,7 @@ msgstr "" "{{axes}}." msgid "Add organisation units to {{axisName}}." -msgstr "{{AxisName}} ўқига ташкилий бўлимлар қўшиш" +msgstr "{{axisName}} ўқига ташкилий бўлимлар қўшиш" msgid "Vertical is empty" msgstr "Вертикал ўқ бўш" @@ -868,6 +875,16 @@ msgstr "" msgid "Something went wrong" msgstr "Нимадир нотўғри бажарилди" +msgid "" +"There's a problem with the generated analytics. Contact a system " +"administrator." +msgstr "" +"Генерацияланган аналитикада муаммо мавжуд. Тизим администраторига мурожаат " +"қилинг." + +msgid "There's a syntax problem with the analytics request." +msgstr "Аналитика сўровида синтаксис муаммо мавжуд." + msgid "or" msgstr "ёки" @@ -1036,6 +1053,9 @@ msgstr "Қаторлар" msgid "Totals" msgstr "Жами" +msgid "Totals are not supported when using cumulative values" +msgstr "Кумулятив қиймат жами қўлланилганда қўллаб қувватланмади" + msgid "Vertical (y) axis {{axisId}}" msgstr "Вертикал (y) ўқ {{axisId}}" diff --git a/i18n/uz_UZ_Latn.po b/i18n/uz_UZ_Latn.po index 4ad1158492..301f98e6ae 100644 --- a/i18n/uz_UZ_Latn.po +++ b/i18n/uz_UZ_Latn.po @@ -1,13 +1,14 @@ # # Translators: # Yury Rogachev , 2022 +# Philip Larsen Donnelly, 2024 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-09-06T13:41:14.540Z\n" +"POT-Creation-Date: 2023-11-13T12:11:28.959Z\n" "PO-Revision-Date: 2019-06-25 18:46+0000\n" -"Last-Translator: Yury Rogachev , 2022\n" +"Last-Translator: Philip Larsen Donnelly, 2024\n" "Language-Team: Uzbek (Latin) (https://app.transifex.com/hisp-uio/teams/100509/uz@Latn/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -180,7 +181,7 @@ msgid "Add Assigned Categories" msgstr "Belgilangan kategoriyalar qoʼshilsin" msgid "Swap with {{axisName}} axis" -msgstr "{{AxisName}} oʼqlari joyini almashtirish" +msgstr "{{axisName}} oʼqlari joyini almashtirish" msgid "vertical" msgstr "" @@ -391,6 +392,9 @@ msgstr "Kumulyatsiyani oʼz ichiga oladi" msgid "Cumulative values" msgstr "Kumulyativ qiymatlar" +msgid "Accumulate cell values along rows" +msgstr "" + msgid "Show data item icon" msgstr "" @@ -710,6 +714,9 @@ msgstr "" "Jahon xaritasiga maʼlumotlarni vizual kiriting. Maʼlumotlar elementlari " "xaritaning alohida qavatlaridan foydalanadi." +msgid "Not supported when using cumulative values" +msgstr "" + msgid "No data available" msgstr "" @@ -746,7 +753,7 @@ msgstr "" " {{axes}}." msgid "Add organisation units to {{axisName}}." -msgstr "{{AxisName}} oʼqiga tashkiliy boʼlimlar qoʼshish" +msgstr "{{axisName}} oʼqiga tashkiliy boʼlimlar qoʼshish" msgid "Vertical is empty" msgstr "Vertikal oʼq boʼsh" @@ -851,6 +858,14 @@ msgstr "" msgid "Something went wrong" msgstr "Nimadir notoʼgʼri bajarildi" +msgid "" +"There's a problem with the generated analytics. Contact a system " +"administrator." +msgstr "" + +msgid "There's a syntax problem with the analytics request." +msgstr "" + msgid "or" msgstr "yoki" @@ -1014,6 +1029,9 @@ msgstr "Qatorlar" msgid "Totals" msgstr "Jami" +msgid "Totals are not supported when using cumulative values" +msgstr "" + msgid "Vertical (y) axis {{axisId}}" msgstr "" diff --git a/i18n/vi.po b/i18n/vi.po index 25966d7935..c5c61a414f 100644 --- a/i18n/vi.po +++ b/i18n/vi.po @@ -1,15 +1,15 @@ # # Translators: -# Philip Larsen Donnelly, 2020 # Viktor Varland , 2021 # Mai Nguyen , 2022 +# Philip Larsen Donnelly, 2024 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-09-06T13:41:14.540Z\n" +"POT-Creation-Date: 2023-11-13T12:11:28.959Z\n" "PO-Revision-Date: 2019-06-25 18:46+0000\n" -"Last-Translator: Mai Nguyen , 2022\n" +"Last-Translator: Philip Larsen Donnelly, 2024\n" "Language-Team: Vietnamese (https://app.transifex.com/hisp-uio/teams/100509/vi/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -24,7 +24,7 @@ msgid "Rename successful" msgstr "Đổi tên thành công" msgid "Untitled {{visualizationType}} visualization, {{date}}" -msgstr "Mô hình hóa {{loại mô hình}} không có tiêu đề, {{ngày}}" +msgstr "Mô hình hóa {{visualizationType}} không có tiêu đề, {{date}}" msgid "\"{{- deletedObject}}\" successfully deleted." msgstr "" @@ -49,21 +49,21 @@ msgid "Yes, leave" msgstr "Có" msgid "Add to {{axisName}}" -msgstr "Thêm {{tên trục}" +msgstr "Thêm {{axisName}}" msgid "" "'{{visualizationType}}' is intended to show a single data item. Only the " "first item will be used and saved." msgstr "" -"'{{loại mô hình hóa}}' nhằm hiển thị một mục dữ liệu. Chỉ mục đầu tiên sẽ " +"'{{visualizationType}}' nhằm hiển thị một mục dữ liệu. Chỉ mục đầu tiên sẽ " "được sử dụng và lưu." msgid "" "'{{visualiationType}}' is intended to show maximum {{maxNumber}} number of " "items. Only the first {{maxNumber}} items will be used and saved." msgstr "" -"'{{Loại mô hình hóa}}' nhằm hiển thị số lượng mục tối đa {{số lượng tối " -"đa}}. Chỉ {{số lượng tối đa}} mục đầu tiên sẽ được sử dụng và lưu." +"'{{visualiationType}}' nhằm hiển thị số lượng mục tối đa {{maxNumber}}. Chỉ " +"{{maxNumber}} mục đầu tiên sẽ được sử dụng và lưu." msgid "" "'Scatter' is intended to show a single data item per axis. Only the first " @@ -166,13 +166,13 @@ msgid "All" msgstr "Tất cả" msgid "{{total}} of {{axisMaxNumberOfItems}} selected" -msgstr "{{tổng}} / {{axisMaxNumberOfItems}}Đã chọn " +msgstr "{{total}} / {{axisMaxNumberOfItems}}Đã chọn " msgid "{{total}} selected" -msgstr "Đã chọn {{tổng}}" +msgstr "Đã chọn {{total}}" msgid "{{dimensionName}} is locked to {{axisName}} for {{visTypeName}}" -msgstr "{{mensName}} bị khóa với {{axisName}} cho {{visTypeName}}" +msgstr "{{dimensionName}} bị khóa với {{axisName}} cho {{visTypeName}}" msgid "Not available for Scatter" msgstr "Không có sẵn cho phân tán" @@ -390,6 +390,9 @@ msgstr "Bao gồm các giá trị tính toán" msgid "Cumulative values" msgstr "Các giá trị tích lũy" +msgid "Accumulate cell values along rows" +msgstr "" + msgid "Show data item icon" msgstr "" @@ -709,6 +712,9 @@ msgstr "" "Vẽ dữ liệu trực quan trên bản đồ thế giới. Các phần tử dữ liệu sử dụng các " "lớp bản đồ riêng biệt." +msgid "Not supported when using cumulative values" +msgstr "" + msgid "No data available" msgstr "Không có dữ liệu" @@ -726,13 +732,13 @@ msgid "Add at least one item to {{axisName}}." msgstr "Thêm ít nhất một mục vào {{axisName}}." msgid "{{columnsAxisName}} and {{rowsAxisName}} are empty" -msgstr "{{columnAxisName}} và {{rowsAxisName}} trống" +msgstr "{{columnsAxisName}} và {{rowsAxisName}} trống" msgid "" "Add at least one item to {{columnsAxisName}} or {{rowsAxisName}} to create a" " {{visualizationType}}." msgstr "" -"Thêm ít nhất một mục vào {{columnAxisName}} hoặc {{rowAxisName}} để tạo " +"Thêm ít nhất một mục vào {{columnsAxisName}} hoặc {{rowsAxisName}} để tạo " "{{visualizationType}}." msgid "No period selected" @@ -742,7 +748,7 @@ msgid "" "{{visualizationType}} must have at least one period selected in {{axes}}." msgstr "" "{{visualizationType}} phải có ít nhất một thời điểm được chọn trong " -"{{axis}}." +"{{axes}}." msgid "Add organisation units to {{axisName}}." msgstr "Thêm đơn vị vào {{axisName}}." @@ -777,10 +783,10 @@ msgid "" " set item in {{axes}}." msgstr "" "{{visualizationType}} phải có ít nhất một mục dữ liệu hoặc mục nhóm phần tử " -"dữ liệu trong {{axis}}." +"dữ liệu trong {{axes}}." msgid "{{visualizationType}} must have at least one data item in {{axes}}." -msgstr "{{visualizationType}} phải có ít nhất một mục dữ liệu trong {{axis}}." +msgstr "{{visualizationType}} phải có ít nhất một mục dữ liệu trong {{axes}}." msgid "There's a problem with the layout" msgstr "Có lỗi bố cục" @@ -843,6 +849,14 @@ msgstr "" msgid "Something went wrong" msgstr "Có lỗi" +msgid "" +"There's a problem with the generated analytics. Contact a system " +"administrator." +msgstr "" + +msgid "There's a syntax problem with the analytics request." +msgstr "" + msgid "or" msgstr "hoặc" @@ -1005,6 +1019,9 @@ msgstr "Các đường" msgid "Totals" msgstr "Tổng" +msgid "Totals are not supported when using cumulative values" +msgstr "" + msgid "Vertical (y) axis {{axisId}}" msgstr "" diff --git a/i18n/zh.po b/i18n/zh.po index 3e4ef431de..8d955a578a 100644 --- a/i18n/zh.po +++ b/i18n/zh.po @@ -1,16 +1,16 @@ # # Translators: -# Philip Larsen Donnelly, 2020 # Viktor Varland , 2020 # 晓东 林 <13981924470@126.com>, 2022 -# easylin , 2023 +# easylin , 2024 +# Philip Larsen Donnelly, 2024 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-09-06T13:41:14.540Z\n" +"POT-Creation-Date: 2023-11-13T12:11:28.959Z\n" "PO-Revision-Date: 2019-06-25 18:46+0000\n" -"Last-Translator: easylin , 2023\n" +"Last-Translator: Philip Larsen Donnelly, 2024\n" "Language-Team: Chinese (https://app.transifex.com/hisp-uio/teams/100509/zh/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -59,7 +59,7 @@ msgid "" "'{{visualiationType}}' is intended to show maximum {{maxNumber}} number of " "items. Only the first {{maxNumber}} items will be used and saved." msgstr "" -"'{{visualizationType}}' 打算显示最多{{maxNumber}}个数据项,仅{{maxNumber}} " +"'{{visualiationType}}' 打算显示最多{{maxNumber}}个数据项,仅{{maxNumber}} " "个中第一个条目将被使用和保存。" msgid "" @@ -379,6 +379,9 @@ msgstr "包括累计" msgid "Cumulative values" msgstr "汇总值" +msgid "Accumulate cell values along rows" +msgstr "沿行累积单元格的值" + msgid "Show data item icon" msgstr "显示数据项图标" @@ -694,6 +697,9 @@ msgid "" "Visually plot data on a world map. Data elements use separate map layers." msgstr "在世界地图上直观地绘制数据。数据元素使用单独的地图图层。" +msgid "Not supported when using cumulative values" +msgstr "使用累积值时不支持" + msgid "No data available" msgstr "没有值" @@ -814,6 +820,14 @@ msgstr "所选数据维度未返回任何有效数据。这种可视化类型只 msgid "Something went wrong" msgstr "出了些问题" +msgid "" +"There's a problem with the generated analytics. Contact a system " +"administrator." +msgstr "生成的分析存在问题。联系系统管理员。" + +msgid "There's a syntax problem with the analytics request." +msgstr "分析请求存在语法问题。" + msgid "or" msgstr "或" @@ -976,6 +990,9 @@ msgstr "线" msgid "Totals" msgstr "总共" +msgid "Totals are not supported when using cumulative values" +msgstr "使用累积值时不支持总计" + msgid "Vertical (y) axis {{axisId}}" msgstr "垂直 (y) 轴 {{axisId}}" diff --git a/i18n/zh_CN.po b/i18n/zh_CN.po index 91ce1f94e0..7260334a59 100644 --- a/i18n/zh_CN.po +++ b/i18n/zh_CN.po @@ -2,13 +2,14 @@ # Translators: # 晓东 林 <13981924470@126.com>, 2020 # easylin , 2022 +# Philip Larsen Donnelly, 2024 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-09-06T13:41:14.540Z\n" +"POT-Creation-Date: 2023-11-13T12:11:28.959Z\n" "PO-Revision-Date: 2019-06-25 18:46+0000\n" -"Last-Translator: easylin , 2022\n" +"Last-Translator: Philip Larsen Donnelly, 2024\n" "Language-Team: Chinese (China) (https://app.transifex.com/hisp-uio/teams/100509/zh_CN/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -26,7 +27,7 @@ msgid "Untitled {{visualizationType}} visualization, {{date}}" msgstr "" msgid "\"{{- deletedObject}}\" successfully deleted." -msgstr "" +msgstr "“{{- deletedObject}}”已成功删除。" msgid "You have unsaved changes." msgstr "你没有保存修改" @@ -375,6 +376,9 @@ msgstr "包括累计" msgid "Cumulative values" msgstr "汇总值" +msgid "Accumulate cell values along rows" +msgstr "" + msgid "Show data item icon" msgstr "" @@ -690,6 +694,9 @@ msgid "" "Visually plot data on a world map. Data elements use separate map layers." msgstr "" +msgid "Not supported when using cumulative values" +msgstr "" + msgid "No data available" msgstr "没有值" @@ -810,6 +817,14 @@ msgstr "" msgid "Something went wrong" msgstr "出了些问题" +msgid "" +"There's a problem with the generated analytics. Contact a system " +"administrator." +msgstr "" + +msgid "There's a syntax problem with the analytics request." +msgstr "" + msgid "or" msgstr "或" @@ -972,6 +987,9 @@ msgstr "线" msgid "Totals" msgstr "总共" +msgid "Totals are not supported when using cumulative values" +msgstr "" + msgid "Vertical (y) axis {{axisId}}" msgstr "" diff --git a/jest.config.js b/jest.config.js index 08483cdd6b..ed1d53c0fd 100644 --- a/jest.config.js +++ b/jest.config.js @@ -5,7 +5,6 @@ module.exports = { setupFilesAfterEnv: ['./config/testSetup.js'], testRunner: 'jest-circus/runner', - testRegex: ['/src/modules/__tests__/.*.spec.js?$'], reporters: [ 'default', [ diff --git a/package.json b/package.json index 1427db228d..2151d1306e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "data-visualizer-app", - "version": "100.3.1", + "version": "100.4.0", "description": "DHIS2 Data Visualizer", "license": "BSD-3-Clause", "private": true, @@ -27,7 +27,7 @@ "@dhis2/cypress-plugins": "^10.0.2", "@reportportal/agent-js-cypress": "git+https://github.com/dhis2/agent-js-cypress.git#develop", "@reportportal/agent-js-jest": "^5.0.6", - "cypress": "^12.16.0", + "cypress": "^13.6.1", "cypress-tags": "^1.1.2", "enzyme": "^3.11.0", "enzyme-adapter-react-16": "^1.15.7", @@ -41,7 +41,7 @@ "start-server-and-test": "^2.0.0" }, "dependencies": { - "@dhis2/analytics": "^26.1.6", + "@dhis2/analytics": "^26.3.0", "@dhis2/app-runtime": "^3.7.0", "@dhis2/app-runtime-adapter-d2": "^1.1.0", "@dhis2/app-service-datastore": "^1.0.0-beta.3", diff --git a/public/push-analytics.json b/public/push-analytics.json new file mode 100644 index 0000000000..b74e9691d6 --- /dev/null +++ b/public/push-analytics.json @@ -0,0 +1,85 @@ +{ + "version": "0.0.1", + "showVisualization": { + "strategy": "navigateToUrl", + "steps": [ + { "goto": "{{appUrl}}/#/{{id}}" }, + { + "waitForSelectorConditionally": [ + { + "dashboardItemProperty": "visualization.type", + "value": "PIVOT_TABLE", + "selector": ".pivot-table-container > table" + }, + { + "dashboardItemProperty": "visualization.type", + "value": [ + "COLUMN", + "STACKED_COLUMN", + "BAR", + "STACKED_BAR", + "LINE", + "AREA", + "STACKED_AREA", + "PIE", + "RADAR", + "GAUGE", + "YEAR_OVER_YEAR_LINE", + "YEAR_OVER_YEAR_COLUMN", + "SCATTER", + "BUBBLE", + "SINGLE_VALUE", + "PIVOT_TABLE", + "OUTLIER_TABLE" + ], + "selector": ".highcharts-container" + } + ] + } + ] + }, + "triggerDownload": { + "strategy": "useUiElements", + "steps": [ + { "click": ".push-analytics-download-dropdown-menu-button" }, + { "click": ".push-analytics-download-menu-item" } + ] + }, + "obtainDownloadArtifactConditionally": [ + { + "dashboardItemProperty": "visualization.type", + "value": "PIVOT_TABLE", + "strategy": "scrapeDownloadPage", + "htmlSelector": "body", + "cssSelector": "style" + }, + { + "dashboardItemProperty": "visualization.type", + "value": [ + "COLUMN", + "STACKED_COLUMN", + "BAR", + "STACKED_BAR", + "LINE", + "AREA", + "STACKED_AREA", + "PIE", + "RADAR", + "GAUGE", + "YEAR_OVER_YEAR_LINE", + "YEAR_OVER_YEAR_COLUMN", + "SCATTER", + "BUBBLE", + "SINGLE_VALUE", + "PIVOT_TABLE", + "OUTLIER_TABLE" + ], + "strategy": "screenShotImgOnDownloadPage", + "htmlSelector": "img" + } + ], + "clearVisualization": { + "strategy": "navigateToUrl", + "steps": [{ "goto": "{{appUrl}}/#/new" }] + } +} diff --git a/src/actions/ui.js b/src/actions/ui.js index fe3e3fcdfe..df704580f7 100644 --- a/src/actions/ui.js +++ b/src/actions/ui.js @@ -1,8 +1,10 @@ +import { getDisabledOptions } from '../modules/disabledOptions.js' import { SET_UI, CLEAR_UI, SET_UI_FROM_VISUALIZATION, SET_UI_TYPE, + SET_UI_DISABLED_OPTIONS, SET_UI_OPTIONS, SET_UI_LAYOUT, ADD_UI_LAYOUT_DIMENSIONS, @@ -21,6 +23,8 @@ import { UPDATE_UI_SERIES_ITEM, SET_UI_OPTION, SET_UI_OPTION_FONT_STYLE, + sGetUiType, + sGetUiOptions, } from '../reducers/ui.js' export const acSetUi = (value) => ({ @@ -43,6 +47,11 @@ export const acSetUiType = (value) => ({ value, }) +export const acSetUiDisabledOptions = (value) => ({ + type: SET_UI_DISABLED_OPTIONS, + value, +}) + export const acSetUiOptions = (value) => ({ type: SET_UI_OPTIONS, value, @@ -129,3 +138,15 @@ export const acSetUiRightSidebarOpen = () => ({ export const acClearSeriesType = () => ({ type: CLEAR_SERIES_TYPE, }) + +export const tSetUiOptionAndDisabledOptions = + (option) => (dispatch, getState) => { + dispatch(acSetUiOption(option)) + + const visType = sGetUiType(getState()) + const options = sGetUiOptions(getState()) + + dispatch( + acSetUiDisabledOptions(getDisabledOptions({ visType, options })) + ) + } diff --git a/src/components/DownloadMenu/GraphicsMenu.js b/src/components/DownloadMenu/GraphicsMenu.js index 83537ebf52..e765a0dda5 100644 --- a/src/components/DownloadMenu/GraphicsMenu.js +++ b/src/components/DownloadMenu/GraphicsMenu.js @@ -27,6 +27,7 @@ export const GraphicsMenu = ({ hoverable, onDownload }) => { icon={} label={i18n.t('Image (.png)')} onClick={() => onDownload({ format: FILE_FORMAT_PNG })} + className="push-analytics-download-menu-item" /> { onDownload({ type: DOWNLOAD_TYPE_TABLE, diff --git a/src/components/DownloadMenu/ToolbarDownloadDropdown.js b/src/components/DownloadMenu/ToolbarDownloadDropdown.js index 5dcbae0948..3f0038764a 100644 --- a/src/components/DownloadMenu/ToolbarDownloadDropdown.js +++ b/src/components/DownloadMenu/ToolbarDownloadDropdown.js @@ -8,7 +8,11 @@ const ToolbarDownloadDropdown = () => { const { disabled, doDownloadData, doDownloadImage, visType } = useDownload() return ( - + ) @@ -218,6 +225,7 @@ export class UnconnectedVisualization extends Component { UnconnectedVisualization.propTypes = { addMetadata: PropTypes.func, addParentGraphMap: PropTypes.func, + displayProperty: PropTypes.string, error: PropTypes.object, isLoading: PropTypes.bool, rightSidebarOpen: PropTypes.bool, @@ -225,24 +233,16 @@ UnconnectedVisualization.propTypes = { setCurrent: PropTypes.func, setLoadError: PropTypes.func, setUiItems: PropTypes.func, - userSettings: PropTypes.object, visualization: PropTypes.object, onLoadingComplete: PropTypes.func, } -export const userSettingsSelector = createSelector( - [sGetSettingsDisplayProperty], - (displayProperty) => ({ - displayProperty, - }) -) - const mapStateToProps = (state) => ({ visualization: sGetCurrent(state), rightSidebarOpen: sGetUiRightSidebarOpen(state), error: sGetLoadError(state), isLoading: sGetIsPluginLoading(state), - userSettings: userSettingsSelector(state), + displayProperty: sGetSettingsDisplayProperty(state), }) const mapDispatchToProps = (dispatch) => ({ diff --git a/src/components/VisualizationOptions/Options/CheckboxBaseOption.js b/src/components/VisualizationOptions/Options/CheckboxBaseOption.js index 829dcdf651..4e7ba0e712 100644 --- a/src/components/VisualizationOptions/Options/CheckboxBaseOption.js +++ b/src/components/VisualizationOptions/Options/CheckboxBaseOption.js @@ -2,8 +2,8 @@ import { CheckboxField } from '@dhis2/ui' import PropTypes from 'prop-types' import React from 'react' import { connect } from 'react-redux' -import { acSetUiOption } from '../../../actions/ui.js' -import { sGetUiOption } from '../../../reducers/ui.js' +import { tSetUiOptionAndDisabledOptions } from '../../../actions/ui.js' +import { sGetUiOption, sGetUiDisabledOption } from '../../../reducers/ui.js' import { tabSectionOption, tabSectionOptionToggleable, @@ -19,6 +19,7 @@ export const UnconnectedCheckboxBaseOption = ({ inverted, fontStyleKey, dataTest, + disabled, }) => (
onChange(inverted ? !checked : checked)} dense dataTest={dataTest} + disabled={disabled} /> {((!inverted && value) || (inverted && !value)) && fontStyleKey ? (
@@ -43,6 +45,7 @@ export const UnconnectedCheckboxBaseOption = ({ UnconnectedCheckboxBaseOption.propTypes = { dataTest: PropTypes.string, + disabled: PropTypes.bool, fontStyleKey: PropTypes.string, helpText: PropTypes.string, inverted: PropTypes.bool, @@ -53,18 +56,25 @@ UnconnectedCheckboxBaseOption.propTypes = { } const mapStateToProps = (state, ownProps) => ({ + disabled: Boolean( + sGetUiDisabledOption(state, ownProps.option) ?? ownProps.disabled + ), + helpText: + sGetUiDisabledOption(state, ownProps.option)?.helpText || + ownProps.helpText, value: sGetUiOption(state, ownProps.option) || false, }) const mapDispatchToProps = (dispatch, ownProps) => ({ - onChange: (value) => + onChange: (value) => { dispatch( - acSetUiOption({ + tSetUiOptionAndDisabledOptions({ optionId: ownProps.option.id || ownProps.option.name, axisId: ownProps.option.axisId, value, }) - ), + ) + }, }) export const CheckboxBaseOption = connect( diff --git a/src/components/VisualizationOptions/Options/ColSubTotals.js b/src/components/VisualizationOptions/Options/ColSubTotals.js index bcb36c01d7..1d5e922278 100644 --- a/src/components/VisualizationOptions/Options/ColSubTotals.js +++ b/src/components/VisualizationOptions/Options/ColSubTotals.js @@ -8,6 +8,7 @@ const ColSubTotals = () => ( option={{ name: 'colSubTotals', }} + dataTest="option-col-subtotals" /> ) diff --git a/src/components/VisualizationOptions/Options/ColTotals.js b/src/components/VisualizationOptions/Options/ColTotals.js index c67b8650e2..322a1dcfbd 100644 --- a/src/components/VisualizationOptions/Options/ColTotals.js +++ b/src/components/VisualizationOptions/Options/ColTotals.js @@ -8,6 +8,7 @@ const ColTotals = () => ( option={{ name: 'colTotals', }} + dataTest="option-col-totals" /> ) diff --git a/src/components/VisualizationOptions/Options/CumulativeValues.js b/src/components/VisualizationOptions/Options/CumulativeValues.js index b4cb4fa7ac..630cc17e90 100644 --- a/src/components/VisualizationOptions/Options/CumulativeValues.js +++ b/src/components/VisualizationOptions/Options/CumulativeValues.js @@ -1,14 +1,27 @@ +import { VIS_TYPE_PIVOT_TABLE } from '@dhis2/analytics' import i18n from '@dhis2/d2-i18n' import React from 'react' +import { useSelector } from 'react-redux' +import { sGetUiType } from '../../../reducers/ui.js' import { CheckboxBaseOption } from './CheckboxBaseOption.js' -const CumulativeValues = () => ( - -) +const CumulativeValues = () => { + const visType = useSelector(sGetUiType) + + return ( + + ) +} export default CumulativeValues diff --git a/src/components/VisualizationOptions/Options/Legend.js b/src/components/VisualizationOptions/Options/Legend.js index 30e4825be1..03923b2979 100644 --- a/src/components/VisualizationOptions/Options/Legend.js +++ b/src/components/VisualizationOptions/Options/Legend.js @@ -4,7 +4,7 @@ import { LEGEND_DISPLAY_STYLE_FILL, } from '@dhis2/analytics' import i18n from '@dhis2/d2-i18n' -import { Checkbox, FieldSet, Legend as UiCoreLegend } from '@dhis2/ui' +import { Checkbox, FieldSet, Help, Legend as UiCoreLegend } from '@dhis2/ui' import cx from 'classnames' import PropTypes from 'prop-types' import React, { useState } from 'react' @@ -15,22 +15,27 @@ import { OPTION_LEGEND_DISPLAY_STYLE, OPTION_LEGEND_SET, } from '../../../modules/options.js' -import { sGetUiOption } from '../../../reducers/ui.js' +import { sGetUiOption, sGetUiDisabledOption } from '../../../reducers/ui.js' import { tabSectionOptionToggleable, tabSectionOption, tabSectionTitle, + tabSectionTitleDisabled, tabSectionTitleMargin, } from '../styles/VisualizationOptions.style.js' import LegendDisplayStrategy from './LegendDisplayStrategy.js' import LegendDisplayStyle from './LegendDisplayStyle.js' import ShowLegendKey from './ShowLegendKey.js' +const optionName = 'legend' + const Legend = ({ legendSet, legendDisplayStrategy, onChange, + helpText, hideStyleOptions, + disabled, }) => { const [legendEnabled, setLegendEnabled] = useState( !( @@ -69,9 +74,11 @@ const Legend = ({ } return ( -
+
{i18n.t('Legend style')}
- +
@@ -104,33 +115,44 @@ const Legend = ({ className={cx(tabSectionTitle.className, { [tabSectionTitleMargin.className]: hideStyleOptions, + [tabSectionTitleDisabled.className]: + disabled, })} > {i18n.t('Legend type')}
- +
- +
) : null} + {helpText && ( + + {helpText} + + )}
) } Legend.propTypes = { onChange: PropTypes.func.isRequired, + disabled: PropTypes.bool, + helpText: PropTypes.string, hideStyleOptions: PropTypes.bool, legendDisplayStrategy: PropTypes.string, legendSet: PropTypes.object, } const mapStateToProps = (state) => ({ + disabled: Boolean(sGetUiDisabledOption(state, { name: optionName })), + helpText: sGetUiDisabledOption(state, { name: optionName })?.helpText, legendSet: sGetUiOption(state, { id: OPTION_LEGEND_SET }), legendDisplayStrategy: sGetUiOption(state, { id: OPTION_LEGEND_DISPLAY_STRATEGY, diff --git a/src/components/VisualizationOptions/Options/LegendDisplayStrategy.js b/src/components/VisualizationOptions/Options/LegendDisplayStrategy.js index 44ee0b5a33..8f3d16ce0c 100644 --- a/src/components/VisualizationOptions/Options/LegendDisplayStrategy.js +++ b/src/components/VisualizationOptions/Options/LegendDisplayStrategy.js @@ -13,13 +13,14 @@ import { sGetUiOption } from '../../../reducers/ui.js' import { tabSectionOptionToggleable } from '../styles/VisualizationOptions.style.js' import LegendSet from './LegendSet.js' -const LegendDisplayStrategy = ({ value, onChange }) => ( +const LegendDisplayStrategy = ({ value, onChange, disabled }) => ( ( key={LEGEND_DISPLAY_STRATEGY_FIXED} label={i18n.t('Select a legend')} value={LEGEND_DISPLAY_STRATEGY_FIXED} + disabled={disabled} checked={value === LEGEND_DISPLAY_STRATEGY_FIXED} onChange={onChange} dense @@ -37,7 +39,7 @@ const LegendDisplayStrategy = ({ value, onChange }) => ( {value === LEGEND_DISPLAY_STRATEGY_FIXED ? (
- +
) : null}
@@ -46,6 +48,7 @@ const LegendDisplayStrategy = ({ value, onChange }) => ( LegendDisplayStrategy.propTypes = { value: PropTypes.string.isRequired, onChange: PropTypes.func.isRequired, + disabled: PropTypes.bool, } const mapStateToProps = (state) => ({ diff --git a/src/components/VisualizationOptions/Options/LegendDisplayStyle.js b/src/components/VisualizationOptions/Options/LegendDisplayStyle.js index bd7699f782..819fdaa481 100644 --- a/src/components/VisualizationOptions/Options/LegendDisplayStyle.js +++ b/src/components/VisualizationOptions/Options/LegendDisplayStyle.js @@ -3,11 +3,12 @@ import { LEGEND_DISPLAY_STYLE_TEXT, } from '@dhis2/analytics' import i18n from '@dhis2/d2-i18n' +import PropTypes from 'prop-types' import React from 'react' import { OPTION_LEGEND_DISPLAY_STYLE } from '../../../modules/options.js' import { default as RadioBaseOption } from './RadioBaseOption.js' -const LegendDisplayStyle = () => ( +const LegendDisplayStyle = ({ disabled }) => ( ( }, ], }} + disabled={disabled} dataTest={'legend-display-style'} /> ) +LegendDisplayStyle.propTypes = { + disabled: PropTypes.bool, +} + export default LegendDisplayStyle diff --git a/src/components/VisualizationOptions/Options/LegendSet.js b/src/components/VisualizationOptions/Options/LegendSet.js index 2fa17a0ec7..307c62afe2 100644 --- a/src/components/VisualizationOptions/Options/LegendSet.js +++ b/src/components/VisualizationOptions/Options/LegendSet.js @@ -29,10 +29,12 @@ const LegendSetSelect = ({ onFocus, onChange, dataTest, + disabled, }) => ( { +const LegendSet = ({ value, onChange, disabled, dataTest }) => { const engine = useDataEngine() const [options, setOptions] = useState([]) @@ -103,6 +106,7 @@ const LegendSet = ({ value, onChange, dataTest }) => { return ( { LegendSet.propTypes = { onChange: PropTypes.func.isRequired, dataTest: PropTypes.string, + disabled: PropTypes.bool, value: PropTypes.object, } diff --git a/src/components/VisualizationOptions/Options/NumberType.js b/src/components/VisualizationOptions/Options/NumberType.js index b69ee1047b..82e24c1800 100644 --- a/src/components/VisualizationOptions/Options/NumberType.js +++ b/src/components/VisualizationOptions/Options/NumberType.js @@ -6,6 +6,7 @@ const NumberType = () => ( ({ + disabled: Boolean( + sGetUiDisabledOption(state, ownProps.option) ?? ownProps.disabled + ), value: ownProps.option.id ? sGetUiOption(state, { id: ownProps.option.id }) : sGetUiOptions(state)[ownProps.option.name], diff --git a/src/components/VisualizationOptions/Options/RowSubTotals.js b/src/components/VisualizationOptions/Options/RowSubTotals.js index 38c1c53f46..f886ec6ee8 100644 --- a/src/components/VisualizationOptions/Options/RowSubTotals.js +++ b/src/components/VisualizationOptions/Options/RowSubTotals.js @@ -8,6 +8,7 @@ const RowSubTotals = () => ( option={{ name: 'rowSubTotals', }} + dataTest="option-row-subtotals" /> ) diff --git a/src/components/VisualizationOptions/Options/RowTotals.js b/src/components/VisualizationOptions/Options/RowTotals.js index ddf7028871..5daa6f2597 100644 --- a/src/components/VisualizationOptions/Options/RowTotals.js +++ b/src/components/VisualizationOptions/Options/RowTotals.js @@ -8,6 +8,7 @@ const RowTotals = () => ( option={{ name: 'rowTotals', }} + dataTest="option-row-totals" /> ) diff --git a/src/components/VisualizationOptions/Options/SelectBaseOption.js b/src/components/VisualizationOptions/Options/SelectBaseOption.js index af8420046e..d6d6c94abb 100644 --- a/src/components/VisualizationOptions/Options/SelectBaseOption.js +++ b/src/components/VisualizationOptions/Options/SelectBaseOption.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types' import React, { useState } from 'react' import { connect } from 'react-redux' import { acSetUiOptions } from '../../../actions/ui.js' -import { sGetUiOptions } from '../../../reducers/ui.js' +import { sGetUiOption, sGetUiDisabledOption } from '../../../reducers/ui.js' import { tabSectionOption, tabSectionOptionToggleable, @@ -41,7 +41,7 @@ export const UnconnectedSelectBaseOption = ({ dataTest={`${dataTest}-checkbox`} /> ) : null} - {(!toggleable || checked) && !disabled ? ( + {!toggleable || checked ? (
{option.items.map(({ value, label }) => ( ({ - value: sGetUiOptions(state)[ownProps.option.name], + disabled: Boolean(sGetUiDisabledOption(state, ownProps.option)), + helpText: + sGetUiDisabledOption(state, ownProps.option)?.helpText || + ownProps.helpText, + value: sGetUiOption(state, ownProps.option), }) const mapDispatchToProps = (dispatch, ownProps) => ({ diff --git a/src/components/VisualizationOptions/Options/ShowLegendKey.js b/src/components/VisualizationOptions/Options/ShowLegendKey.js index a5211b697e..bb999582fb 100644 --- a/src/components/VisualizationOptions/Options/ShowLegendKey.js +++ b/src/components/VisualizationOptions/Options/ShowLegendKey.js @@ -1,16 +1,22 @@ import i18n from '@dhis2/d2-i18n' +import PropTypes from 'prop-types' import React from 'react' import { OPTION_SHOW_LEGEND_KEY } from '../../../modules/options.js' import { CheckboxBaseOption } from './CheckboxBaseOption.js' -const ShowLegendKey = () => ( +const ShowLegendKey = ({ disabled }) => ( ) +ShowLegendKey.propTypes = { + disabled: PropTypes.bool, +} + export default ShowLegendKey diff --git a/src/components/VisualizationOptions/Options/TextBaseOption.js b/src/components/VisualizationOptions/Options/TextBaseOption.js index 2fac2d5aaf..ac4c4795d7 100644 --- a/src/components/VisualizationOptions/Options/TextBaseOption.js +++ b/src/components/VisualizationOptions/Options/TextBaseOption.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types' import React from 'react' import { connect } from 'react-redux' import { acSetUiOption } from '../../../actions/ui.js' -import { sGetUiOption } from '../../../reducers/ui.js' +import { sGetUiOption, sGetUiDisabledOption } from '../../../reducers/ui.js' import { tabSectionOption, tabSectionOptionToggleable, @@ -112,6 +112,10 @@ UnconnectedTextBaseOption.propTypes = { } const mapStateToProps = (state, ownProps) => ({ + disabled: Boolean(sGetUiDisabledOption(state, ownProps.option)), + helpText: + sGetUiDisabledOption(state, ownProps.option)?.helpText || + ownProps.helpText, value: sGetUiOption(state, ownProps.option) || '', checked: sGetUiOption(state, { diff --git a/src/components/VisualizationOptions/VisualizationOptionsManager.js b/src/components/VisualizationOptions/VisualizationOptionsManager.js index 3bc6608ef0..bae0102753 100644 --- a/src/components/VisualizationOptions/VisualizationOptionsManager.js +++ b/src/components/VisualizationOptions/VisualizationOptionsManager.js @@ -8,6 +8,7 @@ import { HoverMenuList, HoverMenuListItem, VisualizationOptions, + VIS_TYPE_PIVOT_TABLE, } from '@dhis2/analytics' import i18n from '@dhis2/d2-i18n' import PropTypes from 'prop-types' @@ -28,6 +29,7 @@ const VisualizationOptionsManager = ({ rowDimensionItems, columns, series, + cumulativeValues, }) => { const [selectedOptionConfigKey, setSelectedOptionConfigKey] = useState(null) const onOptionsUpdate = (handler) => { @@ -54,6 +56,8 @@ const VisualizationOptionsManager = ({ : [0], hasDimensionItemsInColumns: Boolean(columnDimensionItems.length), hasDimensionItemsInRows: Boolean(rowDimensionItems.length), + hasCumulativeValuesInPt: + visualizationType === VIS_TYPE_PIVOT_TABLE && cumulativeValues, }) return ( @@ -94,6 +98,7 @@ VisualizationOptionsManager.propTypes = { visualizationType: PropTypes.string.isRequired, columnDimensionItems: PropTypes.array, columns: PropTypes.array, + cumulativeValues: PropTypes.bool, rowDimensionItems: PropTypes.array, series: PropTypes.array, } @@ -104,6 +109,7 @@ const mapStateToProps = (state) => ({ rowDimensionItems: sGetDimensionItemsByAxis(state, AXIS_ID_ROWS), series: sGetUiOptions(state).series, columns: sGetUiLayout(state).columns, + cumulativeValues: sGetUiOptions(state).cumulativeValues, }) export default connect(mapStateToProps)(VisualizationOptionsManager) diff --git a/src/components/VisualizationOptions/styles/VisualizationOptions.style.js b/src/components/VisualizationOptions/styles/VisualizationOptions.style.js index 897f544d1a..bedbf42c60 100644 --- a/src/components/VisualizationOptions/styles/VisualizationOptions.style.js +++ b/src/components/VisualizationOptions/styles/VisualizationOptions.style.js @@ -42,6 +42,12 @@ export const tabSectionTitle = css.resolve` } ` +export const tabSectionTitleDisabled = css.resolve` + span { + color: ${colors.grey600}; + } +` + export const tabSectionTitleMargin = css.resolve` span { margin-top: ${spacers.dp8}; diff --git a/src/components/VisualizationPlugin/VisualizationPlugin.js b/src/components/VisualizationPlugin/VisualizationPlugin.js index ec8d731b46..5875e674ae 100644 --- a/src/components/VisualizationPlugin/VisualizationPlugin.js +++ b/src/components/VisualizationPlugin/VisualizationPlugin.js @@ -13,17 +13,20 @@ import { import { useDataEngine } from '@dhis2/app-runtime' import { Button, IconLegend24, Layer } from '@dhis2/ui' import cx from 'classnames' +import cloneDeep from 'lodash-es/cloneDeep' import PropTypes from 'prop-types' import React, { useEffect, useState, useCallback } from 'react' import { apiFetchLegendSets } from '../../api/legendSets.js' +import { getDisabledOptions } from '../../modules/disabledOptions.js' import { fetchData } from '../../modules/fetchData.js' +import { getOptionsFromVisualization } from '../../modules/options.js' import ChartPlugin from './ChartPlugin.js' import ContextualMenu from './ContextualMenu.js' import PivotPlugin from './PivotPlugin.js' import styles from './styles/VisualizationPlugin.module.css' export const VisualizationPlugin = ({ - visualization, + visualization: originalVisualization, displayProperty, filters, forDashboard, @@ -36,6 +39,7 @@ export const VisualizationPlugin = ({ onDrill, }) => { const engine = useDataEngine() + const [visualization, setVisualization] = useState(undefined) const [ouLevels, setOuLevels] = useState(undefined) const [fetchResult, setFetchResult] = useState(null) const [contextualMenuRef, setContextualMenuRef] = useState(undefined) @@ -121,28 +125,24 @@ export const VisualizationPlugin = ({ onDrill(args) } - const doFetchData = useCallback(async () => { - const result = await fetchData({ - dataEngine: engine, - visualization, - filters, - forDashboard, - displayProperty, - }) + const doFetchData = useCallback( + async (visualization, filters, forDashboard) => { + const result = await fetchData({ + dataEngine: engine, + visualization, + filters, + forDashboard, + displayProperty, + }) - if (result.responses.length) { - onResponsesReceived(result.responses) - } + if (result.responses.length) { + onResponsesReceived(result.responses) + } - return result - }, [ - engine, - filters, - forDashboard, - displayProperty, - onResponsesReceived, - visualization, - ]) + return result + }, + [engine, displayProperty, onResponsesReceived] + ) const doFetchLegendSets = useCallback( async (legendSetIds) => { @@ -170,9 +170,23 @@ export const VisualizationPlugin = ({ useEffect(() => { setFetchResult(null) + // filter out disabled options + const disabledOptions = getDisabledOptions({ + visType: originalVisualization.type, + options: getOptionsFromVisualization(originalVisualization), + }) + + const filteredVisualization = cloneDeep(originalVisualization) + + Object.keys(disabledOptions).forEach( + (option) => delete filteredVisualization[option] + ) + + setVisualization(filteredVisualization) + const doFetchAll = async () => { const { responses, extraOptions } = await doFetchData( - visualization, + filteredVisualization, filters, forDashboard ) @@ -186,7 +200,7 @@ export const VisualizationPlugin = ({ // DHIS2-10496: show icon on the side of the single value if an icon is assigned in Maintenance app and // the "Show data item icon" option is set in DV options if ( - Boolean(visualization.icons?.length) && + Boolean(filteredVisualization.icons?.length) && dxIds[0] && responses[0].metaData.items[dxIds[0]]?.style?.icon ) { @@ -207,10 +221,10 @@ export const VisualizationPlugin = ({ const legendSetIds = [] - switch (visualization.legend?.strategy) { + switch (filteredVisualization.legend?.strategy) { case LEGEND_DISPLAY_STRATEGY_FIXED: - if (visualization.legend?.set?.id) { - legendSetIds.push(visualization.legend.set.id) + if (filteredVisualization.legend?.set?.id) { + legendSetIds.push(filteredVisualization.legend.set.id) } break case LEGEND_DISPLAY_STRATEGY_BY_DATA_ITEM: { @@ -230,12 +244,12 @@ export const VisualizationPlugin = ({ const legendSets = await doFetchLegendSets(legendSetIds) setFetchResult({ - visualization, + visualization: filteredVisualization, legendSets, responses, extraOptions, }) - setShowLegendKey(visualization.legend?.showKey) + setShowLegendKey(filteredVisualization.legend?.showKey) onLoadingComplete() } @@ -244,7 +258,7 @@ export const VisualizationPlugin = ({ }) /* eslint-disable-next-line react-hooks/exhaustive-deps */ - }, [visualization, filters, forDashboard]) + }, [originalVisualization, filters, forDashboard]) if (!fetchResult || !ouLevels) { return null @@ -257,7 +271,7 @@ export const VisualizationPlugin = ({ : null let legendSets = [] - switch (visualization.legend?.strategy) { + switch (fetchResult.visualization.legend?.strategy) { case LEGEND_DISPLAY_STRATEGY_BY_DATA_ITEM: { if ( @@ -281,7 +295,9 @@ export const VisualizationPlugin = ({ legendSet: item.legendSet, })) - const unsupportedDimensions = (visualization.series || []) + const unsupportedDimensions = ( + fetchResult.visualization.series || [] + ) .filter((serie) => serie.type === VIS_TYPE_LINE) .map((item) => item.dimensionItem) diff --git a/src/modules/disabledOptions.js b/src/modules/disabledOptions.js new file mode 100644 index 0000000000..495c3b6603 --- /dev/null +++ b/src/modules/disabledOptions.js @@ -0,0 +1,34 @@ +import { VIS_TYPE_PIVOT_TABLE } from '@dhis2/analytics' +import i18n from '@dhis2/d2-i18n' + +export const getDisabledOptions = ({ visType, options }) => { + const disabledOptions = {} + + for (const [option, value] of Object.entries(options)) { + switch (option) { + case 'cumulativeValues': { + const helpText = i18n.t( + 'Not supported when using cumulative values' + ) + + // when checked, disabled totals and numberType options + if (visType === VIS_TYPE_PIVOT_TABLE && value) { + disabledOptions.colTotals = {} + disabledOptions.colSubTotals = {} + disabledOptions.rowTotals = {} + disabledOptions.rowSubTotals = {} + disabledOptions.numberType = { + helpText, + } + disabledOptions.legend = { + helpText, + } + } + + break + } + } + } + + return disabledOptions +} diff --git a/src/modules/error.js b/src/modules/error.js index 7f61a71b5a..07160d1aa9 100644 --- a/src/modules/error.js +++ b/src/modules/error.js @@ -335,6 +335,28 @@ export class ValueTypeError extends VisualizationError { } } +export class AnalyticsGenerationError extends VisualizationError { + constructor() { + super( + GenericError, + i18n.t('Something went wrong'), + i18n.t( + "There's a problem with the generated analytics. Contact a system administrator." + ) + ) + } +} + +export class AnalyticsRequestError extends VisualizationError { + constructor() { + super( + GenericError, + i18n.t('Something went wrong'), + i18n.t("There's a syntax problem with the analytics request.") + ) + } +} + export const genericErrorTitle = i18n.t('Something went wrong') const getAvailableAxesDescription = (visType) => { diff --git a/src/modules/options/config.js b/src/modules/options/config.js index 194e291094..b55b1c9d23 100644 --- a/src/modules/options/config.js +++ b/src/modules/options/config.js @@ -20,6 +20,7 @@ import singleValueConfig from './singleValueConfig.js' export const getOptionsByType = ({ type, + hasCumulativeValuesInPt, hasDimensionItemsInColumns, hasDimensionItemsInRows, hasDisabledSections, @@ -33,6 +34,7 @@ export const getOptionsByType = ({ const isVertical = isVerticalType(type) const defaultProps = { + hasCumulativeValuesInPt, hasDisabledSections, isStacked, isColumnBased, @@ -52,6 +54,7 @@ export const getOptionsByType = ({ return singleValueConfig() case VIS_TYPE_PIVOT_TABLE: return pivotTableConfig({ + hasCumulativeValuesInPt, hasDimensionItemsInColumns, hasDimensionItemsInRows, }) diff --git a/src/modules/options/pivotTableConfig.js b/src/modules/options/pivotTableConfig.js index 6e95b5ff87..3b36cfe3d8 100644 --- a/src/modules/options/pivotTableConfig.js +++ b/src/modules/options/pivotTableConfig.js @@ -7,6 +7,7 @@ import ColSubTotals from '../../components/VisualizationOptions/Options/ColSubTo import ColTotals from '../../components/VisualizationOptions/Options/ColTotals.js' import CompletedOnly from '../../components/VisualizationOptions/Options/CompletedOnly.js' import Cumulative from '../../components/VisualizationOptions/Options/Cumulative.js' +import CumulativeValues from '../../components/VisualizationOptions/Options/CumulativeValues.js' import DigitGroupSeparator from '../../components/VisualizationOptions/Options/DigitGroupSeparator.js' import DisplayDensity from '../../components/VisualizationOptions/Options/DisplayDensity.js' import FixColumnHeaders from '../../components/VisualizationOptions/Options/FixColumnHeaders.js' @@ -37,15 +38,21 @@ import getLimitValuesTab from './tabs/limitValues.js' import getSeriesTab from './tabs/series.js' import getStyleTab from './tabs/style.js' -export default ({ hasDimensionItemsInColumns, hasDimensionItemsInRows }) => [ +export default ({ + hasCumulativeValuesInPt, + hasDimensionItemsInColumns, + hasDimensionItemsInRows, +}) => [ getDataTab([ getDisplayTemplate({ content: React.Children.toArray([ + , , , ]), }), getTotalsTemplate({ + hasCumulativeValuesInPt, content: React.Children.toArray([ , , diff --git a/src/modules/options/sections/templates/totals.js b/src/modules/options/sections/templates/totals.js index 30484f3d2e..0d8d6fad07 100644 --- a/src/modules/options/sections/templates/totals.js +++ b/src/modules/options/sections/templates/totals.js @@ -1,7 +1,10 @@ import i18n from '@dhis2/d2-i18n' -export default ({ content }) => ({ +export default ({ hasCumulativeValuesInPt, content }) => ({ key: 'data-totals', label: i18n.t('Totals'), + helpText: hasCumulativeValuesInPt + ? i18n.t('Totals are not supported when using cumulative values') + : null, content, }) diff --git a/src/modules/ui.js b/src/modules/ui.js index 3af241b280..39b6cc6f10 100644 --- a/src/modules/ui.js +++ b/src/modules/ui.js @@ -14,6 +14,7 @@ import { VIS_TYPE_GAUGE, VIS_TYPE_SINGLE_VALUE, } from '@dhis2/analytics' +import { getDisabledOptions } from './disabledOptions.js' import { BASE_FIELD_YEARLY_SERIES } from './fields/baseFields.js' import { getInverseLayout } from './layout.js' import { getOptionsFromVisualization } from './options.js' @@ -25,24 +26,30 @@ export const ITEM_ATTRIBUTE_VERTICAL = 'VERTICAL' export const ITEM_ATTRIBUTE_HORIZONTAL = 'HORIZONTAL' // Transform from backend model to store.ui format -export const getUiFromVisualization = (vis, currentState = {}) => ({ - ...currentState, - type: vis.type || defaultVisType, - options: getOptionsFromVisualization(vis), - layout: layoutGetAxisIdDimensionIdsObject(vis), - itemsByDimension: layoutGetDimensionIdItemIdsObject(vis), - parentGraphMap: - vis.parentGraphMap || - getParentGraphMapFromVisualization(vis) || - currentState.parentGraphMap, - yearOverYearSeries: - isYearOverYear(vis.type) && vis[BASE_FIELD_YEARLY_SERIES] - ? vis[BASE_FIELD_YEARLY_SERIES] - : currentState.yearOverYearSeries, - yearOverYearCategory: isYearOverYear(vis.type) - ? vis.rows[0].items.map((item) => item.id) - : currentState.yearOverYearCategory, -}) +export const getUiFromVisualization = (vis, currentState = {}) => { + const visType = vis.type || defaultVisType + const options = getOptionsFromVisualization(vis) + + return { + ...currentState, + type: visType, + options, + disabledOptions: getDisabledOptions({ visType, options }), + layout: layoutGetAxisIdDimensionIdsObject(vis), + itemsByDimension: layoutGetDimensionIdItemIdsObject(vis), + parentGraphMap: + vis.parentGraphMap || + getParentGraphMapFromVisualization(vis) || + currentState.parentGraphMap, + yearOverYearSeries: + isYearOverYear(vis.type) && vis[BASE_FIELD_YEARLY_SERIES] + ? vis[BASE_FIELD_YEARLY_SERIES] + : currentState.yearOverYearSeries, + yearOverYearCategory: isYearOverYear(vis.type) + ? vis.rows[0].items.map((item) => item.id) + : currentState.yearOverYearCategory, + } +} // Transform from store.ui to default format const defaultUiAdapter = (ui) => ({ @@ -100,20 +107,35 @@ const scatterUiAdapter = (ui) => { } export const getAdaptedUiByType = (ui) => { + let adaptedUi + switch (ui.type) { case VIS_TYPE_YEAR_OVER_YEAR_LINE: case VIS_TYPE_YEAR_OVER_YEAR_COLUMN: { - return yearOverYearUiAdapter(ui) + adaptedUi = yearOverYearUiAdapter(ui) + break } case VIS_TYPE_PIVOT_TABLE: - return ui + adaptedUi = ui + break case VIS_TYPE_GAUGE: case VIS_TYPE_SINGLE_VALUE: - return singleValueUiAdapter(ui) + adaptedUi = singleValueUiAdapter(ui) + break case VIS_TYPE_SCATTER: - return scatterUiAdapter(ui) + adaptedUi = scatterUiAdapter(ui) + break default: - return defaultUiAdapter(ui) + adaptedUi = defaultUiAdapter(ui) + break + } + + return { + ...adaptedUi, + disabledOptions: getDisabledOptions({ + visType: adaptedUi.type, + options: adaptedUi.options, + }), } } diff --git a/src/reducers/ui.js b/src/reducers/ui.js index db5726fd84..d6c7113e53 100644 --- a/src/reducers/ui.js +++ b/src/reducers/ui.js @@ -59,6 +59,7 @@ import { export const SET_UI = 'SET_UI' export const SET_UI_FROM_VISUALIZATION = 'SET_UI_FROM_VISUALIZATION' export const SET_UI_TYPE = 'SET_UI_TYPE' +export const SET_UI_DISABLED_OPTIONS = 'SET_UI_DISABLED_OPTIONS' export const SET_UI_OPTIONS = 'SET_UI_OPTIONS' export const SET_UI_OPTION = 'SET_UI_OPTION' export const SET_UI_OPTION_FONT_STYLE = 'SET_UI_OPTION_FONT_STYLE' @@ -82,6 +83,7 @@ export const UPDATE_UI_SERIES_ITEM = 'UPDATE_UI_SERIES_ITEM' export const DEFAULT_UI = { type: defaultVisType, options: getOptionsForUi(), + disabledOptions: {}, layout: { columns: [DIMENSION_ID_DATA], rows: [DIMENSION_ID_PERIOD], @@ -150,6 +152,12 @@ export default (state = DEFAULT_UI, action) => { type: action.value, } } + case SET_UI_DISABLED_OPTIONS: { + return { + ...state, + disabledOptions: action.value, + } + } case SET_UI_OPTIONS: { return { ...state, @@ -665,6 +673,7 @@ export default (state = DEFAULT_UI, action) => { export const sGetUi = (state) => state.ui export const sGetUiType = (state) => sGetUi(state).type +export const sGetUiDisabledOptions = (state) => sGetUi(state).disabledOptions export const sGetUiOptions = (state) => sGetUi(state).options export const sGetUiLayout = (state) => sGetUi(state).layout export const sGetUiLayoutRows = (state) => sGetUi(state).layout.rows @@ -728,6 +737,9 @@ export const sGetAxisSetup = (state) => { : [] } +export const sGetUiDisabledOption = (state, option) => + sGetUiDisabledOptions(state)[option.name] + export const sGetUiOption = (state, option) => { const options = sGetUi(state).options const [axisType, axisIndex] = (option.axisId || '').split('_') diff --git a/yarn.lock b/yarn.lock index 943e3ca86f..bdccf01e0b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1416,10 +1416,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@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@cypress/request/-/request-3.0.1.tgz#72d7d5425236a2413bd3d8bb66d02d9dc3168960" + integrity sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ== dependencies: aws-sign2 "~0.7.0" aws4 "^1.8.0" @@ -1434,9 +1434,9 @@ json-stringify-safe "~5.0.1" mime-types "~2.1.19" performance-now "^2.1.0" - qs "~6.10.3" + qs "6.10.4" safe-buffer "^5.1.2" - tough-cookie "~2.5.0" + tough-cookie "^4.1.3" tunnel-agent "^0.6.0" uuid "^8.3.2" @@ -2028,10 +2028,10 @@ classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2/analytics@^26.1.6": - version "26.1.6" - resolved "https://registry.yarnpkg.com/@dhis2/analytics/-/analytics-26.1.6.tgz#d76d7aa40c4538fae6afbf7d8d5e7cfbad81efb5" - integrity sha512-XIoe2/mUjIlxzMrmA1iVeSH3zydZG7LC1LqZJQK8TBrneC7IrLhVFka+0zaGvTyA/2P35c5xFxSie7gKC8h8Og== +"@dhis2/analytics@^26.3.0": + version "26.3.0" + resolved "https://registry.yarnpkg.com/@dhis2/analytics/-/analytics-26.3.0.tgz#ada2fe27442f19704fa704e334546416c85ea1d6" + integrity sha512-B/pUh8K8wyivL4yBiwqPoQ94pMWwCqh0xu3Uak4jmJqS+jO0slUlyDLtAmXU/jqRlRgRg1nR4u18npjd511Q7A== dependencies: "@dhis2/d2-ui-rich-text" "^7.4.1" "@dhis2/multi-calendar-dates" "1.0.0" @@ -2985,19 +2985,7 @@ "@reportportal/client-javascript" "^5.0.12" strip-ansi "^6.0.1" -"@reportportal/client-javascript@^5.0.12": - version "5.0.12" - resolved "https://registry.yarnpkg.com/@reportportal/client-javascript/-/client-javascript-5.0.12.tgz#b6d9254014545ca56599c05105854ea814561f1e" - integrity sha512-ECLvuDLV7KyKs0wG9Sis3ZqHOq9VMg3fywm1VDegd5HGDKC1hoXxFKfz6ngPY8FZ5O1nt1UJvgEs47shtPHQCg== - dependencies: - axios "^0.27.2" - axios-retry "^3.4.0" - glob "^7.2.3" - ini "^2.0.0" - uniqid "^5.4.0" - uuid "^9.0.0" - -"@reportportal/client-javascript@^5.0.14": +"@reportportal/client-javascript@^5.0.12", "@reportportal/client-javascript@^5.0.14": version "5.0.14" resolved "https://registry.yarnpkg.com/@reportportal/client-javascript/-/client-javascript-5.0.14.tgz#48a40f2f33129d74bb7e451aaf25d44742e26fcc" integrity sha512-4ge9ddOB1rFlzqI6j43qCw0cyjQOloiPChA1EFyF3dcV2BXHzGbh8S3SNhwxibvlQtV6piU8e0W9CLN4UWXvSA== @@ -3432,10 +3420,12 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== -"@types/node@*", "@types/node@^14.14.31": - version "14.18.32" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.32.tgz#8074f7106731f1a12ba993fe8bad86ee73905014" - integrity sha512-Y6S38pFr04yb13qqHf8uk1nHE3lXgQ30WZbv1mLliV9pt0NjvqdWttLcrOYLnXbOafknVYRHZGoMSpR9UwfYow== +"@types/node@*", "@types/node@^18.17.5": + version "18.19.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.3.tgz#e4723c4cb385641d61b983f6fe0b716abd5f8fc0" + integrity sha512-k5fggr14DwAytoA/t8rPrIz++lXK7/DqckthCmoZOKNsEbJkId4Z//BqgApXBUGrGddrigYa1oqheo/7YmW4rg== + dependencies: + undici-types "~5.26.4" "@types/parse-json@^4.0.0": version "4.0.0" @@ -6220,14 +6210,14 @@ cypress-tags@^1.1.2: boolean-parser "0.0.2" through "^2.3.8" -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@^13.6.1: + version "13.6.1" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.6.1.tgz#c5f714f08551666ed3ac1fa95718eabb23a416df" + integrity sha512-k1Wl5PQcA/4UoTffYKKaxA0FJKwg8yenYNYRzLt11CUR0Kln+h7Udne6mdU1cUIdXBDTVZWtmiUjzqGs7/pEpw== dependencies: - "@cypress/request" "^2.88.10" + "@cypress/request" "^3.0.0" "@cypress/xvfb" "^1.2.4" - "@types/node" "^14.14.31" + "@types/node" "^18.17.5" "@types/sinonjs__fake-timers" "8.1.1" "@types/sizzle" "^2.3.2" arch "^2.2.0" @@ -6260,9 +6250,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" @@ -12866,7 +12857,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== @@ -13003,10 +12994,10 @@ qs@6.10.3: dependencies: side-channel "^1.0.4" -qs@~6.10.3: - version "6.10.5" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.5.tgz#974715920a80ff6a262264acd2c7e6c2a53282b4" - integrity sha512-O5RlPh0VFtR78y79rgcgKK4wbAI0C5zGVLztOIdpWX6ep368q5Hv6XRxDvXuZ9q3C6v+e3n8UfZZJw7IIG27eQ== +qs@6.10.4: + version "6.10.4" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.4.tgz#6a3003755add91c0ec9eacdc5f878b034e73f9e7" + integrity sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g== dependencies: side-channel "^1.0.4" @@ -13035,6 +13026,11 @@ querystring@0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g== +querystringify@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== + quick-lru@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-1.1.0.tgz#4360b17c61136ad38078397ff11416e186dcfbb8" @@ -14070,10 +14066,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.5, semver@^7.3.7: - version "7.3.7" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" - integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== +semver@^7.2.1, semver@^7.3.2, semver@^7.3.5, semver@^7.3.7, 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" @@ -15263,14 +15259,15 @@ tough-cookie@^2.3.3, tough-cookie@^2.3.4, tough-cookie@~2.5.0: psl "^1.1.28" punycode "^2.1.1" -tough-cookie@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" - integrity sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg== +tough-cookie@^4.0.0, 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.1.2" + universalify "^0.2.0" + url-parse "^1.5.3" tr46@^1.0.1: version "1.0.1" @@ -15469,6 +15466,11 @@ undeclared-identifiers@^1.1.2: simple-concat "^1.0.0" xtend "^4.0.1" +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" @@ -15529,11 +15531,16 @@ unique-string@^2.0.0: dependencies: crypto-random-string "^2.0.0" -universalify@^0.1.0, universalify@^0.1.2: +universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== +universalify@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" + integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== + universalify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" @@ -15620,6 +15627,14 @@ url-parse-lax@^3.0.0: dependencies: prepend-http "^2.0.0" +url-parse@^1.5.3: + version "1.5.10" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + url@~0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"