diff --git a/src/common/apis/formPrivilegesApi.js b/src/common/apis/formPrivilegesApi.js
index 1c780d3d..61d82e1b 100644
--- a/src/common/apis/formPrivilegesApi.js
+++ b/src/common/apis/formPrivilegesApi.js
@@ -14,6 +14,15 @@ import { UrlHelper } from 'form-builder/helpers/UrlHelper';
export function saveFormPrivileges(formPrivileges) {
return httpInterceptor.post(formBuilderConstants.saveFormPrivilegesUrl, formPrivileges);
}
+export function buildFormPrivilegesPayload(formId, formVersion, formPrivileges) {
+ return formPrivileges.map((privilege) => ({
+ formId,
+ formVersion,
+ privilegeName: privilege.privilegeName,
+ editable: privilege.editable,
+ viewable: privilege.viewable,
+ }));
+}
export function getFormPrivileges(formId, formVersion) {
return httpInterceptor.get(new UrlHelper()
.getFormPrivilegesUrl(formId, formVersion), 'text');
diff --git a/src/form-builder/components/FormBuilder.jsx b/src/form-builder/components/FormBuilder.jsx
index a8700e4f..d50b2e5a 100644
--- a/src/form-builder/components/FormBuilder.jsx
+++ b/src/form-builder/components/FormBuilder.jsx
@@ -26,6 +26,9 @@ import { remove } from 'lodash';
import Spinner from 'common/Spinner';
import { formEventUpdate, saveEventUpdate } from 'form-builder/actions/control';
import { validateFormHyperlinks } from 'form-builder/helpers/hyperlinkValidationHelper';
+import {
+ saveFormPrivileges, getFormPrivilegesFromUuid, buildFormPrivilegesPayload,
+} from 'common/apis/formPrivilegesApi';
export default class FormBuilder extends Component {
@@ -62,7 +65,8 @@ export default class FormBuilder extends Component {
const version = this.getFormVersion(formName);
let uuid = '';
this.props.data.forEach(form => {
- if (form.name === formName && form.version === version) {
+ // eslint-disable-next-line
+ if (form.name === formName && parseInt(form.version) === version) {
uuid = form.uuid;
}
});
@@ -238,6 +242,7 @@ export default class FormBuilder extends Component {
const formName = formJson.name;
const value = JSON.parse(formJson.resources[0].value);
const nameTranslations = formJson.resources[1] && formJson.resources[1].value;
+ const privileges = formJson.privileges || [];
const form = {
name: formName,
version: '1',
@@ -256,7 +261,9 @@ export default class FormBuilder extends Component {
});
self.updateImportErrors(fileName, message);
} else {
- self.formJSONs.push({ form, value, formName, translations, nameTranslations });
+ self.formJSONs.push({
+ form, value, formName, translations, nameTranslations, privileges,
+ });
}
});
}
@@ -288,16 +295,28 @@ export default class FormBuilder extends Component {
const self = this;
const importFormJsonPromises = [];
formJsons.forEach(formJson => {
- const { form, value, formName, translations, nameTranslations } = formJson;
+ const { form, value, formName, translations, nameTranslations, privileges } = formJson;
importFormJsonPromises.push(self.saveFormJson(form, value, formName, translations,
- nameTranslations));
+ nameTranslations, privileges));
});
Promise.all(importFormJsonPromises)
- .then(() => self.hideLoader())
+ .then(() => {
+ if (self.props.onImportComplete) {
+ self.props.onImportComplete();
+ }
+ self.hideLoader();
+ })
.catch(() => self.hideLoader());
}
- saveFormJson(form, value, formName, translations, nameTranslations) {
+ saveImportedFormPrivileges(formId, formVersion, privileges) {
+ if (!privileges || privileges.length === 0) {
+ return Promise.resolve();
+ }
+ return saveFormPrivileges(buildFormPrivilegesPayload(formId, formVersion, privileges));
+ }
+
+ saveFormJson(form, value, formName, translations, nameTranslations, privileges) {
const self = this;
const val = value;
const hyperlinkErrors = validateFormHyperlinks(val, this.props.allowedDomains || []);
@@ -327,8 +346,15 @@ export default class FormBuilder extends Component {
};
const translationsWithFormUuid = translations.map((eachTranslation) =>
Object.assign({}, eachTranslation, { formUuid: response.uuid }));
- self.props.saveFormResource(formResource, translationsWithFormUuid,
- formNameTranslationsResource);
+ return self.props.saveFormResource(formResource, translationsWithFormUuid,
+ formNameTranslationsResource)
+ .then((savedForm) =>
+ self.saveImportedFormPrivileges(savedForm.id, savedForm.version, privileges))
+ .catch(() => {
+ self.props.onValidationError(
+ `Import failed for form "${formName}": could not save form content`
+ );
+ });
})
.catch(() => {
const formUuid = self.getFormUuid(formName);
@@ -336,7 +362,7 @@ export default class FormBuilder extends Component {
const params =
'v=custom:(id,uuid,name,version,published,auditInfo,' +
'resources:(value,dataType,uuid))';
- httpInterceptor.get(`${formBuilderConstants.formUrl}/${formUuid}?${params}`)
+ return httpInterceptor.get(`${formBuilderConstants.formUrl}/${formUuid}?${params}`)
.then((data) => {
const formResource = {
form: {
@@ -354,7 +380,15 @@ export default class FormBuilder extends Component {
value: nameTranslations,
uuid: '',
};
- self.props.saveFormResource(formResource, translations, formNameTranslationsResource);
+ return self.props
+ .saveFormResource(formResource, translations, formNameTranslationsResource)
+ .then((savedForm) =>
+ self.saveImportedFormPrivileges(savedForm.id, savedForm.version, privileges));
+ })
+ .catch(() => {
+ self.props.onValidationError(
+ `Import failed for form "${formName}": could not resolve the existing form`
+ );
});
});
}
@@ -436,7 +470,6 @@ export default class FormBuilder extends Component {
return;
}
const zip = new JSZip();
- let fileName;
let params = '';
const uuids = this.state.selectedForms;
uuids.forEach((uuid, index) => {
@@ -450,19 +483,27 @@ export default class FormBuilder extends Component {
commonConstants.responseType.error);
}
const formData = exportResponse.bahmniFormDataList;
- formData.forEach(form => {
- fileName = `${form.formJson.name}_${form.formJson.version}`;
- zip.file(`${fileName}.json`, JSON.stringify(form));
- });
- if (formData.length > 0) {
- zip.generateAsync({ type: 'blob', compression: 'DEFLATE' }).then((content) => {
- saveAs(content, commonConstants.exportFileName);
+ const privilegesPromises = formData.map((form) =>
+ getFormPrivilegesFromUuid(form.formJson.uuid).catch(() => []));
+ return Promise.all(privilegesPromises).then((privilegesList) => {
+ formData.forEach((form, index) => {
+ const fileName = `${form.formJson.name}_${form.formJson.version}`;
+ const formWithPrivileges = Object.assign({}, form, {
+ formJson: Object.assign({}, form.formJson,
+ { privileges: privilegesList[index] }),
+ });
+ zip.file(`${fileName}.json`, JSON.stringify(formWithPrivileges));
});
- if (exportResponse.errorFormList.length === 0) {
- this.setMessage(commonConstants.exportFormsSuccessMessage,
- commonConstants.responseType.success);
+ if (formData.length > 0) {
+ zip.generateAsync({ type: 'blob', compression: 'DEFLATE' }).then((content) => {
+ saveAs(content, commonConstants.exportFileName);
+ });
+ if (exportResponse.errorFormList.length === 0) {
+ this.setMessage(commonConstants.exportFormsSuccessMessage,
+ commonConstants.responseType.success);
+ }
}
- }
+ });
})
.catch(() => {
this.setMessage('Export failed', commonConstants.responseType.error);
@@ -529,6 +570,7 @@ FormBuilder.propTypes = {
isExact: PropTypes.bool.isRequired,
params: PropTypes.object,
}),
+ onImportComplete: PropTypes.func,
onValidationError: PropTypes.func,
routes: PropTypes.array,
saveForm: PropTypes.func.isRequired,
diff --git a/src/form-builder/components/FormBuilderContainer.jsx b/src/form-builder/components/FormBuilderContainer.jsx
index fd88f16c..96763810 100644
--- a/src/form-builder/components/FormBuilderContainer.jsx
+++ b/src/form-builder/components/FormBuilderContainer.jsx
@@ -48,8 +48,8 @@ export class FormBuilderContainer extends Component {
}
getFormData() {
- let initialForms = [];
- let forms = [];
+ const initialForms = [];
+ const forms = [];
const queryParams = '?=';
const fetchFormsUrl = `${formBuilderConstants.formUrl}?v=custom:(id,uuid,name,version,published,auditInfo)`;
return httpInterceptor.get(fetchFormsUrl)
@@ -156,7 +156,7 @@ export class FormBuilderContainer extends Component {
saveFormResource(formJson, formTranslations, formNameTranslationsResource) {
const self = this;
self.setMessage('Importing Form...', commonConstants.responseType.success);
- httpInterceptor.post(formBuilderConstants.bahmniFormResourceUrl, formJson)
+ return httpInterceptor.post(formBuilderConstants.bahmniFormResourceUrl, formJson)
.then((form) => {
const updatedTranslations = map(formTranslations, (translation) => {
const formTranslation = translation;
@@ -165,8 +165,8 @@ export class FormBuilderContainer extends Component {
return formTranslation;
});
self.saveTranslations(updatedTranslations, formNameTranslationsResource);
- })
- .catch((error) => this.showErrors(error));
+ return form.form;
+ });
}
render() {
@@ -181,6 +181,7 @@ export class FormBuilderContainer extends Component {
data={this.state.data}
dispatch={this.props.dispatch}
match={this.props.match}
+ onImportComplete={() => this.getFormData()}
onValidationError={(messages) => this.onValidationError(messages)}
routes={this.props.routes}
saveForm={(formName) => this.saveForm(formName)}
diff --git a/src/form-builder/components/FormDetailContainer.jsx b/src/form-builder/components/FormDetailContainer.jsx
index 596eaed1..ba155f2c 100644
--- a/src/form-builder/components/FormDetailContainer.jsx
+++ b/src/form-builder/components/FormDetailContainer.jsx
@@ -44,7 +44,7 @@ import {
} from 'common/apis/formTranslationApi';
import FormPreviewModal from 'form-builder/components/FormPreviewModal.jsx';
import Popup from 'reactjs-popup';
-import { saveFormPrivileges } from 'common/apis/formPrivilegesApi';
+import { saveFormPrivileges, buildFormPrivilegesPayload } from 'common/apis/formPrivilegesApi';
import { validateFormHyperlinks, fetchAllowedDomains } from 'form-builder/helpers/hyperlinkValidationHelper';
export class FormDetailContainer extends Component {
@@ -227,9 +227,8 @@ export class FormDetailContainer extends Component {
});
}
_saveFormPrivileges(formId, formVersion) {
- let formVersionTemp = formVersion;
saveFormPrivileges(
- this._createReqObject(formId, formVersionTemp, this.state.formPrivileges)
+ buildFormPrivilegesPayload(formId, formVersion, this.state.formPrivileges)
)
.then(() => {
const message = 'Form Privileges saved successfully';
@@ -242,22 +241,6 @@ export class FormDetailContainer extends Component {
});
}
- _createReqObject(formId, formVersion, formPrivileges) {
- const formPrivilegeObj = [];
- for (let i = 0; i < formPrivileges.length; i++) {
- const privilege = formPrivileges[i];
- const privilegeCopy = {
- formId,
- privilegeName: privilege.privilegeName,
- editable: privilege.editable,
- viewable: privilege.viewable,
- formVersion,
- };
- formPrivilegeObj.push(privilegeCopy);
- }
- return formPrivilegeObj;
- }
-
onPublish() {
try {
const formJson = this.getFormResource();
diff --git a/test/form-builder/components/FormBuilder.spec.js b/test/form-builder/components/FormBuilder.spec.js
index 2d5c749e..74315f59 100644
--- a/test/form-builder/components/FormBuilder.spec.js
+++ b/test/form-builder/components/FormBuilder.spec.js
@@ -21,6 +21,7 @@ import jsonpath from 'jsonpath/jsonpath';
import * as FormBuilderBreadcrumbs from 'form-builder/components/FormBuilderBreadcrumbs.jsx';
import { MemoryRouter } from 'react-router-dom';
import { formEventUpdate, saveEventUpdate } from 'form-builder/actions/control';
+import { formBuilderConstants } from 'form-builder/constants';
chai.use(chaiEnzyme());
@@ -102,7 +103,8 @@ describe('Import form', () => {
let wrapper;
let breadcrumbsStub;
const saveFormSpy = sinon.spy();
- const saveFormResourceSpy = sinon.spy();
+ const saveFormResourceSpy = sinon.spy(() =>
+ Promise.resolve({ id: 201, uuid: 'new_uuid', version: 1 }));
const dispatchSpy = sinon.spy();
const file = [
{
@@ -428,6 +430,140 @@ describe('Import form', () => {
done();
}, 500);
});
+
+ it('should save imported form privileges against the newly created form', (done) => {
+ const formBuilderInstance = wrapper.find('FormBuilder').instance();
+ const privileges = [
+ { privilegeName: 'sample', editable: true, viewable: false },
+ ];
+ const formJson = {
+ form: { name: 'Vitals', version: '1', published: false },
+ value: { name: 'Vitals', controls: [] },
+ formName: 'Vitals',
+ translations: [],
+ nameTranslations: undefined,
+ privileges,
+ };
+ sinon.stub(httpInterceptor, 'post').callsFake((url) => {
+ if (url === formBuilderConstants.saveFormPrivilegesUrl) {
+ return Promise.resolve();
+ }
+ return Promise.resolve(Object.assign({}, data[0], { uuid: 'new_uuid' }));
+ });
+ formBuilderInstance.importValidForms([formJson]);
+ setTimeout(() => {
+ const privilegesCall = httpInterceptor.post.getCalls()
+ .find((call) => call.args[0] === formBuilderConstants.saveFormPrivilegesUrl);
+ expect(privilegesCall).not.to.eql(undefined);
+ expect(privilegesCall.args[1]).to.deep.eql([
+ {
+ formId: data[0].id,
+ formVersion: data[0].version,
+ privilegeName: 'sample',
+ editable: true,
+ viewable: false,
+ },
+ ]);
+ done();
+ }, 500);
+ });
+
+ it('should not call saveFormPrivileges when the imported form has no privileges', (done) => {
+ const formBuilderInstance = wrapper.find('FormBuilder').instance();
+ const formJson = {
+ form: { name: 'Vitals', version: '1', published: false },
+ value: { name: 'Vitals', controls: [] },
+ formName: 'Vitals',
+ translations: [],
+ nameTranslations: undefined,
+ privileges: [],
+ };
+ sinon.stub(httpInterceptor, 'post').callsFake(() =>
+ Promise.resolve(Object.assign({}, data[0], { uuid: 'new_uuid' })));
+ formBuilderInstance.importValidForms([formJson]);
+ setTimeout(() => {
+ const privilegesCall = httpInterceptor.post.getCalls()
+ .find((call) => call.args[0] === formBuilderConstants.saveFormPrivilegesUrl);
+ expect(privilegesCall).to.eql(undefined);
+ done();
+ }, 500);
+ });
+
+ it('should save privileges and wait for completion on the "form already exists" ' +
+ 'fallback path', (done) => {
+ const callOrder = [];
+ const onImportCompleteSpy = sinon.spy(() => callOrder.push('onImportComplete'));
+ const fallbackSaveFormResourceSpy = sinon.spy(() =>
+ Promise.resolve({ id: 301, uuid: 'existing_form_uuid', version: 2 }));
+ const localWrapper = mount();
+ const formBuilderInstance = localWrapper.find('FormBuilder').instance();
+ const privileges = [
+ { privilegeName: 'sample', editable: true, viewable: false },
+ ];
+ const formJson = {
+ form: { name: '1', version: '1', published: false },
+ value: { name: '1', controls: [] },
+ formName: '1',
+ translations: [],
+ nameTranslations: undefined,
+ privileges,
+ };
+ sinon.stub(httpInterceptor, 'post').callsFake((url) => {
+ if (url === formBuilderConstants.saveFormPrivilegesUrl) {
+ callOrder.push('privileges');
+ return Promise.resolve();
+ }
+ return Promise.reject(new Error('form already exists'));
+ });
+ sinon.stub(httpInterceptor, 'get').callsFake(() =>
+ Promise.resolve({ resources: [{ uuid: 'resource_uuid' }] }));
+ formBuilderInstance.importValidForms([formJson]);
+ setTimeout(() => {
+ sinon.assert.calledOnce(fallbackSaveFormResourceSpy);
+ const privilegesCall = httpInterceptor.post.getCalls()
+ .find((call) => call.args[0] === formBuilderConstants.saveFormPrivilegesUrl);
+ expect(privilegesCall).not.to.eql(undefined);
+ expect(privilegesCall.args[1]).to.deep.eql([
+ {
+ formId: 301,
+ formVersion: 2,
+ privilegeName: 'sample',
+ editable: true,
+ viewable: false,
+ },
+ ]);
+ sinon.assert.calledOnce(onImportCompleteSpy);
+ expect(callOrder).to.eql(['privileges', 'onImportComplete']);
+ done();
+ }, 500);
+ });
+
+ it('should call onImportComplete once after all forms in the batch are imported', (done) => {
+ const onImportCompleteSpy = sinon.spy();
+ const localWrapper = mount();
+ const formBuilderInstance = localWrapper.find('FormBuilder').instance();
+ const formJsons = ['Vitals1', 'Vitals2'].map((name) => ({
+ form: { name, version: '1', published: false },
+ value: { name, controls: [] },
+ formName: name,
+ translations: [],
+ nameTranslations: undefined,
+ privileges: [],
+ }));
+ sinon.stub(httpInterceptor, 'post').callsFake(() =>
+ Promise.resolve(Object.assign({}, data[0], { uuid: 'new_uuid' })));
+ formBuilderInstance.importValidForms(formJsons);
+ setTimeout(() => {
+ sinon.assert.calledOnce(onImportCompleteSpy);
+ done();
+ }, 500);
+ });
});
describe('Export Forms', () => {
@@ -516,6 +652,9 @@ describe('Export Forms', () => {
};
mockHttp.get.withArgs('/openmrs/ws/rest/v1/bahmniie/form/export?uuid=uuid1')
.returns(Promise.resolve(exportResponse));
+ mockHttp.get
+ .withArgs('/openmrs/ws/rest/v1/bahmniie/form/getFormPrivilegesFromUuid?formUuid=undefined')
+ .returns(Promise.resolve([]));
wrapper.instance().exportForms();
setTimeout(() => {
sinon.assert.calledTwice(spyZipFile);
@@ -523,6 +662,32 @@ describe('Export Forms', () => {
done();
}, 50);
});
+
+ it('should include privileges when exporting multiple forms', (done) => {
+ if (JSZip.prototype.file.restore !== undefined) {
+ JSZip.prototype.file.restore();
+ }
+ const spyZipFile = sinon.spy(JSZip.prototype, 'file');
+ wrapper.instance().state.selectedForms = ['uuid1'];
+ const privileges = [{ formId: 1, privilegeName: 'sample', editable: true, viewable: false }];
+ exportResponse = {
+ bahmniFormDataList: [{ formJson: { name: 'Form', version: '1', uuid: 'uuid1' } }],
+ errorFormList: [],
+ };
+ mockHttp.get.withArgs('/openmrs/ws/rest/v1/bahmniie/form/export?uuid=uuid1')
+ .returns(Promise.resolve(exportResponse));
+ mockHttp.get
+ .withArgs('/openmrs/ws/rest/v1/bahmniie/form/getFormPrivilegesFromUuid?formUuid=uuid1')
+ .returns(Promise.resolve(privileges));
+ wrapper.instance().exportForms();
+ setTimeout(() => {
+ sinon.assert.calledOnce(spyZipFile);
+ const zippedContent = JSON.parse(spyZipFile.getCall(0).args[1]);
+ expect(zippedContent.formJson.privileges).to.deep.eql(privileges);
+ spyZipFile.restore();
+ done();
+ }, 50);
+ });
});
describe('Import Multiple Forms', () => {