Skip to content

Commit

Permalink
fix: parse the ou filter correctly for all ou types (#2691)
Browse files Browse the repository at this point in the history
Fixes DHIS2-14544

If path exists, then parse it. Otherwise use id,
which will be the case for groups, levels and UER_ORGUNITS)
  • Loading branch information
jenniferarnesen committed Sep 13, 2023
1 parent 4d25585 commit 5ba8934
Show file tree
Hide file tree
Showing 9 changed files with 260 additions and 54 deletions.
39 changes: 28 additions & 11 deletions cypress/integration/common/view/add_a_FILTERTYPE_filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,35 @@ const FACILITY_TYPE = 'Clinic'
When('I add a {string} filter', (dimensionType) => {
cy.contains('Add filter').click()

// open the dimensions modal
cy.get(filterDimensionsPanelSel).contains(dimensionType).click()

// select an item in the modal
if (dimensionType === 'Period') {
cy.get(unselectedItemsSel).contains(PERIOD).dblclick()
} else if (dimensionType === 'Organisation unit') {
cy.get(orgUnitTreeSel, EXTENDED_TIMEOUT)
.find('[type="checkbox"]', EXTENDED_TIMEOUT)
.check(OU_ID)
} else {
cy.get(unselectedItemsSel).contains(FACILITY_TYPE).dblclick()
switch (dimensionType) {
case 'Period':
cy.get(filterDimensionsPanelSel).contains(dimensionType).click()
cy.get(unselectedItemsSel).contains(PERIOD).dblclick()
break
case 'Organisation unit':
cy.get(filterDimensionsPanelSel).contains(dimensionType).click()
cy.get(orgUnitTreeSel, EXTENDED_TIMEOUT)
.find('[type="checkbox"]', EXTENDED_TIMEOUT)
.check(OU_ID)
break
case 'Org unit group':
cy.get(filterDimensionsPanelSel)
.contains('Organisation unit')
.click()
cy.getByDataTest('org-unit-group-select').click()
cy.getByDataTest('dhis2-uicore-select-menu-menuwrapper')
.contains('District')
.click()
// close the popup
cy.getByDataTest('dhis2-uicore-select-menu-menuwrapper')
.closest('[data-test="dhis2-uicore-layer"]')
.click('topLeft')
break

default:
cy.get(filterDimensionsPanelSel).contains(dimensionType).click()
cy.get(unselectedItemsSel).contains(FACILITY_TYPE).dblclick()
}

// confirm to apply the filter
Expand Down
8 changes: 7 additions & 1 deletion cypress/integration/view/dashboard_filter.feature
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Feature: Dashboard filter

Scenario: I add a Period filter
When I start a new dashboard
And I add a MAP and a CHART and save
And I add items and save
Then the dashboard displays in view mode
When I add a "Period" filter
Then the Period filter is applied to the dashboard
Expand All @@ -19,6 +19,12 @@ Feature: Dashboard filter
When I add a "Facility Type" filter
Then the Facility Type filter is applied to the dashboard

Scenario: I add a Org unit group filter
Given I open existing dashboard
Then the dashboard displays in view mode
When I add a "Org unit group" filter
Then the Org unit group filter is applied to the dashboard

Scenario: I can access the dimensions modal from the filter badge
Given I open existing dashboard
When I add a "Period" filter
Expand Down
87 changes: 60 additions & 27 deletions cypress/integration/view/dashboard_filter/create_dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,48 +9,78 @@ import {
dashboardChipSel,
dashboardTitleSel,
} from '../../../elements/viewDashboard.js'
import { getApiBaseUrl } from '../../../support/server/utils.js'
import {
EXTENDED_TIMEOUT,
createDashboardTitle,
} from '../../../support/utils.js'

const TEST_DASHBOARD_TITLE = createDashboardTitle('af')

When('I add a MAP and a CHART and save', () => {
//add the title
cy.get('[data-test="dashboard-title-input"]').type(TEST_DASHBOARD_TITLE)
const customApp = {
name: 'Users-Role-Monitor-Widget',
id: '5e43908a-3105-4baa-9a00-87a94ebdc034',
}

// add items
cy.get('[data-test="item-search"]').click()
cy.get('[data-test="item-search"]')
.find('input')
.type('Inpatient', { force: true })
When('I add items and save', () => {
// first install a custom app
cy.request('POST', `${getApiBaseUrl()}/api/appHub/${customApp.id}`).then(
(response) => {
expect(response.status).to.eq(204)

//chart
cy.get(
'[data-test="menu-item-Inpatient: BMI this year by districts"]'
).click()
//add the dashboard title
cy.get('[data-test="dashboard-title-input"]').type(
TEST_DASHBOARD_TITLE
)

cy.get('[data-test="dhis2-uicore-layer"]').click('topLeft')
// open item selector
cy.get('[data-test="item-search"]').click()
cy.get('[data-test="item-search"]')
.find('input')
.type('Inpatient', { force: true })

cy.get('[data-test="item-search"]').click()
cy.get('[data-test="item-search"]')
.find('input')
.type('ipt 2', { force: true })
//CHART
cy.get(
'[data-test="menu-item-Inpatient: BMI this year by districts"]'
).click()

//map
cy.get('[data-test="menu-item-ANC: IPT 2 Coverage this year"]').click()
cy.get('[data-test="dhis2-uicore-layer"]').click('topLeft')

cy.get('[data-test="dhis2-uicore-layer"]').click('topLeft')
cy.get('[data-test="item-search"]').click()
cy.get('[data-test="item-search"]')
.find('input')
.type('ipt 2', { force: true })

//move things so the dashboard is more compact
cy.get(`${gridItemSel}.MAP`)
.trigger('mousedown')
.trigger('mousemove', { clientX: 650 })
.trigger('mouseup')
//MAP
cy.get(
'[data-test="menu-item-ANC: IPT 2 Coverage this year"]'
).click()

//save
cy.get('button').contains('Save changes', EXTENDED_TIMEOUT).click()
// close the item selector
cy.get('[data-test="dhis2-uicore-layer"]').click('topLeft')

//add a custom app item
cy.get('[data-test="item-search"]').click()
cy.get('[data-test="item-search"]')
.find('input')
.type('Role Monitor', { force: true })

cy.contains('Role Monitor Widget').click()

// close the item selector
cy.get('[data-test="dhis2-uicore-layer"]').click('topLeft')

//move things so the dashboard is more compact
// eslint-disable-next-line cypress/unsafe-to-chain-command

Check failure on line 74 in cypress/integration/view/dashboard_filter/create_dashboard.js

View workflow job for this annotation

GitHub Actions / lint

Definition for rule 'cypress/unsafe-to-chain-command' was not found
cy.get(`${gridItemSel}.MAP`)
.trigger('mousedown')
.trigger('mousemove', { clientX: 650 })
.trigger('mouseup')

//save
cy.get('button').contains('Save changes', EXTENDED_TIMEOUT).click()
}
)
})

Given('I open existing dashboard', () => {
Expand Down Expand Up @@ -79,4 +109,7 @@ Then('different dashboard displays in view mode', () => {
cy.get(dashboardTitleSel)
.should('be.visible')
.and('not.contain', TEST_DASHBOARD_TITLE)

// remove the custom app
cy.request('DELETE', `${getApiBaseUrl()}/api/apps/${customApp.name}`)
})
17 changes: 17 additions & 0 deletions cypress/integration/view/dashboard_filter/dashboard_filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,23 @@ Then('the Facility Type filter is applied to the dashboard', () => {
.should('be.visible')
})

Then('the Org unit group filter is applied to the dashboard', () => {
// check that the filter badge is correct
cy.get(filterBadgeSel)
.contains('Organisation unit: District')
.should('be.visible')

// check that the custom app is loaded (see ticket DHIS2-14544)
cy.get('iframe')
.invoke('attr', 'title')
.contains('Role Monitor Widget')
.scrollIntoView()
cy.get('iframe')
.invoke('attr', 'title')
.contains('Role Monitor Widget')
.should('be.visible')
})

Then('the filter modal is opened', () => {
cy.get(dimensionsModalSel, EXTENDED_TIMEOUT).should('be.visible')
})
25 changes: 25 additions & 0 deletions cypress/support/commands.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Cypress.Commands.add('getByDataTest', (selector, ...args) =>
cy.get(`[data-test=${selector}]`, ...args)
)
Cypress.Commands.add(
'findByDataTest',
{
prevSubject: true,
},
(subject, selector, ...args) =>
cy.wrap(subject).find(`[data-test="${selector}"]`, ...args)
)

Cypress.Commands.add(
'containsExact',
{
prevSubject: 'optional',
},
(subject, selector) =>
cy.wrap(subject).contains(
new RegExp(
`^${selector.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')}$`, //eslint-disable-line no-useless-escape
'gm'
)
)
)
1 change: 1 addition & 0 deletions cypress/support/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { enableAutoLogin } from '@dhis2/cypress-commands'
import { enableNetworkShim } from './server/index.js'
import { getDefaultMode, isStubMode } from './server/utils.js'
import './commands.js'

enableNetworkShim()

Expand Down
16 changes: 1 addition & 15 deletions src/components/Item/AppItem/Item.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,13 @@ import { Divider, colors, spacers, IconQuestion24 } from '@dhis2/ui'
import PropTypes from 'prop-types'
import React from 'react'
import { connect } from 'react-redux'
import { FILTER_ORG_UNIT } from '../../../actions/itemFilters.js'
import { EDIT, isEditMode } from '../../../modules/dashboardModes.js'
import {
sGetItemFiltersRoot,
DEFAULT_STATE_ITEM_FILTERS,
} from '../../../reducers/itemFilters.js'
import ItemHeader from '../ItemHeader/ItemHeader.js'

const getIframeSrc = (appDetails, item, itemFilters) => {
let iframeSrc = `${appDetails.launchUrl}?dashboardItemId=${item.id}`

if (itemFilters[FILTER_ORG_UNIT] && itemFilters[FILTER_ORG_UNIT].length) {
const ouIds = itemFilters[FILTER_ORG_UNIT].map(
(ouFilter) => ouFilter.path.split('/').slice(-1)[0]
)

iframeSrc += `&userOrgUnit=${ouIds.join(',')}`
}

return iframeSrc
}
import { getIframeSrc } from './getIframeSrc.js'

const AppItem = ({ dashboardMode, item, itemFilters }) => {
const { d2 } = useD2()
Expand Down
106 changes: 106 additions & 0 deletions src/components/Item/AppItem/__tests__/getIframeSrc.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { getIframeSrc } from '../getIframeSrc.js'

const appDetails = { launchUrl: 'debug/dev' }
const dashboardItem = { id: 'rainbowdashitem' }
const expectedSrc = `${appDetails.launchUrl}?dashboardItemId=${dashboardItem.id}`

describe('getIframeSrc', () => {
it('no ou filter', () => {
const ouFilter = []

const src = getIframeSrc(appDetails, dashboardItem, { ou: ouFilter })
expect(src).toEqual(expectedSrc)
})

it('org units chosen from the tree', () => {
const ouFilter = [
{
id: 'fdc6uOvgoji',
path: '/ImspTQPwCqd/fdc6uOvgoji',
name: 'Bombali',
},
{
id: 'lc3eMKXaEfw',
path: '/ImspTQPwCqd/lc3eMKXaEfw',
name: 'Bonthe',
},
]

const src = getIframeSrc(appDetails, dashboardItem, { ou: ouFilter })
expect(src).toEqual(
`${expectedSrc}&userOrgUnit=fdc6uOvgoji,lc3eMKXaEfw`
)
})

it('org unit group and org unit from tree', () => {
const ouFilter = [
{
id: 'OU_GROUP-b0EsAxm8Nge',
name: 'Western Area',
},
{
id: 'lc3eMKXaEfw',
path: '/ImspTQPwCqd/lc3eMKXaEfw',
name: 'Bonthe',
},
]

const src = getIframeSrc(appDetails, dashboardItem, { ou: ouFilter })
expect(src).toEqual(
`${expectedSrc}&userOrgUnit=OU_GROUP-b0EsAxm8Nge,lc3eMKXaEfw`
)
})

it('org unit level and org unit from tree', () => {
const ouFilter = [
{
id: 'LEVEL-m9lBJogzE95',
name: 'Facility',
},
{
id: 'fdc6uOvgoji',
path: '/ImspTQPwCqd/fdc6uOvgoji',
name: 'Bombali',
},
]

const src = getIframeSrc(appDetails, dashboardItem, { ou: ouFilter })
expect(src).toEqual(
`${expectedSrc}&userOrgUnit=LEVEL-m9lBJogzE95,fdc6uOvgoji`
)
})

it('user org unit', () => {
const ouFilter = [
{
id: 'USER_ORGUNIT',
displayName: 'User organisation unit',
},
]

const src = getIframeSrc(appDetails, dashboardItem, { ou: ouFilter })
expect(src).toEqual(`${expectedSrc}&userOrgUnit=USER_ORGUNIT`)
})

it('all user org units', () => {
const ouFilter = [
{
id: 'USER_ORGUNIT_CHILDREN',
displayName: 'User sub-units',
},
{
id: 'USER_ORGUNIT_GRANDCHILDREN',
displayName: 'User sub-x2-units',
},
{
id: 'USER_ORGUNIT',
displayName: 'User organisation unit',
},
]

const src = getIframeSrc(appDetails, dashboardItem, { ou: ouFilter })
expect(src).toEqual(
`${expectedSrc}&userOrgUnit=USER_ORGUNIT_CHILDREN,USER_ORGUNIT_GRANDCHILDREN,USER_ORGUNIT`
)
})
})
15 changes: 15 additions & 0 deletions src/components/Item/AppItem/getIframeSrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { FILTER_ORG_UNIT } from '../../../actions/itemFilters.js'

export const getIframeSrc = (appDetails, item, itemFilters) => {
let iframeSrc = `${appDetails.launchUrl}?dashboardItemId=${item.id}`

if (itemFilters[FILTER_ORG_UNIT] && itemFilters[FILTER_ORG_UNIT].length) {
const ouIds = itemFilters[FILTER_ORG_UNIT].map(({ id, path }) =>
path ? path.split('/').slice(-1)[0] : id
)

iframeSrc += `&userOrgUnit=${ouIds.join(',')}`
}

return iframeSrc
}

0 comments on commit 5ba8934

Please sign in to comment.