Skip to content

Commit

Permalink
feat(iframe-plugin): wait to load plugins from cache to save network …
Browse files Browse the repository at this point in the history
…[DHIS2-15097] (#2285)

* feat(iframe-plugin): receive pwa installation status from plugins

* chore: add todos

* fix: wait to render until the first item of the type has gotten the plugin

* fix: add property to the top-most item of each iframe plugin type

* fix: dont use the <Layer> component

* chore: cli-app-scripts upgrade

* fix: remove unused var

* refactor: combine loops

---------

Co-authored-by: Jen Jones Arnesen <jennifer@dhis2.org>
  • Loading branch information
KaiVandivier and jenniferarnesen authored May 2, 2023
1 parent 9ef65b4 commit a7368b1
Show file tree
Hide file tree
Showing 13 changed files with 261 additions and 61 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"cy:capture": "cypress_dhis2_api_stub_mode=CAPTURE yarn d2-utils-cypress run --appStart 'yarn cypress:start'"
},
"devDependencies": {
"@dhis2/cli-app-scripts": "^10.3.5",
"@dhis2/cli-app-scripts": "^10.3.7",
"@dhis2/cli-style": "^10.5.1",
"@dhis2/cli-utils-cypress": "^7.0.1",
"@dhis2/cypress-commands": "^7.0.1",
Expand Down
6 changes: 6 additions & 0 deletions src/actions/iframePluginStatus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { ADD_IFRAME_PLUGIN_STATUS } from '../reducers/iframePluginStatus.js'

export const acAddIframePluginStatus = (value) => ({
type: ADD_IFRAME_PLUGIN_STATUS,
value,
})
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
import { useConfig } from '@dhis2/app-runtime'
import { useD2 } from '@dhis2/app-runtime-adapter-d2'
import { CenteredContent, CircularLoader } from '@dhis2/ui'
import postRobot from '@krakenjs/post-robot'
import PropTypes from 'prop-types'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { acAddIframePluginStatus } from '../../../../actions/iframePluginStatus.js'
import {
CHART,
REPORT_TABLE,
VISUALIZATION,
} from '../../../../modules/itemTypes.js'
import { getPluginOverrides } from '../../../../modules/localStorage.js'
import { useCacheableSection } from '../../../../modules/useCacheableSection.js'
import {
INSTALLATION_STATUS_INSTALLING,
INSTALLATION_STATUS_READY,
INSTALLATION_STATUS_UNKNOWN,
sGetIframePluginStatus,
} from '../../../../reducers/iframePluginStatus.js'
import { useUserSettings } from '../../../UserSettingsProvider.js'
import MissingPluginMessage from './MissingPluginMessage.js'
import { getPluginLaunchUrl } from './plugin.js'
Expand All @@ -25,7 +34,11 @@ const IframePlugin = ({
dashboardId,
itemId,
itemType,
isFirstOfType,
}) => {
const dispatch = useDispatch()
const iframePluginStatus = useSelector(sGetIframePluginStatus)

const { d2 } = useD2()
const { baseUrl } = useConfig()

Expand All @@ -43,6 +56,11 @@ const IframePlugin = ({

const onError = () => setError('plugin')

const pluginType = [CHART, REPORT_TABLE].includes(activeType)
? VISUALIZATION
: activeType
const installationStatus = iframePluginStatus[pluginType]

const pluginProps = useMemo(
() => ({
isVisualizationLoaded: true,
Expand Down Expand Up @@ -70,10 +88,6 @@ const IframePlugin = ({
)

const getIframeSrc = useCallback(() => {
const pluginType = [CHART, REPORT_TABLE].includes(activeType)
? VISUALIZATION
: activeType

// 1. check if there is an override for the plugin
const pluginOverrides = getPluginOverrides()

Expand All @@ -90,7 +104,7 @@ const IframePlugin = ({
}

setError('missing-plugin')
}, [activeType, d2, baseUrl])
}, [d2, baseUrl, pluginType])

const iframeSrc = getIframeSrc()

Expand All @@ -112,7 +126,10 @@ const IframePlugin = ({
}, [isCached])

useEffect(() => {
if (iframeRef?.current) {
if (
iframeRef?.current &&
(installationStatus === INSTALLATION_STATUS_READY || isFirstOfType)
) {
// if iframe has not sent initial request, set up a listener
if (iframeSrc !== prevPluginRef.current) {
prevPluginRef.current = iframeSrc
Expand All @@ -127,7 +144,6 @@ const IframePlugin = ({
// e.g. if plugin re-requests props for some reason
setRecordOnNextLoad(false)
}

return pluginProps
}
)
Expand All @@ -141,7 +157,36 @@ const IframePlugin = ({
)
}
}
}, [recordOnNextLoad, pluginProps, iframeSrc])
}, [
recordOnNextLoad,
pluginProps,
iframeSrc,
installationStatus,
isFirstOfType,
])

useEffect(() => {
if (iframeRef?.current) {
const listener = postRobot.on(
'installationStatus',
{
window: iframeRef.current.contentWindow,
},
(event) => {
if (isFirstOfType) {
dispatch(
acAddIframePluginStatus({
pluginType,
status: event.data.installationStatus,
})
)
}
}
)

return () => listener.cancel()
}
}, [pluginType, dispatch, visualization, iframePluginStatus, isFirstOfType])

useEffect(() => {
setError(null)
Expand All @@ -166,6 +211,19 @@ const IframePlugin = ({
)
}

if (
[INSTALLATION_STATUS_INSTALLING, INSTALLATION_STATUS_UNKNOWN].includes(
iframePluginStatus[pluginType]
) &&
!isFirstOfType
) {
return (
<CenteredContent>
<CircularLoader />
</CenteredContent>
)
}

return (
<div className={classes.wrapper}>
{iframeSrc ? (
Expand All @@ -189,6 +247,7 @@ IframePlugin.propTypes = {
dashboardId: PropTypes.string,
dashboardMode: PropTypes.string,
filterVersion: PropTypes.string,
isFirstOfType: PropTypes.bool,
itemId: PropTypes.string,
itemType: PropTypes.string,
style: PropTypes.object,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ const Visualization = ({
dashboardId,
itemId: item.id,
itemType: item.type,
isFirstOfType: Boolean(item.firstOfType),
}),
[
originalType,
Expand All @@ -92,6 +93,7 @@ const Visualization = ({
dashboardId,
item.id,
item.type,
item.firstOfType,
]
)

Expand Down
33 changes: 33 additions & 0 deletions src/modules/__tests__/getFirstOfType.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { getFirstOfTypes } from '../getFirstOfType.js'

describe('getFirstOfTypes', () => {
test('returns an empty array if no items are passed', () => {
expect(getFirstOfTypes([])).toEqual([])
})

test('returns only ids for types in the array', () => {
expect(
getFirstOfTypes([
{ id: 'map2', type: 'MAP', x: 1, y: 1 },
{ id: 'map1', type: 'MAP', x: 2, y: 0 },
])
).toEqual(['map1'])
})

test('returns first IDs for MAP, VISUALIZATION and EVENT_VISUALIZATION', () => {
expect(
getFirstOfTypes([
{ id: 'map2', type: 'MAP', x: 2, y: 2 },
{ id: 'map1', type: 'MAP', x: 1, y: 1 },
{ id: 'map0', type: 'MAP', x: 2, y: 0 },
{ id: 'vis1', type: 'CHART', x: 1, y: 1 },
{ id: 'vis0', type: 'REPORT_TABLE', x: 1, y: 0 },
{ id: 'vis2', type: 'VISUALIZATION', x: 2, y: 1 },
{ id: 'text1', type: 'TEXT', x: 0, y: 0 },
{ id: 'ev2', type: 'EVENT_VISUALIZATION', x: 0, y: 3 },
{ id: 'ev1', type: 'EVENT_VISUALIZATION', x: 1, y: 2 },
{ id: 'ev0', type: 'EVENT_VISUALIZATION', x: 0, y: 2 },
])
).toEqual(['map0', 'ev0', 'vis0'])
})
})
31 changes: 31 additions & 0 deletions src/modules/getFirstOfType.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import sortBy from 'lodash/sortBy.js'
import {
MAP,
VISUALIZATION,
EVENT_VISUALIZATION,
REPORT_TABLE,
CHART,
} from './itemTypes.js'

export const getFirstOfTypes = (items) => {
const firstOfTypes = {}

firstOfTypes[MAP] = sortBy(
items.filter((item) => item.type === MAP),
['y', 'x']
)[0]?.id

firstOfTypes[EVENT_VISUALIZATION] = sortBy(
items.filter((item) => item.type === EVENT_VISUALIZATION),
['y', 'x']
)[0]?.id

firstOfTypes[VISUALIZATION] = sortBy(
items.filter((item) =>
[VISUALIZATION, REPORT_TABLE, CHART].includes(item.type)
),
['y', 'x']
)[0]?.id

return Object.values(firstOfTypes).filter((id) => id !== undefined)
}
41 changes: 24 additions & 17 deletions src/pages/edit/ItemGrid.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import NoContentMessage from '../../components/NoContentMessage.js'
import ProgressiveLoadingContainer from '../../components/ProgressiveLoadingContainer.js'
import { useWindowDimensions } from '../../components/WindowDimensionsProvider.js'
import { EDIT } from '../../modules/dashboardModes.js'
import { getFirstOfTypes } from '../../modules/getFirstOfType.js'
import { getGridItemDomElementClassName } from '../../modules/getGridItemDomElementClassName.js'
import {
GRID_ROW_HEIGHT_PX,
Expand Down Expand Up @@ -38,6 +39,7 @@ const EditItemGrid = ({
}) => {
const [gridWidth, setGridWidth] = useState({ width: 0 })
const { width } = useWindowDimensions()
const firstOfTypes = getFirstOfTypes(dashboardItems)

const onLayoutChange = (newLayout) => {
acUpdateDashboardItemShapes(newLayout)
Expand All @@ -46,23 +48,28 @@ const EditItemGrid = ({
const onWidthChanged = (containerWidth) =>
setTimeout(() => setGridWidth({ width: containerWidth }), 200)

const getItemComponent = (item) => (
<ProgressiveLoadingContainer
key={item.i}
className={cx(
item.type,
'edit',
getGridItemDomElementClassName(item.id)
)}
itemId={item.id}
>
<Item
item={item}
gridWidth={gridWidth.width}
dashboardMode={EDIT}
/>
</ProgressiveLoadingContainer>
)
const getItemComponent = (item) => {
if (firstOfTypes.includes(item.id)) {
item.firstOfType = true
}
return (
<ProgressiveLoadingContainer
key={item.i}
className={cx(
item.type,
'edit',
getGridItemDomElementClassName(item.id)
)}
itemId={item.id}
>
<Item
item={item}
gridWidth={gridWidth.width}
dashboardMode={EDIT}
/>
</ProgressiveLoadingContainer>
)
}

const getItemComponents = (items) =>
items.map((item) => getItemComponent(item))
Expand Down
34 changes: 21 additions & 13 deletions src/pages/print/PrintItemGrid.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,34 @@ import React from 'react'
import { connect } from 'react-redux'
import { Item } from '../../components/Item/Item.js'
import { PRINT } from '../../modules/dashboardModes.js'
import { getFirstOfTypes } from '../../modules/getFirstOfType.js'
import { getGridItemDomElementClassName } from '../../modules/getGridItemDomElementClassName.js'
import { hasShape } from '../../modules/gridUtil.js'
import { orArray } from '../../modules/util.js'
import { sGetPrintDashboardItems } from '../../reducers/printDashboard.js'
import StaticGrid from './StaticGrid.js'

const PrintItemGrid = ({ dashboardItems }) => {
const getItemComponent = (item) => (
<div
key={item.i}
className={cx(
item.type,
'print',
'oipp',
getGridItemDomElementClassName(item.id)
)}
>
<Item item={item} dashboardMode={PRINT} />
</div>
)
const firstOfTypes = getFirstOfTypes(dashboardItems)

const getItemComponent = (item) => {
if (firstOfTypes.includes(item.id)) {
item.firstOfType = true
}
return (
<div
key={item.i}
className={cx(
item.type,
'print',
'oipp',
getGridItemDomElementClassName(item.id)
)}
>
<Item item={item} dashboardMode={PRINT} />
</div>
)
}

const getItemComponents = (items) =>
items.map((item) => getItemComponent(item))
Expand Down
13 changes: 11 additions & 2 deletions src/pages/print/PrintLayoutItemGrid.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { connect } from 'react-redux'
import { acUpdatePrintDashboardLayout } from '../../actions/printDashboard.js'
import { Item } from '../../components/Item/Item.js'
import { PRINT_LAYOUT } from '../../modules/dashboardModes.js'
import { getFirstOfTypes } from '../../modules/getFirstOfType.js'
import { getGridItemDomElementClassName } from '../../modules/getGridItemDomElementClassName.js'
import { hasShape } from '../../modules/gridUtil.js'
import { PAGEBREAK } from '../../modules/itemTypes.js'
Expand Down Expand Up @@ -57,8 +58,16 @@ class PrintLayoutItemGrid extends Component {
)
}

getItemComponents = (items) =>
items.map((item) => this.getItemComponent(item))
getItemComponents = (items) => {
const firstOfTypes = getFirstOfTypes(items)

return items.map((item) => {
if (firstOfTypes.includes(item.id)) {
item.firstOfType = true
}
return this.getItemComponent(item)
})
}

hideExtraPageBreaks() {
const sortedElements = getDomGridItemsSortedByYPos(
Expand Down
Loading

0 comments on commit a7368b1

Please sign in to comment.