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): Reuse datasets and values [5] #1188

Merged
merged 42 commits into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
b73ffbc
Add import function to create set modal on pages
jochenklar Nov 7, 2024
a687222
Add separate search endpoint for values and use AsyncSelect in PageHe…
jochenklar Nov 8, 2024
3cffcac
Fix errors after rebase
jochenklar Nov 8, 2024
1c470db
Add error handling to copy-set action and fix/add tests
jochenklar Nov 8, 2024
485f917
Improve PageHeadFormModal
jochenklar Nov 8, 2024
0bf452a
Add import function into existing set
jochenklar Nov 8, 2024
f6b813e
Update/fix tests
jochenklar Nov 8, 2024
81f484d
Rename import/copy answers to reuse answers
jochenklar Nov 9, 2024
5bfbd6f
Fix copy_set action and update tests
jochenklar Nov 9, 2024
27d3369
Add reuse value modal for questions
jochenklar Nov 9, 2024
d5cdce3
Add reuse value model to checkboxes
jochenklar Nov 10, 2024
60eaf38
Add project filter to reuse value modal
jochenklar Nov 10, 2024
d8552ce
Add useLsState hook and use it to store reuse modal information
jochenklar Nov 10, 2024
55eaaf2
Fix page component
jochenklar Nov 10, 2024
11ba0e5
Fix react select error state
jochenklar Nov 10, 2024
332c2ac
Fix reuse modal
jochenklar Nov 10, 2024
9931832
Add tests for value search endpoint
jochenklar Nov 10, 2024
cc742b4
Fix QuestionSetCopySet component
jochenklar Nov 10, 2024
cd0a0e6
Fix Page component
jochenklar Nov 10, 2024
9f53c6b
Refactor interview modals
jochenklar Nov 10, 2024
d2017c7
Fix Value model
jochenklar Nov 10, 2024
fa71195
Fix typo
jochenklar Nov 11, 2024
9b8baf2
Update .gitignore
jochenklar Nov 12, 2024
06e698a
Fix radio input
jochenklar Nov 12, 2024
1770fb6
Fix search component
jochenklar Nov 12, 2024
761cfad
Add set label to search component
jochenklar Nov 14, 2024
d427ca5
Add project_interview_sidebar template
jochenklar Nov 14, 2024
4357cee
Fix SelectWidget
jochenklar Nov 14, 2024
907d243
Add append to reuse value functionality
jochenklar Nov 14, 2024
427aeb1
Fix component arguments and proptypes
jochenklar Jan 23, 2025
f77ea87
Update .gitignore
jochenklar Jan 23, 2025
4d40b43
Filter values with options in the search endpoint for reuse
jochenklar Jan 24, 2025
4c0fa17
Add a check for options to copy_set action
jochenklar Jan 24, 2025
b11f4f7
Fix search endpoint
jochenklar Jan 24, 2025
2c4618f
Refactor projects filter backends
jochenklar Jan 24, 2025
b870fbd
Add tests for search endpoint using the options filter
jochenklar Jan 24, 2025
8c3a08c
Fix QuestionReuseValues component
jochenklar Jan 24, 2025
dc637c9
Add test for copy reuse with filter
jochenklar Jan 24, 2025
6f2163f
Fix Done component
jochenklar Jan 24, 2025
ab71cd9
Add workaround for sqlite to the ValueViewSet.search action
jochenklar Jan 30, 2025
e9099ff
Fix ProjectValueViewSet.copy_set action
jochenklar Jan 30, 2025
2a95493
Refactor and fix useLsState
jochenklar Jan 30, 2025
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
30 changes: 16 additions & 14 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
__pycache__
*.pyc
*~
*.swp
.DS_Store

testing/config/settings/local.py
testing/log/
.pytest*/
Expand All @@ -6,19 +12,12 @@ static_root
media_root
components_root

docs/_build*

__pycache__
*.pyc
*~
*.swp
.DS_Store
.ruff_cache

env
env2
env3

Makefile

.coverage
.coverage.*
htmlcov
Expand All @@ -32,16 +31,19 @@ dist
.imdone/

.pytest_cache
.ruff_cache
.on-save.json

rdmo/management/static

rdmo/core/static/core/js/base*.js
rdmo/core/static/core/js/base.js
rdmo/core/static/core/css/base.css
rdmo/core/static/core/fonts
rdmo/core/static/core/css/base*.css

rdmo/projects/static/projects/js/*.js
rdmo/projects/static/projects/fonts
rdmo/projects/static/projects/css/*.css
rdmo/projects/static/projects/css/interview.css
rdmo/projects/static/projects/css/projects.css
rdmo/projects/static/projects/fonts/
rdmo/projects/static/projects/js/interview.js
rdmo/projects/static/projects/js/projects.js

screenshots
6 changes: 4 additions & 2 deletions rdmo/core/assets/js/components/Modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react'
import PropTypes from 'prop-types'
import { Modal as BootstrapModal } from 'react-bootstrap'

const Modal = ({ title, show, modalProps, submitLabel, submitProps, onClose, onSubmit, children }) => {
const Modal = ({ title, show, modalProps, submitLabel, submitProps, onClose, onSubmit, children, buttons }) => {
return (
<BootstrapModal className="element-modal" onHide={onClose} show={show} {...modalProps}>
<BootstrapModal.Header closeButton>
Expand All @@ -19,6 +19,7 @@ const Modal = ({ title, show, modalProps, submitLabel, submitProps, onClose, onS
<button type="button" className="btn btn-default" onClick={onClose}>
{gettext('Close')}
</button>
{buttons}
{
onSubmit && (
<button type="button" className="btn btn-primary" onClick={onSubmit} {...submitProps}>
Expand All @@ -39,7 +40,8 @@ Modal.propTypes = {
submitProps: PropTypes.object,
onClose: PropTypes.func.isRequired,
onSubmit: PropTypes.func,
children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired,
children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]),
buttons: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]),
}

export default Modal
46 changes: 46 additions & 0 deletions rdmo/core/assets/js/hooks/useLsState.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useState } from 'react'
import { isEmpty, isPlainObject, omit as lodashOmit } from 'lodash'

import { checkStoreId } from '../utils/store'

const useLsState = (path, initialValue, omit = []) => {
MyPyDavid marked this conversation as resolved.
Show resolved Hide resolved
checkStoreId()

const getLs = (path) => {
const data = JSON.parse(localStorage.getItem(path))
return isPlainObject(data) ? lodashOmit(data, omit) : data
}

const setLs = (path, value) => {
const data = isPlainObject(value) ? lodashOmit(value, omit) : value
localStorage.setItem(path, JSON.stringify(data))
}

const getInitialState = () => {
// get the value from the local storage
const lsValue = getLs(path)

// return the state with the value from the local storage or the provided initialValue
if (isPlainObject(lsValue)) {
return { ...initialValue, ...lsValue }
} else if (isEmpty(lsValue)) {
return initialValue
} else {
return lsValue
}
}

// setup the state
const [value, setValue] = useState(getInitialState())

return [
value,
(value) => {
setLs(path, value)
setValue(value)
},
() => setValue(getInitialState())
]
}

export default useLsState
8 changes: 6 additions & 2 deletions rdmo/core/assets/scss/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,7 @@ metadata {
.page h2:nth-child(2) {
margin-top: 0;
}
.sidebar h2:first-child,
.sidebar .import-buttons {
.sidebar > *:first-child {
margin-top: 70px;
}

Expand Down Expand Up @@ -317,6 +316,7 @@ form .yesno label {
/* modals */

.modal-body {
> div:last-child,
> p:last-child,
formgroup:last-child .form-group {
margin-bottom: 0;
Expand Down Expand Up @@ -495,3 +495,7 @@ li.has-warning > a.control-label > i {
color: $link-color;
cursor: pointer;
}

.has-error .react-select__control {
border-color: #a94442;
}
5 changes: 4 additions & 1 deletion rdmo/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,8 @@
'projects/project_interview_page_tabs_help.html',
'projects/project_interview_progress_help.html',
'projects/project_interview_question_help.html',
'projects/project_interview_questionset_help.html'
'projects/project_interview_questionset_help.html',
'projects/project_interview_sidebar.html',
]

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
Expand Down Expand Up @@ -348,6 +349,8 @@

OPTIONSET_PROVIDERS = []

PROJECT_VALUES_SEARCH_LIMIT = 10

PROJECT_VALUES_VALIDATION = False

PROJECT_VALUES_VALIDATION_URL = True
Expand Down
21 changes: 13 additions & 8 deletions rdmo/projects/assets/js/interview/actions/interviewActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -611,18 +611,20 @@ 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}`
export function copySet(currentSet, copySetValue, attrs) {
const pendingId = isNil(currentSet) ? 'copySet' : `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 new set (create) or use the current one (import)
const set = isNil(attrs.id) ? SetFactory.create(attrs) : currentSet

// create a value for the text if the page has an attribute
const value = isNil(attrs.attribute) ? null : ValueFactory.create(attrs)
// create a value for the text if the page has an attribute (create) or use the current one (import)
const value = isNil(attrs.attribute) ? null : (
isNil(attrs.id) ? ValueFactory.create(attrs) : attrs
)

// create a callback function to be called immediately or after saving the value
const copySetCallback = (setValues) => {
Expand All @@ -631,7 +633,10 @@ export function copySet(currentSet, currentSetValue, attrs) {
const state = getState().interview

const page = state.page
const values = [...state.values, ...setValues]
const values = [
...state.values.filter(v => !setValues.some(sv => compareValues(v, sv))), // remove updated values
...setValues
]
const sets = gatherSets(values)

initSets(sets, page)
Expand Down Expand Up @@ -667,7 +672,7 @@ export function copySet(currentSet, currentSetValue, attrs) {
})
)
} else {
promise = ValueApi.copySet(projectId, currentSetValue, value)
promise = ValueApi.copySet(projectId, value, copySetValue)
}

return promise.then((values) => {
Expand Down
4 changes: 4 additions & 0 deletions rdmo/projects/assets/js/interview/api/ProjectApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import BaseApi from 'rdmo/core/assets/js/api/BaseApi'

class ProjectsApi extends BaseApi {

static fetchProjects(params) {
return this.get(`/api/v1/projects/projects/?${encodeParams(params)}`)
}

static fetchOverview(projectId) {
return this.get(`/api/v1/projects/projects/${projectId}/overview/`)
}
Expand Down
12 changes: 9 additions & 3 deletions rdmo/projects/assets/js/interview/api/ValueApi.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import BaseApi from 'rdmo/core/assets/js/api/BaseApi'
import { encodeParams } from 'rdmo/core/assets/js/utils/api'
import isUndefined from 'lodash/isUndefined'
import { isUndefined } from 'lodash'

class ValueApi extends BaseApi {

static fetchValues(projectId, params) {
return this.get(`/api/v1/projects/projects/${projectId}/values/?${encodeParams(params)}`)
}

static searchValues(params) {
return this.get(`/api/v1/projects/values/search/?${encodeParams(params)}`)
}

static storeValue(projectId, value) {
if (isUndefined(value.id)) {
return this.post(`/api/v1/projects/projects/${projectId}/values/`, value)
Expand All @@ -29,8 +33,10 @@ class ValueApi extends BaseApi {
}
}

static copySet(projectId, currentSetValue, setValue) {
return this.post(`/api/v1/projects/projects/${projectId}/values/${currentSetValue.id}/set/`, setValue)
static copySet(projectId, setValue, copySetValue) {
return this.post(`/api/v1/projects/projects/${projectId}/values/set/`, {
...setValue, copy_set_value: copySetValue.id
})
}

static deleteSet(projectId, setValue) {
Expand Down
3 changes: 1 addition & 2 deletions rdmo/projects/assets/js/interview/components/main/Done.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ const Done = ({ templates }) => {
}

Done.propTypes = {
templates: PropTypes.object.isRequired,
overview: PropTypes.object.isRequired
templates: PropTypes.object.isRequired
}

export default Done
Loading
Loading