Skip to content

Commit

Permalink
OCLOMRS-472: Implement the Retire/Un-Retire button
Browse files Browse the repository at this point in the history
  • Loading branch information
Bruce Allan Makaaru authored and Bruce Allan Makaaru committed Mar 28, 2019
1 parent d599d6d commit d7211b8
Show file tree
Hide file tree
Showing 10 changed files with 412 additions and 18 deletions.
43 changes: 32 additions & 11 deletions src/components/dictionaryConcepts/components/ActionButtons.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,15 @@ const ActionButtons = ({
showDeleteModal,
version_url,
retired,
retireConcept,
}) => {
const dictionaryPathName = localStorage.getItem('dictionaryPathName');
let showExtra;
if (actionButtons === true) {
showExtra = true;
}
return (
!retired && <React.Fragment>
{showExtra && (
<React.Fragment>
<Link
className="edit-button-link btn btn-sm mb-1 actionButtons"
to={`/edit/${concept_class}/${id}${dictionaryPathName}`}
>
Edit
</Link>
</React.Fragment>
)}
<React.Fragment>
{source !== CUSTOM_SOURCE && (
<button
type="button"
Expand All @@ -41,6 +32,34 @@ const ActionButtons = ({
Remove
</button>
)}
{!retired && <React.Fragment>
{showExtra && (
<React.Fragment>
<button
className="btn btn-sm mb-1 actionButtons"
type="button"
id="retire"
onClick={() => retireConcept(id, true)}
>
Retire
</button>
<Link
className="edit-button-link btn btn-sm mb-1 actionButtons"
to={`/edit/${concept_class}/${id}${dictionaryPathName}`}
>
Edit
</Link>
</React.Fragment>
)}
</React.Fragment>}
{retired && showExtra && <button
className="btn btn-sm mb-1 actionButtons"
type="button"
id="unRetire"
onClick={() => retireConcept(id, false)}
>
Unretire
</button>}
</React.Fragment>
);
};
Expand All @@ -58,13 +77,15 @@ ActionButtons.propTypes = {
source: PropTypes.string,
mappingLimit: PropTypes.number,
retired: PropTypes.bool,
retireConcept: PropTypes.func,
};

ActionButtons.defaultProps = {
source: '',
mappings: [],
mappingLimit: null,
retired: false,
retireConcept: () => {},
};

export default ActionButtons;
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ const ConceptTable = ({
openDeleteModal,
showDeleteMappingModal,
handleDeleteMapping,
retireConcept,
isOwner,
}) => (
<div className="row col-12 custom-concept-list">
<RemoveConcept
Expand Down Expand Up @@ -74,10 +76,11 @@ const ConceptTable = ({
handleDeleteMapping,
mappingLimit: conceptLimit,
showDeleteMappingModal,
retireConcept,
};
const renderButtons = username === concept.owner || (
concept.owner === org.name && org.userIsMember
);
) || isOwner;
return <ActionButtons actionButtons={renderButtons} {...concept} {...props} />;
},
},
Expand All @@ -101,11 +104,15 @@ ConceptTable.propTypes = {
closeDeleteModal: PropTypes.func.isRequired,
handleDeleteMapping: PropTypes.func.isRequired,
showDeleteMappingModal: PropTypes.func.isRequired,
retireConcept: PropTypes.func,
isOwner: PropTypes.bool,
};
ConceptTable.defaultProps = {
openDeleteModal: false,
url: '',
original: {},
retireConcept: () => {},
isOwner: false,
};

export default ConceptTable;
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,24 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import autoBind from 'react-autobind';
import uuid from 'uuid/v4';
import Header from '../components/Header';
import ConceptDropdown from '../components/ConceptDropdown';
import SideNav from '../components/Sidenav';
import ConceptTable from '../components/ConceptTable';
import SearchBar from '../../bulkConcepts/component/SearchBar';
import { conceptsProps } from '../proptypes';
import { getUsername } from '../components/helperFunction';
import { getUsername, CIEL_SOURCE_URL, INTERNAL_MAPPING_DEFAULT_SOURCE } from '../components/helperFunction';
import {
fetchDictionaryConcepts,
fetchConceptsByName,
filterBySource,
filterByClass,
paginateConcepts,
createNewConcept,
fetchExistingConcept,
} from '../../../redux/actions/concepts/dictionaryConcepts';
import { removeDictionaryConcept, removeConceptMapping } from '../../../redux/actions/dictionaries/dictionaryActionCreators';
import { removeDictionaryConcept, removeConceptMapping, retireConcept } from '../../../redux/actions/dictionaries/dictionaryActionCreators';
import { fetchMemberStatus } from '../../../redux/actions/user/index';

export class DictionaryConcepts extends Component {
Expand Down Expand Up @@ -46,6 +49,11 @@ export class DictionaryConcepts extends Component {
removeDictionaryConcept: PropTypes.func.isRequired,
removeConceptMappingAction: PropTypes.func.isRequired,
searchByName: PropTypes.func.isRequired,
retireCurrentConcept: PropTypes.func.isRequired,
recreateConcept: PropTypes.func.isRequired,
removeConcept: PropTypes.func.isRequired,
getOriginalConcept: PropTypes.func.isRequired,
originalConcept: PropTypes.shape({}).isRequired,
};

constructor(props) {
Expand All @@ -58,6 +66,7 @@ export class DictionaryConcepts extends Component {
references: [],
},
openDeleteModal: false,
isOwner: false,
};
autoBind(this);
}
Expand Down Expand Up @@ -203,6 +212,68 @@ export class DictionaryConcepts extends Component {
searchByName(query);
};

recreateMappings = mappings => mappings.map((mapping) => {
const isInternal = (
mapping.to_source_url.trim().toLowerCase() === CIEL_SOURCE_URL.trim().toLowerCase()
);
const freshMapping = {
isNew: true,
url: String(uuid()),
map_type: mapping.map_type,
to_concept_code: mapping.to_concept_code,
to_concept_name: mapping.to_concept_name,
to_concept_url: mapping.to_concept_url,
source: isInternal ? INTERNAL_MAPPING_DEFAULT_SOURCE : mapping.source,
to_source_url: isInternal
? `${mapping.to_source_url}concepts/${mapping.to_concept_code}/`
: mapping.to_source_url,
};
return freshMapping;
});

retireRemoveRecreate = async (createUrl, concept, versionUrl, type, owner, retired) => {
const {
retireCurrentConcept, recreateConcept, removeConcept,
} = this.props;
const id = String(uuid());
const retireProcesses = await Promise.all([
retireCurrentConcept(concept.url, retired),
removeConcept({ references: [versionUrl] }, type, owner, concept.source),
recreateConcept(
{
...concept,
id,
retired,
answers: concept.answers || [],
mappings: concept && concept.mappings && this.recreateMappings(concept.mappings),
},
createUrl,
),
]);
return retireProcesses;
};

handleRetireConcept = (id, retired) => {
const { concepts, getOriginalConcept } = this.props;
const selectedConcept = concepts.find(concept => concept.id === id);
getOriginalConcept(`${selectedConcept.url}?includeMappings=true`)
.then(() => {
const { originalConcept } = this.props;
const [, type, owner] = selectedConcept.owner_url.split('/');
if (type && owner) {
const createUrl = selectedConcept.url.replace(`${id}/`, '');
this.retireRemoveRecreate(
createUrl,
originalConcept,
selectedConcept.version_url,
type,
owner,
retired,
);
}
});
};

render() {
const {
match: {
Expand Down Expand Up @@ -278,6 +349,8 @@ export class DictionaryConcepts extends Component {
handleDeleteMapping={this.handleDeleteMapping}
openDeleteModal={openDeleteModal}
closeDeleteModal={this.closeDeleteModal}
retireConcept={this.handleRetireConcept}
isOwner={this.state.isOwner}
/>
</div>
</section>
Expand All @@ -302,6 +375,7 @@ export const mapStateToProps = state => ({
filteredList: state.concepts.filteredList,
dictionaries: state.dictionaries.dictionary,
userIsMember: state.user.userIsMember,
originalConcept: state.concepts.existingConcept,
});

export default connect(
Expand All @@ -315,5 +389,9 @@ export default connect(
fetchMemberStatus,
removeDictionaryConcept,
removeConceptMappingAction: removeConceptMapping,
retireCurrentConcept: retireConcept,
recreateConcept: createNewConcept,
removeConcept: removeDictionaryConcept,
getOriginalConcept: fetchExistingConcept,
},
)(DictionaryConcepts);
3 changes: 1 addition & 2 deletions src/redux/actions/concepts/dictionaryConcepts.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ export const CreateMapping = (data, from_concept_url, source) => {
const mappingData = mapping.source !== INTERNAL_MAPPING_DEFAULT_SOURCE ? ({
map_type: mapping.map_type,
from_concept_url,
to_source_url: mapping.source,
to_source_url: mapping.to_source_url || mapping.source,
to_concept_code: mapping.to_concept_code,
to_concept_name: mapping.to_concept_name,
}) : ({
Expand All @@ -293,7 +293,6 @@ export const CreateMapping = (data, from_concept_url, source) => {
to_concept_url: mapping.to_source_url,
to_concept_name: mapping.to_concept_name,
});

return (
mapping.isNew && instance.post(url, mappingData)
);
Expand Down
12 changes: 12 additions & 0 deletions src/redux/actions/dictionaries/dictionaryActionCreators.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
import { filterPayload } from '../../reducers/util';
import { addDictionaryReference } from '../bulkConcepts';
import api from '../../api';
import axiosInstance from '../../../config/axiosConfig';

export const showNetworkError = () => (
notify.show('Network Error. Please try again later!', 'error', 6000));
Expand Down Expand Up @@ -251,3 +252,14 @@ export const createVersion = (url, data) => (dispatch) => {
});
};

export const retireConcept = (conceptUrl, retired) => async (dispatch) => {
try {
const response = await axiosInstance.put(conceptUrl, { retired });
notify.show('Concept successfully un-retired', 'success', 3000);
return response.data;
} catch (error) {
error && error.response && error.response.data && dispatch(isErrored(error.response.data));
notify.show('Failed to un-retire the concept', 'error', 3000);
return null;
}
}
1 change: 1 addition & 0 deletions src/redux/reducers/ConceptReducers.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ export default (state = initialState, action) => {
return {
...state,
newConcept: action.payload,
dictionaryConcepts: [action.payload, ...state.dictionaryConcepts],
};
case SEARCH_CONCEPTS:
return {
Expand Down
46 changes: 45 additions & 1 deletion src/tests/Dashboard/action/dictionaryAction.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ import {
editDictionary,
createVersion,
editMapping,
retireConcept,
} from '../../../redux/actions/dictionaries/dictionaryActionCreators';
import dictionaries, { sampleDictionaries } from '../../__mocks__/dictionaries';
import versions, { HeadVersion } from '../../__mocks__/versions';
import concepts from '../../__mocks__/concepts';
import concepts, { sampleConcept, sampleRetiredConcept } from '../../__mocks__/concepts';

jest.mock('react-notify-toast');

Expand Down Expand Up @@ -381,6 +382,49 @@ describe('Test suite for dictionary actions', () => {
expect(store.getActions()).toEqual(expectedActions);
});
});

it('should retire a concept when the retireConcept action is triggered with the true argument', () => {
moxios.wait(() => {
const request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
response: sampleRetiredConcept,
});
});
const store = mockStore({ payload: {} });
return store.dispatch(retireConcept(sampleConcept.url, { retired: true })).then((result) => {
expect(result.retired).toEqual(true);
});
});

it('should unretire a concept when the retireConcept action is triggered with the false argument', () => {
moxios.wait(() => {
const request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
response: sampleConcept,
});
});
const store = mockStore({ payload: {} });
return store.dispatch(retireConcept(sampleConcept.url, { retired: false })).then((result) => {
expect(result.retired).toEqual(false);
});
});

it('should handle retire/unretire errors', () => {
const message = 'Sample Error message';
moxios.wait(() => {
const request = moxios.requests.mostRecent();
request.reject({
status: 400,
response: { data: message },
});
});
const store = mockStore({ payload: {} });
return store.dispatch(retireConcept(sampleConcept.url, { retired: false })).then(() => {
expect(store.getActions()).toEqual([{ type: FETCHING_DICTIONARIES, payload: message }]);
});
});
});

describe('Test for successful dictionaries fetch, failure and refresh', () => {
Expand Down

0 comments on commit d7211b8

Please sign in to comment.