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
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
th:
|
|
2
|
+
feedback_engine:
|
|
3
|
+
button: "ข้อเสนอแนะ"
|
|
4
|
+
title: "ส่งข้อเสนอแนะ"
|
|
5
|
+
kind: "ประเภท"
|
|
6
|
+
kinds:
|
|
7
|
+
bug: "รายงานข้อบกพร่อง"
|
|
8
|
+
feature: "ขอฟีเจอร์"
|
|
9
|
+
other: "อื่น ๆ"
|
|
10
|
+
section: "ส่วน"
|
|
11
|
+
section_any: "ทั่วไป"
|
|
12
|
+
message: "ข้อความของคุณ"
|
|
13
|
+
message_placeholder: "บอกเราว่าคุณคิดอย่างไร…"
|
|
14
|
+
screenshots: "ภาพหน้าจอ"
|
|
15
|
+
screenshots_hint: "ไม่บังคับ สูงสุด %{count} ไฟล์ ไฟล์ละ %{size} MB"
|
|
16
|
+
submit: "ส่งข้อเสนอแนะ"
|
|
17
|
+
cancel: "ยกเลิก"
|
|
18
|
+
close: "ปิด"
|
|
19
|
+
thanks: "ขอบคุณสำหรับข้อเสนอแนะ!"
|
|
20
|
+
error_blank: "กรุณากรอกข้อความ"
|
|
21
|
+
error_save: "ส่งไม่สำเร็จ กรุณาลองอีกครั้ง"
|
|
22
|
+
error_too_many: "ภาพหน้าจอมากเกินไป (สูงสุด %{count})"
|
|
23
|
+
error_too_large: "ภาพหน้าจอมีขนาดใหญ่เกินไป (สูงสุด %{size} MB)"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
tr:
|
|
2
|
+
feedback_engine:
|
|
3
|
+
button: "Geri bildirim"
|
|
4
|
+
title: "Geri bildirim gönder"
|
|
5
|
+
kind: "Tür"
|
|
6
|
+
kinds:
|
|
7
|
+
bug: "Hata bildirimi"
|
|
8
|
+
feature: "Özellik isteği"
|
|
9
|
+
other: "Diğer"
|
|
10
|
+
section: "Bölüm"
|
|
11
|
+
section_any: "Genel"
|
|
12
|
+
message: "Mesajınız"
|
|
13
|
+
message_placeholder: "Bize düşüncelerinizi anlatın…"
|
|
14
|
+
screenshots: "Ekran görüntüleri"
|
|
15
|
+
screenshots_hint: "isteğe bağlı, en fazla %{count} dosya, her biri %{size} MB"
|
|
16
|
+
submit: "Geri bildirim gönder"
|
|
17
|
+
cancel: "İptal"
|
|
18
|
+
close: "Kapat"
|
|
19
|
+
thanks: "Geri bildiriminiz için teşekkürler!"
|
|
20
|
+
error_blank: "Lütfen bir mesaj girin."
|
|
21
|
+
error_save: "Gönderilemedi. Lütfen tekrar deneyin."
|
|
22
|
+
error_too_many: "Çok fazla ekran görüntüsü (en fazla %{count})."
|
|
23
|
+
error_too_large: "Bir ekran görüntüsü çok büyük (en fazla %{size} MB)."
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
uk:
|
|
2
|
+
feedback_engine:
|
|
3
|
+
button: "Відгук"
|
|
4
|
+
title: "Надіслати відгук"
|
|
5
|
+
kind: "Тип"
|
|
6
|
+
kinds:
|
|
7
|
+
bug: "Повідомити про помилку"
|
|
8
|
+
feature: "Запропонувати функцію"
|
|
9
|
+
other: "Інше"
|
|
10
|
+
section: "Розділ"
|
|
11
|
+
section_any: "Загальне"
|
|
12
|
+
message: "Ваше повідомлення"
|
|
13
|
+
message_placeholder: "Розкажіть, що у вас на думці…"
|
|
14
|
+
screenshots: "Знімки екрана"
|
|
15
|
+
screenshots_hint: "необов'язково, до %{count} файлів, по %{size} МБ"
|
|
16
|
+
submit: "Надіслати відгук"
|
|
17
|
+
cancel: "Скасувати"
|
|
18
|
+
close: "Закрити"
|
|
19
|
+
thanks: "Дякуємо за ваш відгук!"
|
|
20
|
+
error_blank: "Будь ласка, введіть повідомлення."
|
|
21
|
+
error_save: "Не вдалося надіслати. Спробуйте ще раз."
|
|
22
|
+
error_too_many: "Забагато знімків екрана (макс. %{count})."
|
|
23
|
+
error_too_large: "Знімок екрана завеликий (макс. %{size} МБ)."
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
ur:
|
|
2
|
+
feedback_engine:
|
|
3
|
+
button: "رائے"
|
|
4
|
+
title: "رائے بھیجیں"
|
|
5
|
+
kind: "قسم"
|
|
6
|
+
kinds:
|
|
7
|
+
bug: "خرابی کی اطلاع"
|
|
8
|
+
feature: "فیچر کی درخواست"
|
|
9
|
+
other: "دیگر"
|
|
10
|
+
section: "سیکشن"
|
|
11
|
+
section_any: "عمومی"
|
|
12
|
+
message: "آپ کا پیغام"
|
|
13
|
+
message_placeholder: "ہمیں بتائیں آپ کیا سوچ رہے ہیں…"
|
|
14
|
+
screenshots: "اسکرین شاٹس"
|
|
15
|
+
screenshots_hint: "اختیاری، زیادہ سے زیادہ %{count} فائلیں، ہر ایک %{size} MB"
|
|
16
|
+
submit: "رائے بھیجیں"
|
|
17
|
+
cancel: "منسوخ کریں"
|
|
18
|
+
close: "بند کریں"
|
|
19
|
+
thanks: "آپ کی رائے کا شکریہ!"
|
|
20
|
+
error_blank: "براہ کرم پیغام درج کریں۔"
|
|
21
|
+
error_save: "رائے نہیں بھیجی جا سکی۔ دوبارہ کوشش کریں۔"
|
|
22
|
+
error_too_many: "بہت زیادہ اسکرین شاٹس (زیادہ سے زیادہ %{count})۔"
|
|
23
|
+
error_too_large: "ایک اسکرین شاٹ بہت بڑا ہے (زیادہ سے زیادہ %{size} MB)۔"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
vi:
|
|
2
|
+
feedback_engine:
|
|
3
|
+
button: "Phản hồi"
|
|
4
|
+
title: "Gửi phản hồi"
|
|
5
|
+
kind: "Loại"
|
|
6
|
+
kinds:
|
|
7
|
+
bug: "Báo lỗi"
|
|
8
|
+
feature: "Yêu cầu tính năng"
|
|
9
|
+
other: "Khác"
|
|
10
|
+
section: "Mục"
|
|
11
|
+
section_any: "Chung"
|
|
12
|
+
message: "Tin nhắn của bạn"
|
|
13
|
+
message_placeholder: "Hãy cho chúng tôi biết suy nghĩ của bạn…"
|
|
14
|
+
screenshots: "Ảnh chụp màn hình"
|
|
15
|
+
screenshots_hint: "tùy chọn, tối đa %{count} tệp, mỗi tệp %{size} MB"
|
|
16
|
+
submit: "Gửi phản hồi"
|
|
17
|
+
cancel: "Hủy"
|
|
18
|
+
close: "Đóng"
|
|
19
|
+
thanks: "Cảm ơn phản hồi của bạn!"
|
|
20
|
+
error_blank: "Vui lòng nhập tin nhắn."
|
|
21
|
+
error_save: "Không thể gửi phản hồi. Vui lòng thử lại."
|
|
22
|
+
error_too_many: "Quá nhiều ảnh chụp màn hình (tối đa %{count})."
|
|
23
|
+
error_too_large: "Một ảnh chụp màn hình quá lớn (tối đa %{size} MB)."
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
zh-CN:
|
|
2
|
+
feedback_engine:
|
|
3
|
+
button: "反馈"
|
|
4
|
+
title: "发送反馈"
|
|
5
|
+
kind: "类型"
|
|
6
|
+
kinds:
|
|
7
|
+
bug: "错误报告"
|
|
8
|
+
feature: "功能请求"
|
|
9
|
+
other: "其他"
|
|
10
|
+
section: "板块"
|
|
11
|
+
section_any: "常规"
|
|
12
|
+
message: "您的留言"
|
|
13
|
+
message_placeholder: "告诉我们您的想法……"
|
|
14
|
+
screenshots: "截图"
|
|
15
|
+
screenshots_hint: "可选,最多 %{count} 个文件,每个 %{size} MB"
|
|
16
|
+
submit: "发送反馈"
|
|
17
|
+
cancel: "取消"
|
|
18
|
+
close: "关闭"
|
|
19
|
+
thanks: "感谢您的反馈!"
|
|
20
|
+
error_blank: "请输入留言。"
|
|
21
|
+
error_save: "发送失败,请重试。"
|
|
22
|
+
error_too_many: "截图过多(最多 %{count} 张)。"
|
|
23
|
+
error_too_large: "有一张截图过大(最多 %{size} MB)。"
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FeedbackEngine
|
|
4
|
+
# Host-tunable settings. Everything has a safe default, so a fresh install
|
|
5
|
+
# works with zero configuration; the hooks below let an app decide who can
|
|
6
|
+
# send feedback, who can read it, and how submissions are attributed.
|
|
7
|
+
class Configuration
|
|
8
|
+
# Per-request gate for the widget and the submission endpoint. Return false
|
|
9
|
+
# to hide the widget and reject submissions for this request. Receives the
|
|
10
|
+
# request. Defaults to everyone — feedback collection is meant for real
|
|
11
|
+
# users in production.
|
|
12
|
+
attr_accessor :enabled
|
|
13
|
+
|
|
14
|
+
# Per-request gate for the built-in dashboard (browse and triage feedback).
|
|
15
|
+
# Receives the request. Defaults to development only — override it before
|
|
16
|
+
# deploying, e.g. with an admin check.
|
|
17
|
+
attr_accessor :authorize_admin
|
|
18
|
+
|
|
19
|
+
# Resolve the current user for attribution (optional). Return an object
|
|
20
|
+
# responding to #id, or nil. Receives the request.
|
|
21
|
+
attr_accessor :current_user
|
|
22
|
+
|
|
23
|
+
# Turn a resolved user into a short label stored alongside the feedback and
|
|
24
|
+
# shown in the dashboard. Receives whatever #current_user returned.
|
|
25
|
+
attr_accessor :author_label
|
|
26
|
+
|
|
27
|
+
# The feedback types a user can pick from. Labels resolve through I18n
|
|
28
|
+
# (`feedback_engine.kinds.<kind>`), so you can rename or add kinds freely.
|
|
29
|
+
attr_accessor :kinds
|
|
30
|
+
|
|
31
|
+
# Optional list of app areas ("Billing", "Dashboard", …) shown as a select
|
|
32
|
+
# in the widget. Leave empty to hide the select entirely.
|
|
33
|
+
attr_accessor :sections
|
|
34
|
+
|
|
35
|
+
# Allow screenshot uploads. Requires Active Storage in the host app; the
|
|
36
|
+
# widget hides the upload control when this is false or Active Storage is
|
|
37
|
+
# not set up.
|
|
38
|
+
attr_accessor :screenshots
|
|
39
|
+
|
|
40
|
+
# Upload limits, enforced server-side and mirrored in the widget.
|
|
41
|
+
attr_accessor :max_screenshots, :max_screenshot_size
|
|
42
|
+
|
|
43
|
+
# Show the floating feedback button. Set false to trigger the widget from
|
|
44
|
+
# your own UI instead: any element with a `data-feedback-engine-open`
|
|
45
|
+
# attribute opens the form.
|
|
46
|
+
attr_accessor :show_button
|
|
47
|
+
|
|
48
|
+
# Text on the floating button. Leave nil to use the localized default
|
|
49
|
+
# (the `feedback_engine.button` I18n key).
|
|
50
|
+
attr_accessor :button_label
|
|
51
|
+
|
|
52
|
+
# Where the engine is mounted. The widget posts to
|
|
53
|
+
# "#{mount_path}/feedbacks", so keep this in sync with the `mount` line in
|
|
54
|
+
# your routes.
|
|
55
|
+
attr_accessor :mount_path
|
|
56
|
+
|
|
57
|
+
# Called with each saved feedback — notify Slack, send an email, open a
|
|
58
|
+
# ticket. Runs inline after save; keep it fast or hand off to a job.
|
|
59
|
+
attr_accessor :on_submit
|
|
60
|
+
|
|
61
|
+
def initialize
|
|
62
|
+
@enabled = ->(_request) { true }
|
|
63
|
+
@authorize_admin = ->(_request) { Rails.env.development? }
|
|
64
|
+
@current_user = ->(_request) {}
|
|
65
|
+
@author_label = ->(user) { user.respond_to?(:email) ? user.email : user&.to_s }
|
|
66
|
+
@kinds = %w[bug feature other]
|
|
67
|
+
@sections = []
|
|
68
|
+
@screenshots = true
|
|
69
|
+
@max_screenshots = 3
|
|
70
|
+
@max_screenshot_size = 5 * 1024 * 1024
|
|
71
|
+
@show_button = true
|
|
72
|
+
@button_label = nil
|
|
73
|
+
@mount_path = '/feedback'
|
|
74
|
+
@on_submit = ->(_feedback) {}
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def feedbacks_endpoint
|
|
78
|
+
"#{mount_path.chomp('/')}/feedbacks"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Screenshots need Active Storage — both the config switch and the host
|
|
82
|
+
# actually having it loaded.
|
|
83
|
+
def screenshots_enabled?
|
|
84
|
+
screenshots && defined?(::ActiveStorage) ? true : false
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FeedbackEngine
|
|
4
|
+
class Engine < ::Rails::Engine
|
|
5
|
+
isolate_namespace FeedbackEngine
|
|
6
|
+
|
|
7
|
+
initializer 'feedback_engine.helper' do
|
|
8
|
+
ActiveSupport.on_load(:action_view) do
|
|
9
|
+
include FeedbackEngine::WidgetHelper
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module FeedbackEngine
|
|
6
|
+
# Serves the self-contained browser widget. The JavaScript is plain ES (no
|
|
7
|
+
# framework, no build step) and styles itself inline, so it drops into any
|
|
8
|
+
# Rails app regardless of its CSS or JS setup. It is inlined into the page
|
|
9
|
+
# rather than served as a separate asset to avoid any dependency on the
|
|
10
|
+
# host's asset pipeline.
|
|
11
|
+
module Widget
|
|
12
|
+
SOURCE = File.expand_path('../../app/assets/feedback_engine/widget.js', __dir__)
|
|
13
|
+
|
|
14
|
+
# Right-to-left scripts, so the form renders mirrored for those locales.
|
|
15
|
+
# Matched on the language subtag, so region variants ("ar-EG") count too.
|
|
16
|
+
RTL_LANGUAGES = %w[ar arc ckb dv fa ha he ks ku ps sd ug ur yi].freeze
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
def javascript
|
|
20
|
+
@javascript ||= File.read(SOURCE)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# The two <script> tags the helper renders.
|
|
24
|
+
#
|
|
25
|
+
# The config rides in a `type="application/json"` block: it is *data*,
|
|
26
|
+
# not code, so the browser never executes it and Turbo never tries to
|
|
27
|
+
# re-run it on a soft visit — which means it needs no CSP nonce and the
|
|
28
|
+
# widget can re-read the *current* page's config on every `turbo:load`.
|
|
29
|
+
#
|
|
30
|
+
# `nonce:` stamps only the widget script (the code), so it runs under a
|
|
31
|
+
# nonce-based Content-Security-Policy; pass nil when the app has no nonce.
|
|
32
|
+
def snippet(endpoint:, locale:, nonce: nil)
|
|
33
|
+
config = {
|
|
34
|
+
endpoint: endpoint,
|
|
35
|
+
locale: locale.to_s,
|
|
36
|
+
kinds: kinds,
|
|
37
|
+
sections: FeedbackEngine.config.sections.map(&:to_s),
|
|
38
|
+
screenshots: screenshots,
|
|
39
|
+
showButton: FeedbackEngine.config.show_button ? true : false,
|
|
40
|
+
buttonLabel: FeedbackEngine.config.button_label,
|
|
41
|
+
labels: labels,
|
|
42
|
+
rtl: rtl?(locale)
|
|
43
|
+
}
|
|
44
|
+
# Escape "</" so a value can't close the <script> block early.
|
|
45
|
+
json = config.to_json.gsub('</', '<\/')
|
|
46
|
+
nonce_attr = nonce ? %( nonce="#{nonce}") : ''
|
|
47
|
+
|
|
48
|
+
%(<script type="application/json" data-feedback-engine-config>#{json}</script>) +
|
|
49
|
+
%(<script data-feedback-engine-widget#{nonce_attr}>#{javascript}</script>)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def kinds
|
|
55
|
+
FeedbackEngine.config.kinds.map do |kind|
|
|
56
|
+
{ value: kind.to_s, label: t("kinds.#{kind}", kind.to_s.humanize) }
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def screenshots
|
|
61
|
+
{
|
|
62
|
+
enabled: FeedbackEngine.config.screenshots_enabled?,
|
|
63
|
+
max: FeedbackEngine.config.max_screenshots,
|
|
64
|
+
maxSize: FeedbackEngine.config.max_screenshot_size
|
|
65
|
+
}
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Every user-facing string in the widget, resolved through Rails I18n so
|
|
69
|
+
# the form follows the app's current locale. Each lookup carries an
|
|
70
|
+
# English default, so the widget stays fully worded even when the host is
|
|
71
|
+
# missing a key for the active locale.
|
|
72
|
+
def labels
|
|
73
|
+
{
|
|
74
|
+
button: t(:button, 'Feedback'),
|
|
75
|
+
title: t(:title, 'Send feedback'),
|
|
76
|
+
kind: t(:kind, 'Type'),
|
|
77
|
+
section: t(:section, 'Section'),
|
|
78
|
+
sectionAny: t(:section_any, 'General'),
|
|
79
|
+
message: t(:message, 'Your message'),
|
|
80
|
+
messagePlaceholder: t(:message_placeholder, "Tell us what's on your mind…"),
|
|
81
|
+
screenshots: t(:screenshots, 'Screenshots'),
|
|
82
|
+
screenshotsHint: t(:screenshots_hint, 'optional, up to %{count} files, %{size} MB each',
|
|
83
|
+
count: FeedbackEngine.config.max_screenshots,
|
|
84
|
+
size: FeedbackEngine.config.max_screenshot_size / (1024 * 1024)),
|
|
85
|
+
submit: t(:submit, 'Send feedback'),
|
|
86
|
+
cancel: t(:cancel, 'Cancel'),
|
|
87
|
+
close: t(:close, 'Close'),
|
|
88
|
+
thanks: t(:thanks, 'Thanks for your feedback!'),
|
|
89
|
+
errorBlank: t(:error_blank, 'Please enter a message.'),
|
|
90
|
+
errorSave: t(:error_save, 'Could not send feedback. Please try again.'),
|
|
91
|
+
errorTooMany: t(:error_too_many, 'Too many screenshots (max %{count}).',
|
|
92
|
+
count: FeedbackEngine.config.max_screenshots),
|
|
93
|
+
errorTooLarge: t(:error_too_large, 'A screenshot is too large (max %{size} MB).',
|
|
94
|
+
size: FeedbackEngine.config.max_screenshot_size / (1024 * 1024))
|
|
95
|
+
}
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def t(key, default, **args)
|
|
99
|
+
I18n.t(key, scope: :feedback_engine, default: default, **args)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def rtl?(locale)
|
|
103
|
+
RTL_LANGUAGES.include?(locale.to_s.downcase.split(/[-_]/).first)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'feedback_engine/version'
|
|
4
|
+
require 'feedback_engine/configuration'
|
|
5
|
+
require 'feedback_engine/widget'
|
|
6
|
+
require 'feedback_engine/engine'
|
|
7
|
+
|
|
8
|
+
# In-app product feedback collection for Rails. A drop-in widget lets users
|
|
9
|
+
# send bug reports, feature requests, and general feedback (with screenshots)
|
|
10
|
+
# from any page; submissions land in your own database, with a minimal built-in
|
|
11
|
+
# dashboard to browse and triage them.
|
|
12
|
+
module FeedbackEngine
|
|
13
|
+
class << self
|
|
14
|
+
def config
|
|
15
|
+
@config ||= Configuration.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def configure
|
|
19
|
+
yield config
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Can this request send feedback? Checked on the server for the endpoint
|
|
23
|
+
# and by the helper before rendering the widget.
|
|
24
|
+
def enabled?(request)
|
|
25
|
+
!!config.enabled.call(request)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Can this request browse and triage feedback? Checked by every dashboard
|
|
29
|
+
# action.
|
|
30
|
+
def admin?(request)
|
|
31
|
+
!!config.authorize_admin.call(request)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/generators'
|
|
4
|
+
require 'rails/generators/active_record'
|
|
5
|
+
|
|
6
|
+
module FeedbackEngine
|
|
7
|
+
module Generators
|
|
8
|
+
class InstallGenerator < Rails::Generators::Base
|
|
9
|
+
include ActiveRecord::Generators::Migration
|
|
10
|
+
|
|
11
|
+
source_root File.expand_path('templates', __dir__)
|
|
12
|
+
|
|
13
|
+
desc 'Installs feedback_engine: config initializer, migration, and engine mount.'
|
|
14
|
+
|
|
15
|
+
def create_initializer
|
|
16
|
+
copy_file 'initializer.rb', 'config/initializers/feedback_engine.rb'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def create_feedbacks_migration
|
|
20
|
+
migration_template 'create_feedback_engine_feedbacks.rb.tt',
|
|
21
|
+
'db/migrate/create_feedback_engine_feedbacks.rb'
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def mount_engine
|
|
25
|
+
route %(mount FeedbackEngine::Engine => "/feedback")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def post_install
|
|
29
|
+
say "\nfeedback_engine installed. Run `rails db:migrate`, then add", :green
|
|
30
|
+
say '`<%= feedback_engine_tag %>` before </body> in your layout.'
|
|
31
|
+
say "Browse submissions at /feedback (development only until you set config.authorize_admin).\n"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def migration_version
|
|
37
|
+
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateFeedbackEngineFeedbacks < ActiveRecord::Migration<%= migration_version %>
|
|
4
|
+
def change
|
|
5
|
+
create_table :feedback_engine_feedbacks do |t|
|
|
6
|
+
t.string :kind, null: false, default: 'other'
|
|
7
|
+
t.string :section
|
|
8
|
+
t.text :message, null: false
|
|
9
|
+
t.string :status, null: false, default: 'open'
|
|
10
|
+
t.string :page_url
|
|
11
|
+
t.string :user_agent
|
|
12
|
+
t.string :author_id
|
|
13
|
+
t.string :author_label
|
|
14
|
+
|
|
15
|
+
t.timestamps
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
add_index :feedback_engine_feedbacks, :status
|
|
19
|
+
add_index :feedback_engine_feedbacks, :kind
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
FeedbackEngine.configure do |config|
|
|
4
|
+
# Who can send feedback. Return false to hide the widget and reject
|
|
5
|
+
# submissions for this request. Defaults to everyone.
|
|
6
|
+
# config.enabled = ->(request) { true }
|
|
7
|
+
|
|
8
|
+
# Who can browse and triage feedback at the mount path. Defaults to
|
|
9
|
+
# development only — override before deploying.
|
|
10
|
+
# config.authorize_admin = ->(request) { request.env["warden"]&.user&.admin? }
|
|
11
|
+
|
|
12
|
+
# Attribute feedback to a user (optional). Return an object responding to
|
|
13
|
+
# #id, or nil. Receives the request.
|
|
14
|
+
# config.current_user = ->(request) { request.env["warden"]&.user }
|
|
15
|
+
|
|
16
|
+
# Label stored for the author and shown in the dashboard.
|
|
17
|
+
# config.author_label = ->(user) { user.try(:email) }
|
|
18
|
+
|
|
19
|
+
# Feedback types users can pick from. Labels come from I18n
|
|
20
|
+
# (feedback_engine.kinds.<kind>).
|
|
21
|
+
# config.kinds = %w[bug feature other]
|
|
22
|
+
|
|
23
|
+
# App areas shown as a select in the widget. Empty list hides the select.
|
|
24
|
+
# config.sections = ["Dashboard", "Billing", "Settings"]
|
|
25
|
+
|
|
26
|
+
# Screenshot uploads (requires Active Storage).
|
|
27
|
+
# config.screenshots = true
|
|
28
|
+
# config.max_screenshots = 3
|
|
29
|
+
# config.max_screenshot_size = 5.megabytes
|
|
30
|
+
|
|
31
|
+
# Show the floating feedback button. Set false and add
|
|
32
|
+
# `data-feedback-engine-open` to any element to trigger the form yourself.
|
|
33
|
+
# config.show_button = true
|
|
34
|
+
|
|
35
|
+
# Fixed button text; leave nil to use the localized default.
|
|
36
|
+
# config.button_label = nil
|
|
37
|
+
|
|
38
|
+
# Keep in sync with the `mount` in config/routes.rb.
|
|
39
|
+
# config.mount_path = "/feedback"
|
|
40
|
+
|
|
41
|
+
# Called with each saved feedback — notify Slack, send an email, etc.
|
|
42
|
+
# config.on_submit = ->(feedback) { FeedbackMailer.with(feedback:).new_feedback.deliver_later }
|
|
43
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: feedback_engine
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Yaroslav Shmarov
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rails
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '7.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '7.1'
|
|
26
|
+
description: |
|
|
27
|
+
A mountable Rails engine that adds a "Send feedback" widget to your app —
|
|
28
|
+
bug reports, feature requests, screenshots — and stores submissions in your
|
|
29
|
+
own database. Ships with a minimal built-in dashboard to browse and triage
|
|
30
|
+
what users send. Framework-agnostic: no CSS or JS framework required.
|
|
31
|
+
email:
|
|
32
|
+
- yaroslav.shmarov@clickfunnels.com
|
|
33
|
+
executables: []
|
|
34
|
+
extensions: []
|
|
35
|
+
extra_rdoc_files: []
|
|
36
|
+
files:
|
|
37
|
+
- CHANGELOG.md
|
|
38
|
+
- MIT-LICENSE
|
|
39
|
+
- README.md
|
|
40
|
+
- Rakefile
|
|
41
|
+
- app/assets/feedback_engine/widget.js
|
|
42
|
+
- app/controllers/feedback_engine/application_controller.rb
|
|
43
|
+
- app/controllers/feedback_engine/feedbacks_controller.rb
|
|
44
|
+
- app/helpers/feedback_engine/widget_helper.rb
|
|
45
|
+
- app/models/feedback_engine/application_record.rb
|
|
46
|
+
- app/models/feedback_engine/feedback.rb
|
|
47
|
+
- app/views/feedback_engine/feedbacks/index.html.erb
|
|
48
|
+
- app/views/feedback_engine/feedbacks/show.html.erb
|
|
49
|
+
- app/views/layouts/feedback_engine/application.html.erb
|
|
50
|
+
- config/locales/feedback_engine.ar.yml
|
|
51
|
+
- config/locales/feedback_engine.bg.yml
|
|
52
|
+
- config/locales/feedback_engine.bn.yml
|
|
53
|
+
- config/locales/feedback_engine.de.yml
|
|
54
|
+
- config/locales/feedback_engine.el.yml
|
|
55
|
+
- config/locales/feedback_engine.en.yml
|
|
56
|
+
- config/locales/feedback_engine.es.yml
|
|
57
|
+
- config/locales/feedback_engine.fr.yml
|
|
58
|
+
- config/locales/feedback_engine.hi.yml
|
|
59
|
+
- config/locales/feedback_engine.hr.yml
|
|
60
|
+
- config/locales/feedback_engine.id.yml
|
|
61
|
+
- config/locales/feedback_engine.it.yml
|
|
62
|
+
- config/locales/feedback_engine.ja.yml
|
|
63
|
+
- config/locales/feedback_engine.ko.yml
|
|
64
|
+
- config/locales/feedback_engine.lb.yml
|
|
65
|
+
- config/locales/feedback_engine.nl.yml
|
|
66
|
+
- config/locales/feedback_engine.pl.yml
|
|
67
|
+
- config/locales/feedback_engine.pt.yml
|
|
68
|
+
- config/locales/feedback_engine.ro.yml
|
|
69
|
+
- config/locales/feedback_engine.ru.yml
|
|
70
|
+
- config/locales/feedback_engine.th.yml
|
|
71
|
+
- config/locales/feedback_engine.tr.yml
|
|
72
|
+
- config/locales/feedback_engine.uk.yml
|
|
73
|
+
- config/locales/feedback_engine.ur.yml
|
|
74
|
+
- config/locales/feedback_engine.vi.yml
|
|
75
|
+
- config/locales/feedback_engine.zh-CN.yml
|
|
76
|
+
- config/routes.rb
|
|
77
|
+
- lib/feedback_engine.rb
|
|
78
|
+
- lib/feedback_engine/configuration.rb
|
|
79
|
+
- lib/feedback_engine/engine.rb
|
|
80
|
+
- lib/feedback_engine/version.rb
|
|
81
|
+
- lib/feedback_engine/widget.rb
|
|
82
|
+
- lib/generators/feedback_engine/install/install_generator.rb
|
|
83
|
+
- lib/generators/feedback_engine/install/templates/create_feedback_engine_feedbacks.rb.tt
|
|
84
|
+
- lib/generators/feedback_engine/install/templates/initializer.rb
|
|
85
|
+
homepage: https://github.com/yshmarov/feedback-engine
|
|
86
|
+
licenses:
|
|
87
|
+
- MIT
|
|
88
|
+
metadata:
|
|
89
|
+
source_code_uri: https://github.com/yshmarov/feedback-engine
|
|
90
|
+
changelog_uri: https://github.com/yshmarov/feedback-engine/blob/main/CHANGELOG.md
|
|
91
|
+
rubygems_mfa_required: 'true'
|
|
92
|
+
rdoc_options: []
|
|
93
|
+
require_paths:
|
|
94
|
+
- lib
|
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
96
|
+
requirements:
|
|
97
|
+
- - ">="
|
|
98
|
+
- !ruby/object:Gem::Version
|
|
99
|
+
version: '3.2'
|
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
101
|
+
requirements:
|
|
102
|
+
- - ">="
|
|
103
|
+
- !ruby/object:Gem::Version
|
|
104
|
+
version: '0'
|
|
105
|
+
requirements: []
|
|
106
|
+
rubygems_version: 4.0.6
|
|
107
|
+
specification_version: 4
|
|
108
|
+
summary: 'In-app product feedback collection for Rails: a drop-in widget and a built-in
|
|
109
|
+
triage dashboard.'
|
|
110
|
+
test_files: []
|