Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
48 changes: 48 additions & 0 deletions src/__demo__/PivotTable.aggregate.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,54 @@ DeepFilter.story = {
name: 'deep - filter',
}

/* This layout has a filter the lib can read, so it derives "Eastern Area".
* A caller that would rather format the line itself passes `filterText`.
* Whether there is a row at all stays the layout's call either way, which
* the third mode shows. */
const filterTextModes = [
{ label: 'Derived by the lib' },
{ label: 'Supplied by the caller', filterText: 'Age: 5 - 10, Female' },
{
label: 'Supplied, but the layout has no filters',
filterText: 'Age: 5 - 10, Female',
filters: [],
},
]

export const DeepSuppliedFilterText = (_, { pivotTableOptions }) => {
const [modeIndex, setModeIndex] = useState(0)
const mode = filterTextModes[modeIndex]
const visualization = {
...deepWithFiltersVisualization,
...visualizationReset,
...pivotTableOptions,
showDimensionLabels: false,
filters: mode.filters ?? deepWithFiltersVisualization.filters,
}
return (
<div>
{filterTextModes.map((each, index) => (
<button
key={each.label}
disabled={index === modeIndex}
onClick={() => setModeIndex(index)}
>
{each.label}
</button>
))}
<div style={{ width: 800, height: 600 }}>
<PivotTable
data={deepWithFiltersData}
visualization={visualization}
filterText={mode.filterText}
/>
</div>
</div>
)
}

DeepSuppliedFilterText.storyName = 'deep - filter text supplied by the caller'

export const DeepTitleSubtitleFilter = (_, { pivotTableOptions }) => {
const visualization = {
...deepVisualization,
Expand Down
12 changes: 10 additions & 2 deletions src/components/PivotTable/PivotTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,22 @@ const PivotTable = ({
visualization,
data,
legendSets,
filterText,
renderCounter,
onToggleContextualMenu,
}) => {
const containerRef = useRef(undefined)
const { width, height } = useParentSize(containerRef, renderCounter)

const engine = useMemo(
() => new PivotTableEngine(visualization, data, legendSets),
[visualization, data, legendSets]
() =>
new PivotTableEngine({
visualization,
data,
legendSets,
filterText,
}),
[visualization, data, legendSets, filterText]
)

const { sortBy, onSortByColumn } = useSortableColumns(engine)
Expand Down Expand Up @@ -58,6 +65,7 @@ const PivotTable = ({
PivotTable.propTypes = {
data: PropTypes.object.isRequired,
visualization: PropTypes.object.isRequired,
filterText: PropTypes.string,
legendSets: PropTypes.arrayOf(PropTypes.object),
renderCounter: PropTypes.number,
onToggleContextualMenu: PropTypes.func,
Expand Down
11 changes: 7 additions & 4 deletions src/components/PivotTable/PivotTableTitleRows.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,13 @@ export const PivotTableTitleRows = ({ clippingResult, width }) => {
) : null}
{engine.visualization.filters?.length ? (
<PivotTableTitleRow
title={getFilterText(
engine.visualization.filters,
engine.rawData.metaData
)}
title={
engine.options.filterText ||
getFilterText(
engine.visualization.filters,
engine.rawData.metaData
)
}
scrollPosition={clippingResult.scrollPosition}
containerWidth={width}
/>
Expand Down
101 changes: 101 additions & 0 deletions src/components/PivotTable/__tests__/PivotTableTitleRows.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { render, screen } from '@testing-library/react'
import React from 'react'
import deepWithFiltersData from '../../../__demo__/data/aggregate/deepWithFilters.data.json'
import deepWithFiltersVisualization from '../../../__demo__/data/aggregate/deepWithFilters.visualization.json'
import PivotTable from '../PivotTable.js'

/* The engine measures every cell against a canvas 2d context, which jsdom
* does not implement. */
jest.mock('../../../modules/pivotTable/measureText.js', () => ({
measureTextWithWrapping: () => ({ width: 100, height: 20 }),
}))

/* The container renders nothing at all until it has a size, and jsdom reports
* every element as 0 x 0. */
jest.mock('../../../modules/pivotTable/useParentSize.js', () => ({
useParentSize: () => ({ width: 800, height: 600 }),
}))

/* `deepWithFilters` filters on an org unit group set, which `getFilterText`
* resolves through `metaData.dimensions` to this name. */
const DERIVED_FILTER_TEXT = 'Eastern Area'

const visualization = {
...deepWithFiltersVisualization,
title: 'Hello',
subtitle: 'Goodbye',
}

const renderTable = (props) =>
render(
<PivotTable
visualization={visualization}
data={deepWithFiltersData}
{...props}
/>
)

const titleRowTexts = () =>
screen
.queryAllByTestId('visualization-title')
.map((element) => element.textContent)

describe('PivotTable title rows', () => {
it('derives the filter row from the layout when no text is supplied', () => {
renderTable()

expect(titleRowTexts()).toEqual([
'Hello',
'Goodbye',
DERIVED_FILTER_TEXT,
])
})

it('renders no filter row when the layout has no filters', () => {
renderTable({ visualization: { ...visualization, filters: [] } })

expect(titleRowTexts()).toEqual(['Hello', 'Goodbye'])
})

it('renders the supplied text in place of the derived one', () => {
renderTable({ filterText: 'Age 5 - 10, Female' })

expect(titleRowTexts()).toEqual([
'Hello',
'Goodbye',
'Age 5 - 10, Female',
])
})

/* Whether there is a row remains the layout's call - supplying the text
* says what goes in the row, not whether there is one. */
it('does not render a supplied text the layout has no row for', () => {
renderTable({
visualization: { ...visualization, filters: [] },
filterText: 'Age 5 - 10, Female',
})

expect(titleRowTexts()).toEqual(['Hello', 'Goodbye'])
})

/* An empty string is a caller with nothing to say, not a caller asking
* for an empty row. */
it('derives the filter row for an empty supplied text', () => {
renderTable({ filterText: '' })

expect(titleRowTexts()).toEqual([
'Hello',
'Goodbye',
DERIVED_FILTER_TEXT,
])
})

it('leaves the title and subtitle to the visualization', () => {
renderTable({
visualization: { ...visualization, hideSubtitle: true },
filterText: 'Age 5 - 10',
})

expect(titleRowTexts()).toEqual(['Hello', 'Age 5 - 10'])
})
})
5 changes: 4 additions & 1 deletion src/modules/pivotTable/PivotTableEngine.js
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ export class PivotTableEngine {
columnMap = []
accumulators = { rows: {} }

constructor(visualization, data, legendSets) {
constructor({ visualization, data, legendSets, filterText }) {
this.visualization = Object.assign(
{},
defaultVisualizationProps,
Expand Down Expand Up @@ -336,6 +336,9 @@ export class PivotTableEngine {
? visualization.fixRowHeaders
: false,
cumulativeValues: visualization.cumulativeValues,
/* If visualization.filters is populated and this option is present,
* this text is used instead of the text derived from `visualization.filters` */
filterText,
}

this.adaptiveClippingController = new AdaptiveClippingController(this)
Expand Down
56 changes: 31 additions & 25 deletions src/modules/pivotTable/__tests__/pivotTableEngineHierarchy.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ const cellValues = (engine) =>

describe('PivotTableEngine org unit hierarchy', () => {
it('applies the hierarchy to a bare `ou` dimension', () => {
const engine = new PivotTableEngine(
buildVisualization('ou'),
buildData('ou')
)
const engine = new PivotTableEngine({
visualization: buildVisualization('ou'),
data: buildData('ou'),
})

expect(rowHierarchies(engine)).toEqual([
['Sierra Leone', 'Northern Province', 'Bombali'],
Expand All @@ -70,10 +70,10 @@ describe('PivotTableEngine org unit hierarchy', () => {

it('applies the hierarchy to a stage-qualified `ou` dimension', () => {
const dimension = `${STAGE}.ou`
const engine = new PivotTableEngine(
buildVisualization(dimension),
buildData(dimension)
)
const engine = new PivotTableEngine({
visualization: buildVisualization(dimension),
data: buildData(dimension),
})

expect(rowHierarchies(engine)).toEqual([
['Sierra Leone', 'Northern Province', 'Bombali'],
Expand All @@ -82,10 +82,10 @@ describe('PivotTableEngine org unit hierarchy', () => {
})

it('applies the hierarchy to an `enrollmentou` dimension', () => {
const engine = new PivotTableEngine(
buildVisualization('enrollmentou'),
buildData('enrollmentou')
)
const engine = new PivotTableEngine({
visualization: buildVisualization('enrollmentou'),
data: buildData('enrollmentou'),
})

expect(rowHierarchies(engine)).toEqual([
['Sierra Leone', 'Northern Province', 'Bombali'],
Expand All @@ -94,10 +94,13 @@ describe('PivotTableEngine org unit hierarchy', () => {
})

it('leaves items untouched when showHierarchy is off', () => {
const engine = new PivotTableEngine(
{ ...buildVisualization(`${STAGE}.ou`), showHierarchy: false },
buildData(`${STAGE}.ou`)
)
const engine = new PivotTableEngine({
visualization: {
...buildVisualization(`${STAGE}.ou`),
showHierarchy: false,
},
data: buildData(`${STAGE}.ou`),
})

expect(rowHierarchies(engine)).toEqual([undefined, undefined])
expect(engine.getRowHeader(0)[0].uid).toBe(OU_A)
Expand All @@ -106,10 +109,10 @@ describe('PivotTableEngine org unit hierarchy', () => {
/* Sorting rewrites itemIds, which the row lookup resolves data rows
* through. If the two fall out of step every cell renders empty. */
it('keeps values aligned with the re-sorted rows', () => {
const engine = new PivotTableEngine(
buildVisualization(`${STAGE}.ou`),
buildData(`${STAGE}.ou`)
)
const engine = new PivotTableEngine({
visualization: buildVisualization(`${STAGE}.ou`),
data: buildData(`${STAGE}.ou`),
})

expect(rowNames(engine)).toEqual(['Bombali', 'Bo'])
expect(cellValues(engine)).toEqual(['2', '1'])
Expand All @@ -122,7 +125,10 @@ describe('PivotTableEngine org unit hierarchy', () => {
const data = buildData(dimension)
data.metaData.ouNameHierarchy = {}

const engine = new PivotTableEngine(buildVisualization(dimension), data)
const engine = new PivotTableEngine({
visualization: buildVisualization(dimension),
data,
})

expect(rowNames(engine)).toEqual(['Bo', 'Bombali'])
expect(rowHierarchies(engine)).toEqual([undefined, undefined])
Expand All @@ -131,10 +137,10 @@ describe('PivotTableEngine org unit hierarchy', () => {

it('leaves non-org-unit dimensions untouched', () => {
const dimension = `${STAGE}.de1`
const engine = new PivotTableEngine(
buildVisualization(dimension),
buildData(dimension)
)
const engine = new PivotTableEngine({
visualization: buildVisualization(dimension),
data: buildData(dimension),
})

expect(rowHierarchies(engine)).toEqual([undefined, undefined])
})
Expand Down
Loading