Skip to content

Commit

Permalink
feat: offline dashboard (#1700)
Browse files Browse the repository at this point in the history
Implements DHIS2-10874

Add offline functionality to dashboard to support Dashboard PWA. With this implementation, it is possible to cache dashboards so they can be viewed while offline. The app also detects the offline/online state, and updates availability of actions - like editing, starring, changing filters, and viewing certain info like sharing and interpretations - accordingly.

Much of this PR is about disabling/enabling buttons and menu items, and showing a tooltip. ViewDashboard is the main code for navigating to different dashboards while offline, while view/TitleBar/ActionsBar triggers the recording of a dashboard.

Additional info for some of the changed files:

Cypress tests:

all new tests related to offline are in the offline.feature and offline.js files. The rest of the changes are adaptations based on changed html structure, or sharing functionality
goOffline and goOnline functions were copied from this post: https://www.cypress.io/blog/2020/11/12/testing-application-in-offline-network-mode/
d2.config.js

this is the config that enables offline functionality in the app-runtime. The patternsToOmit property is a list of endpoints that should NOT be cached as part of the GENERAL app cache. Therefore, the endpoints listed in this property are related to content for a specific dashboard. Endpoints that go into the general app cache include e.g. /dashboards, static assets (dashboard bundle, plugins, jquery...), /schemas, etc.
actions/selected.js

refactored tSetSelectedDashboardById to use async/await
tSetSelectedDashboardByIdOffline - this is used when user navigates to an uncached dashboard while offline. We still want to navigate to the url and set the state of the app, then when connection is restored, the rest of the data gets loaded.
Tooltip.js

this component is used throughout the app to apply a tooltip when the button is disabled.
MenuItemWithTooltip.js
applies a tooltip to MenuItems - used for offline messages.
ConfirmActionDialog.js (refactor/code sharing)

used by 3 other components
DropdownButton.js

modifed to handle additional property disabledWhenOffline - moved that and other properties into rest
VisualizationItem/Item.js

need to always fetch visualization in ComponentDidMount, to handle case of dashboard being recorded.
ViewAsMenuItems.js

uses MenuItemWithTooltip, and the offline status will override the supplied tooltip.
DefaultPlugin.js

unmount visualization when the item is being removed from page. TODO: confirm changes with Jan.
ProgressiveLoadingContainer.js

forceLoadCount is an incrementing property that triggers force loading when a dashboard is being recorded.
PrintDashboard, PrintLayoutDashboard

bug fixes for bugs that the offline functionality revealed.
CacheableViewDashboard

adds CacheableSection which enables the dashboard to be recorded as a section.
DashboardsBar - useCallback to prevent unneeded rerendering of the content

FilterBadge.js, FilterBar.js

only allow removing filter if online, or if cached then user can remove, but has to remove all filters
view/TitleBar.js

incorporate the Last updated tag
ViewDashboard.js

complex logic in useEffect to determine how to handle opening dashboards depending on the offline/online state and whether the dashboard is cached. The cypress tests cover most of the scenarios.
  • Loading branch information
jenniferarnesen authored Aug 31, 2021
1 parent 75da018 commit 4103643
Show file tree
Hide file tree
Showing 75 changed files with 3,144 additions and 915 deletions.
3 changes: 2 additions & 1 deletion cypress/elements/editDashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { newButtonSel } from './viewDashboard'

export const confirmActionDialogSel = '[data-test="confirm-action-dialog"]'
export const titleInputSel = '[data-test="dashboard-title-input"]'
export const itemMenuSel = '[data-test="item-menu]'
export const itemMenuSel = '[data-test="item-menu"]'
export const itemSearchSel = '[data-test="item-search"]'

export const actionsBarSel = '[data-test="edit-control-bar"]'

Expand Down
2 changes: 2 additions & 0 deletions cypress/elements/sharingDialog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const getSharingDialogUserSearch = () =>
cy.get('[placeholder="Enter names"]').scrollIntoView()
7 changes: 7 additions & 0 deletions cypress/elements/viewDashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ export const outerScrollContainerSel = '[data-test="outer-scroll-container"]'
export const innerScrollContainerSel = '[data-test="inner-scroll-container"]'

/** Functions **/

export const getViewActionButton = action =>
cy
.get(titleBarSel, EXTENDED_TIMEOUT)
.find('button')
.contains(action, EXTENDED_TIMEOUT)

export const clickViewActionButton = action =>
cy
.get(titleBarSel, EXTENDED_TIMEOUT)
Expand Down
3 changes: 2 additions & 1 deletion cypress/integration/edit/edit_dashboard/sharing.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { When, Then } from 'cypress-cucumber-preprocessor/steps'
import { getSharingDialogUserSearch } from '../../../elements/sharingDialog'
import { dashboardTitleSel } from '../../../elements/viewDashboard'
import { EXTENDED_TIMEOUT } from '../../../support/utils'

Expand All @@ -10,7 +11,7 @@ When('I change sharing settings', () => {
//confirm that Boateng is not currently listed
cy.get('hr').should('have.length', 3)

cy.get('[placeholder="Enter names"]').type('Boateng')
getSharingDialogUserSearch().type('Boateng')
cy.contains(USER_NAME).click()

cy.get('div').contains(USER_NAME).should('be.visible')
Expand Down
2 changes: 2 additions & 0 deletions cypress/integration/edit/edit_dashboard/star_dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Then('the dashboard is starred', () => {

cy.get(dashboardChipSel)
.contains(TEST_DASHBOARD_TITLE)
.parent()
.siblings(chipStarSel)
.first()
.should('be.visible')
Expand All @@ -42,6 +43,7 @@ Then('the dashboard is not starred', () => {

cy.get(dashboardChipSel)
.contains(TEST_DASHBOARD_TITLE)
.parent()
.siblings()
.should('not.exist')
})
69 changes: 69 additions & 0 deletions cypress/integration/view/offline.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
Feature: Offline dashboard

Scenario: I cache an uncached dashboard
Given I create a cached and uncached dashboard
Then the cached dashboard has a Last Updated time and chip icon
And the uncached dashboard does not have a Last Updated time and no chip icon

Scenario: I am online with an uncached dashboard when I lose connectivity
Given I open an uncached dashboard
When connectivity is turned off
Then all actions for "uncached" dashboard requiring connectivity are disabled

Scenario: I am online with a cached dashboard when I lose connectivity
Given I open a cached dashboard
Then the cached dashboard options are available
When connectivity is turned off
Then all actions for "cached" dashboard requiring connectivity are disabled

Scenario: I am offline and switch from a cached dashboard to an uncached dashboard
Given I open a cached dashboard
And connectivity is turned off
When I click to open an uncached dashboard when offline
Then the dashboard is not available and offline message is displayed

Scenario: I am offline and switch to a cached dashboard
Given I open an uncached dashboard
And connectivity is turned off
When I click to open a cached dashboard when offline
Then the cached dashboard is loaded and displayed in view mode

Scenario: I am offline and switch to an uncached dashboard and then connectivity is restored
Given I open a cached dashboard
And connectivity is turned off
When I click to open an uncached dashboard when offline
Then the dashboard is not available and offline message is displayed
When connectivity is turned on
Then the uncached dashboard is loaded and displayed in view mode

Scenario: I am in edit mode on an uncached dashboard when I lose connectivity and then I exit without saving and then connectivity is restored
Given I open an uncached dashboard in edit mode
When connectivity is turned off
Then all edit actions requiring connectivity are disabled
When I click Exit without saving
Then the dashboard is not available and offline message is displayed
When connectivity is turned on
Then the uncached dashboard is loaded and displayed in view mode

Scenario: I am in edit mode on a cached dashboard when I lose connectivity and then I exit without saving
Given I open a cached dashboard in edit mode
When connectivity is turned off
Then all edit actions requiring connectivity are disabled
When I click Exit without saving
Then the cached dashboard is loaded and displayed in view mode

Scenario: The sharing dialog is open when connectivity is lost
Given I open a cached dashboard
When I open sharing settings
And connectivity is turned off
Then it is not possible to change sharing settings

Scenario: The interpretations panel is open when connectivity is lost
Given I open a cached dashboard
And I open the interpretations panel
When connectivity is turned off
Then it is not possible to interact with interpretations


Scenario: I delete the cached and uncached dashboard
Given I delete the cached and uncached dashboard
Loading

0 comments on commit 4103643

Please sign in to comment.