feedback_engine 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +27 -0
- data/MIT-LICENSE +20 -0
- data/README.md +265 -0
- data/Rakefile +8 -0
- data/app/assets/feedback_engine/widget.js +368 -0
- data/app/controllers/feedback_engine/application_controller.rb +15 -0
- data/app/controllers/feedback_engine/feedbacks_controller.rb +120 -0
- data/app/helpers/feedback_engine/widget_helper.rb +18 -0
- data/app/models/feedback_engine/application_record.rb +7 -0
- data/app/models/feedback_engine/feedback.rb +30 -0
- data/app/views/feedback_engine/feedbacks/index.html.erb +75 -0
- data/app/views/feedback_engine/feedbacks/show.html.erb +63 -0
- data/app/views/layouts/feedback_engine/application.html.erb +75 -0
- data/config/locales/feedback_engine.ar.yml +23 -0
- data/config/locales/feedback_engine.bg.yml +23 -0
- data/config/locales/feedback_engine.bn.yml +23 -0
- data/config/locales/feedback_engine.de.yml +23 -0
- data/config/locales/feedback_engine.el.yml +23 -0
- data/config/locales/feedback_engine.en.yml +43 -0
- data/config/locales/feedback_engine.es.yml +23 -0
- data/config/locales/feedback_engine.fr.yml +23 -0
- data/config/locales/feedback_engine.hi.yml +23 -0
- data/config/locales/feedback_engine.hr.yml +23 -0
- data/config/locales/feedback_engine.id.yml +23 -0
- data/config/locales/feedback_engine.it.yml +23 -0
- data/config/locales/feedback_engine.ja.yml +23 -0
- data/config/locales/feedback_engine.ko.yml +23 -0
- data/config/locales/feedback_engine.lb.yml +23 -0
- data/config/locales/feedback_engine.nl.yml +23 -0
- data/config/locales/feedback_engine.pl.yml +23 -0
- data/config/locales/feedback_engine.pt.yml +23 -0
- data/config/locales/feedback_engine.ro.yml +23 -0
- data/config/locales/feedback_engine.ru.yml +23 -0
- data/config/locales/feedback_engine.th.yml +23 -0
- data/config/locales/feedback_engine.tr.yml +23 -0
- data/config/locales/feedback_engine.uk.yml +23 -0
- data/config/locales/feedback_engine.ur.yml +23 -0
- data/config/locales/feedback_engine.vi.yml +23 -0
- data/config/locales/feedback_engine.zh-CN.yml +23 -0
- data/config/routes.rb +8 -0
- data/lib/feedback_engine/configuration.rb +87 -0
- data/lib/feedback_engine/engine.rb +13 -0
- data/lib/feedback_engine/version.rb +5 -0
- data/lib/feedback_engine/widget.rb +107 -0
- data/lib/feedback_engine.rb +34 -0
- data/lib/generators/feedback_engine/install/install_generator.rb +41 -0
- data/lib/generators/feedback_engine/install/templates/create_feedback_engine_feedbacks.rb.tt +21 -0
- data/lib/generators/feedback_engine/install/templates/initializer.rb +43 -0
- metadata +110 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: df04e8fe3b41eb7019841bb7badfc9b55702401e84c8a43fd53eb979f130c629
|
|
4
|
+
data.tar.gz: '00149e51b454535b3d4ffb6441bfc2c20c55475f50d2967b25be7070bed57fd4'
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 3b7eed3a13ee10859d286f34feada2b28490b3a8a658c9ee2ab4fd479c69ab4481333ea16c208494f5ee6cd789656baf18b0612d4656267020849acf37d330b0
|
|
7
|
+
data.tar.gz: 18291f5570d23ad44ce193ef0170e30da2b5c594b64af9f04a0ffd764bc6c18026fa3db4c4000d58f2007c1621cc2407b6362391d798a437658b93d8749f415e
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (2026-07-21)
|
|
4
|
+
|
|
5
|
+
Initial release.
|
|
6
|
+
|
|
7
|
+
- Drop-in feedback widget (`<%= feedback_engine_tag %>`): floating button +
|
|
8
|
+
self-styled modal with type, optional section, message, and optional
|
|
9
|
+
screenshots. Plain JavaScript, no build step, CSP-nonce aware, Turbo-safe,
|
|
10
|
+
follows system light/dark appearance and the app's `I18n.locale` (RTL
|
|
11
|
+
supported).
|
|
12
|
+
- `feedback_engine_feedbacks` table with kind, section, message, status
|
|
13
|
+
(open / in_review / resolved), page URL, user agent, and loose author
|
|
14
|
+
attribution.
|
|
15
|
+
- Screenshot uploads via Active Storage with server-side count / size /
|
|
16
|
+
content-type validation (configurable limits).
|
|
17
|
+
- Built-in triage dashboard at the mount path: status tabs with counts, type
|
|
18
|
+
filter, detail view with screenshots, status transitions, delete. Gated by
|
|
19
|
+
`config.authorize_admin` (development-only by default).
|
|
20
|
+
- Configuration hooks: `enabled`, `authorize_admin`, `current_user`,
|
|
21
|
+
`author_label`, `kinds`, `sections`, `screenshots` limits, `show_button`,
|
|
22
|
+
`button_label`, `mount_path`, `on_submit`.
|
|
23
|
+
- `feedback_engine:install` generator (initializer, migration, mount).
|
|
24
|
+
- Widget translations for English plus 25 more languages (ar, bg, bn, de, el,
|
|
25
|
+
es, fr, hi, hr, id, it, ja, ko, lb, nl, pl, pt, ro, ru, th, tr, uk, ur, vi,
|
|
26
|
+
zh-CN), with a parity spec keeping every locale's key set and interpolation
|
|
27
|
+
placeholders in sync.
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright 2026 Yaroslav Shmarov
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# feedback_engine
|
|
2
|
+
|
|
3
|
+
[](https://rubygems.org/gems/feedback_engine)
|
|
4
|
+
[](https://github.com/yshmarov/feedback-engine/actions/workflows/ci.yml)
|
|
5
|
+
[](MIT-LICENSE)
|
|
6
|
+
|
|
7
|
+
In-app product feedback collection for Rails.
|
|
8
|
+
|
|
9
|
+
`feedback_engine` adds a **"Send feedback"** widget to your app — bug reports,
|
|
10
|
+
feature requests, general comments, with optional screenshots — and stores
|
|
11
|
+
every submission in your own database. A minimal built-in dashboard lets you
|
|
12
|
+
browse and triage what users send. No third-party service, no data leaves your
|
|
13
|
+
app.
|
|
14
|
+
|
|
15
|
+
- **Zero UI dependencies.** The widget is plain JavaScript and styles itself.
|
|
16
|
+
No Tailwind, no Stimulus, no importmap, no build step. The dashboard renders
|
|
17
|
+
its own styles too.
|
|
18
|
+
- **One line in your layout.** `<%= feedback_engine_tag %>` and you're
|
|
19
|
+
collecting feedback.
|
|
20
|
+
- **Trigger it your way.** Use the built-in floating button, or hide it and
|
|
21
|
+
open the form from any element with a `data-feedback-engine-open` attribute.
|
|
22
|
+
- **Screenshots included.** Users can attach up to 3 images (via Active
|
|
23
|
+
Storage) — limits are configurable and enforced server-side.
|
|
24
|
+
- **Pluggable gating and attribution.** You decide who can send feedback, who
|
|
25
|
+
can read it, and how a submission is attributed to a user.
|
|
26
|
+
- **Localized.** The widget follows your app's `I18n.locale`; translations ship
|
|
27
|
+
for English plus 25 more languages — Arabic, Bengali, Bulgarian, Chinese
|
|
28
|
+
(Simplified), Croatian, Dutch, French, German, Greek, Hindi, Indonesian,
|
|
29
|
+
Italian, Japanese, Korean, Luxembourgish, Polish, Portuguese, Romanian,
|
|
30
|
+
Russian, Spanish, Thai, Turkish, Ukrainian, Urdu and Vietnamese — with
|
|
31
|
+
English fallbacks for everything else. RTL locales render mirrored.
|
|
32
|
+
|
|
33
|
+
## How it works
|
|
34
|
+
|
|
35
|
+
1. `feedback_engine_tag` renders a floating **Feedback** button (bottom-right).
|
|
36
|
+
2. Clicking it opens a small self-styled modal: type (bug / feature / other),
|
|
37
|
+
an optional section select, a message, and optional screenshots.
|
|
38
|
+
3. The form `POST`s to the mounted engine with the page URL, the user agent,
|
|
39
|
+
and (if you configure it) the current user — stored in the
|
|
40
|
+
`feedback_engine_feedbacks` table.
|
|
41
|
+
4. You browse and triage submissions at the mount path (`/feedback`): status
|
|
42
|
+
tabs (open → in review → resolved), type filter, screenshots inline.
|
|
43
|
+
|
|
44
|
+
## Requirements
|
|
45
|
+
|
|
46
|
+
- Ruby >= 3.2
|
|
47
|
+
- Rails >= 7.1
|
|
48
|
+
- Active Storage (only if you want screenshot uploads)
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
# Gemfile
|
|
54
|
+
gem "feedback_engine"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
bundle install
|
|
59
|
+
bin/rails generate feedback_engine:install
|
|
60
|
+
bin/rails db:migrate
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The generator:
|
|
64
|
+
|
|
65
|
+
- writes `config/initializers/feedback_engine.rb`,
|
|
66
|
+
- creates the `feedback_engine_feedbacks` migration,
|
|
67
|
+
- mounts the engine in `config/routes.rb`:
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
mount FeedbackEngine::Engine => "/feedback"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Then add the widget to your layout, right before `</body>`:
|
|
74
|
+
|
|
75
|
+
```erb
|
|
76
|
+
<%= feedback_engine_tag %>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
> The widget reads the CSRF token from `<meta name="csrf-token">`, which
|
|
80
|
+
> `csrf_meta_tags` in your layout already provides in a standard Rails app.
|
|
81
|
+
|
|
82
|
+
Boot the app and look for the **Feedback** button in the bottom-right corner.
|
|
83
|
+
Submissions appear at [`/feedback`](http://localhost:3000/feedback) (dashboard
|
|
84
|
+
access defaults to development only — see below).
|
|
85
|
+
|
|
86
|
+
## Configuration
|
|
87
|
+
|
|
88
|
+
Everything is optional; the defaults work out of the box.
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
# config/initializers/feedback_engine.rb
|
|
92
|
+
FeedbackEngine.configure do |config|
|
|
93
|
+
# Who can send feedback. Return false to hide the widget and reject
|
|
94
|
+
# submissions for this request. Defaults to everyone.
|
|
95
|
+
config.enabled = ->(request) { true }
|
|
96
|
+
|
|
97
|
+
# Who can browse and triage feedback at the mount path.
|
|
98
|
+
# DEFAULTS TO DEVELOPMENT ONLY — override before deploying.
|
|
99
|
+
config.authorize_admin = ->(request) { request.env["warden"]&.user&.admin? }
|
|
100
|
+
|
|
101
|
+
# Attribute feedback to a user (optional). Return an object responding to
|
|
102
|
+
# #id, or nil. Receives the request.
|
|
103
|
+
config.current_user = ->(request) { request.env["warden"]&.user }
|
|
104
|
+
|
|
105
|
+
# Label stored for the author and shown in the dashboard.
|
|
106
|
+
config.author_label = ->(user) { user.try(:email) }
|
|
107
|
+
|
|
108
|
+
# Feedback types users can pick from. Labels come from I18n
|
|
109
|
+
# (feedback_engine.kinds.<kind>).
|
|
110
|
+
config.kinds = %w[bug feature other]
|
|
111
|
+
|
|
112
|
+
# App areas shown as a select in the widget. Empty list hides the select.
|
|
113
|
+
config.sections = ["Dashboard", "Billing", "Settings"]
|
|
114
|
+
|
|
115
|
+
# Screenshot uploads (requires Active Storage).
|
|
116
|
+
config.screenshots = true
|
|
117
|
+
config.max_screenshots = 3
|
|
118
|
+
config.max_screenshot_size = 5.megabytes
|
|
119
|
+
|
|
120
|
+
# Show the floating feedback button. Set false and trigger the form from
|
|
121
|
+
# your own UI instead (see below).
|
|
122
|
+
config.show_button = true
|
|
123
|
+
|
|
124
|
+
# Fixed button text; leave nil to use the localized default.
|
|
125
|
+
config.button_label = nil
|
|
126
|
+
|
|
127
|
+
# Keep in sync with the `mount` in config/routes.rb.
|
|
128
|
+
config.mount_path = "/feedback"
|
|
129
|
+
|
|
130
|
+
# Called with each saved feedback — notify Slack, send an email, etc.
|
|
131
|
+
config.on_submit = ->(feedback) { FeedbackMailer.with(feedback:).new_feedback.deliver_later }
|
|
132
|
+
end
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Opening the form from your own UI
|
|
136
|
+
|
|
137
|
+
Prefer a nav item over the floating button? Add `data-feedback-engine-open` to
|
|
138
|
+
any element and (optionally) hide the button:
|
|
139
|
+
|
|
140
|
+
```ruby
|
|
141
|
+
config.show_button = false # optional
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
```erb
|
|
145
|
+
<button data-feedback-engine-open>Send feedback</button>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Protecting the dashboard
|
|
149
|
+
|
|
150
|
+
The dashboard (index/show/triage) is gated by `config.authorize_admin`, which
|
|
151
|
+
defaults to **development only** so a fresh install can never leak feedback in
|
|
152
|
+
production. Grant access however your app resolves admins:
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
# Devise:
|
|
156
|
+
config.authorize_admin = ->(request) { request.env["warden"]&.user&.admin? }
|
|
157
|
+
|
|
158
|
+
# Basic auth, feature flag, IP allowlist — anything based on the request:
|
|
159
|
+
config.authorize_admin = ->(request) { Flipper.enabled?(:feedback_admin) }
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
You can also wrap the mount in your own routing constraint (the lambda still
|
|
163
|
+
applies on top):
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
authenticate :user, ->(user) { user.admin? } do
|
|
167
|
+
mount FeedbackEngine::Engine => "/feedback"
|
|
168
|
+
end
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Screenshots
|
|
172
|
+
|
|
173
|
+
Uploads use Active Storage: `bin/rails active_storage:install` if you haven't
|
|
174
|
+
already. Limits (`max_screenshots`, `max_screenshot_size`) are enforced
|
|
175
|
+
server-side and shown as a hint in the widget. If Active Storage isn't loaded,
|
|
176
|
+
the upload control simply doesn't render and uploads are rejected.
|
|
177
|
+
|
|
178
|
+
### Notifications
|
|
179
|
+
|
|
180
|
+
`config.on_submit` runs inline after each save — keep it fast or hand off to a
|
|
181
|
+
job:
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
config.on_submit = ->(feedback) do
|
|
185
|
+
SlackNotifier.post("New #{feedback.kind}: #{feedback.message.truncate(100)}")
|
|
186
|
+
end
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Localizing the widget
|
|
190
|
+
|
|
191
|
+
Every string resolves through Rails I18n under the `feedback_engine.*` scope
|
|
192
|
+
and follows the current `I18n.locale`. Missing keys fall back to English. To
|
|
193
|
+
add a language or reword the bundled copy, define the keys in your own locale
|
|
194
|
+
files (yours win over the gem's):
|
|
195
|
+
|
|
196
|
+
```yaml
|
|
197
|
+
# config/locales/nl.yml
|
|
198
|
+
nl:
|
|
199
|
+
feedback_engine:
|
|
200
|
+
button: "Feedback"
|
|
201
|
+
title: "Feedback versturen"
|
|
202
|
+
kinds:
|
|
203
|
+
bug: "Fout melden"
|
|
204
|
+
feature: "Functie aanvragen"
|
|
205
|
+
other: "Overig"
|
|
206
|
+
# …see config/locales/feedback_engine.en.yml for the full key list
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Custom kinds get their labels the same way (`feedback_engine.kinds.<kind>`),
|
|
210
|
+
falling back to `kind.humanize`.
|
|
211
|
+
|
|
212
|
+
### Light / dark / system appearance
|
|
213
|
+
|
|
214
|
+
Both the widget and the dashboard follow the operating-system appearance via
|
|
215
|
+
`prefers-color-scheme` — no configuration needed.
|
|
216
|
+
|
|
217
|
+
## Working with feedback in code
|
|
218
|
+
|
|
219
|
+
Submissions are ordinary records:
|
|
220
|
+
|
|
221
|
+
```ruby
|
|
222
|
+
FeedbackEngine::Feedback.where(status: "open").newest_first.each do |feedback|
|
|
223
|
+
puts "[#{feedback.kind}] #{feedback.message} — #{feedback.author_label}"
|
|
224
|
+
end
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Each row stores `kind`, `section`, `message`, `status` (`open`, `in_review`,
|
|
228
|
+
`resolved`), `page_url`, `user_agent`, and optional `author_id` /
|
|
229
|
+
`author_label`. Screenshots are Active Storage attachments
|
|
230
|
+
(`feedback.screenshots`).
|
|
231
|
+
|
|
232
|
+
## Security
|
|
233
|
+
|
|
234
|
+
- Submission and dashboard access are both gated **on the server** for every
|
|
235
|
+
request; the dashboard denies everything outside development until you
|
|
236
|
+
configure `authorize_admin`.
|
|
237
|
+
- Screenshot count, size, and content type (images only) are validated
|
|
238
|
+
server-side, regardless of what the client claims.
|
|
239
|
+
- The widget code carries the request's Content-Security-Policy nonce (the
|
|
240
|
+
same one `ActionDispatch` emits), so it runs under a nonce-based
|
|
241
|
+
`script-src` policy with no configuration. The runtime config ships as a
|
|
242
|
+
`<script type="application/json">` block (data, not code), so it needs no
|
|
243
|
+
nonce and stays correct across Turbo visits.
|
|
244
|
+
- Author attribution is stored as loose fields (no foreign key into your user
|
|
245
|
+
table), so the gem never couples to your user model.
|
|
246
|
+
|
|
247
|
+
## Turbo
|
|
248
|
+
|
|
249
|
+
Works with Turbo Drive out of the box. Turbo replaces `<body>` on every visit,
|
|
250
|
+
which would take the floating button with it, so the widget registers its
|
|
251
|
+
document-level listeners once and re-renders on `turbo:load`.
|
|
252
|
+
|
|
253
|
+
## Development
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
bin/setup # or: bundle install
|
|
257
|
+
bundle exec rspec
|
|
258
|
+
bundle exec rubocop
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Tests run against a dummy Rails app under `spec/dummy`.
|
|
262
|
+
|
|
263
|
+
## License
|
|
264
|
+
|
|
265
|
+
Released under the [MIT License](MIT-LICENSE).
|