FEATURE: adds support for custom fields on event
Custom fields are create in the site settings of the event plugin. Once at least one custom field is created, a new form will appear in each event UI. These custom fields are passed when DIscourseEvent triggers of the plugin are called, allowing you to pass custom data of the even to other plugins.
This commit is contained in:
parent
bf31bb359c
commit
6754bdfc8b
|
@ -8,5 +8,13 @@ Topic discussing the plugin itself can be found here: [https://meta.discourse.or
|
|||
|
||||
#### Plugins
|
||||
|
||||
##### Events
|
||||
|
||||
- `discourse_post_event_event_will_start` this DiscourseEvent will be triggered one hour before an event starts
|
||||
- `discourse_post_event_event_started` this DiscourseEvent will be triggered when an event starts
|
||||
- `discourse_post_event_event_ended` this DiscourseEvent will be triggered when an event ends
|
||||
|
||||
#### Custom Fields
|
||||
|
||||
Custom fields can be set in plugin settings. Once added a new form will appear on event UI.
|
||||
These custom fields are available when a plugin event is triggered.
|
||||
|
|
|
@ -113,6 +113,8 @@ module DiscoursePostEvent
|
|||
private
|
||||
|
||||
def event_params
|
||||
allowed_custom_fields = SiteSetting.discourse_post_event_allowed_custom_fields.split('|')
|
||||
|
||||
params
|
||||
.require(:event)
|
||||
.permit(
|
||||
|
@ -122,6 +124,7 @@ module DiscoursePostEvent
|
|||
:ends_at,
|
||||
:status,
|
||||
:url,
|
||||
custom_fields: allowed_custom_fields,
|
||||
raw_invitees: []
|
||||
)
|
||||
end
|
||||
|
|
|
@ -38,10 +38,15 @@ module DiscoursePostEvent
|
|||
def setup_starts_at_handler
|
||||
if !transaction_include_any_action?([:create])
|
||||
Jobs.cancel_scheduled_job(:discourse_post_event_event_started, event_id: self.id)
|
||||
Jobs.cancel_scheduled_job(:discourse_post_event_event_will_start, event_id: self.id)
|
||||
end
|
||||
|
||||
if self.starts_at > Time.now
|
||||
Jobs.enqueue_at(self.starts_at, :discourse_post_event_event_started, event_id: self.id)
|
||||
|
||||
if self.starts_at - 1.hour > Time.now
|
||||
Jobs.enqueue_at(self.starts_at - 1.hour, :discourse_post_event_event_will_start, event_id: self.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -102,6 +107,16 @@ module DiscoursePostEvent
|
|||
end
|
||||
end
|
||||
|
||||
validate :allowed_custom_fields
|
||||
def allowed_custom_fields
|
||||
allowed_custom_fields = SiteSetting.discourse_post_event_allowed_custom_fields.split('|')
|
||||
self.custom_fields.each do |key, value|
|
||||
if !allowed_custom_fields.include?(key)
|
||||
errors.add(:base, I18n.t("discourse_post_event.errors.models.event.custom_field_is_invalid", field: key))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def create_invitees(attrs)
|
||||
timestamp = Time.now
|
||||
attrs.map! do |attr|
|
||||
|
@ -219,6 +234,8 @@ module DiscoursePostEvent
|
|||
end
|
||||
|
||||
def update_with_params!(params)
|
||||
params[:custom_fields] = (params[:custom_fields] || {}).reject { |_, value| value.blank? }
|
||||
|
||||
case params[:status].to_i
|
||||
when Event.statuses[:private]
|
||||
raw_invitees = Array(params[:raw_invitees])
|
||||
|
|
|
@ -18,6 +18,7 @@ module DiscoursePostEvent
|
|||
attributes :is_expired
|
||||
attributes :should_display_invitees
|
||||
attributes :url
|
||||
attributes :custom_fields
|
||||
|
||||
def can_act_on_discourse_post_event
|
||||
scope.can_act_on_discourse_post_event?(object)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { set } from "@ember/object";
|
||||
import TextLib from "discourse/lib/text";
|
||||
import Group from "discourse/models/group";
|
||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
|
@ -5,8 +6,14 @@ import Controller from "@ember/controller";
|
|||
import { action, computed } from "@ember/object";
|
||||
import { equal } from "@ember/object/computed";
|
||||
import { extractError } from "discourse/lib/ajax-error";
|
||||
import { Promise } from "rsvp";
|
||||
|
||||
export default Controller.extend(ModalFunctionality, {
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this._dirtyCustomFields = false;
|
||||
},
|
||||
|
||||
modalTitle: computed("model.eventModel.isNew", {
|
||||
get() {
|
||||
return this.model.eventModel.isNew
|
||||
|
@ -15,12 +22,28 @@ export default Controller.extend(ModalFunctionality, {
|
|||
}
|
||||
}),
|
||||
|
||||
allowedCustomFields: computed(
|
||||
"siteSettings.discourse_post_event_allowed_custom_fields",
|
||||
function() {
|
||||
return this.siteSettings.discourse_post_event_allowed_custom_fields
|
||||
.split("|")
|
||||
.filter(Boolean);
|
||||
}
|
||||
),
|
||||
|
||||
groupFinder(term) {
|
||||
return Group.findAll({ term, ignore_automatic: true });
|
||||
},
|
||||
|
||||
allowsInvitees: equal("model.eventModel.status", "private"),
|
||||
|
||||
@action
|
||||
onChangeCustomField(field, event) {
|
||||
this._dirtyCustomFields = true;
|
||||
const value = event.target.value;
|
||||
set(this.model.eventModel.custom_fields, field, value);
|
||||
},
|
||||
|
||||
@action
|
||||
setRawInvitees(_, newInvitees) {
|
||||
this.set("model.eventModel.raw_invitees", newInvitees);
|
||||
|
@ -106,9 +129,23 @@ export default Controller.extend(ModalFunctionality, {
|
|||
|
||||
@action
|
||||
updateEvent() {
|
||||
const eventParams = this._buildEventParams();
|
||||
return this.store.find("post", this.model.eventModel.id).then(post => {
|
||||
const promises = [];
|
||||
if (this._dirtyCustomFields) {
|
||||
// custom_fields are not stored on the raw and are updated separately
|
||||
const customFields = this.model.eventModel.getProperties(
|
||||
"custom_fields"
|
||||
);
|
||||
promises.push(
|
||||
this.model.eventModel
|
||||
.update(customFields)
|
||||
.finally(() => (this._dirtyCustomFields = false))
|
||||
);
|
||||
}
|
||||
|
||||
const updateRawPromise = new Promise(resolve => {
|
||||
const raw = post.raw;
|
||||
const eventParams = this._buildEventParams();
|
||||
const newRaw = this._replaceRawEvent(eventParams, raw);
|
||||
|
||||
if (newRaw) {
|
||||
|
@ -122,10 +159,16 @@ export default Controller.extend(ModalFunctionality, {
|
|||
return post
|
||||
.save(props)
|
||||
.catch(e => this.flash(extractError(e), "error"))
|
||||
.then(result => result && this.send("closeModal"));
|
||||
.then(result => result && this.send("closeModal"))
|
||||
.finally(resolve);
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
||||
return Promise.all(promises.concat(updateRawPromise));
|
||||
});
|
||||
},
|
||||
|
||||
_buildEventParams() {
|
||||
|
|
|
@ -76,6 +76,21 @@
|
|||
placeholderKey="topic.invite_private.group_name"
|
||||
}}
|
||||
{{/event-field}}
|
||||
|
||||
{{#if allowedCustomFields.length}}
|
||||
{{#event-field label="discourse_post_event.builder_modal.custom_fields.label"}}
|
||||
<p>{{i18n "discourse_post_event.builder_modal.custom_fields.description"}}</p>
|
||||
{{#each allowedCustomFields as |allowedCustomField|}}
|
||||
<span class="label custom-field-label">{{allowedCustomField}}</span>
|
||||
{{input
|
||||
class="custom-field-input"
|
||||
value=(readonly (get model.eventModel.custom_fields allowedCustomField))
|
||||
placeholderKey="discourse_post_event.builder_modal.name.placeholder"
|
||||
input=(action "onChangeCustomField" allowedCustomField)
|
||||
}}
|
||||
{{/each}}
|
||||
{{/event-field}}
|
||||
{{/if}}
|
||||
</form>
|
||||
{{/conditional-loading-section}}
|
||||
{{/d-modal-body}}
|
||||
|
|
|
@ -88,6 +88,15 @@
|
|||
flex: 1;
|
||||
flex-direction: column;
|
||||
|
||||
.custom-field-label {
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.custom-field-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.radio-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
|
@ -320,6 +320,9 @@ en:
|
|||
<blockquote>username or group name,desired attendance (going, not_going, interested, unknown)</blockquote>
|
||||
download_sample_csv: Download a sample CSV file
|
||||
builder_modal:
|
||||
custom_fields:
|
||||
label: Custom Fields
|
||||
description: Allowed custom fields are defined in site settings. Custom fields are used to transmit data to other plugins.
|
||||
create_event_title: Create Event
|
||||
update_event_title: Edit Event
|
||||
confirm_delete: Are you sure you want to delete this event?
|
||||
|
|
|
@ -22,6 +22,7 @@ en:
|
|||
displayed_invitees_limit: "Limits the numbers of invitees displayed on an event."
|
||||
display_post_event_date_on_topic_title: "Displays the date of the event after the topic title."
|
||||
discourse_post_event_allowed_on_groups: "Groups that are allowed to create events."
|
||||
discourse_post_event_allowed_custom_fields: "Allows to let each each event to set the value of custom fields."
|
||||
holiday_calendar_topic_id: "Topic ID of staffs holiday / absence calendar."
|
||||
delete_expired_event_posts_after: "Posts with expired events will be automatically deleted after (n) hours. Set to -1 to disable deletion."
|
||||
all_day_event_start_time: "Events that do not have a start time specified will start at this time. Format is HH:mm. For 6:00 am, enter 06:00"
|
||||
|
@ -54,5 +55,6 @@ en:
|
|||
end_must_be_a_valid_date: "End date must be a valid date."
|
||||
acting_user_not_allowed_to_create_event: "Current user is not allowed to create events."
|
||||
acting_user_not_allowed_to_act_on_this_event: "Current user is not allowed to act on this event."
|
||||
custom_field_is_invalid: "The custom field `%{field}` is not allowed."
|
||||
name:
|
||||
length: "Event name length must be between %{minimum} and %{maximum} characters."
|
||||
|
|
|
@ -62,3 +62,7 @@ discourse_post_event:
|
|||
discourse_post_event_max_bulk_invitees:
|
||||
default: 500
|
||||
hidden: true
|
||||
discourse_post_event_allowed_custom_fields:
|
||||
type: list
|
||||
client: true
|
||||
default: ""
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddCustomFieldsToEvent < ActiveRecord::Migration[6.0]
|
||||
def up
|
||||
add_column :discourse_post_event_events, :custom_fields, :jsonb, null: false, default: {}
|
||||
end
|
||||
|
||||
def down
|
||||
remove_column :discourse_post_event_events, :custom_fields
|
||||
end
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Jobs
|
||||
class DiscoursePostEventEventWillStart < ::Jobs::Base
|
||||
sidekiq_options retry: false
|
||||
|
||||
def execute(args)
|
||||
raise Discourse::InvalidParameters.new(:event_id) if args[:event_id].blank?
|
||||
event = DiscoursePostEvent::Event.find(args[:event_id])
|
||||
DiscourseEvent.trigger(:discourse_post_event_event_will_start, event)
|
||||
end
|
||||
end
|
||||
end
|
18
plugin.rb
18
plugin.rb
|
@ -88,6 +88,7 @@ after_initialize do
|
|||
"../lib/discourse_post_event/event_parser.rb",
|
||||
"../lib/discourse_post_event/event_validator.rb",
|
||||
"../jobs/regular/discourse_post_event/bulk_invite.rb",
|
||||
"../jobs/regular/discourse_post_event/event_will_start.rb",
|
||||
"../jobs/regular/discourse_post_event/event_started.rb",
|
||||
"../jobs/regular/discourse_post_event/event_ended.rb",
|
||||
"../lib/discourse_post_event/event_finder.rb",
|
||||
|
@ -517,5 +518,22 @@ after_initialize do
|
|||
ids
|
||||
end
|
||||
end
|
||||
|
||||
on(:site_setting_changed) do |name, old_val, new_val|
|
||||
next if name != :discourse_post_event_allowed_custom_fields
|
||||
|
||||
previous_fields = old_val.split('|')
|
||||
new_fields = new_val.split('|')
|
||||
removed_fields = previous_fields - new_fields
|
||||
|
||||
next if removed_fields.empty?
|
||||
|
||||
DiscoursePostEvent::Event.all.find_each do |event|
|
||||
removed_fields.each do |field|
|
||||
event.custom_fields.delete(field)
|
||||
end
|
||||
event.save
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
require_relative '../fabricators/event_fabricator'
|
||||
|
||||
describe 'discourse_post_event_allowed_custom_fields' do
|
||||
let(:user_1) { Fabricate(:user, admin: true) }
|
||||
let(:topic_1) { Fabricate(:topic, user: user_1) }
|
||||
let(:post_1) { Fabricate(:post, topic: topic_1) }
|
||||
let(:post_event_1) { Fabricate(:event, post: post_1) }
|
||||
|
||||
before do
|
||||
SiteSetting.discourse_post_event_allowed_custom_fields = 'foo|bar'
|
||||
post_event_1.update!(custom_fields: {})
|
||||
|
||||
SiteSetting.calendar_enabled = true
|
||||
SiteSetting.discourse_post_event_enabled = true
|
||||
end
|
||||
|
||||
it 'removes the key on the custom fields when removing a key from site setting' do
|
||||
post_event_1.update!(custom_fields: { foo: 1, bar: 2 })
|
||||
|
||||
expect(post_event_1.custom_fields['foo']).to eq(1)
|
||||
expect(post_event_1.custom_fields['bar']).to eq(2)
|
||||
|
||||
DiscourseEvent.trigger(:site_setting_changed, :discourse_post_event_allowed_custom_fields, 'foo|bar', 'foo')
|
||||
|
||||
post_event_1.reload
|
||||
|
||||
expect(post_event_1.custom_fields['foo']).to eq(1)
|
||||
expect(post_event_1.custom_fields['bar']).to eq(nil)
|
||||
end
|
||||
|
||||
it 'doesn’t set a not allowed key from site setting' do
|
||||
expect {
|
||||
post_event_1.update!(custom_fields: { baz: 3 })
|
||||
}.to raise_error(ActiveRecord::RecordInvalid)
|
||||
end
|
||||
end
|
|
@ -69,7 +69,8 @@ describe DiscoursePostEvent::Event do
|
|||
expect {
|
||||
Event.create!(id: first_post.id, starts_at: Time.now - 1.day)
|
||||
}.to change {
|
||||
Jobs::DiscoursePostEventEventStarted.jobs.count
|
||||
Jobs::DiscoursePostEventEventStarted.jobs.count +
|
||||
Jobs::DiscoursePostEventEventWillStart.jobs.count
|
||||
}.by(0)
|
||||
end
|
||||
end
|
||||
|
@ -86,13 +87,20 @@ describe DiscoursePostEvent::Event do
|
|||
.with(:discourse_post_event_event_started, event_id: first_post.id)
|
||||
.once
|
||||
|
||||
Jobs
|
||||
.expects(:cancel_scheduled_job)
|
||||
.with(:discourse_post_event_event_will_start, event_id: first_post.id)
|
||||
.once
|
||||
|
||||
Event.create!(id: first_post.id, starts_at: starts_at)
|
||||
|
||||
expect(Jobs::DiscoursePostEventEventStarted.jobs.count).to eq(1)
|
||||
expect(Jobs::DiscoursePostEventEventWillStart.jobs.count).to eq(0)
|
||||
|
||||
Event.find(first_post.id).update!(starts_at: Time.now + 2.hours)
|
||||
|
||||
expect(Jobs::DiscoursePostEventEventStarted.jobs.count).to eq(2)
|
||||
expect(Jobs::DiscoursePostEventEventWillStart.jobs.count).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -130,6 +138,11 @@ describe DiscoursePostEvent::Event do
|
|||
.with(:discourse_post_event_event_started, event_id: first_post.id)
|
||||
.once
|
||||
|
||||
Jobs
|
||||
.expects(:cancel_scheduled_job)
|
||||
.with(:discourse_post_event_event_will_start, event_id: first_post.id)
|
||||
.once
|
||||
|
||||
Event.create!(id: first_post.id, starts_at: Time.now - 1.day, ends_at: Time.now + 12.hours)
|
||||
|
||||
expect(Jobs::DiscoursePostEventEventEnded.jobs.count).to eq(1)
|
||||
|
|
Loading…
Reference in New Issue