Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for hash routing which is used by other analytics apps [DHIS2-15762] #3009

Merged
merged 49 commits into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
cbd0234
chore: useCachedQueryProvider to ensure required data is loaded in ti…
jenniferarnesen Aug 28, 2023
fcfecff
fix: remove unneeded code
jenniferarnesen Aug 28, 2023
5a9608f
chore: use externalLayers query in plugin and app
jenniferarnesen Aug 28, 2023
0155680
fix: move earthEngine import to separate file due to import.meta prob…
jenniferarnesen Aug 28, 2023
72a1b04
fix: minor cleanup
jenniferarnesen Aug 28, 2023
7ef663c
chore: missed a few changes
jenniferarnesen Sep 28, 2023
7461316
Merge branch 'dev' into chore/useQueryCacheProvider
jenniferarnesen Sep 28, 2023
71c7b2d
chore: move functions to separate file and add jest tests
jenniferarnesen Sep 29, 2023
c75ae5f
chore: enable systemsettings cypress tests for relative period
jenniferarnesen Sep 29, 2023
8cb981c
fix: add or enable tests covering system and user settings
jenniferarnesen Sep 29, 2023
00ca2ab
chore: add cypress tests for name property
jenniferarnesen Sep 29, 2023
201f54f
fix: remove only
jenniferarnesen Sep 29, 2023
f06f573
chore: lint
jenniferarnesen Sep 29, 2023
54d67e6
chore: move getHiddenPeriods and basemap filtering
jenniferarnesen Oct 5, 2023
8b8505f
fix: accept url map id in hash location
jenniferarnesen Aug 24, 2023
d3fccad
fix: use ? before first query param
jenniferarnesen Oct 6, 2023
b844ee0
chore: filemenu actions
jenniferarnesen Oct 17, 2023
d9d81a1
Merge branch 'dev' into feat/router-latest
jenniferarnesen Dec 10, 2023
9ef35aa
chore: merge fail
jenniferarnesen Dec 10, 2023
d68d889
chore: reset systemsettings.cy.js to original
jenniferarnesen Dec 10, 2023
8224d7f
chore: support old legacy urls
jenniferarnesen Dec 14, 2023
587d31b
fix: rename destructured prop
jenniferarnesen Dec 14, 2023
97fc3e3
feat: implement download route
jenniferarnesen Dec 14, 2023
519016a
feat: all routes are working now
jenniferarnesen Dec 14, 2023
4e7b383
chore: add cypress tests
jenniferarnesen Dec 14, 2023
fc533fd
Merge branch 'dev' into feat/router-latest
jenniferarnesen Dec 14, 2023
cfd9ddf
chore: add tests for map download
jenniferarnesen Dec 15, 2023
2921804
chore: wrap useAlert in useMemo to prevent unnecessary rerendering
jenniferarnesen Dec 15, 2023
bed67cd
chore: cleanup
jenniferarnesen Dec 15, 2023
2852ec7
chore: remove isPushAnalytics from redux - can instead be read direct…
jenniferarnesen Dec 18, 2023
4ca3652
chore: get initialFocus from url
jenniferarnesen Dec 18, 2023
ff86bca
Merge branch 'dev' into feat/router-latest
jenniferarnesen Dec 19, 2023
cfe1eb6
chore: clean and consolidate
jenniferarnesen Dec 19, 2023
a4940bd
chore: remove empty file
jenniferarnesen Dec 19, 2023
9ec3df0
fix: MAP_SET should still trigger closing right panel
jenniferarnesen Dec 19, 2023
ffc8b80
chore: disable tests - there is a pre-existing bug
jenniferarnesen Dec 19, 2023
600d8a1
fix: use default basemap before fallback basemap
jenniferarnesen Dec 19, 2023
a390af3
Merge branch 'dev' into feat/router-latest
jenniferarnesen Jan 8, 2024
5e01de2
chore: separate path from query params for clarity
jenniferarnesen Jan 9, 2024
7d7b928
chore: no need for extra location object, we know everything from the…
jenniferarnesen Jan 10, 2024
e69c8fc
chore: no need to store isCurrentAO separately, just consider it a mapId
jenniferarnesen Jan 10, 2024
ae34d6c
Merge branch 'feat/router-latest' of github.com:dhis2/maps-app into f…
jenniferarnesen Jan 10, 2024
7cb5c98
chore: get the currentAO only when requested to get the most current …
jenniferarnesen Jan 10, 2024
f4dfcbb
fix: handle download of unsaved map
jenniferarnesen Jan 11, 2024
82788e3
chore: simplify FileMenu
jenniferarnesen Jan 11, 2024
0df3cf8
fix: handle situation with no currentAnalyticalObject
jenniferarnesen Jan 11, 2024
d23c1f9
fix: remove doubleslash for new map download
jenniferarnesen Jan 11, 2024
71d13ae
test: add cypress test for /download path
jenniferarnesen Jan 11, 2024
8418b48
fix: rename smoke to routes cypress test
jenniferarnesen Jan 12, 2024
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
12 changes: 10 additions & 2 deletions cypress/integration/interpretations.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { EXTENDED_TIMEOUT } from '../support/util.js'
const MAP_TITLE = 'test ' + new Date().toUTCString().slice(-24, -4)
context('Interpretations', () => {
it('opens the interpretations panel for a map', () => {
cy.visit('/?id=ZBjCfSaLSqD', EXTENDED_TIMEOUT)
cy.visit('/#/ZBjCfSaLSqD', EXTENDED_TIMEOUT)
const Layer = new ThematicLayer()
Layer.validateCardTitle('ANC LLITN coverage')
cy.get('canvas.maplibregl-canvas').should('be.visible')
Expand Down Expand Up @@ -65,8 +65,12 @@ context('Interpretations', () => {
.find('canvas.maplibregl-canvas')
.should('be.visible')

cy.url().should('include', 'interpretationId=')

cy.get('button').contains('Hide interpretation').click()

cy.url().should('not.include', 'interpretationId=')

deleteMap()
})

Expand All @@ -75,7 +79,7 @@ context('Interpretations', () => {
'postDataStatistics'
)
cy.visit(
'/?id=ZBjCfSaLSqD&interpretationId=yKqhXZdeJ6a',
'/#/ZBjCfSaLSqD?interpretationId=yKqhXZdeJ6a',
EXTENDED_TIMEOUT
) //ANC: LLITN coverage district and facility

Expand All @@ -90,12 +94,16 @@ context('Interpretations', () => {
)
.should('be.visible')

cy.url().should('include', 'interpretationId=')

cy.getByDataTest('interpretation-modal')
.findByDataTest('dhis2-modal-close-button')
.click()

cy.getByDataTest('interpretation-modal').should('not.exist')

cy.url().should('not.include', 'interpretationId=')

cy.getByDataTest('interpretations-list').should('be.visible')
})
})
100 changes: 100 additions & 0 deletions cypress/integration/mapDownload.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { EXTENDED_TIMEOUT } from '../support/util.js'

const mapWithThematicLayer = {
id: 'eDlFx0jTtV9',
name: 'ANC: LLITN Cov Chiefdom this year',
downloadFileName: 'ANC LLITN Cov Chiefdom this year.png',
cardTitle: 'ANC LLITN coverage',
}

const assertDownloadSettingChecked = (label, isChecked) => {
cy.getByDataTest('download-settings')
.find('label')
.contains(label)
.find('input')
.should(`${isChecked ? '' : 'not.'}be.checked`)
}

const clickDownloadSetting = (label) => {
cy.getByDataTest('download-settings')
.find('label')
.contains(label)
.find('input')
.click()
}

describe('Map Download', () => {
beforeEach(() => {
cy.task('emptyDownloadsFolder')
})

it('downloads a map', () => {
cy.visit(`/#/${mapWithThematicLayer.id}`, EXTENDED_TIMEOUT)
cy.get('canvas', EXTENDED_TIMEOUT).should('be.visible')

cy.get('[data-test="layercard"]')
.find('h2')
.contains(mapWithThematicLayer.cardTitle, EXTENDED_TIMEOUT)

cy.getByDataTest('dhis2-analytics-hovermenubar')
.find('button')
.contains('Download')
.click()

cy.log('confirm that download page is open')
cy.getByDataTest('download-settings').should('be.visible')
cy.get('canvas.maplibregl-canvas').should('be.visible')
cy.get('button').contains('Exit download mode').should('be.visible')
cy.url().should('contain', `/#/${mapWithThematicLayer.id}/download`)

// check the current settings
assertDownloadSettingChecked('Show map name', true)

cy.getByDataTest('download-map-info')
.find('h1')
.contains(mapWithThematicLayer.name)
.should('be.visible')

assertDownloadSettingChecked('Show map description', false)
assertDownloadSettingChecked('Show legend', true)
cy.getByDataTest('download-map-info')
.findByDataTest('download-legend-title')
.should('have.length', 1)

assertDownloadSettingChecked('Show overview map', true)
cy.getByDataTest('download-map-info')
.findByDataTest('overview-map')
.should('be.visible')

// make some changes
clickDownloadSetting('Show map name')
cy.getByDataTest('download-map-info').find('h1').should('not.exist')

cy.getByDataTest('download-settings')
.find('button')
.contains('Download')
.click()

// check for downloaded file
cy.wait(3000) // eslint-disable-line cypress/no-unnecessary-waiting
cy.waitUntil(
() => cy.task('getLastDownloadFilePath').then((result) => result),
{ timeout: 3000, interval: 100 }
).then((filePath) => {
expect(filePath).to.include(mapWithThematicLayer.downloadFileName)

cy.readFile(filePath, EXTENDED_TIMEOUT).should((buffer) =>
expect(buffer.length).to.be.gt(10000)
)
})

// leave download mode
cy.get('button').contains('Exit download mode').click()
cy.url().should('contain', `/#/${mapWithThematicLayer.id}`)
cy.url().should('not.contain', '/download')
cy.getByDataTest('download-settings').should('not.exist')
cy.get('[data-test="layercard"]')
.find('h2')
.contains(mapWithThematicLayer.cardTitle, EXTENDED_TIMEOUT)
})
})
82 changes: 78 additions & 4 deletions cypress/integration/smoke.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ context('Smoke Test', () => {
cy.title().should('equal', 'Maps | DHIS2')
})

it('loads with map id', () => {
it('loads with map id (legacy)', () => {
cy.intercept({ method: 'POST', url: /dataStatistics/ }).as(
'postDataStatistics'
)
Expand All @@ -24,7 +24,16 @@ context('Smoke Test', () => {
Layer.validateCardTitle('ANC 3 Coverage')
})

it('loads currentAnalyticalObject', () => {
it('loads with map id (hash)', () => {
cy.visit('/#/zDP78aJU8nX', EXTENDED_TIMEOUT) //ANC: 1st visit coverage (%) by district last year

cy.get('canvas', EXTENDED_TIMEOUT).should('be.visible')

const Layer = new ThematicLayer()
Layer.validateCardTitle('ANC 1 Coverage')
})

it('loads currentAnalyticalObject (legacy)', () => {
cy.intercept('**/userDataStore/analytics/settings', {
fixture: 'analyticalObject.json',
})
Expand All @@ -39,7 +48,22 @@ context('Smoke Test', () => {
cy.get('canvas.maplibregl-canvas').should('be.visible')
})

it('loads with map id and interpretationid lowercase', () => {
it('loads currentAnalyticalObject (hash)', () => {
cy.intercept('**/userDataStore/analytics/settings', {
fixture: 'analyticalObject.json',
})

cy.visit('/#/currentAnalyticalObject', EXTENDED_TIMEOUT)
cy.get('canvas', EXTENDED_TIMEOUT).should('be.visible')

cy.contains('button', 'Proceed').click()

const Layer = new ThematicLayer()
Layer.validateCardTitle('ANC 1 Coverage')
cy.get('canvas.maplibregl-canvas').should('be.visible')
})

it('loads with map id (legacy) and interpretationid lowercase', () => {
cy.intercept({ method: 'POST', url: /dataStatistics/ }).as(
'postDataStatistics'
)
Expand All @@ -60,7 +84,7 @@ context('Smoke Test', () => {
.should('be.visible')
})

it('loads with map id and interpretationId uppercase', () => {
it('loads with map id (legacy) and interpretationId uppercase', () => {
cy.intercept({ method: 'POST', url: /dataStatistics/ }).as(
'postDataStatistics'
)
Expand All @@ -80,4 +104,54 @@ context('Smoke Test', () => {
)
.should('be.visible')
})

it('loads with map id (hash) and interpretationId', () => {
cy.intercept({ method: 'POST', url: /dataStatistics/ }).as(
'postDataStatistics'
)
cy.visit(
'/#/ZBjCfSaLSqD?interpretationId=yKqhXZdeJ6a',
EXTENDED_TIMEOUT
) //ANC: LLITN coverage district and facility

cy.wait('@postDataStatistics')
.its('response.statusCode')
.should('eq', 201)

cy.getByDataTest('interpretation-modal')
.find('h1')
.contains(
'Viewing interpretation: ANC: LLITN coverage district and facility'
)
.should('be.visible')
})

it('loads download page for map id (hash)', () => {
cy.intercept({ method: 'POST', url: /dataStatistics/ }).as(
'postDataStatistics'
)
cy.visit('/#/ZBjCfSaLSqD/download', EXTENDED_TIMEOUT) //ANC: LLITN coverage district and facility

cy.wait('@postDataStatistics')
.its('response.statusCode')
.should('eq', 201)

cy.getByDataTest('download-settings').should('be.visible')
cy.get('canvas.maplibregl-canvas').should('be.visible')
cy.get('button').contains('Exit download mode').should('be.visible')
})

it('loads download page currentAnalyticalObject (hash)', () => {
cy.intercept('**/userDataStore/analytics/settings', {
fixture: 'analyticalObject.json',
})

cy.visit('/#/currentAnalyticalObject/download', EXTENDED_TIMEOUT)

cy.contains('button', 'Proceed').click()

cy.getByDataTest('download-settings').should('be.visible')
cy.get('canvas.maplibregl-canvas').should('be.visible')
cy.get('button').contains('Exit download mode').should('be.visible')
})
})
10 changes: 8 additions & 2 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -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-10-23T12:55:26.288Z\n"
"PO-Revision-Date: 2023-10-23T12:55:26.288Z\n"
"POT-Creation-Date: 2023-12-18T10:13:08.754Z\n"
"PO-Revision-Date: 2023-12-18T10:13:08.754Z\n"

msgid "Untitled map, {{date}}"
msgstr "Untitled map, {{date}}"
Expand All @@ -17,6 +17,12 @@ msgstr "Map \"{{- name}}\" is saved."
msgid "Failed to save map: {{message}}"
msgstr "Failed to save map: {{message}}"

msgid "Failed to open the map"
msgstr "Failed to open the map"

msgid "Unrecognized url path to map"
msgstr "Unrecognized url path to map"

msgid "Classification"
msgstr "Classification"

Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,12 @@
"d3-time": "^3.1.0",
"d3-time-format": "^4.1.0",
"file-saver": "^2.0.5",
"history": "^5.3.0",
"html-to-image": "^1.11.1",
"lodash": "^4.17.21",
"loglevel": "^1.8.1",
"prop-types": "^15.8.1",
"query-string": "^8.1.0",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"react-redux": "^8.1.2",
Expand Down
43 changes: 42 additions & 1 deletion src/AppWrapper.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { CachedDataQueryProvider } from '@dhis2/analytics'
import { D2Shim } from '@dhis2/app-runtime-adapter-d2'
import { DataStoreProvider } from '@dhis2/app-service-datastore'
import { CenteredContent, CircularLoader } from '@dhis2/ui'
import { CssVariables, CenteredContent, CircularLoader } from '@dhis2/ui'
import log from 'loglevel'
import queryString from 'query-string'
import React from 'react'
import { Provider as ReduxProvider } from 'react-redux'
import App from './components/app/App.js'
Expand All @@ -12,6 +13,7 @@ import store from './store/index.js'
import { USER_DATASTORE_NAMESPACE } from './util/analyticalObject.js'
import { appQueries, providerDataTransformation } from './util/app.js'
import './locales/index.js'
import history from './util/history.js'

log.setLevel(
process.env.NODE_ENV === 'production' ? log.levels.INFO : log.levels.TRACE
Expand All @@ -38,7 +40,45 @@ const d2Config = {
],
}

const replaceLegacyUrl = () => {
// support legacy urls
const queryParams = queryString.parse(window.location.search, {
parseBooleans: true,
})
const [base] = window.location.href.split('?')

if (queryParams.id) {
// /?id=ytkZY3ChM6J
// /?id=ZBjCfSaLSqD&interpretationid=yKqhXZdeJ6a
// /?id=ZBjCfSaLSqD&interpretationId=yKqhXZdeJ6a
let newPath = queryParams.id

let interpretationId
if (queryParams.interpretationId) {
interpretationId = queryParams.interpretationId
} else if (queryParams.interpretationid) {
interpretationId = queryParams.interpretationid
}

if (interpretationId) {
newPath += `?interpretationId=${interpretationId}`
}

// replace history && hash history
window.history.replaceState({}, '', `${base}#/${newPath}`)
history.replace(`/${newPath}`)
jenniferarnesen marked this conversation as resolved.
Show resolved Hide resolved
} else if (queryParams.currentAnalyticalObject === true) {
// /?currentAnalyticalObject=true

// replace history && hash history
window.history.replaceState({}, '', `${base}#/currentAnalyticalObject`)
history.replace('/currentAnalyticalObject')
}
}

const AppWrapper = () => {
replaceLegacyUrl()

return (
<ReduxProvider store={store}>
<DataStoreProvider namespace={USER_DATASTORE_NAMESPACE}>
Expand Down Expand Up @@ -67,6 +107,7 @@ const AppWrapper = () => {
>
<WindowDimensionsProvider>
<OrgUnitsProvider>
<CssVariables colors spacers theme />
<App />
</OrgUnitsProvider>
</WindowDimensionsProvider>
Expand Down
Loading
Loading