From 898f5e6ec1ca8df20fd502dc32eaadf5bc1c448e Mon Sep 17 00:00:00 2001 From: Jochen Klar Date: Fri, 27 Sep 2024 16:24:44 +0200 Subject: [PATCH 1/8] Add PROJECT_CONTACT settings and the corresponding API, actions and components --- rdmo/core/assets/js/api/BaseApi.js | 6 +- rdmo/core/assets/scss/base.scss | 1 + rdmo/core/assets/scss/forms.scss | 3 + rdmo/core/settings.py | 9 +- rdmo/core/utils.py | 4 + .../js/interview/actions/actionTypes.js | 10 +++ .../js/interview/actions/contactActions.js | 84 +++++++++++++++++++ .../assets/js/interview/api/ContactApi.js | 17 ++++ .../js/interview/components/main/Contact.js | 82 ++++++++++++++++++ .../js/interview/components/main/page/Page.js | 8 +- .../components/main/question/Question.js | 10 ++- .../main/question/QuestionContact.js | 24 ++++++ .../main/questionset/QuestionSet.js | 12 ++- .../assets/js/interview/containers/Main.js | 22 ++++- .../js/interview/reducers/contactReducer.js | 33 ++++++++ .../js/interview/store/configureStore.js | 2 + rdmo/projects/assets/scss/interview.scss | 23 +++++ .../email/project_contact_message.txt | 34 ++++++++ .../email/project_contact_subject.txt | 3 + .../projects/email/project_invite_subject.txt | 2 +- .../project_interview_contact_help.html | 8 ++ rdmo/projects/utils.py | 63 ++++++++++++++ rdmo/projects/viewsets.py | 26 ++++++ rdmo/questions/models/catalog.py | 18 ++++ testing/config/settings/base.py | 3 + 25 files changed, 492 insertions(+), 15 deletions(-) create mode 100644 rdmo/core/assets/scss/forms.scss create mode 100644 rdmo/projects/assets/js/interview/actions/contactActions.js create mode 100644 rdmo/projects/assets/js/interview/api/ContactApi.js create mode 100644 rdmo/projects/assets/js/interview/components/main/Contact.js create mode 100644 rdmo/projects/assets/js/interview/components/main/question/QuestionContact.js create mode 100644 rdmo/projects/assets/js/interview/reducers/contactReducer.js create mode 100644 rdmo/projects/templates/projects/email/project_contact_message.txt create mode 100644 rdmo/projects/templates/projects/email/project_contact_subject.txt create mode 100644 rdmo/projects/templates/projects/project_interview_contact_help.html diff --git a/rdmo/core/assets/js/api/BaseApi.js b/rdmo/core/assets/js/api/BaseApi.js index 7db8e00011..c7d6d76510 100644 --- a/rdmo/core/assets/js/api/BaseApi.js +++ b/rdmo/core/assets/js/api/BaseApi.js @@ -50,7 +50,11 @@ class BaseApi { throw new ApiError(error.message) }).then(response => { if (response.ok) { - return response.json() + if (response.status == 204) { + return null + } else { + return response.json() + } } else if (response.status == 400) { return response.json().then(errors => { throw new ValidationError(errors) diff --git a/rdmo/core/assets/scss/base.scss b/rdmo/core/assets/scss/base.scss index dadff686a5..66febc9427 100644 --- a/rdmo/core/assets/scss/base.scss +++ b/rdmo/core/assets/scss/base.scss @@ -10,6 +10,7 @@ $icon-font-path: "bootstrap-sass/assets/fonts/bootstrap/"; @import 'codemirror'; @import 'fonts'; @import 'footer'; +@import 'forms'; @import 'header'; @import 'swagger'; @import 'utils'; diff --git a/rdmo/core/assets/scss/forms.scss b/rdmo/core/assets/scss/forms.scss new file mode 100644 index 0000000000..3149abb28f --- /dev/null +++ b/rdmo/core/assets/scss/forms.scss @@ -0,0 +1,3 @@ +textarea { + resize: vertical; +} diff --git a/rdmo/core/settings.py b/rdmo/core/settings.py index c6610be4de..63d3b17459 100644 --- a/rdmo/core/settings.py +++ b/rdmo/core/settings.py @@ -220,13 +220,15 @@ 'MULTISITE', 'GROUPS', 'EXPORT_FORMATS', - 'PROJECT_TABLE_PAGE_SIZE' + 'PROJECT_TABLE_PAGE_SIZE', + 'PROJECT_CONTACT' ] TEMPLATES_API = [ 'projects/project_interview_add_set_help.html', 'projects/project_interview_add_value_help.html', 'projects/project_interview_buttons_help.html', + 'projects/project_interview_contact_help.html', 'projects/project_interview_done.html', 'projects/project_interview_error.html', 'projects/project_interview_multiple_values_warning.html', @@ -236,7 +238,7 @@ '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' ] EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' @@ -305,6 +307,9 @@ PROJECT_VIEWS = True +PROJECT_CONTACT = False +PROJECT_CONTACT_RECIPIENTS = [] + PROJECT_EXPORTS = [ ('xml', _('RDMO XML'), 'rdmo.projects.exports.RDMOXMLExport'), ('csvcomma', _('CSV (comma separated)'), 'rdmo.projects.exports.CSVCommaExport'), diff --git a/rdmo/core/utils.py b/rdmo/core/utils.py index 791917b375..0813b05c74 100644 --- a/rdmo/core/utils.py +++ b/rdmo/core/utils.py @@ -418,3 +418,7 @@ def save_metadata(metadata): f = open(tmp_metadata_file) log.info('Save metadata file %s %s', tmp_metadata_file, str(metadata)) return tmp_metadata_file + + +def remove_double_newlines(string): + return re.sub(r'[\n]{2,}', '\n\n', string) diff --git a/rdmo/projects/assets/js/interview/actions/actionTypes.js b/rdmo/projects/assets/js/interview/actions/actionTypes.js index b42995cdbf..4972df8501 100644 --- a/rdmo/projects/assets/js/interview/actions/actionTypes.js +++ b/rdmo/projects/assets/js/interview/actions/actionTypes.js @@ -54,3 +54,13 @@ 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' + +export const FETCH_CONTACT_INIT = 'FETCH_CONTACT_INIT' +export const FETCH_CONTACT_SUCCESS = 'FETCH_CONTACT_SUCCESS' +export const FETCH_CONTACT_ERROR = 'FETCH_CONTACT_ERROR' + +export const SEND_CONTACT_INIT = 'SEND_CONTACT_INIT' +export const SEND_CONTACT_SUCCESS = 'SEND_CONTACT_SUCCESS' +export const SEND_CONTACT_ERROR = 'SEND_CONTACT_ERROR' + +export const CLOSE_CONTACT = 'CLOSE_CONTACT' diff --git a/rdmo/projects/assets/js/interview/actions/contactActions.js b/rdmo/projects/assets/js/interview/actions/contactActions.js new file mode 100644 index 0000000000..472afd6bca --- /dev/null +++ b/rdmo/projects/assets/js/interview/actions/contactActions.js @@ -0,0 +1,84 @@ +import ContactApi from '../api/ContactApi' + +import { projectId } from '../utils/meta' + +import { + FETCH_CONTACT_INIT, + FETCH_CONTACT_SUCCESS, + FETCH_CONTACT_ERROR, + SEND_CONTACT_INIT, + SEND_CONTACT_SUCCESS, + SEND_CONTACT_ERROR, + CLOSE_CONTACT +} from './actionTypes' + +import { addToPending, removeFromPending } from 'rdmo/core/assets/js/actions/pendingActions' + +export function fetchContact({ questionset, question, values }) { + const pendingId = 'fetchContact' + + return (dispatch, getState) => { + const params = { + page: getState().interview.page.id, + questionset: questionset && questionset.id, + question: question && question.id, + values: values.filter(value => value.id).map(value => value.id) + } + + dispatch(addToPending(pendingId)) + dispatch(fetchContactInit()) + + return ContactApi.fetchContact(projectId, params).then((values) => { + dispatch(removeFromPending(pendingId)) + dispatch(fetchContactSuccess(values)) + }).catch((error) => { + dispatch(removeFromPending(pendingId)) + dispatch(fetchContactError(error)) + }) + } +} + +export function fetchContactInit() { + return {type: FETCH_CONTACT_INIT} +} + +export function fetchContactSuccess(values) { + return {type: FETCH_CONTACT_SUCCESS, values} +} + +export function fetchContactError(error) { + return {type: FETCH_CONTACT_ERROR, error} +} + +export function sendContact(values) { + const pendingId = 'sendContact' + + return (dispatch) => { + dispatch(addToPending(pendingId)) + dispatch(sendContactInit()) + + return ContactApi.sendContact(projectId, values).then(() => { + dispatch(removeFromPending(pendingId)) + dispatch(sendContactSuccess()) + }).catch((error) => { + dispatch(removeFromPending(pendingId)) + dispatch(sendContactError(error)) + }) + } +} + +export function sendContactInit() { + return {type: SEND_CONTACT_INIT} +} + +export function sendContactSuccess() { + return {type: SEND_CONTACT_SUCCESS} +} + +export function sendContactError(error) { + return {type: SEND_CONTACT_ERROR, error} +} + +export function closeContact() { + return {type: CLOSE_CONTACT} +} diff --git a/rdmo/projects/assets/js/interview/api/ContactApi.js b/rdmo/projects/assets/js/interview/api/ContactApi.js new file mode 100644 index 0000000000..012996a0e3 --- /dev/null +++ b/rdmo/projects/assets/js/interview/api/ContactApi.js @@ -0,0 +1,17 @@ +import { encodeParams } from 'rdmo/core/assets/js/utils/api' + +import BaseApi from 'rdmo/core/assets/js/api/BaseApi' + +class ContactApi extends BaseApi { + + static fetchContact(projectId, params) { + return this.get(`/api/v1/projects/projects/${projectId}/contact/?${encodeParams(params)}`) + } + + static sendContact(projectId, data) { + return this.post(`/api/v1/projects/projects/${projectId}/contact/`, data) + } + +} + +export default ContactApi diff --git a/rdmo/projects/assets/js/interview/components/main/Contact.js b/rdmo/projects/assets/js/interview/components/main/Contact.js new file mode 100644 index 0000000000..84d88109d1 --- /dev/null +++ b/rdmo/projects/assets/js/interview/components/main/Contact.js @@ -0,0 +1,82 @@ +import React, { useState, useEffect } from 'react' +import PropTypes from 'prop-types' + +import Modal from 'rdmo/core/assets/js/components/Modal' + +import Html from 'rdmo/core/assets/js/components/Html' + +const Contact = ({ templates, contact, sendContact, closeContact }) => { + const { showModal, values: initialValues, errors } = contact + + const [values, setValues] = useState({}) + + useEffect(() => setValues(initialValues), [initialValues]) + + const onSubmit = (event) => { + event.preventDefault() + sendContact(values) + } + + const onClose = () => closeContact() + + return ( + <> + + +
+
+ + setValues({ ...values, subject: event.target.value })} + /> +
    + { + errors && errors.subject && errors.subject.map((error, errorIndex) => ( +
  • {errors.subject}
  • + )) + } +
+
+
+ +