Skip to content

Commit

Permalink
geosolutions-it#9248 - Import/export an application context
Browse files Browse the repository at this point in the history
  • Loading branch information
dsuren1 committed Jul 4, 2023
1 parent 3df202c commit f4c7fd2
Show file tree
Hide file tree
Showing 24 changed files with 766 additions and 104 deletions.
6 changes: 6 additions & 0 deletions docs/user-guide/application-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ This first step allows to configure the **Name** and the **Window title** of the
!!! note
The **Window title** is the name of the browser window.

### Import/Export context

In general settings, the user can import/export the context in view with all the configurations of the context. The plugins are configured under `context-creator` in the localConfig and are enabled by default.

<img src="../img/application-context/context_import_export.jpg" class="ms-docimage"/>

## Configure Map

To create the context viewer, the map configuration (like the one described [here](exploring-maps.md#exploring-maps) opens so that the admin can set the initial state of the context map.
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 19 additions & 1 deletion web/client/actions/__tests__/contextcreator-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,11 @@ import {
SET_SELECTED_THEME,
setSelectedTheme,
onToggleCustomVariables,
ON_TOGGLE_CUSTOM_VARIABLES
ON_TOGGLE_CUSTOM_VARIABLES,
onContextExport,
CONTEXT_EXPORT,
onContextImport,
CONTEXT_IMPORT
} from '../contextcreator';

describe('contextcreator actions', () => {
Expand Down Expand Up @@ -177,4 +181,18 @@ describe('contextcreator actions', () => {
expect(retval).toBeTruthy();
expect(retval.type).toBe(ON_TOGGLE_CUSTOM_VARIABLES);
});
it('onContextExport', () => {
const fileName = "test.json";
const retVal = onContextExport(fileName);
expect(retVal).toBeTruthy();
expect(retVal.type).toBe(CONTEXT_EXPORT);
expect(retVal.fileName).toBe(fileName);
});
it('onContextImport', () => {
const file = {name: "test"};
const retVal = onContextImport(file);
expect(retVal).toBeTruthy();
expect(retVal.type).toBe(CONTEXT_IMPORT);
expect(retVal.file).toEqual(file);
});
});
19 changes: 19 additions & 0 deletions web/client/actions/contextcreator.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ export const CONTEXT_TUTORIALS = {
"configure-map": "contextcreator_configuremap_tutorial",
"configure-plugins": "contextcreator_configureplugins_tutorial"
};
export const CONTEXT_EXPORT = 'CONTEXT:EXPORT';
export const CONTEXT_IMPORT = 'CONTEXT:IMPORT';

/**
* Merges initState into context creator state. Meant to be called on ContextCreator component mount
* @param {object} initState state to merge
Expand Down Expand Up @@ -562,3 +565,19 @@ export const showBackToPageConfirmation = (show) => ({
export const onToggleCustomVariables = () => ({
type: ON_TOGGLE_CUSTOM_VARIABLES
});

/**
* Triggers context export
*/
export const onContextExport = (fileName) => ({
type: CONTEXT_EXPORT,
fileName
});

/**
* Triggers context import
*/
export const onContextImport = (file) => ({
type: CONTEXT_IMPORT,
file
});
36 changes: 24 additions & 12 deletions web/client/components/contextcreator/ContextCreator.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,8 @@ export default class ContextCreator extends React.Component {
basicVariables: PropTypes.object,
customVariablesEnabled: PropTypes.bool,
onToggleCustomVariables: PropTypes.func,
enableClickOnStep: PropTypes.bool
enableClickOnStep: PropTypes.bool,
items: PropTypes.array
};

static contextTypes = {
Expand Down Expand Up @@ -244,7 +245,9 @@ export default class ContextCreator extends React.Component {
editingAllowedRoles: []
}
}
}
},
"ContextImport",
"ContextExport"
],
ignoreViewerPlugins: false,
allAvailablePlugins: [],
Expand Down Expand Up @@ -282,11 +285,24 @@ export default class ContextCreator extends React.Component {
}

render() {
const extraToolbarButtons = (stepId) => this.props.tutorials[stepId] ? [{
id: 'show-tutorial',
onClick: () => this.props.onShowTutorial(stepId),
label: 'contextCreator.showTutorial'
}] : [];
const extraToolbarButtons = (stepId) => {
let toolbarButton = this.props.tutorials[stepId] ? [{
id: 'show-tutorial',
onClick: () => this.props.onShowTutorial(stepId),
label: 'contextCreator.showTutorial'
}] : [];
if (stepId === 'general-settings') {
const importExportButtons = this.props.items?.map(({toolbarBtn} = {}) => toolbarBtn) ?? [];
toolbarButton = toolbarButton.concat(importExportButtons);
} else if (stepId === 'configure-map') {
toolbarButton = toolbarButton.concat({
id: "map-reload",
onClick: () => this.props.onReloadConfirm(true),
label: 'contextCreator.configureMap.reload'
});
}
return toolbarButton;
};

return (
<Stepper
Expand Down Expand Up @@ -319,11 +335,7 @@ export default class ContextCreator extends React.Component {
}, {
id: 'configure-map',
label: 'contextCreator.configureMap.label',
extraToolbarButtons: [...extraToolbarButtons('configure-map'), {
id: "map-reload",
onClick: () => this.props.onReloadConfirm(true),
label: 'contextCreator.configureMap.reload'
}],
extraToolbarButtons: extraToolbarButtons('configure-map'),
component:
<ConfigureMap
pluginsConfig={this.props.ignoreViewerPlugins ?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,37 @@ describe('ContextCreator component', () => {
});

});
it("test plugin container items in general-settings", () => {
const eng = {
"locale": "en-US",
"messages": {
"aboutLbl": "About"
}
};
const actions = {
onSave: () => { }
};
const allAvailablePlugins = [
{enabled: true, title: 'title', pluginConfig: {cfg: {}}},
{enabled: false, title: 'title', pluginConfig: {cfg: {}}}
];
ReactDOM.render(
<Localized messages={eng.messages} locale="en-US">
<Provider store={store}>
<ContextCreator
isCfgValidated
allAvailablePlugins={allAvailablePlugins}
curStepId="general-settings"
onSave={actions.onSave}
items={[{
toolbarBtn: {
component: () => <button>ITEM1</button>
}
}]}
/>
</Provider>
</Localized>, document.getElementById("container"));
const itemBtn = document.querySelectorAll('.footer-button-toolbar-extra button')[1];
expect(itemBtn.innerText).toBe('ITEM1');
});
});
13 changes: 8 additions & 5 deletions web/client/components/misc/Stepper.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,14 @@ export default ({
<div className="ms2-stepper">
<div className="footer-button-toolbar-div">
<ButtonToolbar className="footer-button-toolbar-extra">
{(steps[curStepIndex].extraToolbarButtons || []).map(({onClick = () => {}, label, id}, idx) =>
<Button key={id || label || idx} bsStyle="primary" bsSize="sm" onClick={onClick}>
<Message msgId={label}/>
</Button>
)}
{(steps[curStepIndex].extraToolbarButtons || []).map((toolbarButton, idx) => {
const {component: Component, onClick = () => {}, label, id} = toolbarButton;
return Component ? <Component/> : (
<Button key={id || label || idx} bsStyle="primary" bsSize="sm" onClick={onClick}>
<Message msgId={label}/>
</Button>
);
})}
</ButtonToolbar>
<ButtonToolbar className="footer-button-toolbar">
<Button
Expand Down
2 changes: 2 additions & 0 deletions web/client/configs/localConfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,8 @@
"backToPageConfirmationMessage": "contextCreator.undo"
}
},
"ContextImport",
"ContextExport",
"Notifications",
{
"name": "FeedbackMask",
Expand Down
108 changes: 106 additions & 2 deletions web/client/epics/__tests__/contextcreator-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ import {
saveContextResource,
checkIfContextExists,
editTemplateEpic,
mapViewerLoadEpic
mapViewerLoadEpic,
exportContextEpic,
importContextEpic
} from '../contextcreator';
import {
editPlugin,
Expand All @@ -33,6 +35,8 @@ import {
saveNewContext,
editTemplate,
mapViewerLoad,
onContextExport,
onContextImport,
SET_EDITED_PLUGIN,
SET_EDITED_CFG,
SET_CFG_ERROR,
Expand All @@ -51,7 +55,8 @@ import {
CONTEXT_SAVED,
SET_EDITED_TEMPLATE,
SET_PARSED_TEMPLATE,
SET_FILE_DROP_STATUS
SET_FILE_DROP_STATUS,
SET_RESOURCE
} from '../../actions/contextcreator';
import {INIT_MAP} from '../../actions/map';
import {LOAD_MAP_CONFIG} from '../../actions/config';
Expand All @@ -61,6 +66,7 @@ import {

import axios from "../../libs/ajax";
import MockAdapter from "axios-mock-adapter";
import {TOGGLE_CONTROL} from "../../actions/controls";

describe('contextcreator epics', () => {
let mockAxios;
Expand Down Expand Up @@ -954,4 +960,102 @@ describe('contextcreator epics', () => {
done();
});
});
it('exportContextEpic, export context with plugins and themes', (done) => {
testEpic(exportContextEpic, 1, onContextExport('file.json'), ([a]) => {
expect(a.type).toEqual(TOGGLE_CONTROL);
expect(a.control).toEqual("export");
done();
}, {
map: {
present: {}
},
contextcreator: {
plugins: [{name: "Search", enabled: true}, {name: "PluginUser", isUserPlugin: true, enabled: true}],
selectedTheme: {
color: "#000"
},
newContext: {
templates: []
},
resource: {
name: "test",
description: "Some context"
},
exportData: {
pluginsConfig: [],
allTemplates: []
}
}
});
});
it('importContextEpic into existing context', (done) => {
const blob = new Blob([JSON.stringify({
resource: {id: 'context2', name: 'test1'},
pluginsConfig: [],
allTemplates: []
})], {
type: "application/json"
});
const jsonFile = new File([blob], "file.json", {
type: "application/json"
});
const epicResult = ([a, b]) => {
expect(a.type).toBe(SET_RESOURCE);
expect(a.resource).toEqual({id: 'context1', name: 'test'});
expect(a.pluginsConfig).toEqual([]);
expect(a.allTemplates).toEqual([]);
expect(b.type).toBe(TOGGLE_CONTROL);
expect(b.control).toBe('import');
done();
};
testEpic(importContextEpic, 2, onContextImport([jsonFile]), epicResult, {
contextcreator: {
resource: {
name: "test",
id: "context1"
}
}
}, done);
});

it('importContextEpic into new context', (done) => {
const blob = new Blob([JSON.stringify({
resource: {id: 'context2', name: 'test1'},
pluginsConfig: [],
allTemplates: []
})], {
type: "application/json"
});
const jsonFile = new File([blob], "file.json", {
type: "application/json"
});
const epicResult = ([a, b]) => {
expect(a.type).toBe(SET_RESOURCE);
expect(a.resource).toEqual({ name: 'test'});
expect(a.pluginsConfig).toEqual([]);
expect(a.allTemplates).toEqual([]);
expect(b.type).toBe(TOGGLE_CONTROL);
expect(b.control).toBe('import');
done();
};
testEpic(importContextEpic, 2, onContextImport([jsonFile]), epicResult, {
contextcreator: {
resource: {
name: "test"
}
}
}, done);
});

it('importContextEpic, throws error if no file data is provided', (done) => {
const startActions = [onContextImport(null)];
const epicResult = actions => {
expect(actions.length).toBe(1);
expect(actions[0].type).toBe(SHOW_NOTIFICATION);
expect(actions[0].level).toBe('error');
expect(typeof(actions[0].title) === 'string').toBeTruthy();
done();
};
testEpic(importContextEpic, 1, startActions, epicResult, {}, done);
});
});
Loading

0 comments on commit f4c7fd2

Please sign in to comment.