support_desk 0.1.3 → 0.2.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 +4 -4
- data/CHANGELOG.md +58 -0
- data/README.md +105 -10
- data/Rakefile +16 -0
- data/app/controllers/support_desk/tickets_controller.rb +5 -3
- data/app/helpers/support_desk/engine_helper.rb +7 -1
- data/app/views/support_desk/console/tickets/_context_card.html.erb +5 -0
- data/app/views/support_desk/console/tickets/_new_conversation_form.html.erb +91 -0
- data/app/views/support_desk/console/tickets/_ticket_row.html.erb +8 -0
- data/app/views/support_desk/console/tickets/index.html.erb +14 -2
- data/app/views/support_desk/console/tickets/new.html.erb +25 -0
- data/config/console_routes.rb +1 -1
- data/config/locales/support_desk.console.en.yml +26 -0
- data/config/locales/support_desk.console.es.yml +26 -0
- data/config/locales/support_desk.en.yml +3 -0
- data/config/locales/support_desk.es.yml +6 -0
- data/lib/generators/support_desk/console_generator.rb +1 -1
- data/lib/generators/support_desk/install_generator.rb +14 -0
- data/lib/generators/support_desk/templates/add_opened_by_to_support_desk_tickets.rb.erb +79 -0
- data/lib/generators/support_desk/templates/console/controller.rb.erb +1 -1
- data/lib/generators/support_desk/templates/initializer.rb +30 -0
- data/lib/generators/support_desk/upgrade_generator.rb +56 -0
- data/lib/support_desk/configuration.rb +148 -2
- data/lib/support_desk/console.rb +347 -25
- data/lib/support_desk/console_engine.rb +2 -0
- data/lib/support_desk/console_routes.rb +20 -8
- data/lib/support_desk/context_card.rb +23 -0
- data/lib/support_desk/doctor.rb +51 -1
- data/lib/support_desk/errors.rb +5 -0
- data/lib/support_desk/macros.rb +28 -12
- data/lib/support_desk/models/assignment.rb +3 -1
- data/lib/support_desk/models/concerns/agent.rb +39 -4
- data/lib/support_desk/models/concerns/requester.rb +27 -12
- data/lib/support_desk/models/ticket.rb +464 -100
- data/lib/support_desk/summary.rb +1 -1
- data/lib/support_desk/test_helpers.rb +8 -3
- data/lib/support_desk/version.rb +1 -1
- data/lib/support_desk/wizard.rb +4 -12
- data/lib/support_desk.rb +45 -0
- data/lib/tasks/support_desk.rake +27 -0
- metadata +7 -2
data/lib/support_desk/summary.rb
CHANGED
|
@@ -46,7 +46,7 @@ module SupportDesk
|
|
|
46
46
|
# this speaks the reader's language. Duration#inspect is English
|
|
47
47
|
# whatever the locale, which left one untranslatable string in an
|
|
48
48
|
# otherwise Spanish console.
|
|
49
|
-
|
|
49
|
+
SupportDesk.humanize_duration(duration)
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
# The whole line.
|
|
@@ -6,6 +6,7 @@ module SupportDesk
|
|
|
6
6
|
# include SupportDesk::TestHelpers
|
|
7
7
|
#
|
|
8
8
|
# ticket = open_support_ticket(for: users(:alice), about: rides(:sevilla), message: "No aparece")
|
|
9
|
+
# ticket = open_support_ticket(for: users(:alice), by: users(:lucia), message: "Vimos que…")
|
|
9
10
|
# reply_as users(:lucia), ticket, "Lo miramos"
|
|
10
11
|
# assert_awaiting_requester ticket
|
|
11
12
|
# assert_ticket_event ticket, :handed_off, from: users(:lucia), to: users(:pedro)
|
|
@@ -15,10 +16,14 @@ module SupportDesk
|
|
|
15
16
|
# The assertions read the same way the gem's own suite does, which is the
|
|
16
17
|
# point: your acceptance tests and ours describe the same behaviour.
|
|
17
18
|
module TestHelpers
|
|
18
|
-
# Open a ticket the way a requester would,
|
|
19
|
-
|
|
19
|
+
# Open a ticket the way a requester would — or, with `by:`, the way the
|
|
20
|
+
# desk does when it writes first. Through the public sugar in both cases,
|
|
21
|
+
# so this helper exercises what hosts actually type.
|
|
22
|
+
def open_support_ticket(message: "Necesito ayuda", about: nil, topic: nil, by: nil, **options)
|
|
20
23
|
requester = options.fetch(:for) { raise ArgumentError, "open_support_ticket needs for: a requester" }
|
|
21
|
-
requester.ask_support!(message, about: about, topic: topic)
|
|
24
|
+
return requester.ask_support!(message, about: about, topic: topic) if by.nil? || by == requester
|
|
25
|
+
|
|
26
|
+
by.open_support_conversation_with!(requester, message, about: about, topic: topic)
|
|
22
27
|
end
|
|
23
28
|
|
|
24
29
|
# Answer as an agent. Returns the Chats::Message.
|
data/lib/support_desk/version.rb
CHANGED
data/lib/support_desk/wizard.rb
CHANGED
|
@@ -188,18 +188,10 @@ module SupportDesk
|
|
|
188
188
|
# The promise as a Duration, for hosts that want to phrase it themselves.
|
|
189
189
|
def promise_within = target_desk.config.reply_within
|
|
190
190
|
|
|
191
|
-
# "1 day", "4 horas" —
|
|
192
|
-
#
|
|
193
|
-
#
|
|
194
|
-
def self.humanize_duration(duration)
|
|
195
|
-
return duration.inspect unless defined?(ActionView::Helpers::DateHelper)
|
|
196
|
-
|
|
197
|
-
@duration_words ||= Object.new.extend(ActionView::Helpers::DateHelper)
|
|
198
|
-
words = @duration_words.distance_of_time_in_words(duration.to_i).to_s
|
|
199
|
-
# An app whose locale has no date translations (no rails-i18n) would
|
|
200
|
-
# otherwise show "Translation missing" to a customer.
|
|
201
|
-
words.start_with?("Translation missing") ? duration.inspect : words
|
|
202
|
-
end
|
|
191
|
+
# "1 day", "4 horas" — the summary, the helper and an opening line's
|
|
192
|
+
# `%{reply_within}` all say it too, so it lives on SupportDesk now. Kept
|
|
193
|
+
# here as a delegation: it has been public since 0.1.
|
|
194
|
+
def self.humanize_duration(duration) = SupportDesk.humanize_duration(duration)
|
|
203
195
|
|
|
204
196
|
# The open case this would land in, when the requester already has one
|
|
205
197
|
# about this thing — so the wizard can say "you already have a
|
data/lib/support_desk.rb
CHANGED
|
@@ -158,6 +158,17 @@ module SupportDesk
|
|
|
158
158
|
# Every class that has declared itself able to answer.
|
|
159
159
|
def agent_class_names = @agent_class_names ||= Set.new
|
|
160
160
|
|
|
161
|
+
# The registered classes themselves, for the places that have to restrict
|
|
162
|
+
# a lookup to them — the console's GlobalID allow-lists, where a raw
|
|
163
|
+
# token is an identifier and never an authorization to call `find` on
|
|
164
|
+
# whatever class name it names.
|
|
165
|
+
#
|
|
166
|
+
# Names that no longer resolve are dropped rather than raised on: the
|
|
167
|
+
# registries store names so they survive reloads, and a class that went
|
|
168
|
+
# away is simply not one of the classes people can be looked up as.
|
|
169
|
+
def requester_classes = resolve_all(requester_class_names)
|
|
170
|
+
def supportable_classes = resolve_all(supportable_class_names)
|
|
171
|
+
|
|
161
172
|
# Whether +klass+ (a Class, an instance, or a class name) is supportable.
|
|
162
173
|
def supportable_class?(klass) = registered?(supportable_class_names, klass)
|
|
163
174
|
# Whether +klass+ asks for support / answers it. Ancestor-aware, so an
|
|
@@ -165,6 +176,20 @@ module SupportDesk
|
|
|
165
176
|
def requester_class?(klass) = registered?(requester_class_names, klass)
|
|
166
177
|
def agent_class?(klass) = registered?(agent_class_names, klass)
|
|
167
178
|
|
|
179
|
+
# --- Macro conditions -----------------------------------------------------------
|
|
180
|
+
|
|
181
|
+
# Whether +record+ passes an `if:` condition from a macro — nil is always
|
|
182
|
+
# yes. Both `has_support_tickets if:` (may this person ask for help, and
|
|
183
|
+
# be written to) and `acts_as_support_agent if:` (may this person answer)
|
|
184
|
+
# are read through here, so the two conditions can never drift into two
|
|
185
|
+
# meanings of the same option.
|
|
186
|
+
def eligible?(record, condition)
|
|
187
|
+
return true if condition.nil?
|
|
188
|
+
return !!record.public_send(condition) if condition.is_a?(Symbol)
|
|
189
|
+
|
|
190
|
+
!!condition.call(record)
|
|
191
|
+
end
|
|
192
|
+
|
|
168
193
|
# --- The chats seam -------------------------------------------------------------
|
|
169
194
|
|
|
170
195
|
# Subscribe the gem's own `:message_created` listener, which is what
|
|
@@ -257,6 +282,22 @@ module SupportDesk
|
|
|
257
282
|
]
|
|
258
283
|
end
|
|
259
284
|
|
|
285
|
+
# A duration in the reader's own language ("1 día", "4 hours") — the
|
|
286
|
+
# answer promise in the wizard's line, the wait in a console summary, and
|
|
287
|
+
# the `%{reply_within}` an opening line can interpolate.
|
|
288
|
+
#
|
|
289
|
+
# Duration#inspect is English whatever the locale, which is what left one
|
|
290
|
+
# untranslatable string in an otherwise Spanish console.
|
|
291
|
+
def humanize_duration(duration)
|
|
292
|
+
return duration.inspect unless defined?(ActionView::Helpers::DateHelper)
|
|
293
|
+
|
|
294
|
+
@duration_words ||= Object.new.extend(ActionView::Helpers::DateHelper)
|
|
295
|
+
words = @duration_words.distance_of_time_in_words(duration.to_i).to_s
|
|
296
|
+
# An app whose locale has no date translations (no rails-i18n) would
|
|
297
|
+
# otherwise show "Translation missing" to a customer.
|
|
298
|
+
words.start_with?("Translation missing") ? duration.inspect : words
|
|
299
|
+
end
|
|
300
|
+
|
|
260
301
|
# A stable, URL-safe key for an actor (agent, requester, desk), used in
|
|
261
302
|
# cache keys and event payloads. GlobalID params already encode class +
|
|
262
303
|
# id, so two classes can never collide.
|
|
@@ -274,6 +315,10 @@ module SupportDesk
|
|
|
274
315
|
klass
|
|
275
316
|
end
|
|
276
317
|
|
|
318
|
+
def resolve_all(registry)
|
|
319
|
+
registry.filter_map { |name| name.safe_constantize }
|
|
320
|
+
end
|
|
321
|
+
|
|
277
322
|
def registered?(registry, klass)
|
|
278
323
|
klass = klass.class unless klass.is_a?(Class) || klass.is_a?(String)
|
|
279
324
|
name = klass.is_a?(String) ? klass : klass.name
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :support_desk do
|
|
4
|
+
desc "Point every case with no provenance at its requester (the 0.1 → 0.2 catch-up)"
|
|
5
|
+
task backfill_opened_by: :environment do
|
|
6
|
+
# The same UPDATE the 0.2.0 migration runs, for the handover window: a
|
|
7
|
+
# 0.1 process still serving traffic writes NULL provenance after the
|
|
8
|
+
# migration's own backfill, so the catch-up is run once more when those
|
|
9
|
+
# processes are gone.
|
|
10
|
+
#
|
|
11
|
+
# Idempotent, and safe to run at any time, for one reason: 0.2 opens
|
|
12
|
+
# every case as a record (automation openers are refused — see
|
|
13
|
+
# docs/12-open-questions.md Q17), so a NULL `opened_by` can only be a row
|
|
14
|
+
# 0.1 wrote. The day automation lands, NULL becomes legitimate provenance
|
|
15
|
+
# and this task stops being safe — that is part of the work Q17 names.
|
|
16
|
+
relation = SupportDesk::Ticket.where(opened_by_id: nil)
|
|
17
|
+
pending = relation.count
|
|
18
|
+
|
|
19
|
+
if pending.zero?
|
|
20
|
+
puts "[support_desk] every case already says who opened it."
|
|
21
|
+
next
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
updated = relation.update_all("opened_by_type = requester_type, opened_by_id = requester_id")
|
|
25
|
+
puts "[support_desk] #{updated} case(s) now point at their requester."
|
|
26
|
+
end
|
|
27
|
+
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: support_desk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- rameerez
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-09-
|
|
10
|
+
date: 2026-09-18 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: activerecord
|
|
@@ -144,11 +144,13 @@ files:
|
|
|
144
144
|
- app/views/support_desk/console/tickets/_context_card.html.erb
|
|
145
145
|
- app/views/support_desk/console/tickets/_message.html.erb
|
|
146
146
|
- app/views/support_desk/console/tickets/_nav_badge.html.erb
|
|
147
|
+
- app/views/support_desk/console/tickets/_new_conversation_form.html.erb
|
|
147
148
|
- app/views/support_desk/console/tickets/_tabs.html.erb
|
|
148
149
|
- app/views/support_desk/console/tickets/_ticket_row.html.erb
|
|
149
150
|
- app/views/support_desk/console/tickets/_timeline.html.erb
|
|
150
151
|
- app/views/support_desk/console/tickets/_transcript.html.erb
|
|
151
152
|
- app/views/support_desk/console/tickets/index.html.erb
|
|
153
|
+
- app/views/support_desk/console/tickets/new.html.erb
|
|
152
154
|
- app/views/support_desk/console/tickets/show.html.erb
|
|
153
155
|
- app/views/support_desk/tickets/_context_card.html.erb
|
|
154
156
|
- app/views/support_desk/tickets/_door.html.erb
|
|
@@ -172,10 +174,12 @@ files:
|
|
|
172
174
|
- gemfiles/rails_8.1.gemfile
|
|
173
175
|
- lib/generators/support_desk/console_generator.rb
|
|
174
176
|
- lib/generators/support_desk/install_generator.rb
|
|
177
|
+
- lib/generators/support_desk/templates/add_opened_by_to_support_desk_tickets.rb.erb
|
|
175
178
|
- lib/generators/support_desk/templates/console/controller.rb.erb
|
|
176
179
|
- lib/generators/support_desk/templates/console/resource.rb.erb
|
|
177
180
|
- lib/generators/support_desk/templates/create_support_desk_tables.rb.erb
|
|
178
181
|
- lib/generators/support_desk/templates/initializer.rb
|
|
182
|
+
- lib/generators/support_desk/upgrade_generator.rb
|
|
179
183
|
- lib/generators/support_desk/views_generator.rb
|
|
180
184
|
- lib/support_desk.rb
|
|
181
185
|
- lib/support_desk/configuration.rb
|
|
@@ -205,6 +209,7 @@ files:
|
|
|
205
209
|
- lib/support_desk/topic_tree.rb
|
|
206
210
|
- lib/support_desk/version.rb
|
|
207
211
|
- lib/support_desk/wizard.rb
|
|
212
|
+
- lib/tasks/support_desk.rake
|
|
208
213
|
homepage: https://github.com/rameerez/support_desk
|
|
209
214
|
licenses:
|
|
210
215
|
- MIT
|