Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(interview): Copy dataset [3] #1128

Draft
wants to merge 5 commits into
base: interview-apply-to-all
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions rdmo/core/assets/js/components/Modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@ const Modal = ({ title, show, modalProps, submitLabel, submitProps, onClose, onS
<BootstrapModal.Header closeButton>
<h2 className="modal-title">{title}</h2>
</BootstrapModal.Header>
<BootstrapModal.Body>
{ children }
</BootstrapModal.Body>
{
children && (
<BootstrapModal.Body>
{ children }
</BootstrapModal.Body>
)
}
<BootstrapModal.Footer>
<button type="button" className="btn btn-default" onClick={onClose}>
{gettext('Close')}
Expand Down
4 changes: 4 additions & 0 deletions rdmo/projects/assets/js/interview/actions/actionTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,7 @@ export const CREATE_SET = 'CREATE_SET'
export const DELETE_SET_INIT = 'DELETE_SET_INIT'
export const DELETE_SET_SUCCESS = 'DELETE_SET_SUCCESS'
export const DELETE_SET_ERROR = 'DELETE_SET_ERROR'

export const COPY_SET_INIT = 'COPY_SET_INIT'
export const COPY_SET_SUCCESS = 'COPY_SET_SUCCESS'
export const COPY_SET_ERROR = 'COPY_SET_ERROR'
91 changes: 86 additions & 5 deletions rdmo/projects/assets/js/interview/actions/interviewActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ import {
CREATE_SET,
DELETE_SET_INIT,
DELETE_SET_SUCCESS,
DELETE_SET_ERROR
DELETE_SET_ERROR,
COPY_SET_INIT,
COPY_SET_SUCCESS,
COPY_SET_ERROR
} from './actionTypes'

import { updateConfig } from 'rdmo/core/assets/js/actions/configActions'
Expand Down Expand Up @@ -459,8 +462,8 @@ export function createSet(attrs) {
// create a value for the text if the page has an attribute
const value = isNil(attrs.attribute) ? null : ValueFactory.create(attrs)

// create an action to be called immediately or after saving the value
const createSetSuccess = (value) => {
// create a callback function to be called immediately or after saving the value
const createSetCallback = (value) => {
dispatch(activateSet(set))

const state = getState().interview
Expand All @@ -476,12 +479,12 @@ export function createSet(attrs) {
}

if (isNil(value)) {
return createSetSuccess()
return createSetCallback()
} else {
return dispatch(storeValue(value)).then(() => {
const storedValue = getState().interview.values.find((v) => compareValues(v, value))
if (!isNil(storedValue)) {
createSetSuccess(storedValue)
createSetCallback(storedValue)
}
})
}
Expand Down Expand Up @@ -559,3 +562,81 @@ export function deleteSetSuccess(set) {
export function deleteSetError(errors) {
return {type: DELETE_SET_ERROR, errors}
}

export function copySet(currentSet, currentSetValue, attrs) {
const pendingId = `copySet/${currentSet.set_prefix}/${currentSet.set_index}`

return (dispatch, getState) => {
dispatch(addToPending(pendingId))
dispatch(copySetInit())

// create a new set
const set = SetFactory.create(attrs)

// create a value for the text if the page has an attribute
const value = isNil(attrs.attribute) ? null : ValueFactory.create(attrs)

// create a callback function to be called immediately or after saving the value
const copySetCallback = (setValues) => {
dispatch(activateSet(set))

const state = getState().interview

const page = state.page
const values = [...state.values, ...setValues]
const sets = gatherSets(values)

initSets(sets, page)
initValues(sets, values, page)

return dispatch({type: COPY_SET_SUCCESS, values, sets})
}

let promise
if (isNil(value)) {
// gather all values for the currentSet and it's descendants
const currentValues = getDescendants(getState().interview.values, currentSet)

// store each value in currentSet with the new set_index
promise = Promise.all(
currentValues.filter((currentValue) => !isEmptyValue(currentValue)).map((currentValue) => {
const value = {...currentValue}
const setPrefixLength = set.set_prefix.split('|').length

if (value.set_prefix == set.set_prefix) {
value.set_index = set.set_index
} else {
value.set_prefix = value.set_prefix.split('|').reduce((acc, cur, idx) => {
return [...acc, (idx == setPrefixLength - 1) ? set.set_index : cur]
}, []).join('|')
}

delete value.id
return ValueApi.storeValue(projectId, value)
})
)
} else {
promise = ValueApi.copySet(projectId, currentSetValue, value)
}

return promise.then((values) => {
dispatch(removeFromPending(pendingId))
dispatch(copySetCallback(values))
}).catch((errors) => {
dispatch(removeFromPending(pendingId))
dispatch(copySetError(errors))
})
}
}

export function copySetInit() {
return {type: COPY_SET_INIT}
}

export function copySetSuccess(values, sets) {
return {type: COPY_SET_SUCCESS, values, sets}
}

export function copySetError(errors) {
return {type: COPY_SET_ERROR, errors}
}
8 changes: 6 additions & 2 deletions rdmo/projects/assets/js/interview/api/ValueApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,12 @@ class ValueApi extends BaseApi {
}
}

static deleteSet(projectId, value) {
return this.delete(`/api/v1/projects/projects/${projectId}/values/${value.id}/set/`)
static copySet(projectId, currentSetValue, setValue) {
return this.post(`/api/v1/projects/projects/${projectId}/values/${currentSetValue.id}/set/`, setValue)
}

static deleteSet(projectId, setValue) {
return this.delete(`/api/v1/projects/projects/${projectId}/values/${setValue.id}/set/`)
}

}
Expand Down
18 changes: 13 additions & 5 deletions rdmo/projects/assets/js/interview/components/main/page/Page.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,17 @@ import PageHead from './PageHead'

const Page = ({ config, templates, overview, page, sets, values, fetchPage,
createValue, updateValue, deleteValue, copyValue,
activateSet, createSet, updateSet, deleteSet }) => {
activateSet, createSet, updateSet, deleteSet, copySet }) => {

const currentSetPrefix = ''
const currentSetIndex = page.is_collection ? get(config, 'page.currentSetIndex', 0) : 0
const currentSet = sets.find((set) => (set.set_prefix == currentSetPrefix && set.set_index == currentSetIndex)) ||
sets.find((set) => (set.set_prefix == currentSetPrefix && set.set_index == 0)) // sanity check
let currentSetIndex = page.is_collection ? get(config, 'page.currentSetIndex', 0) : 0
let currentSet = sets.find((set) => (set.set_prefix == currentSetPrefix && set.set_index == currentSetIndex))

// sanity check
if (isNil(currentSet)) {
currentSetIndex = 0
currentSet = sets.find((set) => (set.set_prefix == currentSetPrefix && set.set_index == 0))
}

const isManager = (overview.is_superuser || overview.is_editor || overview.is_reviewer)

Expand All @@ -36,6 +41,7 @@ const Page = ({ config, templates, overview, page, sets, values, fetchPage,
createSet={createSet}
updateSet={updateSet}
deleteSet={deleteSet}
copySet={copySet}
/>
<div className="row">
{
Expand All @@ -55,6 +61,7 @@ const Page = ({ config, templates, overview, page, sets, values, fetchPage,
createSet={createSet}
updateSet={updateSet}
deleteSet={deleteSet}
copySet={copySet}
createValue={createValue}
updateValue={updateValue}
deleteValue={deleteValue}
Expand Down Expand Up @@ -108,11 +115,12 @@ Page.propTypes = {
createValue: PropTypes.func.isRequired,
updateValue: PropTypes.func.isRequired,
deleteValue: PropTypes.func.isRequired,
copyValue: PropTypes.func.isRequired,
activateSet: PropTypes.func.isRequired,
createSet: PropTypes.func.isRequired,
updateSet: PropTypes.func.isRequired,
deleteSet: PropTypes.func.isRequired,
copyValue: PropTypes.func.isRequired
copySet: PropTypes.func.isRequired
}

export default Page
43 changes: 35 additions & 8 deletions rdmo/projects/assets/js/interview/components/main/page/PageHead.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import useModal from 'rdmo/core/assets/js/hooks/useModal'
import PageHeadDeleteModal from './PageHeadDeleteModal'
import PageHeadFormModal from './PageHeadFormModal'

const PageHead = ({ templates, page, sets, values, currentSet, activateSet, createSet, updateSet, deleteSet }) => {
const PageHead = ({ templates, page, sets, values, currentSet,
activateSet, createSet, updateSet, deleteSet, copySet }) => {

const currentSetValue = isNil(currentSet) ? null : (
values.find((value) => (
Expand All @@ -20,6 +21,7 @@ const PageHead = ({ templates, page, sets, values, currentSet, activateSet, crea
const {show: showCreateModal, open: openCreateModal, close: closeCreateModal} = useModal()
const {show: showUpdateModal, open: openUpdateModal, close: closeUpdateModal} = useModal()
const {show: showDeleteModal, open: openDeleteModal, close: closeDeleteModal} = useModal()
const {show: showCopyModal, open: openCopyModal, close: closeCopyModal} = useModal()

const handleActivateSet = (event, set) => {
event.preventDefault()
Expand Down Expand Up @@ -53,6 +55,16 @@ const PageHead = ({ templates, page, sets, values, currentSet, activateSet, crea
closeDeleteModal()
}

const handleCopySet = (text) => {
copySet(currentSet, currentSetValue, {
attribute: page.attribute,
set_index: last(sets) ? last(sets).set_index + 1 : 0,
set_collection: page.is_collection,
text
})
closeCopyModal()
}

return page.is_collection && (
<div className="interview-page-tabs">
<Html html={templates.project_interview_page_help} />
Expand Down Expand Up @@ -82,12 +94,13 @@ const PageHead = ({ templates, page, sets, values, currentSet, activateSet, crea
</li>
</ul>
<div className="interview-page-tabs-buttons">
{
page.attribute && (
<button className="btn-link fa fa-pencil" title={gettext('Edit tab')} onClick={openUpdateModal} />
)
}
<button className="btn-link fa fa-trash" title={gettext('Remove tab')} onClick={openDeleteModal} />
{
page.attribute && (
<button className="btn-link fa fa-pencil" title={gettext('Edit tab')} onClick={openUpdateModal} />
)
}
<button className="btn-link fa fa-copy" title={gettext('Copy tab')} onClick={openCopyModal} />
<button className="btn-link fa fa-trash" title={gettext('Remove tab')} onClick={openDeleteModal} />
</div>
</>
) : (
Expand All @@ -99,15 +112,28 @@ const PageHead = ({ templates, page, sets, values, currentSet, activateSet, crea

<PageHeadFormModal
title={capitalize(page.verbose_name)}
submitLabel={gettext('Create')}
submitColor="success"
show={showCreateModal}
initial={isNil(page.attribute) ? null : ''}
onClose={closeCreateModal}
onSubmit={handleCreateSet}
/>
<PageHeadFormModal
title={capitalize(page.verbose_name)}
submitLabel={gettext('Copy')}
submitColor="info"
show={showCopyModal}
initial={isNil(page.attribute) ? null : ''}
onClose={closeCopyModal}
onSubmit={handleCopySet}
/>
{
currentSetValue && (
<PageHeadFormModal
title={capitalize(page.verbose_name)}
submitLabel={gettext('Update')}
submitColor="primary"
show={showUpdateModal}
initial={currentSetValue.text}
onClose={closeUpdateModal}
Expand All @@ -134,7 +160,8 @@ PageHead.propTypes = {
activateSet: PropTypes.func.isRequired,
createSet: PropTypes.func.isRequired,
updateSet: PropTypes.func.isRequired,
deleteSet: PropTypes.func.isRequired
deleteSet: PropTypes.func.isRequired,
copySet: PropTypes.func.isRequired
}

export default PageHead
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,11 @@ import Modal from 'rdmo/core/assets/js/components/Modal'
import useFocusEffect from '../../../hooks/useFocusEffect'


const PageHeadFormModal = ({ title, show, initial, onClose, onSubmit }) => {
const PageHeadFormModal = ({ title, submitLabel, submitColor, show, initial, onClose, onSubmit }) => {

const ref = useRef(null)
const [inputValue, setInputValue] = useState('')
const [hasError, setHasError] = useState(false)
const submitLabel = isEmpty(initial) ? gettext('Create') : gettext('Update')
const submitProps = {
className: classNames('btn', {
'btn-success': isEmpty(initial),
'btn-primary': !isEmpty(initial),
})
}

const handleSubmit = () => {
if (isEmpty(inputValue) && !isNil(initial)) {
Expand Down Expand Up @@ -46,7 +39,7 @@ const PageHeadFormModal = ({ title, show, initial, onClose, onSubmit }) => {
useFocusEffect(ref, show)

return (
<Modal title={title} show={show} submitLabel={submitLabel} submitProps={submitProps}
<Modal title={title} show={show} submitLabel={submitLabel} submitProps={{className: `btn btn-${submitColor}`}}
onClose={onClose} onSubmit={handleSubmit} disableSubmit={hasError}>
{
isNil(initial) ? (
Expand Down Expand Up @@ -82,6 +75,8 @@ const PageHeadFormModal = ({ title, show, initial, onClose, onSubmit }) => {

PageHeadFormModal.propTypes = {
title: PropTypes.string.isRequired,
submitLabel: PropTypes.string.isRequired,
submitColor: PropTypes.string.isRequired,
show: PropTypes.bool.isRequired,
initial: PropTypes.string,
onClose: PropTypes.func.isRequired,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ import Question from '../question/Question'

import QuestionSetAddSet from './QuestionSetAddSet'
import QuestionSetAddSetHelp from './QuestionSetAddSetHelp'
import QuestionSetCopySet from './QuestionSetCopySet'
import QuestionSetHelp from './QuestionSetHelp'
import QuestionSetHelpTemplate from './QuestionSetHelpTemplate'
import QuestionSetRemoveSet from './QuestionSetRemoveSet'

const QuestionSet = ({ templates, questionset, sets, values, disabled, isManager,
parentSet, createSet, updateSet, deleteSet,
parentSet, createSet, updateSet, deleteSet, copySet,
createValue, updateValue, deleteValue, copyValue }) => {

const setPrefix = getChildPrefix(parentSet)
Expand All @@ -35,7 +36,8 @@ const QuestionSet = ({ templates, questionset, sets, values, disabled, isManager
currentSets.map((set, setIndex) => (
<div key={setIndex} className="interview-block">
<div className="interview-block-options">
<QuestionSetRemoveSet questionset={questionset} set={set} deleteSet={deleteSet} />
<QuestionSetCopySet questionset={questionset} sets={sets} currentSet={set} copySet={copySet} />
<QuestionSetRemoveSet questionset={questionset} currentSet={set} deleteSet={deleteSet} />
</div>
<div className="row">
{
Expand All @@ -55,6 +57,7 @@ const QuestionSet = ({ templates, questionset, sets, values, disabled, isManager
createSet={createSet}
updateSet={updateSet}
deleteSet={deleteSet}
copySet={copySet}
createValue={createValue}
updateValue={updateValue}
deleteValue={deleteValue}
Expand Down Expand Up @@ -112,6 +115,7 @@ QuestionSet.propTypes = {
createSet: PropTypes.func.isRequired,
updateSet: PropTypes.func.isRequired,
deleteSet: PropTypes.func.isRequired,
copySet: PropTypes.func.isRequired,
createValue: PropTypes.func.isRequired,
updateValue: PropTypes.func.isRequired,
deleteValue: PropTypes.func.isRequired,
Expand Down
Loading