bug_reports_client 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 +14 -0
- data/MIT-LICENSE +20 -0
- data/README.md +281 -0
- data/app/assets/tailwind/bug_reports_client/engine.css +7 -0
- data/app/controllers/bug_reports_client/application_controller.rb +35 -0
- data/app/controllers/bug_reports_client/bug_reports_controller.rb +308 -0
- data/app/controllers/bug_reports_client/webhooks_controller.rb +94 -0
- data/app/helpers/bug_reports_client/bug_reports_helper.rb +61 -0
- data/app/javascript/bug_reports_client/controllers/error_summary_controller.js +14 -0
- data/app/javascript/bug_reports_client/controllers/file_limit_controller.js +31 -0
- data/app/javascript/bug_reports_client/controllers/report_type_controller.js +54 -0
- data/app/javascript/bug_reports_client/controllers/screenshot_dropzone_controller.js +151 -0
- data/app/jobs/bug_reports_client/report_error_job.rb +44 -0
- data/app/models/bug_reports_client/application_record.rb +5 -0
- data/app/models/bug_reports_client/bug_report.rb +74 -0
- data/app/models/bug_reports_client/error_event.rb +46 -0
- data/app/models/concerns/bug_reports_client/reporter.rb +15 -0
- data/app/services/bug_reports_client/api_client.rb +174 -0
- data/app/services/bug_reports_client/description_builder.rb +91 -0
- data/app/views/bug_reports_client/bug_reports/_field_checkbox.html.erb +18 -0
- data/app/views/bug_reports_client/bug_reports/_field_select.html.erb +15 -0
- data/app/views/bug_reports_client/bug_reports/_field_text.html.erb +13 -0
- data/app/views/bug_reports_client/bug_reports/_field_textarea.html.erb +13 -0
- data/app/views/bug_reports_client/bug_reports/_filters.html.erb +31 -0
- data/app/views/bug_reports_client/bug_reports/_form.html.erb +185 -0
- data/app/views/bug_reports_client/bug_reports/_pagination.html.erb +21 -0
- data/app/views/bug_reports_client/bug_reports/_rating_badge.html.erb +11 -0
- data/app/views/bug_reports_client/bug_reports/_related_error.html.erb +20 -0
- data/app/views/bug_reports_client/bug_reports/_status_badge.html.erb +12 -0
- data/app/views/bug_reports_client/bug_reports/_type_badge.html.erb +6 -0
- data/app/views/bug_reports_client/bug_reports/all.html.erb +66 -0
- data/app/views/bug_reports_client/bug_reports/edit.html.erb +40 -0
- data/app/views/bug_reports_client/bug_reports/index.html.erb +104 -0
- data/app/views/bug_reports_client/bug_reports/new.html.erb +33 -0
- data/app/views/bug_reports_client/shared/_after_dismiss.turbo_stream.erb +4 -0
- data/app/views/bug_reports_client/shared/_alerts.html.erb +22 -0
- data/config/form_schema.yml +114 -0
- data/config/importmap.rb +6 -0
- data/config/locales/en.yml +213 -0
- data/config/routes.rb +14 -0
- data/db/migrate/20260721000001_create_bug_reports.rb +32 -0
- data/db/migrate/20260723000002_create_bug_report_error_events.rb +20 -0
- data/db/migrate/20260723000003_add_activity_to_bug_report_error_events.rb +10 -0
- data/lib/bug_reports_client/configuration.rb +121 -0
- data/lib/bug_reports_client/engine.rb +49 -0
- data/lib/bug_reports_client/error_context.rb +31 -0
- data/lib/bug_reports_client/error_reporter.rb +116 -0
- data/lib/bug_reports_client/form_schema.rb +204 -0
- data/lib/bug_reports_client/main_app_routes.rb +68 -0
- data/lib/bug_reports_client/version.rb +3 -0
- data/lib/bug_reports_client.rb +39 -0
- data/lib/generators/bug_reports_client/install/install_generator.rb +39 -0
- data/lib/generators/bug_reports_client/install/templates/AFTER_INSTALL +28 -0
- data/lib/generators/bug_reports_client/install/templates/bug_report_issue.md.example +23 -0
- data/lib/generators/bug_reports_client/install/templates/initializer.rb +31 -0
- data/lib/generators/bug_reports_client/views/views_generator.rb +19 -0
- metadata +134 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<%# Admin-only list of every report from this app, gated by config.admin_check. %>
|
|
2
|
+
<% content_for(:title) { t("bug_reports_client.titles.all") } %>
|
|
3
|
+
|
|
4
|
+
<%# No page container here - the host layout owns page width and padding. %>
|
|
5
|
+
<div>
|
|
6
|
+
<div class="mb-6">
|
|
7
|
+
<div class="flex justify-between items-center">
|
|
8
|
+
<div>
|
|
9
|
+
<h1 class="font-bold text-3xl mb-2 text-slate-900"><%= t("bug_reports_client.all.heading") %></h1>
|
|
10
|
+
<p class="text-sm text-slate-600"><%= t("bug_reports_client.all.subheading") %></p>
|
|
11
|
+
</div>
|
|
12
|
+
<div class="flex items-center gap-2">
|
|
13
|
+
<%= link_to bug_reports_path, class: "inline-flex items-center rounded-lg border border-slate-300 px-3 py-1.5 text-sm font-medium text-slate-700 hover:bg-slate-100", title: t("bug_reports_client.index.heading") do %>
|
|
14
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
15
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
|
16
|
+
</svg>
|
|
17
|
+
<% end %>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<%= render "filters" %>
|
|
23
|
+
|
|
24
|
+
<% if @bug_reports.present? %>
|
|
25
|
+
<div class="bg-slate-50 rounded-xl overflow-hidden border border-slate-200">
|
|
26
|
+
<table class="w-full text-sm text-left">
|
|
27
|
+
<thead class="text-xs text-slate-500 uppercase bg-slate-100 border-b border-slate-200">
|
|
28
|
+
<tr>
|
|
29
|
+
<th scope="col" class="px-6 py-4"><%= t("bug_reports_client.index.table.title") %></th>
|
|
30
|
+
<th scope="col" class="px-6 py-4"><%= t("bug_reports_client.all.reporter") %></th>
|
|
31
|
+
<th scope="col" class="px-6 py-4"><%= t("bug_reports_client.index.table.priority") %></th>
|
|
32
|
+
<th scope="col" class="px-6 py-4"><%= t("bug_reports_client.index.table.status") %></th>
|
|
33
|
+
<th scope="col" class="px-6 py-4"><%= t("bug_reports_client.index.table.submitted") %></th>
|
|
34
|
+
</tr>
|
|
35
|
+
</thead>
|
|
36
|
+
<tbody class="divide-y divide-slate-200">
|
|
37
|
+
<% @bug_reports.each do |report| %>
|
|
38
|
+
<tr class="bg-white hover:bg-slate-50 transition-colors">
|
|
39
|
+
<td class="px-6 py-4 font-medium text-slate-900">
|
|
40
|
+
<div class="flex items-center gap-2">
|
|
41
|
+
<%= render "type_badge", report_type: report.report_type %>
|
|
42
|
+
<%= link_to report.title, edit_bug_report_path(report), class: "text-slate-800 hover:underline" %>
|
|
43
|
+
</div>
|
|
44
|
+
</td>
|
|
45
|
+
<td class="px-6 py-4 text-slate-600">
|
|
46
|
+
<%= BugReportsClient.config.reporter_name_for(report.user).presence || BugReportsClient.config.reporter_email_for(report.user) %>
|
|
47
|
+
</td>
|
|
48
|
+
<td class="px-6 py-4"><%= render "rating_badge", value: report.importance %></td>
|
|
49
|
+
<td class="px-6 py-4"><%= render "status_badge", status: report.status %></td>
|
|
50
|
+
<td class="px-6 py-4 text-slate-500"><%= report.created_at.strftime("%-d %b %Y") %></td>
|
|
51
|
+
</tr>
|
|
52
|
+
<% end %>
|
|
53
|
+
</tbody>
|
|
54
|
+
</table>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<%= render "pagination" %>
|
|
58
|
+
<% else %>
|
|
59
|
+
<div class="bg-slate-50 rounded-xl border border-slate-200 p-12 text-center">
|
|
60
|
+
<h3 class="text-sm font-medium text-slate-900"><%= t("bug_reports_client.index.empty.filtered") %></h3>
|
|
61
|
+
<p class="mt-1 text-sm text-slate-500">
|
|
62
|
+
<%= link_to t("bug_reports_client.index.empty.view_all"), all_bug_reports_path, class: "text-slate-800 hover:underline font-medium" %>
|
|
63
|
+
</p>
|
|
64
|
+
</div>
|
|
65
|
+
<% end %>
|
|
66
|
+
</div>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<% content_for(:title) { @bug_report.title } %>
|
|
2
|
+
|
|
3
|
+
<%# No page container here - the host layout owns page width and padding. %>
|
|
4
|
+
<div>
|
|
5
|
+
<div class="mb-6">
|
|
6
|
+
<div class="flex justify-between items-center">
|
|
7
|
+
<div>
|
|
8
|
+
<h1 class="font-bold text-3xl mb-2 text-slate-900"><%= @bug_report.title %></h1>
|
|
9
|
+
<div class="flex flex-wrap items-center gap-3">
|
|
10
|
+
<%= render "type_badge", report_type: @bug_report.report_type %>
|
|
11
|
+
<%= render "status_badge", status: @bug_report.status %>
|
|
12
|
+
<%= render "rating_badge", value: @bug_report.importance %>
|
|
13
|
+
<span class="text-sm text-slate-500"><%= t("bug_reports_client.edit.submitted_at", timestamp: @bug_report.created_at.strftime("%-d %b %Y at %H:%M")) %></span>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="flex items-center gap-2">
|
|
17
|
+
<%= link_to bug_reports_path, class: "inline-flex items-center rounded-lg border border-slate-300 px-3 py-1.5 text-sm font-medium text-slate-700 hover:bg-slate-100", title: t("bug_reports_client.edit.back") do %>
|
|
18
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
19
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
|
20
|
+
</svg>
|
|
21
|
+
<% end %>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<% unless @bug_report.open? %>
|
|
27
|
+
<div class="bg-green-50 border border-green-200 rounded-lg p-4 mb-6">
|
|
28
|
+
<div class="flex gap-3">
|
|
29
|
+
<div class="flex-shrink-0">
|
|
30
|
+
<svg class="h-5 w-5 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
31
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
32
|
+
</svg>
|
|
33
|
+
</div>
|
|
34
|
+
<p class="text-sm text-green-800"><%= t("bug_reports_client.edit.resolved_notice", type_noun: @bug_report.type_noun) %></p>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
<% end %>
|
|
38
|
+
|
|
39
|
+
<%= render "form", bug_report: @bug_report %>
|
|
40
|
+
</div>
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<% content_for(:title) { t("bug_reports_client.titles.index") } %>
|
|
2
|
+
|
|
3
|
+
<%# No page container here - the host layout owns page width and padding. %>
|
|
4
|
+
<div>
|
|
5
|
+
<div class="mb-6">
|
|
6
|
+
<div class="flex justify-between items-center">
|
|
7
|
+
<div>
|
|
8
|
+
<h1 class="font-bold text-3xl mb-2 text-slate-900"><%= t("bug_reports_client.index.heading") %></h1>
|
|
9
|
+
<p class="text-sm text-slate-600"><%= t("bug_reports_client.index.subheading") %></p>
|
|
10
|
+
</div>
|
|
11
|
+
<div class="flex items-center gap-2">
|
|
12
|
+
<% if bug_reports_admin? %>
|
|
13
|
+
<%= link_to all_bug_reports_path, class: "inline-flex items-center rounded-lg border border-slate-300 px-3 py-1.5 text-sm font-medium text-slate-700 hover:bg-slate-100", title: t("bug_reports_client.index.all_reports") do %>
|
|
14
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
15
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
|
|
16
|
+
</svg>
|
|
17
|
+
<% end %>
|
|
18
|
+
<% end %>
|
|
19
|
+
<%= link_to new_bug_report_path, class: "inline-flex items-center rounded-lg bg-slate-800 px-3 py-1.5 text-sm font-medium text-white hover:bg-slate-700", title: t("bug_reports_client.index.new_report") do %>
|
|
20
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
21
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
|
22
|
+
</svg>
|
|
23
|
+
<% end %>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<%= render "filters" %>
|
|
29
|
+
|
|
30
|
+
<% if @bug_reports.present? %>
|
|
31
|
+
<div class="bg-slate-50 rounded-xl overflow-hidden border border-slate-200">
|
|
32
|
+
<div class="hidden md:block">
|
|
33
|
+
<table class="w-full text-sm text-left">
|
|
34
|
+
<thead class="text-xs text-slate-500 uppercase bg-slate-100 border-b border-slate-200">
|
|
35
|
+
<tr>
|
|
36
|
+
<th scope="col" class="px-6 py-4"><%= t("bug_reports_client.index.table.title") %></th>
|
|
37
|
+
<th scope="col" class="px-6 py-4"><%= t("bug_reports_client.index.table.priority") %></th>
|
|
38
|
+
<th scope="col" class="px-6 py-4"><%= t("bug_reports_client.index.table.status") %></th>
|
|
39
|
+
<th scope="col" class="px-6 py-4"><%= t("bug_reports_client.index.table.submitted") %></th>
|
|
40
|
+
</tr>
|
|
41
|
+
</thead>
|
|
42
|
+
<tbody class="divide-y divide-slate-200">
|
|
43
|
+
<% @bug_reports.each do |report| %>
|
|
44
|
+
<tr class="bg-white hover:bg-slate-50 transition-colors">
|
|
45
|
+
<td class="px-6 py-4 font-medium text-slate-900">
|
|
46
|
+
<div class="flex items-center gap-2">
|
|
47
|
+
<%= render "type_badge", report_type: report.report_type %>
|
|
48
|
+
<%= link_to report.title, edit_bug_report_path(report), class: "text-slate-800 hover:underline" %>
|
|
49
|
+
</div>
|
|
50
|
+
</td>
|
|
51
|
+
<td class="px-6 py-4">
|
|
52
|
+
<%= render "rating_badge", value: report.importance %>
|
|
53
|
+
</td>
|
|
54
|
+
<td class="px-6 py-4">
|
|
55
|
+
<%= render "status_badge", status: report.status %>
|
|
56
|
+
</td>
|
|
57
|
+
<td class="px-6 py-4 text-slate-500">
|
|
58
|
+
<%= report.created_at.strftime("%-d %b %Y") %>
|
|
59
|
+
</td>
|
|
60
|
+
</tr>
|
|
61
|
+
<% end %>
|
|
62
|
+
</tbody>
|
|
63
|
+
</table>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<div class="md:hidden divide-y divide-slate-200">
|
|
67
|
+
<% @bug_reports.each do |report| %>
|
|
68
|
+
<%= link_to edit_bug_report_path(report), class: "block p-4 bg-white hover:bg-slate-50 transition-colors" do %>
|
|
69
|
+
<div class="flex items-start justify-between mb-2">
|
|
70
|
+
<span class="font-medium text-slate-900"><%= report.title %></span>
|
|
71
|
+
<%= render "status_badge", status: report.status %>
|
|
72
|
+
</div>
|
|
73
|
+
<div class="flex flex-wrap items-center gap-2 text-xs text-slate-500">
|
|
74
|
+
<%= render "type_badge", report_type: report.report_type %>
|
|
75
|
+
<%= render "rating_badge", value: report.importance %>
|
|
76
|
+
<span>·</span>
|
|
77
|
+
<span><%= report.created_at.strftime("%-d %b %Y") %></span>
|
|
78
|
+
</div>
|
|
79
|
+
<% end %>
|
|
80
|
+
<% end %>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<%= render "pagination" %>
|
|
85
|
+
<% else %>
|
|
86
|
+
<div class="bg-slate-50 rounded-xl border border-slate-200 p-12 text-center">
|
|
87
|
+
<svg class="mx-auto h-12 w-12 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
88
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
89
|
+
</svg>
|
|
90
|
+
<% if params[:status].present? || params[:report_type].present? %>
|
|
91
|
+
<h3 class="mt-2 text-sm font-medium text-slate-900"><%= t("bug_reports_client.index.empty.filtered") %></h3>
|
|
92
|
+
<p class="mt-1 text-sm text-slate-500">
|
|
93
|
+
<%= link_to t("bug_reports_client.index.empty.view_all"), bug_reports_path, class: "text-slate-800 hover:underline font-medium" %>
|
|
94
|
+
</p>
|
|
95
|
+
<% else %>
|
|
96
|
+
<h3 class="mt-2 text-sm font-medium text-slate-900"><%= t("bug_reports_client.index.empty.none") %></h3>
|
|
97
|
+
<p class="mt-1 text-sm text-slate-500">
|
|
98
|
+
<%= t("bug_reports_client.index.empty.prompt") %>
|
|
99
|
+
<%= link_to t("bug_reports_client.index.empty.submit"), new_bug_report_path, class: "text-slate-800 hover:underline font-medium" %>
|
|
100
|
+
</p>
|
|
101
|
+
<% end %>
|
|
102
|
+
</div>
|
|
103
|
+
<% end %>
|
|
104
|
+
</div>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<% content_for(:title) { t("bug_reports_client.titles.new") } %>
|
|
2
|
+
|
|
3
|
+
<%# No page container here - the host layout owns page width and padding. %>
|
|
4
|
+
<div>
|
|
5
|
+
<div class="mb-6">
|
|
6
|
+
<div class="flex justify-between items-center">
|
|
7
|
+
<div>
|
|
8
|
+
<h1 class="font-bold text-3xl mb-2 text-slate-900"><%= t("bug_reports_client.new.heading") %></h1>
|
|
9
|
+
<p class="text-sm text-slate-600"><%= t("bug_reports_client.new.subheading") %></p>
|
|
10
|
+
</div>
|
|
11
|
+
<div class="flex items-center gap-2">
|
|
12
|
+
<%= link_to bug_reports_path, class: "inline-flex items-center rounded-lg border border-slate-300 px-3 py-1.5 text-sm font-medium text-slate-700 hover:bg-slate-100", title: t("bug_reports_client.new.my_reports") do %>
|
|
13
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
14
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" />
|
|
15
|
+
</svg>
|
|
16
|
+
<% end %>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
|
|
22
|
+
<div class="flex gap-3">
|
|
23
|
+
<div class="flex-shrink-0">
|
|
24
|
+
<svg class="h-5 w-5 text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
25
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
26
|
+
</svg>
|
|
27
|
+
</div>
|
|
28
|
+
<p class="text-sm text-blue-800"><%= t("bug_reports_client.new.intro") %></p>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<%= render "form", bug_report: @bug_report %>
|
|
33
|
+
</div>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<%# Hook rendered into the Turbo Stream response after an alert is dismissed.
|
|
2
|
+
Empty by default - hosts can override this partial (create
|
|
3
|
+
app/views/bug_reports_client/shared/_after_dismiss.turbo_stream.erb) to
|
|
4
|
+
update their own UI, e.g. refresh a notification badge count. %>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<%# Dismissable alerts for resolved reports. Rendered in HOST layouts via the
|
|
2
|
+
bug_report_alerts helper, so routes go through the mount proxy. %>
|
|
3
|
+
<div id="bug_report_alerts" class="mb-6">
|
|
4
|
+
<% resolved_bug_reports.each do |report| %>
|
|
5
|
+
<div id="bug_report_<%= report.id %>" class="flex items-center justify-between p-3 mb-2 border border-green-200 rounded-lg bg-green-50">
|
|
6
|
+
<div class="flex items-center gap-2">
|
|
7
|
+
<svg class="flex-shrink-0 w-4 h-4 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
8
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
9
|
+
</svg>
|
|
10
|
+
<span class="text-sm text-green-800">
|
|
11
|
+
<%= t("bug_reports_client.alerts.resolved_html", type_noun: report.type_noun, title: report.title) %>
|
|
12
|
+
</span>
|
|
13
|
+
</div>
|
|
14
|
+
<%= button_to bug_reports_client.bug_report_path(report),
|
|
15
|
+
method: :patch,
|
|
16
|
+
class: "text-xs text-green-600 hover:text-green-800 font-medium flex-shrink-0 cursor-pointer",
|
|
17
|
+
data: { turbo_stream: true } do %>
|
|
18
|
+
<%= t("bug_reports_client.alerts.dismiss") %>
|
|
19
|
+
<% end %>
|
|
20
|
+
</div>
|
|
21
|
+
<% end %>
|
|
22
|
+
</div>
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Default form definition for bug_reports_client. Host apps can replace this
|
|
2
|
+
# wholesale by creating config/bug_report_form.yml (copied for you by the
|
|
3
|
+
# install generator). Field wording lives in i18n under
|
|
4
|
+
# bug_reports_client.fields.<field>.* unless overridden inline here.
|
|
5
|
+
#
|
|
6
|
+
# Each field: field (snake_case key), type (text|textarea|select|checkbox),
|
|
7
|
+
# required, section (starts a new card), rows (textareas), options (selects -
|
|
8
|
+
# strings, or {value:, label:} pairs when the stored value should differ).
|
|
9
|
+
bug:
|
|
10
|
+
- field: impact
|
|
11
|
+
type: textarea
|
|
12
|
+
required: true
|
|
13
|
+
section: about_the_bug
|
|
14
|
+
|
|
15
|
+
- field: expected_behaviour
|
|
16
|
+
type: textarea
|
|
17
|
+
required: true
|
|
18
|
+
section: behaviour
|
|
19
|
+
|
|
20
|
+
- field: actual_behaviour
|
|
21
|
+
type: textarea
|
|
22
|
+
required: true
|
|
23
|
+
section: behaviour
|
|
24
|
+
|
|
25
|
+
- field: steps_to_reproduce
|
|
26
|
+
type: textarea
|
|
27
|
+
rows: 4
|
|
28
|
+
section: reproduction
|
|
29
|
+
|
|
30
|
+
- field: specific_examples
|
|
31
|
+
type: textarea
|
|
32
|
+
rows: 4
|
|
33
|
+
section: reproduction
|
|
34
|
+
|
|
35
|
+
- field: page_url
|
|
36
|
+
type: text
|
|
37
|
+
section: reproduction
|
|
38
|
+
|
|
39
|
+
- field: browser
|
|
40
|
+
type: select
|
|
41
|
+
options: [Chrome, Safari, Firefox, Edge, Other]
|
|
42
|
+
section: reproduction
|
|
43
|
+
|
|
44
|
+
- field: additional_context
|
|
45
|
+
type: textarea
|
|
46
|
+
section: additional_information
|
|
47
|
+
|
|
48
|
+
feature:
|
|
49
|
+
- field: priority
|
|
50
|
+
type: select
|
|
51
|
+
required: true
|
|
52
|
+
section: your_idea
|
|
53
|
+
options:
|
|
54
|
+
- { value: high, label: "High - would save significant time or unblock something" }
|
|
55
|
+
- { value: medium, label: "Medium - a real improvement, but manageable for now" }
|
|
56
|
+
- { value: low, label: "Low - nice to have, no rush" }
|
|
57
|
+
|
|
58
|
+
- field: problem
|
|
59
|
+
type: textarea
|
|
60
|
+
required: true
|
|
61
|
+
rows: 4
|
|
62
|
+
section: your_idea
|
|
63
|
+
|
|
64
|
+
- field: solution
|
|
65
|
+
type: textarea
|
|
66
|
+
required: true
|
|
67
|
+
rows: 4
|
|
68
|
+
section: your_idea
|
|
69
|
+
|
|
70
|
+
- field: time_per_occurrence
|
|
71
|
+
type: select
|
|
72
|
+
required: true
|
|
73
|
+
section: impact_and_frequency
|
|
74
|
+
options:
|
|
75
|
+
- "Less than 5 minutes"
|
|
76
|
+
- "5-15 minutes"
|
|
77
|
+
- "15-30 minutes"
|
|
78
|
+
- "30 minutes to 1 hour"
|
|
79
|
+
- "1-2 hours"
|
|
80
|
+
- "More than 2 hours"
|
|
81
|
+
- "Not sure"
|
|
82
|
+
|
|
83
|
+
- field: frequency
|
|
84
|
+
type: select
|
|
85
|
+
required: true
|
|
86
|
+
section: impact_and_frequency
|
|
87
|
+
options:
|
|
88
|
+
- "Multiple times a day"
|
|
89
|
+
- "Daily"
|
|
90
|
+
- "A few times a week"
|
|
91
|
+
- "Weekly"
|
|
92
|
+
- "A few times a month"
|
|
93
|
+
- "Monthly"
|
|
94
|
+
- "Rarely"
|
|
95
|
+
|
|
96
|
+
- field: people_affected
|
|
97
|
+
type: text
|
|
98
|
+
section: impact_and_frequency
|
|
99
|
+
|
|
100
|
+
- field: user_story
|
|
101
|
+
type: textarea
|
|
102
|
+
section: success_criteria_and_risks
|
|
103
|
+
|
|
104
|
+
- field: done_criteria
|
|
105
|
+
type: textarea
|
|
106
|
+
section: success_criteria_and_risks
|
|
107
|
+
|
|
108
|
+
- field: edge_cases
|
|
109
|
+
type: textarea
|
|
110
|
+
section: success_criteria_and_risks
|
|
111
|
+
|
|
112
|
+
- field: additional_context
|
|
113
|
+
type: textarea
|
|
114
|
+
section: additional_information
|
data/config/importmap.rb
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Pins the engine's Stimulus controllers into the host importmap. The
|
|
2
|
+
# controllers/ prefix means the host's standard Stimulus loading picks them up
|
|
3
|
+
# automatically with namespaced identifiers (bug-reports-client--*).
|
|
4
|
+
pin_all_from BugReportsClient::Engine.root.join("app/javascript/bug_reports_client/controllers"),
|
|
5
|
+
under: "controllers/bug_reports_client",
|
|
6
|
+
to: "bug_reports_client/controllers"
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# All user-facing wording for bug_reports_client. Host apps override any
|
|
2
|
+
# subset of these keys in their own config/locales files - no view copying
|
|
3
|
+
# needed for wording changes. Field keys mirror the form schema; a custom
|
|
4
|
+
# schema field picks up wording from bug_reports_client.fields.<field>.*.
|
|
5
|
+
en:
|
|
6
|
+
bug_reports_client:
|
|
7
|
+
type_nouns:
|
|
8
|
+
bug: "bug report"
|
|
9
|
+
feature: "feature request"
|
|
10
|
+
|
|
11
|
+
titles:
|
|
12
|
+
new: "Report a Bug or Request a Feature"
|
|
13
|
+
index: "My Reports"
|
|
14
|
+
all: "All Reports"
|
|
15
|
+
|
|
16
|
+
new:
|
|
17
|
+
heading: "Report a Bug or Request a Feature"
|
|
18
|
+
subheading: "We'll create a ticket and notify you when it's resolved."
|
|
19
|
+
intro: "Please fill in as much as you can - it really helps us address your report."
|
|
20
|
+
my_reports: "My Reports"
|
|
21
|
+
|
|
22
|
+
index:
|
|
23
|
+
heading: "My Reports"
|
|
24
|
+
subheading: "Track the status of the bugs and features you've reported."
|
|
25
|
+
new_report: "New report"
|
|
26
|
+
all_reports: "All Reports"
|
|
27
|
+
table:
|
|
28
|
+
title: "Title"
|
|
29
|
+
priority: "Priority"
|
|
30
|
+
status: "Status"
|
|
31
|
+
submitted: "Submitted"
|
|
32
|
+
empty:
|
|
33
|
+
filtered: "No reports match this filter."
|
|
34
|
+
view_all: "View all reports"
|
|
35
|
+
none: "You haven't submitted any reports yet."
|
|
36
|
+
prompt: "Found a bug or have an idea?"
|
|
37
|
+
submit: "Submit a report"
|
|
38
|
+
|
|
39
|
+
all:
|
|
40
|
+
heading: "All Reports"
|
|
41
|
+
subheading: "Every report submitted from this app."
|
|
42
|
+
reporter: "Reporter"
|
|
43
|
+
|
|
44
|
+
edit:
|
|
45
|
+
back: "Back to My Reports"
|
|
46
|
+
submitted_at: "Submitted %{timestamp}"
|
|
47
|
+
resolved_notice: "This %{type_noun} has been resolved. The details below are shown for reference only."
|
|
48
|
+
|
|
49
|
+
form:
|
|
50
|
+
errors_heading: "Please fix the following errors:"
|
|
51
|
+
overview: "Overview"
|
|
52
|
+
report_type:
|
|
53
|
+
label: "What are you reporting?"
|
|
54
|
+
prompt: "Select report type"
|
|
55
|
+
bug: "Bug - something isn't working"
|
|
56
|
+
feature: "Feature request - an idea or improvement"
|
|
57
|
+
bug_title: "Bug"
|
|
58
|
+
bug_description: "Something isn't working"
|
|
59
|
+
feature_title: "Feature request"
|
|
60
|
+
feature_description: "An idea or improvement"
|
|
61
|
+
locked: "The report type can't be changed after submission."
|
|
62
|
+
title:
|
|
63
|
+
label: "What's the short summary?"
|
|
64
|
+
help: "A brief one-line summary."
|
|
65
|
+
bug_placeholder: "e.g. Page not loading on mobile"
|
|
66
|
+
feature_placeholder: "e.g. Add a way to export my data"
|
|
67
|
+
severity:
|
|
68
|
+
label: "How serious is this?"
|
|
69
|
+
prompt: "Select severity"
|
|
70
|
+
low: "Low - minor or cosmetic issue"
|
|
71
|
+
medium: "Medium - it works, but something is off"
|
|
72
|
+
high: "High - a key feature is broken, but there's a workaround"
|
|
73
|
+
critical: "Critical - something important is completely broken"
|
|
74
|
+
related_error:
|
|
75
|
+
label: "Is this about a problem you ran into?"
|
|
76
|
+
prompt: "Not related / not sure"
|
|
77
|
+
help: "We noticed something went wrong for you recently. Linking it helps us find the cause much faster."
|
|
78
|
+
option_with_activity: "Something went wrong while %{activity}"
|
|
79
|
+
option_generic: "Something went wrong"
|
|
80
|
+
screenshots:
|
|
81
|
+
label: "Screenshots or mockups"
|
|
82
|
+
hint: "(optional, up to %{max})"
|
|
83
|
+
replace_notice: "Uploading new images will replace the existing ones."
|
|
84
|
+
drop_prompt: "Drag and drop images here, or click to browse"
|
|
85
|
+
drop_hint: "Up to %{max} images"
|
|
86
|
+
limit: "You can attach a maximum of %{max} images."
|
|
87
|
+
optional: "(optional)"
|
|
88
|
+
select_prompt: "Select an option"
|
|
89
|
+
submit: "Submit Report"
|
|
90
|
+
update: "Update Report"
|
|
91
|
+
submitting: "Submitting..."
|
|
92
|
+
|
|
93
|
+
sections:
|
|
94
|
+
about_the_bug: "About the Bug"
|
|
95
|
+
behaviour: "Expected vs Actual Behaviour"
|
|
96
|
+
reproduction: "Reproduction Details"
|
|
97
|
+
your_idea: "Your Idea"
|
|
98
|
+
impact_and_frequency: "Impact & Frequency"
|
|
99
|
+
success_criteria_and_risks: "Success Criteria & Risks"
|
|
100
|
+
additional_information: "Additional Information"
|
|
101
|
+
|
|
102
|
+
fields:
|
|
103
|
+
impact:
|
|
104
|
+
label: "Who does this affect and why does it matter?"
|
|
105
|
+
placeholder: "e.g. I can't complete my task because the page never finishes loading"
|
|
106
|
+
help: "Help us understand who's affected and the real-world impact."
|
|
107
|
+
expected_behaviour:
|
|
108
|
+
label: "What should happen?"
|
|
109
|
+
placeholder: "e.g. The page should load and show the latest information"
|
|
110
|
+
actual_behaviour:
|
|
111
|
+
label: "What actually happens?"
|
|
112
|
+
placeholder: "e.g. The page shows an error message or stays blank"
|
|
113
|
+
steps_to_reproduce:
|
|
114
|
+
label: "How can we see this?"
|
|
115
|
+
placeholder: "1. Go to the page\n2. Try to use the filter\n3. Notice it doesn't work"
|
|
116
|
+
help: "Numbered steps are ideal, but describe it however makes sense."
|
|
117
|
+
specific_examples:
|
|
118
|
+
label: "Specific examples"
|
|
119
|
+
placeholder: "Any specific records, names, or dates involved"
|
|
120
|
+
help: "Any specific examples that show the problem."
|
|
121
|
+
page_url:
|
|
122
|
+
label: "Page or URL"
|
|
123
|
+
placeholder: "e.g. https://example.com/some/page"
|
|
124
|
+
browser:
|
|
125
|
+
label: "Browser"
|
|
126
|
+
prompt: "Select browser"
|
|
127
|
+
additional_context:
|
|
128
|
+
label: "Anything else?"
|
|
129
|
+
placeholder: "Any other context, links, or related requests"
|
|
130
|
+
priority:
|
|
131
|
+
label: "How important is this to you?"
|
|
132
|
+
prompt: "Select priority"
|
|
133
|
+
problem:
|
|
134
|
+
label: "What problem does this solve?"
|
|
135
|
+
placeholder: "e.g. There's no easy way to see everything in one place, so I keep missing things."
|
|
136
|
+
help: "Tell us about the pain point or frustration this would fix."
|
|
137
|
+
solution:
|
|
138
|
+
label: "What would the ideal solution look like?"
|
|
139
|
+
placeholder: "e.g. Add a page that lists everything with the key details."
|
|
140
|
+
help: "Don't worry about technical details - just describe what you'd want to happen."
|
|
141
|
+
time_per_occurrence:
|
|
142
|
+
label: "How long does it currently take each time?"
|
|
143
|
+
prompt: "Select an estimate"
|
|
144
|
+
frequency:
|
|
145
|
+
label: "How often does this come up?"
|
|
146
|
+
prompt: "Select frequency"
|
|
147
|
+
people_affected:
|
|
148
|
+
label: "How many people would this help?"
|
|
149
|
+
placeholder: "e.g. just me, or lots of users"
|
|
150
|
+
user_story:
|
|
151
|
+
label: "Who benefits and how?"
|
|
152
|
+
placeholder: "e.g. As a user, I want to see everything in one place, so that I never miss anything."
|
|
153
|
+
done_criteria:
|
|
154
|
+
label: "How would we know this is done?"
|
|
155
|
+
placeholder: "- I can see everything on one page\n- Each item shows the key details"
|
|
156
|
+
edge_cases:
|
|
157
|
+
label: "Anything that might complicate this?"
|
|
158
|
+
placeholder: "e.g. What should happen if something is rescheduled or cancelled?"
|
|
159
|
+
|
|
160
|
+
flashes:
|
|
161
|
+
created: "%{type_noun} submitted successfully. We'll notify you when it's resolved."
|
|
162
|
+
updated: "%{type_noun} updated successfully."
|
|
163
|
+
submit_failed: "Unable to submit your report. Please try again later."
|
|
164
|
+
only_open_editable: "Only open %{type_noun}s can be updated."
|
|
165
|
+
dismissed: "%{type_noun} notification dismissed."
|
|
166
|
+
not_permitted: "You do not have permission for this action."
|
|
167
|
+
|
|
168
|
+
alerts:
|
|
169
|
+
resolved_html: "Your %{type_noun} <span class=\"font-medium\">\"%{title}\"</span> has been resolved."
|
|
170
|
+
dismiss: "Dismiss"
|
|
171
|
+
|
|
172
|
+
filters:
|
|
173
|
+
type_label: "Type"
|
|
174
|
+
all: "All"
|
|
175
|
+
open: "Open"
|
|
176
|
+
resolved: "Resolved"
|
|
177
|
+
all_types: "All types"
|
|
178
|
+
bugs: "Bugs"
|
|
179
|
+
features: "Features"
|
|
180
|
+
|
|
181
|
+
badges:
|
|
182
|
+
bug: "Bug"
|
|
183
|
+
feature: "Feature"
|
|
184
|
+
open: "Open"
|
|
185
|
+
resolved: "Resolved"
|
|
186
|
+
|
|
187
|
+
pagination:
|
|
188
|
+
previous: "Previous"
|
|
189
|
+
next: "Next"
|
|
190
|
+
|
|
191
|
+
# Verbs for the human-readable "what you were doing" descriptions built
|
|
192
|
+
# from the controller/action where an error was captured.
|
|
193
|
+
activities:
|
|
194
|
+
index: "viewing"
|
|
195
|
+
show: "viewing"
|
|
196
|
+
new: "adding to"
|
|
197
|
+
create: "saving to"
|
|
198
|
+
edit: "editing"
|
|
199
|
+
update: "saving changes to"
|
|
200
|
+
destroy: "deleting from"
|
|
201
|
+
other: "using"
|
|
202
|
+
|
|
203
|
+
errors:
|
|
204
|
+
required_field: "%{field} can't be blank"
|
|
205
|
+
too_many_screenshots: "You can attach a maximum of %{max} images"
|
|
206
|
+
screenshot_type: "%{filename} is not a supported image type"
|
|
207
|
+
screenshot_size: "%{filename} is too large (maximum %{max_mb} MB)"
|
|
208
|
+
|
|
209
|
+
description:
|
|
210
|
+
severity: "Severity"
|
|
211
|
+
screenshots: "Screenshots"
|
|
212
|
+
reported_by: "Reported by"
|
|
213
|
+
related_error: "Related captured error"
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Engine routes. Hosts mount the engine (usually at /bug_reports), giving:
|
|
2
|
+
# GET /bug_reports - my reports
|
|
3
|
+
# GET /bug_reports/new - the report form
|
|
4
|
+
# GET /bug_reports/all - every report (admins, via config.admin_check)
|
|
5
|
+
# POST /bug_reports/webhook - signed closure callbacks from the API
|
|
6
|
+
BugReportsClient::Engine.routes.draw do
|
|
7
|
+
resources :bug_reports, path: "", only: %i[index new create edit update] do
|
|
8
|
+
collection do
|
|
9
|
+
get :all
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
post "webhook", to: "webhooks#receive"
|
|
14
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Creates the local bug_reports table. Deliberately unnamespaced so an app
|
|
2
|
+
# with an existing bug_reports table (from a bespoke integration) can adopt
|
|
3
|
+
# the engine without moving data - the guard below makes this migration a
|
|
4
|
+
# no-op in that case.
|
|
5
|
+
class CreateBugReports < ActiveRecord::Migration[8.0]
|
|
6
|
+
def change
|
|
7
|
+
return if table_exists?(:bug_reports)
|
|
8
|
+
|
|
9
|
+
create_table :bug_reports do |t|
|
|
10
|
+
t.bigint :user_id, null: false
|
|
11
|
+
t.string :title, null: false
|
|
12
|
+
t.string :status, null: false, default: "open"
|
|
13
|
+
t.string :severity
|
|
14
|
+
t.string :report_type, null: false, default: "bug"
|
|
15
|
+
t.integer :remote_bug_report_id
|
|
16
|
+
t.datetime :dismissed_at
|
|
17
|
+
|
|
18
|
+
# All schema-driven form answers, keyed by field name. jsonb on
|
|
19
|
+
# PostgreSQL, plain json elsewhere (e.g. SQLite in tests).
|
|
20
|
+
if connection.adapter_name.match?(/postg/i)
|
|
21
|
+
t.jsonb :responses, null: false, default: {}
|
|
22
|
+
else
|
|
23
|
+
t.json :responses, null: false, default: {}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
t.timestamps
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
add_index :bug_reports, :user_id
|
|
30
|
+
add_index :bug_reports, :remote_bug_report_id, unique: true
|
|
31
|
+
end
|
|
32
|
+
end
|