Skip to content

Commit

Permalink
feat: use new pivot engine (#574)
Browse files Browse the repository at this point in the history
* feat: use new pivot engine

* chore: use published pre-release of @dhis2/analytics

* fix: correct sizing and placement, delay redraw on config change

* chore: change position of plugin and remove unnecessary resolution

* fix: correct option name

* fix: don't re-render when renderId changes

* chore: don't map options unnecessarily

* fix: update analytics dep

Co-authored-by: Jan Henrik Øverland <jan@dhis2.org>
  • Loading branch information
amcgee and janhenrikoverland committed Jan 31, 2020
1 parent 489fbf9 commit f4ccef1
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 191 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"license": "BSD-3-Clause",
"devDependencies": {
"@dhis2/cli-style": "^5.0.2",
"concurrently": "^5.1.0",
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.15.1",
"eslint-plugin-react-hooks": "^2.3.0",
Expand All @@ -18,7 +19,7 @@
"lint": "d2-style js check || d2-style text check",
"format": "d2-style js apply || d2-style text apply",
"start-app": "cd packages/app && yarn start",
"start": "yarn build-plugin && yarn start-app",
"start": "concurrently -n plugin,app \"yarn build-plugin --watch\" \"yarn start-app\"",
"test-plugin": "cd packages/plugin && yarn test",
"test-app": "cd packages/app && yarn test",
"test": "yarn test-plugin && yarn test-app",
Expand Down
2 changes: 1 addition & 1 deletion packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"redux-mock-store": "^1.5.3"
},
"dependencies": {
"@dhis2/analytics": "^3.0.0",
"@dhis2/analytics": "^3.1.0",
"@dhis2/d2-ui-core": "^6.5.1",
"@dhis2/d2-ui-file-menu": "^6.5.1",
"@dhis2/d2-ui-interpretations": "^6.5.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
export default {
chartCanvas: {
display: 'flex',
justifyContent: 'center',
justifyContent: 'flex-start',
alignItems: 'flex-start',
height: '100%',
},
}
2 changes: 1 addition & 1 deletion packages/plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"module": "./build/es/lib.js",
"license": "BSD-3-Clause",
"dependencies": {
"@dhis2/analytics": "^3.0.0",
"@dhis2/analytics": "^3.1.0",
"@material-ui/core": "^3.1.2",
"d2-analysis": "33.2.11",
"lodash-es": "^4.17.11",
Expand Down
231 changes: 66 additions & 165 deletions packages/plugin/src/PivotPlugin.js
Original file line number Diff line number Diff line change
@@ -1,192 +1,94 @@
import React, { Component, Fragment } from 'react'
import React, { useState, useEffect } from 'react'
import PropTypes from 'prop-types'
import isEqual from 'lodash-es/isEqual'
import i18n from '@dhis2/d2-i18n'
import {
api as d2aApi,
table as d2aTable,
config as d2aConfig,
Layout as d2aLayout,
Response as d2aResponse,
} from 'd2-analysis'
import { apiFetchAnalytics } from './api/analytics'
import { getOptionsForRequest } from './modules/options'
import LoadingMask from './widgets/LoadingMask'

import { pivotTableStyles } from './styles/PivotPlugin.style.js'
import { PivotTable } from '@dhis2/analytics'

class PivotPlugin extends Component {
constructor(props) {
super(props)

this.canvasRef = React.createRef()

this.recreateVisualization = Function.prototype

this.state = {
isLoading: true,
}
}

componentDidMount() {
this.renderTable()
}

componentDidUpdate(prevProps) {
if (!isEqual(this.props.config, prevProps.config)) {
this.renderTable()
return
}

if (!isEqual(this.props.filters, prevProps.filters)) {
this.renderTable()
return
}

// id set by DV app, style works in dashboards
const getRequestOptions = (visualization, filters) => {
const options = getOptionsForRequest().reduce((map, [option, props]) => {
// only add parameter if value !== default
if (
this.props.id !== prevProps.id ||
!isEqual(this.props.style, prevProps.style)
visualization[option] !== undefined &&
visualization[option] !== props.defaultValue
) {
this.recreateVisualization(0) // disable animation
return
map[option] = visualization[option]
}
}

getRequestOptions = (visualization, filters) => {
const options = getOptionsForRequest().reduce(
(map, [option, props]) => {
// only add parameter if value !== default
if (
visualization[option] !== undefined &&
visualization[option] !== props.defaultValue
) {
map[option] = visualization[option]
}

return map
},
{}
)

// interpretation filter
if (filters.relativePeriodDate) {
options.relativePeriodDate = filters.relativePeriodDate
}
return map
}, {})

// global filters
// userOrgUnit
if (filters.userOrgUnit && filters.userOrgUnit.length) {
const ouIds = filters.userOrgUnit.map(
ouPath => ouPath.split('/').slice(-1)[0]
)

options.userOrgUnit = ouIds.join(';')
}

return options
// interpretation filter
if (filters.relativePeriodDate) {
options.relativePeriodDate = filters.relativePeriodDate
}

renderTable = async () => {
const {
config: visualization,
filters,
onResponsesReceived,
onError,
} = this.props

const i18nManager = {
get: string => i18n.t(string),
}

const appManager = {
getLegendSetById: () => '',
getApiPath: () => '',
}

const uiManager = {}

const d2aOptionConfig = new d2aConfig.OptionConfig()
d2aOptionConfig.setI18nManager(i18nManager)
d2aOptionConfig.init()

const refs = {
api: d2aApi,
appManager,
uiManager,
i18nManager,
optionConfig: d2aOptionConfig,
}

try {
const options = this.getRequestOptions(visualization, filters)
// global filters
// userOrgUnit
if (filters.userOrgUnit && filters.userOrgUnit.length) {
const ouIds = filters.userOrgUnit.map(
ouPath => ouPath.split('/').slice(-1)[0]
)

const responses = await apiFetchAnalytics(
this.props.d2,
visualization,
options
)
options.userOrgUnit = ouIds.join(';')
}

if (responses.length) {
onResponsesReceived(responses)
}
return options
}

this.recreateVisualization = () => {
const remappedOptions = {
showColTotals: visualization.colTotals,
showRowTotals: visualization.rowTotals,
showColSubTotals: visualization.colSubTotals,
showRowSubTotals: visualization.rowSubTotals,
numberType: visualization.numberType || 'VALUE', // TODO read default from options, perhaps better idea is to compute layout content in app
const PivotPlugin = ({
config,
filters,
style,
onError,
onResponsesReceived,
d2,
}) => {
const [isLoading, setIsLoading] = useState(true)
const [visualization, setVisualization] = useState(null)
const [data, setData] = useState(null)

useEffect(() => {
setIsLoading(true)
const options = getRequestOptions(config, filters)
apiFetchAnalytics(d2, config, options)
.then(responses => {
if (!responses.length) {
return
}
if (onResponsesReceived) {
onResponsesReceived(responses)
}

const layout = new d2aLayout(
refs,
visualization,
remappedOptions
)

const extraOptions = { renderLimit: 100000, trueTotals: true }

const pivotTable = new d2aTable.PivotTable(
refs,
layout,
new d2aResponse(refs, responses[0].response),
extraOptions
)

pivotTable.initialize()

pivotTable.build()

this.canvasRef.current.innerHTML = pivotTable.render()
}

this.recreateVisualization()

this.setState({ isLoading: false })
} catch (error) {
onError(error)
}
}

render() {
return (
<Fragment>
{this.state.isLoading ? <LoadingMask /> : null}
<div ref={this.canvasRef} style={this.props.style}>
<style jsx>{pivotTableStyles}</style>
setVisualization(config)
setData(responses[0].response)
setIsLoading(false)
})
.catch(error => {
onError(error)
})

// TODO: cancellation
}, [config, filters, onResponsesReceived, onError, d2])

return (
<div style={{ width: '100%', height: '100%', ...style }}>
{isLoading ? (
<div style={{ placeSelf: 'center', flex: '1 0 0%' }}>
<LoadingMask />
</div>
</Fragment>
)
}
) : (
<PivotTable visualization={visualization} data={data} />
)}
</div>
)
}

PivotPlugin.defaultProps = {
config: {},
filters: {},
style: {},
animation: 200,
onError: Function.prototype,
onResponsesReceived: Function.prototype,
}
Expand All @@ -196,7 +98,6 @@ PivotPlugin.propTypes = {
d2: PropTypes.object.isRequired,
onError: PropTypes.func.isRequired,
filters: PropTypes.object,
id: PropTypes.number,
style: PropTypes.object,
onResponsesReceived: PropTypes.func,
}
Expand Down
Loading

0 comments on commit f4ccef1

Please sign in to comment.