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

Booking form extra fields #6126

Merged
merged 27 commits into from
Mar 28, 2024

Conversation

VojtechPetru
Copy link
Contributor

Hello 👋

  • adds possibility for plugins to add extra fields in a room booking form.

Example:
image

CHANGES.rst Outdated Show resolved Hide resolved
@@ -53,14 +53,19 @@ def past_booking_occurrences_are_cancelled(dummy_user, create_reservation):
start_dt = datetime.today().replace(hour=8, minutes=30) - timedelta(days=2)
end_dt = datetime.today().replace(hour=17, minutes=30) + timedelta(days=4)
reservation = create_reservation(start_dt=start_dt, end_dt=end_dt, repeat_frequency=RepeatFrequency.DAY)
new_reservation = split_booking(reservation, {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh fun, this unit test is completely broken and has never been executed (opened #6210 for it since I don't feel like touching that...). Anyway your change in here looks fine.

@ThiefMaster
Copy link
Member

@VojtechPetru are you able to share either an example plugin that uses these signals or your real plugin (even if privately e.g. via email or by granting me access to a private github repo)?

I don't see any likely breakage, but I'd prefer to actually test it before merging, and I don't think it's a good use of my own time if I have to write my own test plugin for this while you may already have something! :)

@VojtechPetru
Copy link
Contributor Author

@VojtechPetru are you able to share either an example plugin that uses these signals or your real plugin (even if privately e.g. via email or by granting me access to a private github repo)?

I don't see any likely breakage, but I'd prefer to actually test it before merging, and I don't think it's a good use of my own time if I have to write my own test plugin for this while you may already have something! :)

Sure! I've sent you an invite to the repo. Thank you :)

@ThiefMaster
Copy link
Member

When testing with your plugin, I noticed some minor issues:

  1. warning about changing between controlled and uncontrolled fields. this is a bug in your plugin though, you need to default the value of expected_participants_count to an empty string (i tested with value={bookingData?.expected_participants_count || ''} and it removed he warning)
  2. the new fields are not integrating with final-form. during booking this is not really a problem unless you want to do validation, but during editing this means that the submit button won't become active when only some of your custom fields are changed (ie you would need to change e.g. the booking reason as well).

For (2) the cleanest solution would be to integrate the fields with final-form: This would require you to use FinalField, FinalInput etc. in your plugin, and also adapt this PR to nor use setState for tracking this data (it would be done automatically).

Here's a patch that could provide a good starting point. You most likely also need to find a good way to get the extraFields into the booking data so the initial values are correctly pre-populated (you can set an initial value directly on the FinalField level but iirc it has some drawbacks)

diff --git a/indico/modules/rb/client/js/common/bookings/BookingEdit.jsx b/indico/modules/rb/client/js/common/bookings/BookingEdit.jsx
index b7df8d365a..28e38cdcaf 100644
--- a/indico/modules/rb/client/js/common/bookings/BookingEdit.jsx
+++ b/indico/modules/rb/client/js/common/bookings/BookingEdit.jsx
@@ -77,7 +77,6 @@ class BookingEdit extends React.Component {
       willBookingSplit: false,
       calendars: null,
       timelineError: null,
-      extraFields: null,
     };
   }

@@ -180,7 +179,7 @@ class BookingEdit extends React.Component {
   };

   renderBookingEditModal = fprops => {
-    const {submitting, submitSucceeded, hasValidationErrors, pristine} = fprops;
+    const {form, submitting, submitSucceeded, hasValidationErrors, pristine} = fprops;
     const {booking, onClose, actionButtons, isOngoingBooking} = this.props;
     const {room} = booking;
     const {
@@ -314,6 +313,7 @@ class BookingEdit extends React.Component {
       user,
       reason,
       recurrence,
+      extraFields,
       internalNote,
     } = data;
     const {
@@ -326,7 +326,6 @@ class BookingEdit extends React.Component {
       isAdminOverrideEnabled,
     } = this.props;
     const [repeatFrequency, repeatInterval] = serializeRecurrenceInfo(recurrence);
-    const {extraFields} = this.state;
     const params = {
       start_dt: `${startDate} ${startTime}`,
       end_dt: `${endDate ? endDate : startDate} ${endTime}`,
diff --git a/indico/modules/rb/client/js/common/bookings/BookingEditForm.jsx b/indico/modules/rb/client/js/common/bookings/BookingEditForm.jsx
index 7be09936ad..efa0b207e3 100644
--- a/indico/modules/rb/client/js/common/bookings/BookingEditForm.jsx
+++ b/indico/modules/rb/client/js/common/bookings/BookingEditForm.jsx
@@ -388,6 +388,11 @@ class BookingEditForm extends React.Component {
             required={requireReason}
             disabled={submitSucceeded}
           />
+          <FinalTextArea
+            name="extraFields.test"
+            nullIfEmpty
+            placeholder={Translate.string('Test')}
+          />
         </Segment>
         {renderPluginComponents('rb-booking-form-extra-fields', {
           room,

@VojtechPetru
Copy link
Contributor Author

VojtechPetru commented Mar 1, 2024

Hello @ThiefMaster 👋
thank you for the detailed description of the issues!

The suggested final-form integration is definitely worth looking at, although unfortunately I don't have experience with it so far. Is the (2) blocker for the merge of this PR, or is it something that can be updated later?

@ThiefMaster
Copy link
Member

Considering that this is just a plugin API where there's a good chance nobody else will use it anytime soon, I'd be OK with merging this as-is for now. I'll see if I can find a way so changes to custom fields can at least mark the form as dirty so the submit button becomes (permanently) enabled once something is changed there...

{renderPluginComponents('rb-booking-form-extra-fields', {
room,
booking,
formProps,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm replacing this (which you do not use anyway in your plugin) with disabled (submitting || submitSucceeded) here and adding this in booking creation. You should use that in your plugin to disable the custom fields.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, will do! 👍

@ThiefMaster
Copy link
Member

While testing I noticed another problem: when no core fields are changed, the Reservation.modify method exits early so the signal never gets called.

So I'm starting to wonder whether it wouldn't make more sense to revert the changes made to pass the request data to the booking-related signals, and instead rely on the schema-level signals (pre_load etc)?

@VojtechPetru
Copy link
Contributor Author

While testing I noticed another problem: when no core fields are changed, the Reservation.modify method exits early so the signal never gets called.

So I'm starting to wonder whether it wouldn't make more sense to revert the changes made to pass the request data to the booking-related signals, and instead rely on the schema-level signals (pre_load etc)?

Thanks for such an elaborate review @ThiefMaster! 👑 I'll check it on Monday, but from a brief look it seems like something which could work. Just to be sure, we're talking about this part of code, right?

What worries me a little bit is coupling the plugin logic so tightly to the validation schema.
Just an idea, do you think moving the current code to an interceptable Reservation.get_changes method and intercepting that in the plugin be an option? 🤔

@ThiefMaster
Copy link
Member

something like this could also work:

if changes:
    # the usual logging here
elif 'extra_data' not in data:
    return

that way we'd avoid creating empty log entries (if nothing changes that the core is aware of), but still keep going if any extra data is present...

@ThiefMaster
Copy link
Member

but i'm wondering why you changes work at all :D the default when using a marshmallow schema for processing request data is to ignore any unknown fields, and you did not add extra_data to the schema itself! (i'll have a look, maybe i'm missing something)

@VojtechPetru
Copy link
Contributor Author

You're right 👍 Giving it a second thought, what I like about the interceptable Reservation.get_changes method is, that it would also allow log any changes in the extra fields defined by the plugin. So I'd try that solution first, unless you have another preference @ThiefMaster?

As for the schema and why the plugin works, it should be these few lines which extend the validation schema when the plugin is initialized

@ThiefMaster
Copy link
Member

ThiefMaster commented Mar 4, 2024

Ah I see, you patch the core schema from your plugin, that's why it works.

        CreateBookingSchema._declared_fields['extra_fields'] = mm.fields.Nested(
            schemas.ReservationExtraFieldsSchema,
            required=False,
            allow_none=True,
        )

It also means that the core code as it is right now cannot look at extra_fields as I thought before, since it's plugin-specific and not something the core even knows about.

BUT: The core JS code is already aware of extra_fields, so it makes perfect sense to add it to the core schema (just as a Dict field w/o any special validation). From your plugin you can then either patch the schema with a custom @validates('extra_field') method or validate from your post_load signal handler that the extra fields data matches your ReservationExtraFieldsSchema schema.

When making that change it may also be a good idea to not pass the full request_data around but rather just extra_fields.

@VojtechPetru
Copy link
Contributor Author

Hello @ThiefMaster 👋
sorry for late reply, I've been sick for the last couple of days. I've made the changes you suggested - added extra_fields to the core schema and passing only those to the booking_modified signal.
I also implemented the change you've suggested here to stop Reservation.modify returning early if extra_fields are present.

@ThiefMaster
Copy link
Member

Could you rebase it and fix the conflicts?

@ThiefMaster ThiefMaster force-pushed the booking_form-extra_fields branch 2 times, most recently from 27a6c80 to 31aba5f Compare March 21, 2024 13:27
if (has_slot_changed and not room.can_book(session.user, allow_admin=False) and
room.can_prebook(session.user, allow_admin=False) and self.booking.is_accepted):
self.booking.reset_approval(session.user)
else:
new_booking = split_booking(self.booking, new_booking_data)
new_booking = split_booking(self.booking, new_booking_data, extra_fields=args.get('extra_fields', {}))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the .get() really needed here? I'd expect it to be missing from the request data (in that case the schema uses the load_default to give you {}) or set to a value (which is always a dict anyway).

VojtechPetru and others added 27 commits March 28, 2024 17:47
Removed passing the full formProps since those should not be necessary.
@ThiefMaster ThiefMaster merged commit 19e7f2f into indico:master Mar 28, 2024
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants