forked from geosolutions-it/geonode-mapstore-client
-
Notifications
You must be signed in to change notification settings - Fork 109
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Sync plugin for dashboard and geostory (#702)
- Loading branch information
1 parent
ba3475a
commit afc5074
Showing
16 changed files
with
563 additions
and
69 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,18 @@ | ||
/* | ||
* Copyright 2021, GeoSolutions Sas. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the BSD-style license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
|
||
/** | ||
* Sync geostory components with their live resources on geonode | ||
*/ | ||
export const SYNC_RESOURCES = 'GEONODE:SYNC_RESOURCES'; | ||
|
||
export function syncResources() { | ||
return { | ||
type: SYNC_RESOURCES | ||
}; | ||
} |
87 changes: 87 additions & 0 deletions
87
geonode_mapstore_client/client/js/epics/__tests__/sync-test.js
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,87 @@ | ||
/* | ||
* Copyright 2022, GeoSolutions Sas. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the BSD-style license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
|
||
import expect from 'expect'; | ||
import MockAdapter from 'axios-mock-adapter'; | ||
import axios from '@mapstore/framework/libs/ajax'; | ||
import { testEpic } from '@mapstore/framework/epics/__tests__/epicTestUtils'; | ||
import { gnSyncComponentsWithResources } from '../gnsync'; | ||
import { syncResources } from '@js/actions/gnsync'; | ||
import { | ||
SAVE_SUCCESS, SAVING_RESOURCE | ||
} from '@js/actions/gnsave'; | ||
import { EDIT_RESOURCE } from '@mapstore/framework/actions/geostory'; | ||
import { | ||
SHOW_NOTIFICATION | ||
} from '@mapstore/framework/actions/notifications'; | ||
|
||
let mockAxios; | ||
|
||
describe('gnsave epics', () => { | ||
beforeEach(done => { | ||
global.__DEVTOOLS__ = true; | ||
mockAxios = new MockAdapter(axios); | ||
setTimeout(done); | ||
}); | ||
afterEach(done => { | ||
delete global.__DEVTOOLS__; | ||
mockAxios.restore(); | ||
setTimeout(done); | ||
}); | ||
|
||
const pk = 1; | ||
|
||
const geostoryState = { | ||
geostory: {currentStory: {resources: [{data: {id: pk, sourceId: 'geonode', title: 'test'}, type: 'video', id: pk}]}}, | ||
gnresource: {type: 'geostory'} | ||
}; | ||
|
||
|
||
it('should sync resources for geostory', (done) => { | ||
const NUM_ACTIONS = 4; | ||
const resource = { | ||
document: { | ||
pk, | ||
title: 'Test title', | ||
thumbnail: 'Test', | ||
src: 'Test src', | ||
description: 'A test', | ||
credits: null, | ||
resource_type: 'video' | ||
} | ||
}; | ||
|
||
mockAxios.onGet().reply((config) => { | ||
// debug config | ||
if (config.url.match(`/api/v2/documents/${pk}/`)) { | ||
return [200, resource]; | ||
} | ||
if (config.url.match(`/api/v2/maps/${pk}/`)) { | ||
return [200, resource]; | ||
} | ||
return [200, resource]; | ||
}); | ||
testEpic( | ||
gnSyncComponentsWithResources, | ||
NUM_ACTIONS, | ||
syncResources(), | ||
(actions) => { | ||
try { | ||
expect(actions.map(({ type }) => type)) | ||
.toEqual([ | ||
SAVING_RESOURCE, EDIT_RESOURCE, SAVE_SUCCESS, SHOW_NOTIFICATION | ||
]); | ||
} catch (e) { | ||
done(e); | ||
} | ||
done(); | ||
}, | ||
geostoryState | ||
); | ||
}); | ||
}); |
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,164 @@ | ||
/* | ||
* Copyright 2021, GeoSolutions Sas. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the BSD-style license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
|
||
import { Observable } from 'rxjs'; | ||
import axios from '@mapstore/framework/libs/ajax'; | ||
import { merge } from 'lodash'; | ||
import { SYNC_RESOURCES } from '@js/actions/gnsync'; | ||
import { | ||
savingResource, saveSuccess | ||
} from '@js/actions/gnsave'; | ||
import { getViewedResourceType, getGeonodeResourceDataFromGeostory, getGeonodeResourceFromDashboard } from '@js/selectors/resource'; | ||
import { getMapByPk, getDocumentByPk } from '@js/api/geonode/v2'; | ||
import { editResource } from '@mapstore/framework/actions/geostory'; | ||
import { | ||
show as showNotification, | ||
error as errorNotification | ||
} from '@mapstore/framework/actions/notifications'; | ||
import { parseMapConfig, parseDocumentConfig } from '@js/utils/ResourceUtils'; | ||
import { dashboardResource, originalDataSelector } from '@mapstore/framework/selectors/dashboard'; | ||
import { dashboardLoaded } from '@mapstore/framework/actions/dashboard'; | ||
|
||
const getRelevantResourceParams = (resourceType, state) => { | ||
let resources = []; | ||
switch (resourceType) { | ||
case 'geostory': { | ||
resources = getGeonodeResourceDataFromGeostory(state); | ||
return resources; | ||
} | ||
case 'dashboard': { | ||
resources = getGeonodeResourceFromDashboard(state); | ||
return resources; | ||
} | ||
default: | ||
return resources; | ||
} | ||
}; | ||
|
||
const setResourceApi = { | ||
map: getMapByPk, | ||
image: getDocumentByPk, | ||
video: getDocumentByPk | ||
}; | ||
|
||
/** | ||
* Get resource type and data for state update in sync process | ||
* @param {String} appType geostory or dashboard | ||
* @param {Object} resourceData Resource Object | ||
* @param {Optional: Array} successArr Array of success responses only used in case of dashboard | ||
* @returns {Object} | ||
*/ | ||
const getSyncInfo = (appType, resourceData, successArr = []) => { | ||
let type = ''; | ||
let updatedData = {}; | ||
|
||
|
||
if (appType === 'geostory') { | ||
type = resourceData.subtype || resourceData.resource_type; | ||
updatedData = type !== 'map' ? parseDocumentConfig(resourceData, resourceData) : parseMapConfig(resourceData); | ||
|
||
} else if (appType === 'dashboard') { | ||
const updatedWidgets = resourceData.widgets?.map((widget) => { | ||
const currentWidget = successArr.find(res => !!(res.data.pk === widget.map?.extraParams?.pk)); | ||
if (currentWidget) { | ||
return { ...widget, map: { ...widget.map, ...currentWidget.data.data.map } }; | ||
} | ||
|
||
return widget; | ||
}); | ||
updatedData = merge(resourceData, { widgets: updatedWidgets }); | ||
} | ||
|
||
return { type, data: updatedData }; | ||
}; | ||
|
||
/** | ||
* Get notification title, leve, and message for showNotification | ||
* @param {Number} errors length of errors array | ||
* @param {Number} successes length of success arra | ||
* @returns {Object} | ||
*/ | ||
const getNotificationInfo = (errors, successes) => { | ||
let verdict = 'Success'; | ||
if (errors > 0 && successes > 0) verdict = 'Warning'; | ||
else if (errors === 0 && successes > 0) verdict = 'Success'; | ||
else if (errors > 0 && successes === 0) verdict = 'Error'; | ||
|
||
return {level: verdict.toLowerCase(), title: `gnviewer.sync${verdict}Title`, message: `gnviewer.sync${verdict}Message`}; | ||
}; | ||
|
||
/** | ||
* Sync reources in current geostory or dashboard with their respective sources | ||
* @param {*} action$ the actions | ||
* @param {Object} store | ||
* @returns {Observable} | ||
*/ | ||
export const gnSyncComponentsWithResources = (action$, store) => action$.ofType(SYNC_RESOURCES) | ||
.switchMap(() => { | ||
const state = store.getState(); | ||
const resourceType = getViewedResourceType(state); | ||
const resources = getRelevantResourceParams(resourceType, state); | ||
|
||
return Observable.defer(() => | ||
axios.all(resources.map((resource) => (resourceType === 'geostory' ? | ||
setResourceApi[resource.type](resource.id) | ||
: getMapByPk(resource?.map?.extraParams?.pk)).then(data => ({ data, status: 'success', title: data.title })) | ||
.catch(() => ({ data: resource, status: 'error', title: resource?.data?.title || resource?.map?.extraParams?.pk || resource?.data?.name })) | ||
))) | ||
.switchMap(updatedResources => { | ||
|
||
const errorsResponses = updatedResources.filter(({ status }) => status === 'error'); | ||
const successResponses = updatedResources.filter(({ status }) => status === 'success'); | ||
|
||
const getUpdateActions = () => { | ||
if (successResponses.length === 0) { | ||
return []; | ||
} | ||
if (resourceType === 'geostory') { | ||
return successResponses.map(({ data }) => { | ||
const { type, data: updatedData } = getSyncInfo('geostory', data); | ||
return editResource(data.pk, type, updatedData); | ||
}); | ||
} | ||
if (resourceType === 'dashboard') { | ||
const originalData = originalDataSelector(state); | ||
const { data: newResourceData } = getSyncInfo('dashboard', originalData, successResponses); | ||
return [dashboardLoaded(dashboardResource(state), newResourceData)]; | ||
} | ||
return []; | ||
}; | ||
|
||
const updateActions = getUpdateActions(); | ||
|
||
// notification action into | ||
const {level, title, message} = getNotificationInfo(errorsResponses.length, successResponses.length); | ||
|
||
return Observable.of( | ||
...updateActions, | ||
saveSuccess(), | ||
showNotification({ | ||
title, | ||
message, | ||
values: { | ||
successTitles: successResponses.map((response) => response.title)?.join(', '), | ||
errorTitles: errorsResponses.map((resource) => resource.title)?.join(', ') | ||
} | ||
}, level) | ||
); | ||
}).catch((error) => { | ||
return Observable.of( | ||
saveSuccess(), | ||
errorNotification({ title: "gnviewer.syncErrorTitle", message: error?.data?.detail || error?.originalError?.message || error?.message || "gnviewer.syncErrorDefault" }) | ||
); | ||
}).startWith(savingResource()); | ||
}); | ||
|
||
|
||
export default { | ||
gnSyncComponentsWithResources | ||
}; |
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.