diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 6a4161ea5..ae93dbbdb 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -20,7 +20,7 @@ _text_ - [ ] Cypress tests - [ ] Update docs -- [ ] KFMT +- [ ] Manual testing - [ ] _task_ --- diff --git a/cypress/integration/repeatedEvents.cy.js b/cypress/integration/repeatedEvents.cy.js index f7347db55..1207fb51f 100644 --- a/cypress/integration/repeatedEvents.cy.js +++ b/cypress/integration/repeatedEvents.cy.js @@ -20,7 +20,9 @@ import { getTableHeaderCells, expectTableToBeVisible, getTableDataCells, + getTableRows, } from '../helpers/table.js' +import { EXTENDED_TIMEOUT } from '../support/util.js' const getRepeatedEventsTab = () => cy.getBySel('conditions-modal-content').contains('Repeated events') @@ -228,4 +230,41 @@ describe('repeated events', () => { getRepeatedEventsTab().should('not.have.class', 'disabled') }) + it('undefined values display properly for a repeated event', () => { + const TEST_CELL = { + row: 6, + column: 3, + } + goToAO('WrIV7ZoYECj') + + cy.getBySel('titlebar', EXTENDED_TIMEOUT) + .should('be.visible') + .and('contain', 'E2E: Enrollment - Hemoglobin (repeated)') + + getTableRows() + .eq(TEST_CELL.row) + .find('td') + .eq(TEST_CELL.column) + .invoke('text') + .invoke('trim') + .should('equal', '') + + getTableRows() + .eq(TEST_CELL.row) + .find('td') + .eq(TEST_CELL.column) + .should(($td) => { + const className = $td[0].className + + expect(className).to.match(/Visualization_undefinedCell*/) + }) + + getTableRows() + .eq(TEST_CELL.row) + .find('td') + .eq(TEST_CELL.column) + .trigger('mouseover') + + cy.getBySelLike('tooltip-content').contains('No event') + }) }) diff --git a/i18n/en.pot b/i18n/en.pot index c90fc5403..77840d779 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-27T14:21:34.503Z\n" -"PO-Revision-Date: 2023-09-27T14:21:34.503Z\n" +"POT-Creation-Date: 2023-11-29T10:50:15.561Z\n" +"PO-Revision-Date: 2023-11-29T10:50:15.561Z\n" msgid "Add to {{axisName}}" msgstr "Add to {{axisName}}" @@ -383,6 +383,9 @@ msgstr "" msgid "Sort by {{column}}" msgstr "Sort by {{column}}" +msgid "No event" +msgstr "No event" + msgid "Rows per page" msgstr "Rows per page" diff --git a/src/components/Visualization/Visualization.js b/src/components/Visualization/Visualization.js index 67f6eb019..d630ce75d 100644 --- a/src/components/Visualization/Visualization.js +++ b/src/components/Visualization/Visualization.js @@ -59,6 +59,7 @@ import { } from '../../modules/visualization.js' import styles from './styles/Visualization.module.css' import { + cellIsUndefined, getAdaptedVisualization, useAnalyticsData, } from './useAnalyticsData.js' @@ -366,6 +367,50 @@ export const Visualization = ({ ) + const renderCellContent = ({ columnIndex, value, isUndefined, props }) => ( + +
+ {formatCellValue(value, data.headers[columnIndex])} +
+
+ ) + return (
{/* https://jira.dhis2.org/browse/LIBS-278 */} - {data.rows.map((row, index) => ( + {data.rows.map((row, rowIndex) => ( - {row.map((value, index) => ( - -
+ cellIsUndefined( + data.rowContext, + rowIndex, + columnIndex + ) ? ( + - {formatCellValue( - value, - data.headers[index] - )} -
-
- ))} + {(props) => + renderCellContent({ + columnIndex, + value, + isUndefined: true, + props, + }) + } + + ) : ( + renderCellContent({ + columnIndex, + value, + }) + ) + )}
))}
diff --git a/src/components/Visualization/styles/Visualization.module.css b/src/components/Visualization/styles/Visualization.module.css index cd6155430..caec932cf 100644 --- a/src/components/Visualization/styles/Visualization.module.css +++ b/src/components/Visualization/styles/Visualization.module.css @@ -132,6 +132,17 @@ white-space: nowrap; } +.dataTable .undefinedCell { + background-image: repeating-linear-gradient( + 45deg, + var(--colors-grey300) 0, + var(--colors-grey300) 0.8px, + transparent 0, + transparent 50% + ); + background-size: 8px 8px; +} + /* Sizes for the table footer */ .dataTable .stickyNavigation.sizeComfortable { padding: 14px 12px; diff --git a/src/components/Visualization/useAnalyticsData.js b/src/components/Visualization/useAnalyticsData.js index 4f6f5219f..7eec884b5 100644 --- a/src/components/Visualization/useAnalyticsData.js +++ b/src/components/Visualization/useAnalyticsData.js @@ -57,12 +57,18 @@ const lookupOptionSetOptionMetadata = (optionSetId, code, metaDataItems) => { return undefined } +const NOT_DEFINED_VALUE = 'ND' -const formatRowValue = (rowValue, header, metaDataItems) => { +export const cellIsUndefined = (rowContext = {}, rowIndex, columnIndex) => + (rowContext[rowIndex] || {})[columnIndex]?.valueStatus === NOT_DEFINED_VALUE + +const formatRowValue = ({ rowValue, header, metaDataItems, isUndefined }) => { switch (header.valueType) { case VALUE_TYPE_BOOLEAN: case VALUE_TYPE_TRUE_ONLY: - return getBooleanValues()[rowValue || NULL_VALUE] + return !isUndefined + ? getBooleanValues()[rowValue || NULL_VALUE] + : '' default: { if (!rowValue) { return rowValue @@ -166,6 +172,9 @@ const fetchAnalyticsData = async ({ .withParameters({ headers, totalPages: false, + ...(visualization.outputType !== OUTPUT_TYPE_EVENT + ? { rowContext: true } + : {}), ...parameters, }) .withProgram(visualization.program.id) @@ -271,15 +280,19 @@ const extractRows = (analyticsResponse, headers) => { headerIndex++ ) { const header = headers[headerIndex] - const rowValue = row[header.index] filteredRow.push( - formatRowValue( + formatRowValue({ rowValue, header, - analyticsResponse.metaData.items - ) + metaDataItems: analyticsResponse.metaData.items, + isUndefined: cellIsUndefined( + analyticsResponse.rowContext, + rowIndex, + headerIndex + ), + }) ) } @@ -289,6 +302,8 @@ const extractRows = (analyticsResponse, headers) => { return filteredRows } +const extractRowContext = (analyticsResponse) => analyticsResponse.rowContext + const valueTypeIsNumeric = (valueType) => [ VALUE_TYPE_NUMBER, @@ -334,6 +349,7 @@ const useAnalyticsData = ({ }) const headers = extractHeaders(analyticsResponse) const rows = extractRows(analyticsResponse, headers) + const rowContext = extractRowContext(analyticsResponse) const pager = analyticsResponse.metaData.pager const legendSetIds = [] const headerLegendSetMap = headers.reduce( @@ -386,7 +402,7 @@ const useAnalyticsData = ({ } mounted.current && setError(undefined) - mounted.current && setData({ headers, rows, pager }) + mounted.current && setData({ headers, rows, pager, rowContext }) onResponsesReceived(analyticsResponse) } catch (error) { mounted.current && setError(error)