decidim 0.20.0 → 0.23.1.rc1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of decidim might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +35 -51
- data/docs/advanced/api.md +15 -0
- data/docs/advanced/authorship.md +1 -2
- data/docs/advanced/components.md +40 -0
- data/docs/advanced/content_blocks.md +5 -3
- data/docs/advanced/data-picker.md +37 -9
- data/docs/advanced/embeddable.md +20 -0
- data/docs/advanced/endorsable.md +113 -0
- data/docs/advanced/fixing_locales.md +88 -0
- data/docs/advanced/how_to_fix_metrics.md +238 -0
- data/docs/advanced/machine_translation_service.md +12 -0
- data/docs/advanced/metrics.md +8 -10
- data/docs/advanced/newsletter_templates.md +64 -0
- data/docs/advanced/notifications.md +114 -0
- data/docs/advanced/permissions.md +23 -0
- data/docs/advanced/profiling.md +43 -0
- data/docs/advanced/releases.md +114 -0
- data/docs/advanced/share_tokens.md +53 -0
- data/docs/advanced/templates.md +56 -0
- data/docs/advanced/testing.md +3 -0
- data/docs/customization/machine_translations.md +30 -0
- data/docs/customization/maps.md +610 -0
- data/docs/customization/views.md +31 -2
- data/docs/development_guide.md +55 -33
- data/docs/getting_started.md +14 -4
- data/docs/manual-installation.md +4 -0
- data/docs/services/elections_bulletin_board.md +38 -0
- data/docs/services/etherpad.md +37 -2
- data/docs/services/maps.md +362 -0
- data/docs/services/social_providers.md +49 -11
- data/lib/decidim/gem_manager.rb +1 -1
- data/lib/decidim/version.rb +1 -1
- metadata +62 -46
- data/docs/services/geocoding.md +0 -35
@@ -0,0 +1,238 @@
|
|
1
|
+
# How to fix metrics
|
2
|
+
|
3
|
+
At the request of some instances, we have analyzed the issues related to metrics and looked for possible solutions.
|
4
|
+
|
5
|
+
## Problems
|
6
|
+
|
7
|
+
We have identified two main problems:
|
8
|
+
|
9
|
+
- Metrics generation crashing, which cause `MetricJob`s to run again and again.
|
10
|
+
- Peaks in generated metrics, sudden changes from day to day when displaying metrics.
|
11
|
+
|
12
|
+
### Metrics generation crashing
|
13
|
+
|
14
|
+
We have identified only one culprit here: "orphans" records, meaning records whose related component or participatory space cannot be found in the database. This is because in a previous decidim release `PartipatorySpaces` could be deleted but they were not deleted properly. So any application that has deleted a participatory space in the past, will probably have unrelated records that will make some metrics calculation crash.
|
15
|
+
|
16
|
+
### Peaks in generated metrics
|
17
|
+
|
18
|
+
If somehow the metrics jobs fail to execute for a period of time, big differences can appear in metrics. So first make sure that you have metrics for every day, if not [generate them](https://github.com/decidim/decidim/blob/master/docs/advanced/metrics.md).
|
19
|
+
|
20
|
+
If you have metrics generated for almost everyday and still see drastic changes from day to day, take into account that changing the visibility of a component or participatory space (making them private or unpublishing them) will naturally cause big differences in generated metrics.
|
21
|
+
|
22
|
+
Finally, if you see that the differences in some days are multiples of a previous generated metric, meaning suddenly you have exactly the double or the triple of a calculated metric, it's very likely that you have duplicate generated metrics. We have only seen this problem with instances using Sidekiq, not Delayed Job. We do not know the cause of this, but it seems to be a known issue [Avoiding duplicate jobs in Sidekiq](https://blog.francium.tech/avoiding-duplicate-jobs-in-sidekiq-dcbb1aca1e20).
|
23
|
+
|
24
|
+
## Solutions
|
25
|
+
|
26
|
+
We cannot offer a definitive solution for duplicate metrics, other than to delete old duplicate metrics and generate them again. If this problem persists, however, consider using Delayed Job.
|
27
|
+
For a given metric type (`rake decidim:metrics:list`) that has duplicates:
|
28
|
+
|
29
|
+
- Option 1: Remove individually each metric record per day.
|
30
|
+
- Option 2: Delete all metric records and recalculate them. [CHANGELOG](https://github.com/decidim/decidim/blob/release/0.18-stable/CHANGELOG.md#participants-metrics) of decidim version 0.18 has an example for "participants".
|
31
|
+
|
32
|
+
For orphan records, you can do the following:
|
33
|
+
|
34
|
+
- Back up the database.
|
35
|
+
- Delete orphan records fromt the console (code is below).
|
36
|
+
- Delete "comments" metrics and recalculate them following the [aforementioned example](https://github.com/decidim/decidim/blob/release/0.18-stable/CHANGELOG.md#participants-metrics).
|
37
|
+
|
38
|
+
### Some queries that may help
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
GROUP_BY_FIELDS= %w(
|
42
|
+
day
|
43
|
+
metric_type
|
44
|
+
decidim_organization_id
|
45
|
+
participatory_space_type
|
46
|
+
participatory_space_id
|
47
|
+
related_object_type
|
48
|
+
related_object_id
|
49
|
+
decidim_category_id).join(', ')
|
50
|
+
|
51
|
+
def remove_duplicates
|
52
|
+
sql= <<~EOSQL.strip
|
53
|
+
DELETE FROM decidim_metrics WHERE decidim_metrics.id NOT IN
|
54
|
+
(SELECT id FROM (
|
55
|
+
SELECT DISTINCT ON (#{GROUP_BY_FIELDS}) * FROM decidim_metrics));
|
56
|
+
EOSQL
|
57
|
+
end
|
58
|
+
|
59
|
+
# DELETE FROM decidim_metrics WHERE decidim_metrics.id NOT IN \n (SELECT id FROM (\n SELECT DISTINCT ON (day, metric_type, decidim_organization_id, participatory_space_type, participatory_space_id, related_object_type, related_object_id, decidim_category_id) * FROM decidim_metrics));
|
60
|
+
def count_duplicates
|
61
|
+
sql= <<~EOSQL.strip
|
62
|
+
SELECT count(1), #{GROUP_BY_FIELDS} FROM decidim_metrics GROUP BY #{GROUP_BY_FIELDS} HAVING COUNT(1) > 1;
|
63
|
+
EOSQL
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
### Delete orphan records
|
68
|
+
|
69
|
+
"proposals", "meetings", "accountability", "debates", "pages", "budgets", "surveys"
|
70
|
+
|
71
|
+
#### Proposals
|
72
|
+
|
73
|
+
Delete proposals whose component does not have a participatory space and delete components of a proposal type that do not have a participatory space
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
Decidim::Component.where(manifest_name: "proposals").find_each(batch_size: 100) { |c|
|
77
|
+
if c.participatory_space.blank?
|
78
|
+
Decidim::Proposals::Proposal.where(component: c).destroy_all
|
79
|
+
c.destroy
|
80
|
+
end
|
81
|
+
}
|
82
|
+
```
|
83
|
+
|
84
|
+
Delete proposals that do not have a component
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
Decidim::Proposals::Proposal.find_each(batch_size: 100) { |proposal|
|
88
|
+
proposal.delete if proposal.component.blank?
|
89
|
+
}
|
90
|
+
````
|
91
|
+
|
92
|
+
#### Meetings
|
93
|
+
|
94
|
+
Delete meetings whose component has no participatory space and delete components of meeting type that do not have a participatory space
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
Decidim::Component.where(manifest_name: "meetings").find_each(batch_size: 100) { |c|
|
98
|
+
if c.participatory_space.blank?
|
99
|
+
Decidim::Meetings::Meeting.where(component: c).destroy_all
|
100
|
+
c.destroy
|
101
|
+
end
|
102
|
+
}
|
103
|
+
```
|
104
|
+
|
105
|
+
Delete meetings that do not have a component
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
Decidim::Meetings::Meeting.find_each(batch_size: 100) { |meeting|
|
109
|
+
meeting.delete if meeting.component.blank?
|
110
|
+
}
|
111
|
+
````
|
112
|
+
|
113
|
+
#### Debates
|
114
|
+
|
115
|
+
Delete debates that its component has no participatory space and the debate components that do not have a participatory space
|
116
|
+
|
117
|
+
```ruby
|
118
|
+
Decidim::Component.where(manifest_name: "debates").find_each(batch_size: 100) { |c|
|
119
|
+
if c.participatory_space.blank?
|
120
|
+
Decidim::Debates::Debate.where(component: c).destroy_all
|
121
|
+
c.destroy
|
122
|
+
end
|
123
|
+
}
|
124
|
+
```
|
125
|
+
|
126
|
+
Destroy debates that do not have a component
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
Decidim::Debates::Debate.find_each(batch_size: 100) { |debate|
|
130
|
+
debate.delete if debate.component.blank?
|
131
|
+
}
|
132
|
+
```
|
133
|
+
|
134
|
+
#### Posts
|
135
|
+
|
136
|
+
Destroy posts whose component has no participatory space and blog components that do not have a participatory space
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
Decidim::Component.where(manifest_name: "blogs").find_each(batch_size: 100) { |c|
|
140
|
+
if c.participatory_space.blank?
|
141
|
+
Decidim::Blogs::Post.where(component: c).destroy_all
|
142
|
+
c.destroy
|
143
|
+
end
|
144
|
+
}
|
145
|
+
```
|
146
|
+
|
147
|
+
Destroy posts that do not have a component
|
148
|
+
|
149
|
+
```ruby
|
150
|
+
Decidim::Blogs::Post.find_each(batch_size: 100) { |post|
|
151
|
+
post.delete if post.component.blank?
|
152
|
+
}
|
153
|
+
```
|
154
|
+
|
155
|
+
#### Accountability
|
156
|
+
|
157
|
+
Destroy results whose component has no participatory space and components of accountability type that do not have a participatory space
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
Decidim::Component.where(manifest_name: "accountability").find_each(batch_size: 100) { |c|
|
161
|
+
if c.participatory_space.blank?
|
162
|
+
Decidim::Accountability::Result.where(component: c).destroy_all
|
163
|
+
c.destroy
|
164
|
+
end
|
165
|
+
}
|
166
|
+
```
|
167
|
+
|
168
|
+
Destroy results that do not have a component
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
Decidim::Accountability::Result.find_each(batch_size: 100) { |result|
|
172
|
+
result.delete if result.component.blank?
|
173
|
+
}
|
174
|
+
```
|
175
|
+
|
176
|
+
#### Pages
|
177
|
+
|
178
|
+
Destroy page components that do not have a participatory space
|
179
|
+
|
180
|
+
```ruby
|
181
|
+
Decidim::Component.where(manifest_name: "pages").find_each(batch_size: 100) { |c|
|
182
|
+
if c.participatory_space.blank?
|
183
|
+
c.destroy
|
184
|
+
end
|
185
|
+
}
|
186
|
+
```
|
187
|
+
|
188
|
+
#### Budgets
|
189
|
+
|
190
|
+
Destroy projects whose component has no participatory space and budget components that do not have a participatory space
|
191
|
+
|
192
|
+
```ruby
|
193
|
+
Decidim::Component.where(manifest_name: "budgets").find_each(batch_size: 100) { |c|
|
194
|
+
if c.participatory_space.blank?
|
195
|
+
Decidim::Budgets::Project.where(component: c).destroy_all
|
196
|
+
c.destroy
|
197
|
+
end
|
198
|
+
}
|
199
|
+
```
|
200
|
+
|
201
|
+
Destroy results that do not have a component
|
202
|
+
|
203
|
+
```ruby
|
204
|
+
Decidim::Budgets::Project.find_each(batch_size: 100) { |project|
|
205
|
+
project.delete if project.component.blank?
|
206
|
+
}
|
207
|
+
```
|
208
|
+
|
209
|
+
#### Surveys
|
210
|
+
|
211
|
+
```ruby
|
212
|
+
Decidim::Component.where(manifest_name: "surveys").find_each(batch_size: 100) { |c|
|
213
|
+
if c.participatory_space.blank?
|
214
|
+
Decidim::Surveys::Survey.where(component: c).destroy_all
|
215
|
+
c.destroy
|
216
|
+
end
|
217
|
+
}
|
218
|
+
```
|
219
|
+
|
220
|
+
Destroy surveys that do not have a component
|
221
|
+
|
222
|
+
```ruby
|
223
|
+
Decidim::Surveys::Survey.find_each(batch_size: 100) { |survey|
|
224
|
+
survey.delete if survey.component.blank?
|
225
|
+
}
|
226
|
+
```
|
227
|
+
|
228
|
+
#### Comments
|
229
|
+
|
230
|
+
Destroy comments whose commentable root is a proposal that does not have a participatory space.
|
231
|
+
|
232
|
+
```ruby
|
233
|
+
proposal_ids = Decidim::Comments::Comment.where(decidim_root_commentable_type: "Decidim::Proposals::Proposal").pluck(:decidim_root_commentable_id)
|
234
|
+
|
235
|
+
proposal_ids_without_space = Decidim::Proposals::Proposal.where(id: proposal_ids).find_all{|p| p.participatory_space.blank? }.pluck(:id)
|
236
|
+
|
237
|
+
Decidim::Comments::Comment.where(decidim_root_commentable_type: "Decidim::Proposals::Proposal", decidim_root_commentable_id: proposal_ids_without_space).destroy_all
|
238
|
+
```
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# Create your own machine translation service
|
2
|
+
|
3
|
+
You can use the `Decidim::Dev::DummyTranslator` service as a base. Any new translator service will need to implement the same API as this class.
|
4
|
+
|
5
|
+
## Integrating with async services
|
6
|
+
|
7
|
+
Some translation services are async, which means that some extra work is needed. This is the main overview:
|
8
|
+
|
9
|
+
- The Translation service will only send the translation request. It should have a way to send what resource, field and target locale are related to that translation.
|
10
|
+
- You'll need to create a custom controller in your application to receive the callback from the translation service when the translation is finished
|
11
|
+
- From that new endpoint, find a way to find the related resource, field and target locale. Then start a `Decidim::MachineTranslationSaveJob` with that data. This job will handle how to save the data in the DB.
|
12
|
+
|
data/docs/advanced/metrics.md
CHANGED
@@ -37,6 +37,7 @@ Metrics calculations must be executed everyday. Some `rake task` have been added
|
|
37
37
|
```ruby
|
38
38
|
bundle exec rake decidim:metrics:list
|
39
39
|
```
|
40
|
+
|
40
41
|
Currently, available metrics are:
|
41
42
|
|
42
43
|
- **users**, created `Users`
|
@@ -66,7 +67,7 @@ Only available for `ParticipatorySpaces` (restricted to `ParticipatoryProcesses`
|
|
66
67
|
|
67
68
|
## Configuration
|
68
69
|
|
69
|
-
- A **crontab** line must be added to your server to maintain them updated daily. You could use [Whenever](https://github.com/javan/whenever) to manage it directly from the APP
|
70
|
+
- A **crontab** line must be added to your server to maintain them updated daily. You could use [Whenever](https://github.com/javan/whenever) to manage it directly from the APP. You probably want to schedule a `bundle exec rake decidim:metrics:all` every night.
|
70
71
|
- An **ActiveJob** queue, like [Sidekiq](https://github.com/mperham/sidekiq) or [DelayedJob](https://github.com/collectiveidea/delayed_job/)
|
71
72
|
|
72
73
|
## Persistence
|
@@ -78,20 +79,17 @@ persist metrics from all times and types.
|
|
78
79
|
The `decidim_metrics` table has the following fields:
|
79
80
|
|
80
81
|
- `day`: the day for which the metric has been computed.
|
81
|
-
- `metric_type`: the type of the metric. One of: users, proposals,
|
82
|
-
accepted_proposals, supports, assemblies.
|
82
|
+
- `metric_type`: the type of the metric. One of: users, proposals, accepted_proposals, supports, assemblies.
|
83
83
|
- `cumulative`: quantity accumulated to day ”day”.
|
84
84
|
- `quantity`: quantity for the current day, ”day”.
|
85
|
-
- `decidim_organization_id`: the FK to the organization to which this Metric
|
86
|
-
belongs to.
|
87
|
-
- `
|
88
|
-
participatory space to which this Metric belongs to, if any.
|
89
|
-
- `related_object_type` + `related_object_id`: the FK to the object to which
|
90
|
-
this Metric belongs to, if any.
|
85
|
+
- `decidim_organization_id`: the FK to the organization to which this Metric belongs to.
|
86
|
+
- `participatory_space_type` + `participatory_space_id`: the FK to the participatory space to which this Metric belongs to, if any.
|
87
|
+
- `related_object_type` + `related_object_id`: the FK to the object to which this Metric belongs to, if any.
|
91
88
|
- `decidim_category_id`: the FK to the category for this Metric, if any.
|
92
89
|
|
93
90
|
Relations around `decidim_metrics` table:
|
94
|
-
|
91
|
+
|
92
|
+
```ascii
|
95
93
|
+------------------------+
|
96
94
|
+--------------+ | ParticipatoryProcesses |
|
97
95
|
| Organization | +----+------------------------+
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# Newsletter templates
|
2
|
+
|
3
|
+
The newsletter templates allow the user to select a template amongst a set of of them, and use it as base to send newsletters. This allow for more customization on newsletter, tematic newsletters, etc.
|
4
|
+
|
5
|
+
Code-wise, they use the content blocks system internally, so [check the docs](https://github.com/decidim/decidim/blob/master/docs/advanced/content_blocks.md) for that section first.
|
6
|
+
|
7
|
+
## Adding a new template
|
8
|
+
|
9
|
+
You'll first need to register the template as a content block, but specifying `:newsletter_template` as its scope:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
Decidim.content_blocks.register(:newsletter_template, :my_template) do |content_block|
|
13
|
+
content_block.cell "decidim/newsletter_templates/my_template"
|
14
|
+
content_block.settings_form_cell = "decidim/newsletter_templates/my_template_settings_form"
|
15
|
+
content_block.public_name_key "decidim.newsletter_templates.my_template.name"
|
16
|
+
|
17
|
+
content_block.images = [
|
18
|
+
{
|
19
|
+
name: :main_image,
|
20
|
+
uploader: "Decidim::NewsletterTemplateImageUploader",
|
21
|
+
preview: -> { ActionController::Base.helpers.asset_path("decidim/placeholder.jpg") }
|
22
|
+
}
|
23
|
+
]
|
24
|
+
|
25
|
+
content_block.settings do |settings|
|
26
|
+
settings.attribute(
|
27
|
+
:body,
|
28
|
+
type: :text,
|
29
|
+
translated: true,
|
30
|
+
preview: -> { ([I18n.t("decidim.newsletter_templates.my_template.body_preview")] * 100).join(" ") }
|
31
|
+
)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
```
|
35
|
+
|
36
|
+
You'll need to add this into an initializer. Note that if you're adding this from a module, then you need to add it from the `engine.rb` file (check the docs for content blocks for more info).
|
37
|
+
|
38
|
+
This is the simplest template. It has a single attribute, in this case a translatable chunk of text. Let's go line by line.
|
39
|
+
|
40
|
+
Inside the block, first we define the path of the cell we'll use to render the email publicly. This cell will receive the `Decidim::ContentBlock` object, which will contain the attributes and any image we have (one in this example). In order to render cells, please note that emails have a very picky HTML syntax, so we suggest using some specialized tools to design the template, export the layout to HTML and render that through the cell. We suggest you make this cell inherit from `Decidim::NewsletterTemplates::BaseCell` for convenience.
|
41
|
+
|
42
|
+
Then we define the cell that will be used to render the form in the admin section. This form needs to show inputs for the attributes defined when registering the template. It will receive the `form` object to render the input fields. We suggest this cell to inherit from `Decidim::NewsletterTemplates::BaseSettingsFormCell`.
|
43
|
+
|
44
|
+
In the third line inside the block we define the I18n path to the public name of the template. This name will serve as identifier for the users who write the newsletters, so be sure to make it descriptive.
|
45
|
+
|
46
|
+
After that we define the images this newsletter supports. We give it a unique name, the class name of the uploader we'll use (the example one is the default one, but you might want to customize this value) and a way to preview this image. This preview image will only be used in the "Preview" page of the template, it's not a fallback. If you want a fallback, please implement it through a custom uploader (see `carrierwave`'s docs for that). There's no limit of the amount of images you add.
|
47
|
+
|
48
|
+
Finally, we define the attributes for the newsletter. In this case we define a body attribute, which is a translatable text. Whether this text require an editor or not will be defined by the settings cell. We also have a way to preview that attribute. There's no limit on the number of attributes you define.
|
49
|
+
|
50
|
+
## Interpolating the recipient name
|
51
|
+
|
52
|
+
Decidim accepts `%{name}` as a placeholder for the recipient name. If you want your template to use it, you'll need to call `parse_interpolations` in your public cell:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
class Decidim::NewsletterTemplates::MyTemplate < Decidim::ViewModel
|
56
|
+
include Decidim::NewslettersHelper
|
57
|
+
|
58
|
+
def body
|
59
|
+
parse_interpolations(uninterpolated_body, recipient_user, newsletter.id)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
The newsletter subject is automatically interpolated.
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# Notifications
|
2
|
+
|
3
|
+
In Decidim, notifications may mean two things:
|
4
|
+
|
5
|
+
- he concept of notifying an event to a user. This is the wider use of "notification".
|
6
|
+
- the notification's participant space, which lists the `Decidim::Notification`s she has received.
|
7
|
+
|
8
|
+
So, in the wider sense, notifications are messages that are sent to the users, admins or participants, when something interesting occurs in the platform.
|
9
|
+
|
10
|
+
Each notification is sent via two communication channels: email and internal notifications.
|
11
|
+
|
12
|
+
## A Decidim Event
|
13
|
+
|
14
|
+
Technically, a Decidim event is nothing but an `ActiveSupport::Notification` with a payload of the form
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
ActiveSupport::Notifications.publish(
|
18
|
+
event,
|
19
|
+
event_class: event_class.name,
|
20
|
+
resource: resource,
|
21
|
+
affected_users: affected_users.uniq.compact,
|
22
|
+
followers: followers.uniq.compact,
|
23
|
+
extra: extra
|
24
|
+
)
|
25
|
+
```
|
26
|
+
|
27
|
+
To publish an event to send a notification, Decidim's `EventManager` should be used:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
# Note the convention between the `event` key, and the `event_class` that will be used later to wrap the payload and be used as the email or notification model.
|
31
|
+
data = {
|
32
|
+
event: "decidim.events.comments.comment_created",
|
33
|
+
event_class: Decidim::Comments::CommentCreatedEvent,
|
34
|
+
resource: comment.root_commentable,
|
35
|
+
extra: {
|
36
|
+
comment_id: comment.id
|
37
|
+
},
|
38
|
+
affected_users: [user1, user2],
|
39
|
+
followers: [user3, user4]
|
40
|
+
}
|
41
|
+
|
42
|
+
Decidim::EventsManager.publish(data)
|
43
|
+
```
|
44
|
+
|
45
|
+
Both, `EmailNotificationGenerator` and `NotificationGenerator` are use the same arguments:
|
46
|
+
|
47
|
+
- **event**: A String with the name of the event.
|
48
|
+
- **event_class**: A class that wraps the event.
|
49
|
+
- **resource**: an instance of a class implementing the `Decidim::Resource` concern.
|
50
|
+
- **followers**: a collection of Users that receive the notification because they're following it.
|
51
|
+
- **affected_users**: a collection of Users that receive the notification because they're affected by it.
|
52
|
+
- **force_send**: boolean indicating if EventPublisherJob should skip the `notifiable?` check it performs before notifying.
|
53
|
+
- **extra**: a Hash with extra information to be included in the notification.
|
54
|
+
|
55
|
+
Again, both generators will check for each user
|
56
|
+
|
57
|
+
- in the *followers* array, if she has the `notification_types` set to "all" or "followed-only".
|
58
|
+
- in the *affected_users* array, if she has the `notification_types` set to "all" or "own-only".
|
59
|
+
|
60
|
+
Event names must start with "decidim.events." (the `event` data key). This way `Decidim::EventPublisherJob` will automatically process them. Otherwise no artifact in Decidim will process them, and will be the developer's responsibility to subscribe to them and process.
|
61
|
+
|
62
|
+
Sometimes, when something that must be notified to users happen, a service is defined to manage the logic involved to decide which events should be published. See for example `Decidim::Comments::NewCommentNotificationCreator`.
|
63
|
+
|
64
|
+
Please refer to [Ruby on Rails Notifications documentation](https://api.rubyonrails.org/classes/ActiveSupport/Notifications.html) if you need to hack the Decidim's events system.
|
65
|
+
|
66
|
+
## How Decidim's `EventPublisherJob` processes the events?
|
67
|
+
|
68
|
+
The `EventPublisherJob` in Decidim's core engine subscribes to all notifications matching the regular expression `/^decidim\.events\./`. This is, starting with "decidim.events.". It will then be invoked when an imaginary event named "decidim.events.harmonica_blues" is published.
|
69
|
+
|
70
|
+
When invoked it simply performs some validations and enqueue an `EmailNotificationGeneratorJob` and a `NotificationGeneratorJob`.
|
71
|
+
|
72
|
+
The validations it performs check if the resource, the component, or the participatory space are published (when the concept applies to the artifact).
|
73
|
+
|
74
|
+
## The \*Event class
|
75
|
+
|
76
|
+
Generates the email and notification messages from the information related with the notification.
|
77
|
+
|
78
|
+
Event classes are subclasses of `Decidim::Events::SimpleEvent`.
|
79
|
+
A subset of the payload of the notification is passed to the event class's constructor:
|
80
|
+
|
81
|
+
- The `resource`
|
82
|
+
- The `event` name
|
83
|
+
- The notified user, either from the `followers` or from the `affected_users` arrays
|
84
|
+
- The `extra` hash, with content specific for the given SimpleEvent subclass
|
85
|
+
- The user_role, either :follower or :affected_user
|
86
|
+
|
87
|
+
With the previous information the event class is able to generate the following contents.
|
88
|
+
|
89
|
+
Developers will be able to customize those messages by adding translations to the `config/locales/en.yml` file of the corresponding module.
|
90
|
+
The keys to be used will have the translation scope corresponding to the event name ("decidim.events.comments.comment_by_followed_user" for example) and the key will be the content to override (email_subject, email_intro, etc.)
|
91
|
+
|
92
|
+
### Email contents
|
93
|
+
|
94
|
+
The following are the parts of the notification email:
|
95
|
+
|
96
|
+
- *email_subject*, to be customized
|
97
|
+
- email_greeting, with a good default, usually there's no need to cusomize it
|
98
|
+
- *email_intro*, to be customized
|
99
|
+
- *resource_text* (optional), rendered `html_safe` if present
|
100
|
+
- *resource_url*, a link to the involved resource if resource_url and resource_title are present
|
101
|
+
- *email_outro*
|
102
|
+
|
103
|
+
All contents except the `email_greeting` use to require customization on each notification.
|
104
|
+
|
105
|
+
### Notification contents
|
106
|
+
|
107
|
+
Only the `notification_title` is generated in the event class. The rest of the contents are produced by the templates from the `resource` and the `notification` objects.
|
108
|
+
|
109
|
+
## Testing notifications
|
110
|
+
|
111
|
+
- Test that the event has been published (usually a command test)
|
112
|
+
- Test the event returns the expected contents for the email and the notification.
|
113
|
+
|
114
|
+
Developers should we aware when adding URLs in the email's content, be sure to use absolute URLs and not relative paths.
|