From e059c320aef31c4a29a281c4873ae9ee3ee55c4c Mon Sep 17 00:00:00 2001 From: Emmanuel Demey Date: Mon, 6 May 2019 13:55:05 +0200 Subject: [PATCH 1/8] wip: SIMS on series and indicators --- src/js/actions/operations/sims/item.js | 75 +++++++-- src/js/actions/operations/sims/item.spec.js | 148 ++++++++++++++++-- .../edition-creation/creation-container.js | 8 +- .../edition-creation/creation-container.js | 8 +- .../concepts/visualization/home-container.js | 63 ++++---- .../indicators/visualization/index.js | 16 +- src/js/components/operations/msd/index.js | 4 +- .../msd/pages/sims-creation/index.js | 2 +- .../msd/pages/sims-visualisation/index.js | 2 +- .../operations/series/visualization/index.js | 16 +- src/js/i18n/build-dictionary.js | 6 + src/js/remote-api/concepts-api.js | 2 +- src/js/utils/msd/index.js | 8 +- 13 files changed, 285 insertions(+), 73 deletions(-) diff --git a/src/js/actions/operations/sims/item.js b/src/js/actions/operations/sims/item.js index 49da9ac03..0a4af226d 100644 --- a/src/js/actions/operations/sims/item.js +++ b/src/js/actions/operations/sims/item.js @@ -1,7 +1,14 @@ import api from 'js/remote-api/operations-api'; import * as A from 'js/actions/constants'; import { LOADING } from 'js/constants'; -import { getLabelsFromOperation } from 'js/utils/msd'; +import { getLabelsFromParent } from 'js/utils/msd'; + +/** + * @typedef {Object} Sims + * @property {string=} id + * @property {string=} labelLg1 + * @property {SimsDocuments[]} documents + */ /** * @typedef {Object} SimsDocuments @@ -15,16 +22,45 @@ import { getLabelsFromOperation } from 'js/utils/msd'; * @property {string} descriptionLg2 */ -export const saveSims = (sims, callback) => (dispatch, getState) => { +/** + * Method used to merge a SIMS with the label of its corresponding + * parent. + * + * @param {*} sims + * @param {*} promise + */ +function getFetchLabelsPromise(sims, promise) { + function mergeLabels(parent) { + return { + ...sims, + ...getLabelsFromParent(parent), + }; + } + if (sims.idOperation) { + return api.getOperation(sims.idOperation).then(mergeLabels); + } + if (sims.idSeries) { + return api.getSerie(sims.idSeries).then(mergeLabels); + } + if (sims.idIndicator) { + return api.getIndicator(sims.idIndicator).then(mergeLabels); + } + return promise; +} +/** + * This method is called when we need to save a SIMS. + * If the sims passed as a parameter already have an id, + * we will send a PUR request. If this property is not + * present, a POST request will be send. + * + * @param {Sims} sims + * @param {(string) => void} callback + */ +export const saveSims = (sims, callback) => dispatch => { let promise = Promise.resolve(sims); - if (!sims.labelLg1 && sims.idOperation) { - promise = api.getOperation(sims.idOperation).then(result => { - return { - ...sims, - ...getLabelsFromOperation(result), - }; - }); + if (!sims.labelLg1) { + promise = getFetchLabelsPromise(sims, promise); } const method = sims.id ? 'putSims' : 'postSims'; @@ -51,6 +87,15 @@ export const saveSims = (sims, callback) => (dispatch, getState) => { }); }; +function getParentsWithoutSims(idOperation) { + if (idOperation) { + return api + .getOperation(idOperation) + .then(operation => api.getOperationsWithoutReport(operation.series.id)); + } + return Promise.resolve([]); +} + export default id => (dispatch, getState) => { if (!id || getState().operationsSimsCurrentStatus === LOADING) { return; @@ -61,17 +106,16 @@ export default id => (dispatch, getState) => { id, }, }); + return api.getSims(id).then( results => { - api - .getOperation(results.idOperation) - .then(operation => api.getOperationsWithoutReport(operation.series.id)) - .then((operationsWithoutSims = []) => { + return getParentsWithoutSims(results.idOperation).then( + (parentsWithoutSims = []) => { dispatch({ type: A.LOAD_OPERATIONS_SIMS_SUCCESS, payload: { ...results, - operationsWithoutSims, + parentsWithoutSims, rubrics: results.rubrics.reduce((acc, rubric) => { // TO BE DELETED const documents = [ @@ -115,7 +159,8 @@ export default id => (dispatch, getState) => { }, {}), }, }); - }); + } + ); }, err => { diff --git a/src/js/actions/operations/sims/item.spec.js b/src/js/actions/operations/sims/item.spec.js index 1f607e28a..c57de4f7e 100644 --- a/src/js/actions/operations/sims/item.spec.js +++ b/src/js/actions/operations/sims/item.spec.js @@ -17,19 +17,64 @@ describe('SIMS actions', () => { expect(dispatch).not.toHaveBeenCalledWith(); }); describe('get a sims', () => { - it('should call dispatch LOAD_OPERATIONS_SIMS_SUCCESS action with the right sims if the status is not LOADING', async () => { - api.getSims = function(id) { - return Promise.resolve({ label: 'bbb', id, rubrics: [] }); - }; - api.getOperation = function(id) { - return Promise.resolve({ series: { id: 1 } }); + beforeEach(() => { + api.getOperation = jest.fn(id => Promise.resolve({ series: { id: 2 } })); + api.getOperationsWithoutReport = jest.fn(() => ['value1']); + }); + + it('should call getOperation/getOperationsWithoutReport method and dispatch LOAD_OPERATIONS_SIMS_SUCCESS action with the right sims if the status is not LOADING', async () => { + api.getSims = jest.fn(id => { + return Promise.resolve({ + label: 'bbb', + idOperation: 3, + id, + rubrics: [], + }); + }); + + const getState = () => { + return { operationsSimsCurrentStatus: NOT_LOADED }; }; + const id = 1; + await get(id)(dispatch, getState); + + expect(api.getSims).toHaveBeenCalledWith(1); + expect(api.getOperation).toHaveBeenCalledWith(3); + expect(api.getOperationsWithoutReport).toHaveBeenCalledWith(2); + expect(dispatch).toHaveBeenCalledWith({ + type: A.LOAD_OPERATIONS_SIMS, + payload: { id }, + }); + expect(dispatch).toHaveBeenLastCalledWith({ + type: A.LOAD_OPERATIONS_SIMS_SUCCESS, + payload: { + id, + label: 'bbb', + rubrics: {}, + idOperation: 3, + parentsWithoutSims: ['value1'], + }, + }); + }); + it('should not call getOperation/getOperationsWithoutReport method and dispatch LOAD_OPERATIONS_SIMS_SUCCESS action with the right sims if the status is not LOADING', async () => { + api.getSims = jest.fn(id => { + return Promise.resolve({ + label: 'bbb', + idSims: 3, + id, + rubrics: [], + }); + }); const getState = () => { return { operationsSimsCurrentStatus: NOT_LOADED }; }; const id = 1; await get(id)(dispatch, getState); + + expect(api.getSims).toHaveBeenCalledWith(1); + expect(api.getOperation).not.toHaveBeenCalledWith(3); + expect(api.getOperationsWithoutReport).not.toHaveBeenCalledWith(2); expect(dispatch).toHaveBeenCalledWith({ type: A.LOAD_OPERATIONS_SIMS, payload: { id }, @@ -39,8 +84,9 @@ describe('SIMS actions', () => { payload: { id, label: 'bbb', - operationsWithoutSims: [], rubrics: {}, + idSims: 3, + parentsWithoutSims: [], }, }); }); @@ -64,12 +110,76 @@ describe('SIMS actions', () => { }); }); describe('save a sims', () => { - it('should call dispatch SAVE_OPERATIONS_SIMS_SUCCESS action with the udpated sims', async () => { - api.putSims = function(id) { + const sims = { id: 1, label: 'aaa' }; + + beforeEach(() => { + api.putSims = jest.fn(id => { return Promise.resolve(''); - }; - const sims = { id: 1, label: 'aaa' }; + }); + api.postSims = jest.fn(id => { + return Promise.resolve(''); + }); + api.getOperation = jest.fn(() => Promise.resolve('result get operation')); + api.getSerie = jest.fn(() => Promise.resolve('result get serie')); + api.getIndicator = jest.fn(() => Promise.resolve('result get indicator')); + }); + + describe.each` + method | id + ${'getOperation'} | ${'idOperation'} + ${'getSerie'} | ${'idSeries'} + ${'getIndicator'} | ${'idIndicator'} + `('get labels from parent', ({ method, id, expected }) => { + const apis = ['getOperation', 'getSerie', 'getIndicator']; + + it(`should call ${method} if the labelLg1 is not defined and if the ${id} is defined`, async () => { + await saveSims( + { + ...sims, + [id]: 1, + }, + () => {} + )(dispatch); + + apis + .filter(api => api !== method) + .forEach(method => { + expect(api[method]).not.toHaveBeenCalled(); + }); + expect(api[method]).toHaveBeenCalledWith(1); + }); + + it(`should not call ${method} if the labelLg1 is defined`, async () => { + await saveSims( + { + ...sims, + labelLg1: 'labelLg1', + }, + () => {} + )(dispatch); + apis.forEach(method => { + expect(api[method]).not.toHaveBeenCalled(); + }); + }); + + it(`should not call getOperation if the labelLg1 is not defined and if the idOperation is not defined`, async () => { + await saveSims( + { + ...sims, + }, + () => {} + )(dispatch); + expect(api.getOperation).not.toHaveBeenCalled(); + expect(api.getSerie).not.toHaveBeenCalled(); + expect(api.getIndicator).not.toHaveBeenCalled(); + }); + }); + + it('should call putSims method and dispatch SAVE_OPERATIONS_SIMS_SUCCESS action with the udpated sims', async () => { await saveSims(sims, () => {})(dispatch); + + expect(api.putSims).toHaveBeenCalled(); + expect(api.postSims).not.toHaveBeenCalled(); expect(dispatch).toHaveBeenCalledWith({ type: A.SAVE_OPERATIONS_SIMS, payload: sims, @@ -79,5 +189,21 @@ describe('SIMS actions', () => { payload: sims, }); }); + + it('should call postSims method and dispatch SAVE_OPERATIONS_SIMS_SUCCESS action with the udpated sims', async () => { + const creationSims = { label: 'label' }; + await saveSims(creationSims, () => {})(dispatch); + + expect(api.postSims).toHaveBeenCalled(); + expect(api.putSims).not.toHaveBeenCalled(); + expect(dispatch).toHaveBeenCalledWith({ + type: A.SAVE_OPERATIONS_SIMS, + payload: creationSims, + }); + expect(dispatch).toHaveBeenLastCalledWith({ + type: A.SAVE_OPERATIONS_SIMS_SUCCESS, + payload: creationSims, + }); + }); }); }); diff --git a/src/js/components/collections/edition-creation/creation-container.js b/src/js/components/collections/edition-creation/creation-container.js index edf772cd4..bbc19890a 100644 --- a/src/js/components/collections/edition-creation/creation-container.js +++ b/src/js/components/collections/edition-creation/creation-container.js @@ -83,7 +83,6 @@ const mapStateToProps = (state, ownProps) => { collectionList: select.getCollectionList(state), conceptList: select.getConceptList(state), stampList: select.getStampList(state), - //TODO build appropriate selector creationStatus: select.getStatus(state, CREATE_COLLECTION), langs: select.getLangs(state), }; @@ -96,8 +95,9 @@ const mapDispatchToProps = { createCollection, }; -CreationContainer = connect(mapStateToProps, mapDispatchToProps)( - CreationContainer -); +CreationContainer = connect( + mapStateToProps, + mapDispatchToProps +)(CreationContainer); export default CreationContainer; diff --git a/src/js/components/concepts/edition-creation/creation-container.js b/src/js/components/concepts/edition-creation/creation-container.js index 7193bc422..bb3c07ba6 100644 --- a/src/js/components/concepts/edition-creation/creation-container.js +++ b/src/js/components/concepts/edition-creation/creation-container.js @@ -84,7 +84,6 @@ const mapStateToProps = (state, ownProps) => { stampList: select.getStampList(state), disseminationStatusList: select.getDisseminationStatusList(state), maxLengthScopeNote: Number(state.app.properties.maxLengthScopeNote), - //TODO build appropriate selector id: select.getNewlyCreatedId(state), creationStatus: select.getStatus(state, CREATE_CONCEPT), langs: select.getLangs(state), @@ -98,8 +97,9 @@ const mapDispatchToProps = { createConcept, }; -CreationContainer = connect(mapStateToProps, mapDispatchToProps)( - CreationContainer -); +CreationContainer = connect( + mapStateToProps, + mapDispatchToProps +)(CreationContainer); export default CreationContainer; diff --git a/src/js/components/concepts/visualization/home-container.js b/src/js/components/concepts/visualization/home-container.js index 91db3d4c2..406193b9b 100644 --- a/src/js/components/concepts/visualization/home-container.js +++ b/src/js/components/concepts/visualization/home-container.js @@ -30,7 +30,7 @@ class ConceptVisualizationContainer extends Component { this.state = { validationRequested: false, deletionRequested: false, - showModalError: false + showModalError: false, }; this.handleConceptValidation = id => { this.props.validateConcept(id); @@ -45,7 +45,6 @@ class ConceptVisualizationContainer extends Component { }); }; this.closeModal = () => this.setState({ showModalError: false }); - } componentWillMount() { const { id, allNotes } = this.props; @@ -72,7 +71,7 @@ class ConceptVisualizationContainer extends Component { //component again this.setState({ deletionRequested: false, - showModalError: true + showModalError: true, }); //we need to load the concept again this.props.loadConcept(id); @@ -88,7 +87,7 @@ class ConceptVisualizationContainer extends Component { const { deleteStatus } = this.props; const modalButtons = [ { - label: "OK", + label: 'OK', action: this.closeModal, style: 'primary', }, @@ -106,11 +105,18 @@ class ConceptVisualizationContainer extends Component { if (deletionRequested && deleteStatus === OK) { console.log('delete ok: redirection'); //if deletion is OK: we redirect to the concepts list. - // TODO: pop-up the error message. return ; } - const { id, permission, concept, allNotes, secondLang, langs, error } = this.props; + const { + id, + permission, + concept, + allNotes, + secondLang, + langs, + error, + } = this.props; if (concept && allNotes) { const { general, links } = concept; let { notes } = concept; @@ -140,28 +146,28 @@ class ConceptVisualizationContainer extends Component { return ( <> - - - + + + ); } return ; @@ -187,7 +193,6 @@ const mapStateToProps = (state, ownProps) => { deleteFailureStatus: select.getStatus(state, DELETE_CONCEPT_FAILURE), langs: select.getLangs(state), error: select.getError(state, DELETE_CONCEPT), - }; }; diff --git a/src/js/components/operations/indicators/visualization/index.js b/src/js/components/operations/indicators/visualization/index.js index 5338a1563..36b4caa49 100644 --- a/src/js/components/operations/indicators/visualization/index.js +++ b/src/js/components/operations/indicators/visualization/index.js @@ -56,7 +56,21 @@ class IndicatorVisualizationContainer extends Component { context="operations" /> -
+
+ {attr.idSims && ( +
); diff --git a/src/js/components/shared/search-rmes/search-rmes.js b/src/js/components/shared/search-rmes/search-rmes.js index 899736233..16bc5146f 100644 --- a/src/js/components/shared/search-rmes/search-rmes.js +++ b/src/js/components/shared/search-rmes/search-rmes.js @@ -67,7 +67,7 @@ class SearchRmes extends Component { )}
-

{nbResults(hits)}

+

{nbResults(hits)}

From d1a8c52cad813ad3b4ef3c6d5d09c7732c195196 Mon Sep 17 00:00:00 2001 From: Emmanuel Demey Date: Tue, 7 May 2019 15:22:22 +0200 Subject: [PATCH 3/8] fix: finish start the SIMS feature for indicator and series --- cypress/integration/operations_list.spec.js | 6 +- src/js/actions/operations/sims/item.js | 35 +++------ .../indicators/visualization/index.js | 2 +- src/js/components/operations/msd/index.js | 71 ++++++++++++++----- .../msd/pages/sims-creation/index.js | 39 +++++----- .../msd/pages/sims-creation/sims-field.js | 2 +- .../msd/pages/sims-visualisation/index.js | 17 ++--- src/js/components/operations/msd/utils.js | 69 ++++++++++++++++++ .../components/operations/msd/utils.spec.js | 60 +++++++++++++++- .../operations/visualization/index.js | 2 +- .../operations/series/visualization/index.js | 2 +- src/js/components/router/operations.js | 22 +++--- src/js/i18n/build-dictionary.js | 22 ++++-- src/js/i18n/dictionary/operations/index.js | 14 +++- src/js/types/index.js | 42 +++++++++++ src/js/utils/msd/index.js | 26 ++++--- src/js/utils/msd/index.spec.js | 44 +++++++++++- 17 files changed, 364 insertions(+), 111 deletions(-) create mode 100644 src/js/types/index.js diff --git a/cypress/integration/operations_list.spec.js b/cypress/integration/operations_list.spec.js index e04bafe08..47f20f451 100644 --- a/cypress/integration/operations_list.spec.js +++ b/cypress/integration/operations_list.spec.js @@ -19,13 +19,13 @@ expect(lis).to.have.length(1); }); - cy.get('h2.page-title-operations').should('be.visible'); + cy.get('h1.page-title-operations').should('be.visible'); cy.get('.list-group').should('be.visible'); cy.get('.pagination').should('be.visible'); cy.get('input').type('FAKE DATA'); - cy.get('h4').should(h4 => { - expect(h4.first()).to.contain('0'); + cy.get('p').should(p => { + expect(p.first()).to.contain('0'); }); cy.get('.pagination li').should(lis => { expect(lis).to.have.length(0); diff --git a/src/js/actions/operations/sims/item.js b/src/js/actions/operations/sims/item.js index 0a4af226d..86ff18b2b 100644 --- a/src/js/actions/operations/sims/item.js +++ b/src/js/actions/operations/sims/item.js @@ -3,25 +3,6 @@ import * as A from 'js/actions/constants'; import { LOADING } from 'js/constants'; import { getLabelsFromParent } from 'js/utils/msd'; -/** - * @typedef {Object} Sims - * @property {string=} id - * @property {string=} labelLg1 - * @property {SimsDocuments[]} documents - */ - -/** - * @typedef {Object} SimsDocuments - * @property {string} uri - * @property {string} url - * @property {string=} updatedDate - * @property {string} labelLg1 - * @property {string} labelLg2 - * @property {string=} lang - * @property {string} descriptionLg1 - * @property {string} descriptionLg2 - */ - /** * Method used to merge a SIMS with the label of its corresponding * parent. @@ -30,20 +11,26 @@ import { getLabelsFromParent } from 'js/utils/msd'; * @param {*} promise */ function getFetchLabelsPromise(sims, promise) { - function mergeLabels(parent) { + function mergeLabels(parent, parentType) { return { ...sims, - ...getLabelsFromParent(parent), + ...getLabelsFromParent(parent, parentType), }; } if (sims.idOperation) { - return api.getOperation(sims.idOperation).then(mergeLabels); + return api + .getOperation(sims.idOperation) + .then(parent => mergeLabels(parent, 'operation')); } if (sims.idSeries) { - return api.getSerie(sims.idSeries).then(mergeLabels); + return api + .getSerie(sims.idSeries) + .then(parent => mergeLabels(parent, 'series')); } if (sims.idIndicator) { - return api.getIndicator(sims.idIndicator).then(mergeLabels); + return api + .getIndicator(sims.idIndicator) + .then(parent => mergeLabels(parent, 'indicator')); } return promise; } diff --git a/src/js/components/operations/indicators/visualization/index.js b/src/js/components/operations/indicators/visualization/index.js index 36b4caa49..ee04891e3 100644 --- a/src/js/components/operations/indicators/visualization/index.js +++ b/src/js/components/operations/indicators/visualization/index.js @@ -66,7 +66,7 @@ class IndicatorVisualizationContainer extends Component { )} {!attr.idSims && (
); } From 817f67adedbdf2084e3674ccc1051eb0f739e664 Mon Sep 17 00:00:00 2001 From: Emmanuel Demey Date: Wed, 8 May 2019 20:41:30 +0200 Subject: [PATCH 7/8] fix: add title for collapsible panel --- src/js/components/operations/msd/outline/index.js | 4 +++- src/js/components/operations/msd/outline/outline-block.js | 3 ++- src/js/i18n/dictionary/app.js | 8 ++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/js/components/operations/msd/outline/index.js b/src/js/components/operations/msd/outline/index.js index 7a25f5b4c..025522366 100644 --- a/src/js/components/operations/msd/outline/index.js +++ b/src/js/components/operations/msd/outline/index.js @@ -3,6 +3,8 @@ import { toggleOpen, isOpen } from 'js/components/operations/msd/utils'; import { HashLink as Link } from 'react-router-hash-link'; import OutlineBlock from 'js/components/operations/msd/outline/outline-block'; import PropTypes from 'prop-types'; +import D from 'js/i18n'; + import './style.scss'; class Outline extends Component { @@ -53,7 +55,7 @@ class Outline extends Component { {Object.keys(metadataStructure.children).length > 0 && (