Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
200c85f
feat: preserve optionSet on event layer table headers
BRaimbault Jul 13, 2026
0423ee5
feat: compute columnOptions for categorical data table columns
BRaimbault Jul 13, 2026
99f5503
feat: support array-valued (multi-select) filters in filterData
BRaimbault Jul 13, 2026
4f7b70b
feat: add filterByGlobalSearch utility and thread globalSearch throug…
BRaimbault Jul 13, 2026
5fd8c23
feat: add multi-select dropdown filter to FilterInput
BRaimbault Jul 13, 2026
a56849c
feat: add numeric filter syntax help tooltip
BRaimbault Jul 13, 2026
f52ee20
feat: pass columnOptions and optionSet through DataTable to FilterInput
BRaimbault Jul 13, 2026
2a4b647
feat: add global search box to data table toolbar
BRaimbault Jul 13, 2026
c2b5aac
feat: wire legend item clicks to pre-populate data table filter
BRaimbault Jul 13, 2026
5f15009
chore: fix lint/prettier issues in PR3 files
BRaimbault Jul 13, 2026
eb6c26b
Merge branch 'feat/datatable-pr2-bidirectional-sync' into feat/datata…
BRaimbault Jul 13, 2026
4760f79
fix: toolbar polish - clear filters button, search sizing, collapse i…
BRaimbault Jul 14, 2026
a4b06bf
feat: round out data table filtering with reverse-selection, zoom-to-…
BRaimbault Jul 14, 2026
f4375b7
Merge branch 'feat/datatable-pr2-bidirectional-sync' into feat/datata…
BRaimbault Jul 14, 2026
9a24751
revert: remove legend-click-to-filter, deferred to a future PR
BRaimbault Jul 14, 2026
6ab8861
chore: sonarqube issues
BRaimbault Jul 14, 2026
fe76deb
chore: sonarqube issues
BRaimbault Jul 14, 2026
7e54524
chore: sonarqube issue
BRaimbault Jul 14, 2026
e2bedd3
fix: handle narrow columns and hide index column
BRaimbault Jul 14, 2026
90986ee
fix: rollback merging range and color columns
BRaimbault Jul 16, 2026
0edacf0
chore: PR clean-up
BRaimbault Jul 16, 2026
ebaa8cc
chore: sonarqube issues
BRaimbault Jul 16, 2026
131c0b2
fix: properly format dropdown numeric values
BRaimbault Jul 16, 2026
c174607
chore: fix cypress tests
BRaimbault Jul 16, 2026
17f3bc2
Merge branch 'feat/datatable-pr2-bidirectional-sync' into feat/datata…
BRaimbault Jul 16, 2026
bacf8a1
chore: cypress test fix
BRaimbault Jul 16, 2026
d90bb66
chore: cypress test fix
BRaimbault Jul 16, 2026
9d3a043
chore: cypress test fix
BRaimbault Jul 17, 2026
395aa9f
Merge branch 'feat/datatable-pr2-bidirectional-sync' into feat/datata…
BRaimbault Jul 17, 2026
b10e7b5
Merge branch 'feat/datatable-pr2-bidirectional-sync' into feat/datata…
BRaimbault Jul 17, 2026
95f1cef
Merge branch 'feat/datatable-pr2-bidirectional-sync' into feat/datata…
BRaimbault Jul 19, 2026
6a052df
Merge branch 'feat/datatable-pr2-bidirectional-sync' into feat/datata…
BRaimbault Jul 19, 2026
96b2714
Merge branch 'feat/datatable-pr2-bidirectional-sync' into feat/datata…
BRaimbault Jul 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 70 additions & 68 deletions cypress/integration/dataTable.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const checkTableCell = ({ row = 0, column = 0, expectedContent }) => {
}

describe('data table', () => {
it('opens data table and filters and sorts', () => {
it('opens data table for a Thematic layer and filters and sorts', () => {
const viewportHeight = Cypress.config('viewportHeight')
const expectedBottoms1 = [viewportHeight]
const expectedHeights1 = [
Expand All @@ -47,7 +47,7 @@ describe('data table', () => {
cy.visit(`/#/${map.id}`, EXTENDED_TIMEOUT)
cy.get('canvas', EXTENDED_TIMEOUT).should('be.visible')

//check that the map resizes properly
// Check that the map resizes properly
assertMapPosition(expectedBottoms1, expectedHeights1)

cy.getByDataTest('moremenubutton').first().click()
Expand All @@ -62,10 +62,10 @@ describe('data table', () => {
.contains('Show data table')
.click()

//check that the bottom panel is present
// Check that the bottom panel is present
cy.getByDataTest('bottom-panel').should('be.visible')

//check that the map resizes properly
// Check that the map resizes properly
cy.getByDataTest('bottom-panel')
.invoke('height')
.then((height) => {
Expand All @@ -80,25 +80,25 @@ describe('data table', () => {
// Collapse the Layers Panel to give the table more width
cy.getByDataTest('layers-toggle-button').click()

// check number of columns
// Check number of columns
cy.getByDataTest('bottom-panel')
.findByDataTest('dhis2-uicore-datatablecellhead')
.should('have.length', 11)
.should('have.length', 10)

// Filter by name
cy.getByDataTest('data-table-column-filter-input-Name')
cy.getByDataTest('data-table-column-filter-search-Name')
.find('input')
.type('bar')
.type('bar{enter}')

// check that the filter returned the correct number of rows
// Check that the filter returned the correct number of rows
cy.getByDataTest('bottom-panel')
.findByDataTest('dhis2-uicore-tablebody')
.findByDataTest('dhis2-uicore-datatablerow')
.should('have.length', 7)

// confirm that the sort order is initially ascending by Name
checkTableCell({ row: 0, column: 2, expectedContent: 'Bargbe' })
checkTableCell({ row: 6, column: 2, expectedContent: 'Upper Bambara' })
// Confirm that the sort order is initially ascending by Name
checkTableCell({ row: 0, column: 1, expectedContent: 'Bargbe' })
checkTableCell({ row: 6, column: 1, expectedContent: 'Upper Bambara' })

// Sort by name
cy.getByDataTest('data-table-column-sort-button-Name').click()
Expand All @@ -108,16 +108,16 @@ describe('data table', () => {
// so we reset to top before asserting on row indices below
cy.get('[data-testid="virtuoso-scroller"]').scrollTo('top')

// confirm that the rows are sorted by Name descending
checkTableCell({ row: 0, column: 2, expectedContent: 'Upper Bambara' })
checkTableCell({ row: 6, column: 2, expectedContent: 'Bargbe' })
// Confirm that the rows are sorted by Name descending
checkTableCell({ row: 0, column: 1, expectedContent: 'Upper Bambara' })
checkTableCell({ row: 6, column: 1, expectedContent: 'Bargbe' })

// filter by Value (numeric)
cy.getByDataTest('data-table-column-filter-input-Value')
// Filter by Value (numeric)
cy.getByDataTest('data-table-column-filter-search-Value')
.find('input')
.type('>26')
.type('>26{enter}')

// check that the (combined) filter returned the correct number of rows
// Check that the (combined) filter returned the correct number of rows
cy.getByDataTest('bottom-panel')
.findByDataTest('dhis2-uicore-tablebody')
.findByDataTest('dhis2-uicore-datatablerow')
Expand All @@ -129,11 +129,11 @@ describe('data table', () => {
// Reset scroll position after sorting - see comment above
cy.get('[data-testid="virtuoso-scroller"]').scrollTo('top')

// check that the rows are sorted by Value ascending
checkTableCell({ row: 0, column: 4, expectedContent: '35' })
checkTableCell({ row: 4, column: 4, expectedContent: '76' })
// Check that the rows are sorted by Value ascending
checkTableCell({ row: 0, column: 3, expectedContent: '35' })
checkTableCell({ row: 4, column: 3, expectedContent: '76' })

// right-click a row and select "View profile"
// Right-click a row and select "View profile"
cy.getByDataTest('bottom-panel')
.findByDataTest('dhis2-uicore-tablebody')
.findByDataTest('dhis2-uicore-datatablerow')
Expand All @@ -142,26 +142,26 @@ describe('data table', () => {

cy.getByDataTest('data-table-context-menu-view-profile').click()

// check that the org unit profile drawer is opened
// Check that the org unit profile drawer is opened
cy.getByDataTest('org-unit-profile').should('be.visible')

cy.getByDataTest('layers-toggle-button').click()

// close the datatable
// Close the datatable
cy.getByDataTest('moremenubutton').first().click()
cy.getByDataTest('more-menu')
.find('li')
.contains('Hide data table')
.click()

//check that the bottom panel is closed
// Check that the bottom panel is closed
cy.getByDataTest('bottom-panel').should('not.exist')

//check that the map resizes properly
// Check that the map resizes properly
assertMapPosition(expectedBottoms1, expectedHeights1)
})

it('opens the data table for an Event layer', () => {
it('opens data table for an Event layer', () => {
cy.visit('/', EXTENDED_TIMEOUT)

const EvenLayer = new EventLayer()
Expand Down Expand Up @@ -192,56 +192,59 @@ describe('data table', () => {
// Collapse the Layers Panel to give the table more width
cy.getByDataTest('layers-toggle-button').click()

// check number of columns
// Check number of columns
cy.getByDataTest('bottom-panel')
.findByDataTest('dhis2-uicore-datatablecellhead')
.should('have.length', 11)
.should('have.length', 10)

cy.getByDataTest('bottom-panel')
.findByDataTest('dhis2-uicore-datatablecellhead')
.contains('Age in years', { matchCase: false })
.should('be.visible')

// filter by Org unit
// Filter by Org unit
const ouName = 'Moyowa'
cy.getByDataTest('data-table-column-filter-input-Org unit')
cy.getByDataTest('data-table-column-filter-search-Org unit')
.find('input')
.type(ouName)
.type(`${ouName}{enter}`)

// check that all the rows have Org unit Moyowa
checkTableCell({ row: 0, column: 2, expectedContent: ouName })
checkTableCell({ row: 2, column: 2, expectedContent: ouName })
// Check that all the rows have Org unit Moyowa
checkTableCell({ row: 0, column: 1, expectedContent: ouName })
checkTableCell({ row: 2, column: 1, expectedContent: ouName })

cy.getByDataTest('bottom-panel')
.findByDataTest('dhis2-uicore-tablebody')
.findByDataTest('dhis2-uicore-datatablerow')
.should('have.length', 3)

// filter by Mode of Discharge
cy.getByDataTest('data-table-column-filter-input-Mode of Discharge')
// Filter by Mode of Discharge
cy.getByDataTest('data-table-column-filter-search-Mode of Discharge')
.find('input')
.type('Absconded')
cy.contains('label', 'Absconded').click()

cy.getByDataTest('bottom-panel')
.findByDataTest('dhis2-uicore-tablebody')
.findByDataTest('dhis2-uicore-datatablerow')
.should('have.length', 1)

cy.getByDataTest('data-table-column-filter-input-Mode of Discharge')
.find('input')
.clear()
cy.get('.backdrop').last().click()

cy.getByDataTest('data-table-column-filter-search-Mode of Discharge')
.find('.clear-button')
.click()

cy.getByDataTest('bottom-panel')
.findByDataTest('dhis2-uicore-tablebody')
.findByDataTest('dhis2-uicore-datatablerow')
.should('have.length', 3)

// filter by Age in years (numeric)
cy.getByDataTest('data-table-column-filter-input-Age in years')
// Filter by Age in years (numeric)
cy.getByDataTest('data-table-column-filter-search-Age in years')
.find('input')
.type('<51')
.type('<51{enter}')

// check that the filter returned the correct number of rows
// Check that the filter returned the correct number of rows
cy.getByDataTest('bottom-panel')
.findByDataTest('dhis2-uicore-tablebody')
.findByDataTest('dhis2-uicore-datatablerow')
Expand All @@ -252,10 +255,10 @@ describe('data table', () => {

// Confirm that the rows are sorted by Age in years ascending
// (the first click on a new column always sorts ascending)
checkTableCell({ row: 0, column: 8, expectedContent: '6' })
checkTableCell({ row: 1, column: 8, expectedContent: '32' })
checkTableCell({ row: 0, column: 7, expectedContent: '6' })
checkTableCell({ row: 1, column: 7, expectedContent: '32' })

// right-click a row: Event layers have no profile to view
// Right-click a row: Event layers have no profile to view
cy.getByDataTest('bottom-panel')
.findByDataTest('dhis2-uicore-tablebody')
.findByDataTest('dhis2-uicore-datatablerow')
Expand All @@ -266,7 +269,7 @@ describe('data table', () => {
'not.exist'
)

// check that the org unit profile drawer is NOT opened
// Check that the org unit profile drawer is NOT opened
cy.getByDataTest('org-unit-profile').should('not.exist')
})

Expand Down Expand Up @@ -314,7 +317,7 @@ describe('data table', () => {
cy.getByDataTest('layers-toggle-button').click()

// Confirm that the sort order is initially ascending by Name
checkTableCell({ row: 0, column: 2, expectedContent: 'Bendu CHC' })
checkTableCell({ row: 0, column: 1, expectedContent: 'Bendu CHC' })

// First click on a new column always sorts ascending
cy.getByDataTest('data-table-column-sort-button-Value').click()
Expand All @@ -323,40 +326,39 @@ describe('data table', () => {
cy.get('[data-testid="virtuoso-scroller"]').scrollTo('top')

// Check that first row has Tihun CHC with value 28.63
checkTableCell({ row: 0, column: 2, expectedContent: 'Tihun CHC' })
checkTableCell({ row: 0, column: 4, expectedContent: '28.63' })
checkTableCell({ row: 0, column: 1, expectedContent: 'Tihun CHC' })
checkTableCell({ row: 0, column: 3, expectedContent: '28.63' })

// Check that row 5 has Gbamgbama CHC with value 117.98
checkTableCell({ row: 5, column: 2, expectedContent: 'Gbamgbama CHC' })
checkTableCell({ row: 5, column: 4, expectedContent: '117.98' })
checkTableCell({ row: 5, column: 1, expectedContent: 'Gbamgbama CHC' })
checkTableCell({ row: 5, column: 3, expectedContent: '117.98' })

// Check that row 6 has no value (undefined)
checkTableCell({ row: 6, column: 4, expectedContent: '' })
checkTableCell({ row: 6, column: 3, expectedContent: '' })

// Sort descending by Value
cy.getByDataTest('data-table-column-sort-button-Value').click()

// Reset scroll position after sorting - see comment above
cy.get('[data-testid="virtuoso-scroller"]').scrollTo('top')

checkTableCell({ row: 0, column: 2, expectedContent: 'Gbamgbama CHC' })
checkTableCell({ row: 0, column: 4, expectedContent: '117.98' })
checkTableCell({ row: 0, column: 1, expectedContent: 'Gbamgbama CHC' })
checkTableCell({ row: 0, column: 3, expectedContent: '117.98' })

checkTableCell({ row: 5, column: 2, expectedContent: 'Tihun CHC' })
checkTableCell({ row: 5, column: 4, expectedContent: '28.63' })
checkTableCell({ row: 5, column: 1, expectedContent: 'Tihun CHC' })
checkTableCell({ row: 5, column: 3, expectedContent: '28.63' })

checkTableCell({ row: 6, column: 4, expectedContent: '' })
checkTableCell({ row: 6, column: 3, expectedContent: '' })

// Sort by index (a new column, so ascending)
cy.getByDataTest('data-table-column-sort-button-Index').click()
// Third click on the same column cycles back to natural (unsorted)
// order - there's no dedicated Index column/button any more
cy.getByDataTest('data-table-column-sort-button-Value').click()

// Reset scroll position after sorting - see comment above
cy.get('[data-testid="virtuoso-scroller"]').scrollTo('top')

checkTableCell({ row: 0, column: 1, expectedContent: '0' })

// Check that row 0 range value is empty
checkTableCell({ row: 0, column: 6, expectedContent: '' })
checkTableCell({ row: 0, column: 5, expectedContent: '' })

// Sort by range, which is a string
cy.getByDataTest('data-table-column-sort-button-Range').click()
Expand All @@ -365,12 +367,12 @@ describe('data table', () => {
cy.get('[data-testid="virtuoso-scroller"]').scrollTo('top')

// Check that row 0 range value has value '0-40'
checkTableCell({ row: 0, column: 6, expectedContent: '0 – 40' })
checkTableCell({ row: 0, column: 5, expectedContent: '0 – 40' })

// Check that row 5 range value has value '90 - 120'
checkTableCell({ row: 5, column: 6, expectedContent: '90 – 120' })
checkTableCell({ row: 5, column: 5, expectedContent: '90 – 120' })

// Check that row 6 range value is empty
checkTableCell({ row: 6, column: 6, expectedContent: '' })
checkTableCell({ row: 6, column: 5, expectedContent: '' })
})
})
Loading
Loading