Skip to content

Commit

Permalink
feat: Improved Period Selection and Usability Enhancements (#3407)
Browse files Browse the repository at this point in the history
feat: Improved Period Selection and Usability Enhancements

Thematic Layers
The Period tab has been completely redesigned to improve flexibility and clarity:
* It now uses the same period selector as other analytics apps, allowing selection
  of multiple periods and support for non-Gregorian calendars (#DHIS2-15796).
* Mapping options (Single, Timeline, or Split Map) are now more clearly displayed
  with icons and improved information messages.
* All mapping options now support multiple periods selections, including mixed
  selections of different period types, fixed and/or relative presets (#DHIS2-16358).
* Start and End Date inputs use the new standard calendar (currently limited to
  accepting dates converted to the Gregorian calendar) which supports translation
  and improves keyboard entry.
* When switching between Presets and Start-end dates, the previous selection is
  restored (#DHIS2-18722).

Event & Tracked Entity Layers
In the Period tab, the new standard calendar is also used for start and end date
selection.

Other Usability Enhancements
* Users can now select their organisation unit in combination with other organisation
  units across all layer types (#DHIS2-18066).
* New keyboard shortcuts are available:
  * Tab / Shift+Tab to switch back and forth between start and end date inputs.
  * Press Esc to close dialogs (layer add/edit, download mode, layer sources
    management...) and popups in the map.
  * Long-press Esc to close sidebars (interpretations, organisation unit profile) and
    the data table.
* Dialogs (layer add/edit and layer sources management) now feature an "x" button
  for easier closing.
* The timeline now resizes dynamically when the window size changes or sidebars are
  opened/closed.
  • Loading branch information
BRaimbault authored Feb 7, 2025
1 parent 1afa064 commit 412a3ad
Show file tree
Hide file tree
Showing 86 changed files with 4,059 additions and 817 deletions.
6 changes: 5 additions & 1 deletion cypress/elements/event_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ export class EventLayer extends Layer {
return this
}

selectPeriodType(periodType) {
selectPeriodType({ periodType } = {}) {
if (!periodType) {
throw new Error("The 'periodType' parameter is required.")
}

cy.getByDataTest('relative-period-select-content').click()
cy.contains(periodType).click()

Expand Down
31 changes: 19 additions & 12 deletions cypress/elements/layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export class Layer {
.contains(level)
.find('input')
.check()
cy.get('body').click() // Close the modal menu
cy.get('body').click()

return this
}
Expand All @@ -59,27 +59,34 @@ export class Layer {
.find('input')
.uncheck()

cy.get('body').click() // Close the modal menu
cy.get('body').click()

return this
}

typeStartDate(dateString) {
cy.get('label')
.contains('Start date')
.next()
.find('input')
.type(dateString)
cy.getByDataTest('calendar-clear-button').eq(0).click()

if (dateString) {
cy.getByDataTest('start-date-input-content')
.find('input')
.type(dateString)
cy.get('body').click(0, 0)
}

return this
}

typeEndDate(dateString) {
cy.get('label')
.contains('End date')
.next()
.find('input')
.type(dateString)
cy.getByDataTest('calendar-clear-button').eq(1).click()

if (dateString) {
cy.getByDataTest('end-date-input-content')
.find('input')
.type(dateString)
cy.get('body').click(0, 0)
}

return this
}

Expand Down
77 changes: 74 additions & 3 deletions cypress/elements/thematic_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,80 @@ export class ThematicLayer extends Layer {
return this
}

selectPeriodType(periodType) {
cy.get('[data-test="periodtypeselect"]').click()
cy.contains(periodType).click()
removeAllPeriods() {
cy.getByDataTest('period-dimension-transfer-actions-removeall').click()

return this
}

selectPeriodType({
periodType,
periodDimension = 'fixed',
n = 'last',
y = '',
removeAll = true,
} = {}) {
if (!periodType) {
throw new Error("The 'periodType' parameter is required.")
}

// Select fixed / relative periods
cy.getByDataTest(
`period-dimension-${periodDimension}-periods-button`
).click()
// Open dropdown for period type
cy.getByDataTest(
`period-dimension-${periodDimension}-period-filter${
periodDimension === 'fixed' ? '-period-type' : ''
}-content`
).click()
// Select period type in dropdown if not active already
cy.get(`[data-value="${periodType}"]`).then(($el) => {
if ($el.hasClass('active')) {
cy.get('body').click('topLeft')
} else {
cy.wrap($el).click()
}
})

if (removeAll) {
cy.getByDataTest(
'period-dimension-transfer-actions-removeall'
).click()
}

if (y !== '') {
cy.getByDataTest(
'period-dimension-fixed-period-filter-year-content'
)
.get('input[type="number"]')
.clear()
cy.getByDataTest(
'period-dimension-fixed-period-filter-year-content'
)
.get('input[type="number"]')
.type(y)
}
if (n === 'last') {
cy.getByDataTest('period-dimension-transfer-option-content')
.last()
.dblclick()
} else {
cy.getByDataTest('period-dimension-transfer-option-content')
.eq(n)
.dblclick()
}

return this
}

selectPresets() {
cy.contains('Choose from presets').click()

return this
}
selectStartEndDates() {
cy.contains('Define start - end dates').click()

return this
}
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion cypress/integration/dataTable.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ describe('data table', () => {
.selectProgram('Inpatient morbidity and mortality')
.validateStage('Inpatient morbidity and mortality')
.selectTab('Period')
.selectPeriodType('Start/end dates')
.selectPeriodType({ periodType: 'Start/end dates' })
.typeStartDate(`${CURRENT_YEAR - 1}-01-01`)
.typeEndDate(`${CURRENT_YEAR - 1}-01-03`)
.addToMap()
Expand Down
81 changes: 81 additions & 0 deletions cypress/integration/keyboard.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { EXTENDED_TIMEOUT } from '../support/util.js'

const map = {
id: 'eDlFx0jTtV9',
cardTitle: 'ANC LLITN coverage',
}

const alt = {
group: 'HIV',
name: 'VCCT post-test counselling rate',
}

describe('keyboard navigation', () => {
it('tab', () => {
cy.visit('/')

// StartEndDate
cy.getByDataTest('add-layer-button').click()

cy.get(`[data-test="addlayeritem-thematic"]`).click()
cy.getByDataTest('layeredit').should('be.visible')

cy.getByDataTest('dhis2-uicore-tabbar-tabs')
.find('button')
.contains('Period')
.click()
cy.contains('Define start - end dates').click()
cy.getByDataTest('calendar-clear-button').eq(0).click()
cy.getByDataTest('start-date-input-content').find('input').type('123')
cy.getByDataTest('calendar').should('be.visible')
cy.realPress('Tab')
cy.getByDataTest('calendar').should('not.exist')
cy.realPress('Tab')
cy.getByDataTest('calendar').should('be.visible')
cy.realPress('Tab')
cy.getByDataTest('calendar').should('not.exist')
})
it('esc', () => {
cy.visit(`/#/${map.id}`, EXTENDED_TIMEOUT)
cy.get('canvas', EXTENDED_TIMEOUT).should('be.visible')

// Layer popover
cy.getByDataTest('add-layer-button').click()
cy.getByDataTest('addlayerpopover').should('be.visible')
cy.realPress('Escape')
cy.getByDataTest('addlayerpopover').should('not.exist')

// Manage layer sources modal
cy.getByDataTest('add-layer-button').click()
cy.getByDataTest('managelayersources-button').click()
cy.getByDataTest('managelayersourcesmodal').should('be.visible')
cy.realPress('Escape')
cy.getByDataTest('managelayersourcesmodal').should('not.exist')

// Download mode
cy.getByDataTest('dhis2-analytics-hovermenubar')
.find('button')
.contains('Download')
.click()
cy.getByDataTest('download-settings').should('be.visible')
cy.realPress('Escape')
cy.getByDataTest('download-settings').should('not.exist')

// Layer edit
cy.getByDataTest('layer-edit-button').click()
cy.getByDataTest('layeredit').should('be.visible')
cy.get('[data-test="indicatorgroupselect"]').click()
cy.contains(alt.group).click()
cy.get('[data-test="indicatorselect"]').click()
cy.contains(alt.name).click()
cy.realPress('Escape')
cy.getByDataTest('layeredit').should('not.exist')
cy.getByDataTest('layercard')
.contains(map.cardTitle, { timeout: 50000 })
.should('be.visible')

// StartEndDate
cy.getByDataTest('layer-edit-button').click()
cy.getByDataTest('layeredit').should('be.visible')
})
})
112 changes: 86 additions & 26 deletions cypress/integration/layers/eventlayer.cy.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,51 @@
import { EventLayer } from '../../elements/event_layer.js'
import { EXTENDED_TIMEOUT } from '../../support/util.js'
import { CURRENT_YEAR, EXTENDED_TIMEOUT } from '../../support/util.js'

const programE2E = {
name: 'E2E program',
stage: 'Stage 1 - Repeatable',
de: 'E2E - Yes/no',
options: ['Yes', 'No', 'Not set'],
}

const programIP = {
name: 'Inpatient morbidity and mortality',
stage: 'Inpatient morbidity and mortality',
startDate: `${CURRENT_YEAR - 5}-00-00`,
endDate: `${CURRENT_YEAR}-11-30`,
periodText: `Jan 1, ${CURRENT_YEAR - 5} - Nov 30, ${CURRENT_YEAR}`,
ous: ['Bombali', 'Bo'],
}

context('Event Layers', () => {
beforeEach(() => {
cy.visit('/', EXTENDED_TIMEOUT)
cy.visit('/')
})

const Layer = new EventLayer()

it('adds an event layer and applies style for boolean data element', () => {
Layer.openDialog('Events')
.selectProgram(programE2E.name)
.validateStage(programE2E.stage)
.selectTab('Style')

cy.getByDataTest('style-by-data-element-select').click()

cy.getByDataTest('dhis2-uicore-singleselectoption')
.contains(programE2E.de)
.click()

cy.getByDataTest('dhis2-uicore-modalactions')
.contains('Add layer')
.click()

Layer.validateDialogClosed(true)

Layer.validateCardTitle(programE2E.stage)
Layer.validateCardItems(programE2E.options)
})

it('shows error if no program selected', () => {
Layer.openDialog('Events').addToMap()

Expand All @@ -16,47 +54,69 @@ context('Event Layers', () => {
cy.contains('Program is required').should('be.visible')
})

it('adds an event layer', () => {
it('shows error if no endDate is specified', () => {
Layer.openDialog('Events')
.selectProgram('Inpatient morbidity and mortality')
.validateStage('Inpatient morbidity and mortality')
.selectProgram(programIP.name)
.validateStage(programIP.stage)
.selectTab('Period')
.selectPeriodType({ periodType: 'Start/end dates' })
.typeEndDate()
.addToMap()

Layer.validateDialogClosed(false)
cy.contains('End date is invalid').should('be.visible')

Layer.selectTab('Period').typeEndDate('2')

cy.contains('End date is invalid').should('not.exist')
})

it('adds an event layer - relative period', () => {
Layer.openDialog('Events')
.selectProgram(programIP.name)
.validateStage(programIP.stage)
.selectTab('Org Units')
.selectOu('Bombali')
.selectOu('Bo')
.selectOu(programIP.ous[0])
.selectOu(programIP.ous[1])
.addToMap()

Layer.validateDialogClosed(true)

Layer.validateCardTitle('Inpatient morbidity and mortality')
Layer.validateCardTitle(programIP.name)
Layer.validateCardItems(['Event'])
})

it('adds an event layer and applies style for boolean data element', () => {
it('adds an event layer - start-end dates', () => {
Layer.openDialog('Events')
.selectProgram('E2E program')
.validateStage('Stage 1 - Repeatable')
.selectProgram(programIP.name)
.validateStage(programIP.stage)
.selectTab('Period')
.selectPeriodType({ periodType: 'Start/end dates' })
.typeStartDate(programIP.startDate)
.typeEndDate(programIP.endDate)
.selectTab('Org Units')
.selectOu(programIP.ous[0])
.selectOu(programIP.ous[1])
.selectTab('Style')

cy.getByDataTest('style-by-data-element-select').click()

cy.getByDataTest('dhis2-uicore-singleselectoption')
.contains('E2E - Yes/no')
.click()

cy.getByDataTest('dhis2-uicore-modalactions')
.contains('Add layer')
.click()
.selectViewAllEvents()
.addToMap()

Layer.validateDialogClosed(true)

Layer.validateCardTitle('Stage 1 - Repeatable')
Layer.validateCardItems(['Yes', 'No', 'Not set'])
Layer.validateCardTitle(programIP.name).validateCardPeriod(
programIP.periodText
)
Layer.validateCardItems(['Event'])
})

it('opens an event popup', () => {
Layer.openDialog('Events')
.selectProgram('Inpatient morbidity and mortality')
.validateStage('Inpatient morbidity and mortality')
.selectProgram(programIP.name)
.validateStage(programIP.stage)
.selectTab('Period')
.selectPeriodType({ periodType: 'Start/end dates' })
.typeStartDate(programIP.startDate)
.typeEndDate(programIP.endDate)
.selectTab('Style')
.selectViewAllEvents()
.selectTab('Org Units')
Expand Down Expand Up @@ -106,7 +166,7 @@ context('Event Layers', () => {
.contains('Mode of Discharge')
.should('be.visible')

Layer.validateCardTitle('Inpatient morbidity and mortality')
Layer.validateCardTitle(programIP.name)
Layer.validateCardItems(['Event'])
})
})
Loading

0 comments on commit 412a3ad

Please sign in to comment.