-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature/open chart as map [DHIS2-5987] (#213)
This enables "Open as: Map" chart type in DV app. User Data Store (UDS) has been chosen for data sharing across DV and Maps apps. More details about user data store in [docs](https://docs.dhis2.org/master/en/developer/html/webapi_user_data_store. Chart config format After some discussion decision was made to remove some attributes from analytical object (AO) before putting it into user data store: - id - name - displayName Reason: if user decides to open existing chart/AO in another app (e.g. maps), then we need to allow sharing only chart configuration (excluding name and interpretations). How it works Open as map use case - Prepares AO format - Removes `id`, `name`, `displayName` attributes from object - Appends `path` attributes to org unit dimension items - Appends dimension item names - Saves transformed AO in user data store with `namespace=analytics` and `key=currentAnalyticalObject` - Redirects to maps app with URL flag `currentAnalyticalObject=true` Open visualization from map use case - On app start checks if routing id equals to `currentAnalyticalObject` (assume yes for this use case) - Fetches visualization config from user data store using same namespace and key (`analytics` and `currentAnalyticalObject` correspondingly) - Generates `parentGraphMap` and puts it in `ui` redux object (required for org units tree functioning) - Calls redux `acSetVisualization` action with fetched AO as argument - Calls redux `acSetUiFromVisualization` action with fetched AO as argument - Calls redux `acSetCurrentFromUi` action with updated `current` redux store object
- Loading branch information
Showing
17 changed files
with
945 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import * as d2lib from 'd2'; | ||
import * as userDataStore from '../userDataStore'; | ||
import { | ||
apiSave, | ||
apiFetch, | ||
hasNamespace, | ||
getNamespace, | ||
apiSaveAOInUserDataStore, | ||
apiFetchAOFromUserDataStore, | ||
NAMESPACE, | ||
CURRENT_AO_KEY, | ||
} from '../userDataStore'; | ||
|
||
let mockD2; | ||
let mockNamespace; | ||
|
||
describe('api: user data store', () => { | ||
beforeEach(() => { | ||
mockNamespace = { | ||
get: jest.fn(), | ||
set: jest.fn(), | ||
}; | ||
mockD2 = { | ||
currentUser: { | ||
dataStore: { | ||
has: jest.fn().mockResolvedValue(false), // false default value for test purposes | ||
get: jest.fn().mockResolvedValue(mockNamespace), | ||
create: jest.fn().mockResolvedValue(mockNamespace), | ||
}, | ||
}, | ||
}; | ||
d2lib.getInstance = () => Promise.resolve(mockD2); | ||
}); | ||
|
||
describe('hasNamespace', () => { | ||
it('uses result of "has" method of d2.currentUser.dataStore object', async () => { | ||
const result = await hasNamespace(mockD2); | ||
|
||
expect(mockD2.currentUser.dataStore.has).toBeCalledTimes(1); | ||
expect(mockD2.currentUser.dataStore.has).toBeCalledWith(NAMESPACE); | ||
expect(result).toEqual(false); | ||
}); | ||
}); | ||
|
||
describe('getNamespace', () => { | ||
it('retrieves and returns namespace if it exists', async () => { | ||
const result = await getNamespace(mockD2, true); | ||
|
||
expect(mockD2.currentUser.dataStore.get).toBeCalledTimes(1); | ||
expect(mockD2.currentUser.dataStore.create).toBeCalledTimes(0); | ||
expect(result).toMatchObject(mockNamespace); | ||
}); | ||
|
||
it('creates and returns namespace if it doesnt exist', async () => { | ||
const result = await getNamespace(mockD2, false); | ||
|
||
expect(mockD2.currentUser.dataStore.get).toBeCalledTimes(0); | ||
expect(mockD2.currentUser.dataStore.create).toBeCalledTimes(1); | ||
expect(result).toMatchObject(mockNamespace); | ||
}); | ||
}); | ||
|
||
describe('apiSave', () => { | ||
it('uses d2 namespace.set for saving data under given key', async () => { | ||
const data = {}; | ||
const key = 'someKey'; | ||
|
||
await apiSave(data, key, mockNamespace); | ||
|
||
expect(mockNamespace.set).toBeCalledTimes(1); | ||
expect(mockNamespace.set).toBeCalledWith(key, data); | ||
}); | ||
}); | ||
|
||
describe('apiFetch', () => { | ||
it('uses d2 namespace.get for retrieving data by given key', async () => { | ||
const key = 'someKey'; | ||
|
||
await apiFetch(key, mockNamespace); | ||
|
||
expect(mockNamespace.get).toBeCalledTimes(1); | ||
expect(mockNamespace.get).toBeCalledWith(key); | ||
}); | ||
}); | ||
|
||
describe('apiSaveAoInUserDataStore', () => { | ||
beforeEach(() => { | ||
userDataStore.getNamespace = () => Promise.resolve(mockNamespace); | ||
}); | ||
|
||
it('uses default key unless specified', async () => { | ||
const data = {}; | ||
|
||
await apiSaveAOInUserDataStore(data); | ||
|
||
expect(mockNamespace.set).toBeCalledTimes(1); | ||
expect(mockNamespace.set).toBeCalledWith(CURRENT_AO_KEY, data); | ||
}); | ||
}); | ||
|
||
describe('apiFetchAOFromUserDataStore', () => { | ||
beforeEach(() => { | ||
userDataStore.getNamespace = () => Promise.resolve(mockNamespace); | ||
}); | ||
|
||
it('uses default key unless specified', async () => { | ||
await apiFetchAOFromUserDataStore(); | ||
|
||
expect(mockNamespace.get).toBeCalledTimes(1); | ||
expect(mockNamespace.get).toBeCalledWith(CURRENT_AO_KEY); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { getInstance } from 'd2'; | ||
import { onError } from './index'; | ||
|
||
export const NAMESPACE = 'analytics'; | ||
export const CURRENT_AO_KEY = 'currentAnalyticalObject'; | ||
|
||
export const hasNamespace = async d2 => | ||
await d2.currentUser.dataStore.has(NAMESPACE); | ||
|
||
export const getNamespace = async (d2, hasNamespace) => | ||
hasNamespace | ||
? await d2.currentUser.dataStore.get(NAMESPACE) | ||
: await d2.currentUser.dataStore.create(NAMESPACE); | ||
|
||
export const apiSave = async (data, key, namespace) => { | ||
try { | ||
const d2 = await getInstance(); | ||
const ns = | ||
namespace || (await getNamespace(d2, await hasNamespace(d2))); | ||
|
||
return ns.set(key, data); | ||
} catch (error) { | ||
return onError(error); | ||
} | ||
}; | ||
|
||
export const apiFetch = async (key, namespace) => { | ||
try { | ||
const d2 = await getInstance(); | ||
const ns = | ||
namespace || (await getNamespace(d2, await hasNamespace(d2))); | ||
|
||
return ns.get(key); | ||
} catch (error) { | ||
return onError(error); | ||
} | ||
}; | ||
|
||
export const apiSaveAOInUserDataStore = (current, key = CURRENT_AO_KEY) => | ||
apiSave(current, key); | ||
|
||
export const apiFetchAOFromUserDataStore = (key = CURRENT_AO_KEY) => | ||
apiFetch(key); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import React from 'react'; | ||
import SvgIcon from '@material-ui/core/SvgIcon'; | ||
|
||
const GlobeIcon = ({ | ||
style = { width: 24, height: 24, paddingRight: '8px' }, | ||
}) => ( | ||
<SvgIcon viewBox="0 0 48 48" style={style}> | ||
<title>icon_chart_GIS</title> | ||
<desc>Created with Sketch.</desc> | ||
<defs> | ||
<rect id="path-1" x="0" y="0" width="48" height="48" /> | ||
</defs> | ||
<g | ||
id="Symbols" | ||
stroke="none" | ||
strokeWidth="1" | ||
fill="none" | ||
fillRule="evenodd" | ||
> | ||
<g id="Icon/48x48/chart_GIS"> | ||
<g id="icon_chart_GIS"> | ||
<mask id="mask-2" fill="white"> | ||
<use xlinkHref="#path-1" /> | ||
</mask> | ||
<g id="Bounds" /> | ||
<circle | ||
id="Oval-4" | ||
stroke="#1976D2" | ||
strokeWidth="2" | ||
mask="url(#mask-2)" | ||
cx="24" | ||
cy="24" | ||
r="23" | ||
/> | ||
<polyline | ||
id="Path-6" | ||
stroke="#1976D2" | ||
strokeWidth="2" | ||
mask="url(#mask-2)" | ||
points="1 21 4 24 8 26 9 24 6 19 11 18 18 12 14 9 16 6 15 3" | ||
/> | ||
<polyline | ||
id="Path-7" | ||
stroke="#1976D2" | ||
strokeWidth="2" | ||
mask="url(#mask-2)" | ||
points="47 25 45 21 43 19 40 18 37 18 34 17 32 18 30 23 33 27 37 27 38 30 38 38 38.5 42" | ||
/> | ||
<polyline | ||
id="Path-5" | ||
stroke="#1976D2" | ||
strokeWidth="2" | ||
mask="url(#mask-2)" | ||
points="38 6 37 7 34 6 32 8 34 10 33 12 33 15 37 14 39 15 43 12" | ||
/> | ||
<polyline | ||
id="Path-8" | ||
stroke="#1976D2" | ||
strokeWidth="2" | ||
mask="url(#mask-2)" | ||
points="18 46 16 41 15 36 13 34 10 31 11 28 14 26 18 27 20 29 23 30 25 32 25 36 23 40 21 47" | ||
/> | ||
</g> | ||
</g> | ||
</g> | ||
</SvgIcon> | ||
); | ||
|
||
export default GlobeIcon; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.