testimonials 0.9.1 → 1.0.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/AGENTS.md +7 -1
- data/CHANGELOG.md +33 -0
- data/README.md +56 -2
- data/Rakefile +9 -1
- data/SECURITY.md +57 -0
- data/app/controllers/concerns/testimonials/request_context.rb +17 -0
- data/app/controllers/testimonials/media_controller.rb +28 -8
- data/app/controllers/testimonials/nps_controller.rb +1 -0
- data/app/controllers/testimonials/submissions_controller.rb +1 -0
- data/lib/testimonials/version.rb +1 -1
- data/{app/helpers → lib}/testimonials/widget_helper.rb +4 -24
- data/lib/testimonials.rb +1 -0
- metadata +11 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d114855497d2d5186be09b6e76bab5f0d78e079012cfb765dd2399b61271e191
|
|
4
|
+
data.tar.gz: 0c3c906e8dd2aa883cf0261565b2c4f118c0ab8825c06683cbce18f4e59e3a17
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b9a26a9dd5f656c84cf657ed34bda8f130d3783be1efc98987d8bf438abb25728b6c34dc8c62b28e6e39a65a9a117baf411542da563ddfe9549b96edf1e545a9
|
|
7
|
+
data.tar.gz: de494a843ba1747d54a500815eaa2e212b99bc66b5302f6e59aed64f0ea7afba73a1257af1967a5052e441db5a59f6ba56ec9cb578938e18e91450f4d761f88d
|
data/AGENTS.md
CHANGED
|
@@ -5,7 +5,7 @@ Instructions for coding agents. Two audiences:
|
|
|
5
5
|
- **[Installing testimonials into a Rails app](#installing-into-a-rails-app)** — you are working in a host app and were asked to add testimonials, reviews, or NPS.
|
|
6
6
|
- **[Working on the gem itself](#working-on-the-gem-itself)** — you are working in this repository.
|
|
7
7
|
|
|
8
|
-
Requirements: Ruby >= 3.2, Rails >= 7.1. Active Storage only for video and avatar uploads.
|
|
8
|
+
Requirements: Ruby >= 3.2, Rails >= 7.1 and < 9. Active Storage only for video and avatar uploads.
|
|
9
9
|
|
|
10
10
|
If you are in a host app and this file is not in front of you, it ships inside the gem: `cat "$(bundle show testimonials)/AGENTS.md"`.
|
|
11
11
|
|
|
@@ -196,8 +196,14 @@ Turbo Drive and nonce-based CSP work out of the box; the widget config rides in
|
|
|
196
196
|
|
|
197
197
|
## Working on the gem itself
|
|
198
198
|
|
|
199
|
+
The development toolchain is pinned to Ruby 4.0.5 in `mise.toml`. Do not use
|
|
200
|
+
macOS's `/usr/bin/ruby`. In shells, hooks, or automation where mise activation
|
|
201
|
+
is uncertain, explicitly prepend the configured Ruby, for example
|
|
202
|
+
`PATH="$(mise where ruby)/bin:$PATH" bundle exec rake test`.
|
|
203
|
+
|
|
199
204
|
```bash
|
|
200
205
|
bundle exec rake test # minitest, dummy app under test/dummy
|
|
206
|
+
bundle exec rake test:system # real Chrome: NPS, testimonial, moderation, public read
|
|
201
207
|
bundle exec rubocop # must be clean
|
|
202
208
|
BUNDLE_GEMFILE=gemfiles/rails_7.1.gemfile bundle exec rake test # 7.1, 7.2, 8.0, 8.1 in gemfiles/
|
|
203
209
|
```
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.0 - 2026-08-11
|
|
4
|
+
|
|
5
|
+
- **The 1.x record names are settled.** `Testimonials::Testimonial`,
|
|
6
|
+
`Testimonials::NpsResponse`, and `Testimonials::PromptEvent` are now
|
|
7
|
+
documented as the stable model constants rather than being hidden behind a
|
|
8
|
+
generic `Post` name. Their existing optional-install boundaries are
|
|
9
|
+
unchanged.
|
|
10
|
+
- **A real Chrome test now proves the revenue-adjacent path:** an NPS promoter
|
|
11
|
+
is offered the testimonial form, submits text with public consent, lands in
|
|
12
|
+
pending moderation, is approved, and appears through the public read API
|
|
13
|
+
without leaking email. Capybara and Selenium are development/test
|
|
14
|
+
dependencies only.
|
|
15
|
+
- **Uploaded videos, posters, and avatars stay behind the engine gate.** Media
|
|
16
|
+
responses now stream full and byte-range data directly after authorization
|
|
17
|
+
instead of redirecting to a reusable signed Active Storage URL. Code that
|
|
18
|
+
inspected a media response's `Location` header must consume the engine URL
|
|
19
|
+
directly; browser `<video>` and image URLs are unchanged.
|
|
20
|
+
- **Hosts that load Action View from an initializer boot normally.** The widget
|
|
21
|
+
helper is loaded from `lib` before the engine registers its Action View hook;
|
|
22
|
+
`testimonials_tag` and the opener helpers are unchanged.
|
|
23
|
+
- Added a security policy documenting stored testimonial, NPS, prompt-event,
|
|
24
|
+
consent, media, deletion, and retention boundaries. GitHub private
|
|
25
|
+
vulnerability reporting is the disclosure channel. Submitted page URLs now
|
|
26
|
+
accept only HTTP(S) and drop query strings/fragments before storage, keeping
|
|
27
|
+
tokens out of the database and executable schemes out of dashboard links.
|
|
28
|
+
- Supported combinations are Ruby 3.2 through 4.0 and Rails 7.1 through 8.1;
|
|
29
|
+
Rails now has a `< 9` upper bound. Release validation runs the browser test,
|
|
30
|
+
lint and JavaScript checks, builds the gem, verifies RubyGems indexing, and
|
|
31
|
+
creates the GitHub Release.
|
|
32
|
+
- Existing 0.x installs need no migration, initializer change, constant alias,
|
|
33
|
+
or model rename. The new Rails upper bound and non-redirecting media response
|
|
34
|
+
are the only compatibility costs.
|
|
35
|
+
|
|
3
36
|
## 0.9.1
|
|
4
37
|
|
|
5
38
|
- **The demo data now teaches responsible collection.** Its testimonials cover
|
data/README.md
CHANGED
|
@@ -66,7 +66,8 @@ refreshes those records instead of duplicating them.
|
|
|
66
66
|
> The dashboard defaults to **development only**. Set `authorize_admin` before
|
|
67
67
|
> you deploy — see [Configure](#configure).
|
|
68
68
|
|
|
69
|
-
Ruby >= 3.2 · Rails >= 7.1 · Active Storage only if you want
|
|
69
|
+
Ruby >= 3.2 · Rails >= 7.1 and < 9 · Active Storage only if you want
|
|
70
|
+
video/avatar uploads.
|
|
70
71
|
|
|
71
72
|
Installing with a coding agent? Point it at [AGENTS.md](AGENTS.md) — the same
|
|
72
73
|
steps in the order an agent needs them, plus the gates it tends to get wrong
|
|
@@ -240,7 +241,7 @@ playback, feature toggle, and a **Best line** picker.
|
|
|
240
241
|
<img src="https://raw.githubusercontent.com/yshmarov/testimonials/main/docs/screenshots/dashboard-show.jpg" alt="A single testimonial in the dashboard: video playback, best-line picker, attribution metadata, and approve/archive/feature/delete actions" width="620">
|
|
241
242
|
|
|
242
243
|
Every submission keeps its provenance — who sent it, the exact consent text
|
|
243
|
-
they agreed to, the page, locale, browser, timestamp. **Best line** is the only
|
|
244
|
+
they agreed to, the query-free page URL, locale, browser, timestamp. **Best line** is the only
|
|
244
245
|
thing you write: the customer's words stay verbatim, you just choose which
|
|
245
246
|
sentence your landing page pulls out.
|
|
246
247
|
|
|
@@ -255,6 +256,8 @@ Admin-only by default. `config.public_api = true` serves them without auth
|
|
|
255
256
|
(CORS `*`) — ideal for a static marketing site rendering your wall of love at
|
|
256
257
|
build time. **Emails and author ids are never serialized.** Video and avatar
|
|
257
258
|
files go through the same gate, with Range support so `<video>` just works.
|
|
259
|
+
The media response stays on that authorized engine URL with a private,
|
|
260
|
+
`no-store` cache policy; it never redirects to a reusable signed blob URL.
|
|
258
261
|
|
|
259
262
|
## Rendering what you collect
|
|
260
263
|
|
|
@@ -358,6 +361,49 @@ collection — so nothing changes until you set `config.tenant`.
|
|
|
358
361
|
|
|
359
362
|
</details>
|
|
360
363
|
|
|
364
|
+
## Compatibility and public API
|
|
365
|
+
|
|
366
|
+
The following are the model and integration contracts that 1.x will keep
|
|
367
|
+
stable under semantic versioning:
|
|
368
|
+
|
|
369
|
+
- `Testimonials::Testimonial`, including `KINDS`, `STATUSES`, `SOURCES`, its
|
|
370
|
+
persisted fields, attachment predicates, status predicates, `for_tenant`,
|
|
371
|
+
`newest_first`, `approved`, `publishable`, `featured_first`, `display_name`,
|
|
372
|
+
and `quote`.
|
|
373
|
+
- `Testimonials::NpsResponse`, including its persisted fields, `for_tenant`,
|
|
374
|
+
`newest_first`, `promoter?`, `passive?`, `detractor?`, `.score`, and `.band`
|
|
375
|
+
when NPS is installed.
|
|
376
|
+
- `Testimonials::PromptEvent`, including `KINDS`, `ACTIONS`, its persisted
|
|
377
|
+
fields, `.eligible?`, and `.record!` when prompt events are installed.
|
|
378
|
+
- `has_testimonials`, `Testimonials.configure`, `mount_testimonials`,
|
|
379
|
+
`testimonials_tag`, the opener helpers/attributes, and `testimonial_prompt!`.
|
|
380
|
+
- `config.on_submit` and `config.on_detractor`, each receiving the documented
|
|
381
|
+
saved record.
|
|
382
|
+
|
|
383
|
+
Those domain model names are intentional and will not become a generic `Post`
|
|
384
|
+
during 1.x. Engine controllers, partials, CSS classes, generated HTML, and
|
|
385
|
+
widget implementation objects are private. Incompatible changes to the public
|
|
386
|
+
list above wait for a new major version; a deprecation normally ships first.
|
|
387
|
+
|
|
388
|
+
### Upgrading from 0.x
|
|
389
|
+
|
|
390
|
+
Version 1.0.0 keeps the existing domain models and schema. There is no
|
|
391
|
+
migration, initializer change, compatibility alias, or rename: continue using
|
|
392
|
+
`Testimonials::Testimonial`, `Testimonials::NpsResponse`, and
|
|
393
|
+
`Testimonials::PromptEvent`. Rails 9 is excluded until a 1.x release explicitly
|
|
394
|
+
adds it. Media URLs are unchanged, but their responses now contain the gated
|
|
395
|
+
bytes instead of redirecting to an Active Storage URL.
|
|
396
|
+
|
|
397
|
+
## Security and deletion
|
|
398
|
+
|
|
399
|
+
See [SECURITY.md](https://github.com/yshmarov/testimonials/blob/main/SECURITY.md)
|
|
400
|
+
for private vulnerability reporting and the stored-data, consent, media,
|
|
401
|
+
deletion, and retention boundaries. In short: the public API is off by default;
|
|
402
|
+
when enabled it returns only approved, publicly consented records without email
|
|
403
|
+
or author ids. Deleting the live testimonial, NPS, and prompt-event rows is the
|
|
404
|
+
host's responsibility, as are object-store, backup, analytics, cache, and export
|
|
405
|
+
retention policies.
|
|
406
|
+
|
|
361
407
|
## Who's using it
|
|
362
408
|
|
|
363
409
|
- [PaidCollabs](https://paidcollabs.com) — the screenshots above
|
|
@@ -369,9 +415,17 @@ add yourself.
|
|
|
369
415
|
|
|
370
416
|
```bash
|
|
371
417
|
bundle exec rake test
|
|
418
|
+
bundle exec rake test:system
|
|
372
419
|
bundle exec rubocop
|
|
420
|
+
node --check lib/testimonials/widget.js
|
|
421
|
+
node --check lib/testimonials/dashboard.js
|
|
373
422
|
```
|
|
374
423
|
|
|
424
|
+
The default suite covers the Rails engine contract. The system task drives a
|
|
425
|
+
real headless Chrome session from NPS through promoter handoff, testimonial
|
|
426
|
+
submission, moderation, and public read. CI runs Rails 7.1, 7.2, 8.0, and 8.1
|
|
427
|
+
against Ruby 3.2 through 4.0.
|
|
428
|
+
|
|
375
429
|
Bug reports and pull requests welcome. The fastest way to help is to install it
|
|
376
430
|
in a real app and
|
|
377
431
|
[tell me where it hurt](https://github.com/yshmarov/testimonials/issues).
|
data/Rakefile
CHANGED
|
@@ -5,7 +5,15 @@ require 'rake/testtask'
|
|
|
5
5
|
|
|
6
6
|
Rake::TestTask.new(:test) do |t|
|
|
7
7
|
t.libs << 'test'
|
|
8
|
-
t.
|
|
8
|
+
t.test_files = FileList['test/**/*_test.rb'].exclude('test/system/**/*')
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
namespace :test do
|
|
12
|
+
desc 'Run browser (system) tests'
|
|
13
|
+
Rake::TestTask.new(:system) do |task|
|
|
14
|
+
task.libs << 'test'
|
|
15
|
+
task.test_files = FileList['test/system/**/*_test.rb']
|
|
16
|
+
end
|
|
9
17
|
end
|
|
10
18
|
|
|
11
19
|
task default: :test
|
data/SECURITY.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Security policy
|
|
2
|
+
|
|
3
|
+
## Supported versions
|
|
4
|
+
|
|
5
|
+
Security fixes are released for the latest 1.x version. If a report also
|
|
6
|
+
affects the newest 0.x release, a backport may be published when the fix is
|
|
7
|
+
small and the affected application cannot upgrade immediately.
|
|
8
|
+
|
|
9
|
+
## Reporting a vulnerability
|
|
10
|
+
|
|
11
|
+
Please do not open a public issue for a vulnerability. Use
|
|
12
|
+
[GitHub private vulnerability reporting](https://github.com/yshmarov/testimonials/security/advisories/new)
|
|
13
|
+
and include the affected version, a minimal reproduction, impact, and any known
|
|
14
|
+
workaround. Do not include credentials, session cookies, private application
|
|
15
|
+
URLs, customer submissions, uploaded media, or production database contents.
|
|
16
|
+
|
|
17
|
+
Public disclosure should wait until a fixed version is available and affected
|
|
18
|
+
users have had a reasonable opportunity to upgrade.
|
|
19
|
+
|
|
20
|
+
## Data and deployment boundary
|
|
21
|
+
|
|
22
|
+
The public collection pages and widget are controlled by `config.enabled`;
|
|
23
|
+
the dashboard is separately protected by `authorize_admin` and fails closed
|
|
24
|
+
outside development until the host grants access. `config.public_api` is off by
|
|
25
|
+
default. When enabled, it serves only approved testimonials carrying public
|
|
26
|
+
consent and never serializes email or `author_id`.
|
|
27
|
+
|
|
28
|
+
Testimonials can store their text, rating, best line, moderation status,
|
|
29
|
+
consent choice and exact consent-text snapshot, attribution, source, page URL,
|
|
30
|
+
user agent, locale, tenant key, timestamps, and optional video, poster, and
|
|
31
|
+
avatar uploads. Page URLs are accepted only as HTTP(S) and stored without query
|
|
32
|
+
strings or fragments. NPS responses can store the score, comment, attribution,
|
|
33
|
+
the same query-free page URL, user agent, locale, tenant key, and timestamps.
|
|
34
|
+
Prompt events store the prompt kind/action, author id or pseudonymous visitor
|
|
35
|
+
token, tenant key, and timestamps.
|
|
36
|
+
|
|
37
|
+
Uploaded media streams through the engine's authorization gate with private,
|
|
38
|
+
non-stored responses; the route never redirects to a reusable signed blob URL.
|
|
39
|
+
Hosts own the storage service, bucket policy, backups, retention periods,
|
|
40
|
+
access controls, and incident response. Enabling the public API deliberately
|
|
41
|
+
makes approved, publicly consented testimonial media readable to anyone who
|
|
42
|
+
has its engine URL.
|
|
43
|
+
|
|
44
|
+
Delete records when an application policy or customer request requires it:
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
Testimonials::Testimonial.find(id).destroy!
|
|
48
|
+
Testimonials::NpsResponse.find(id).destroy!
|
|
49
|
+
Testimonials::PromptEvent.where(author_id: user.id.to_s).delete_all
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Destroying a testimonial lets Rails apply its configured Active Storage
|
|
53
|
+
attachment lifecycle. Confirm deletion separately in object storage, replicas,
|
|
54
|
+
backups, caches, analytics, notifications, and exports; deleting live database
|
|
55
|
+
rows does not rewrite those systems. Consent is evidence of the choice made at
|
|
56
|
+
submission time, not a substitute for the host's withdrawal and retention
|
|
57
|
+
process.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'uri'
|
|
4
|
+
|
|
3
5
|
module Testimonials
|
|
4
6
|
# Who is asking, which tenant they are in, and the gates that answer both.
|
|
5
7
|
#
|
|
@@ -41,6 +43,21 @@ module Testimonials
|
|
|
41
43
|
Testimonial.for_tenant(current_tenant)
|
|
42
44
|
end
|
|
43
45
|
|
|
46
|
+
# Browser URLs can carry password-reset tokens, signed ids, and campaign
|
|
47
|
+
# details in their query or fragment. Store only a safe HTTP(S) page origin
|
|
48
|
+
# and path; invalid or executable schemes become nil before the dashboard
|
|
49
|
+
# ever renders them as links.
|
|
50
|
+
def clean_page_url(value)
|
|
51
|
+
uri = URI.parse(value.to_s)
|
|
52
|
+
return unless uri.is_a?(URI::HTTP) && uri.host.present? && uri.userinfo.nil?
|
|
53
|
+
|
|
54
|
+
uri.query = nil
|
|
55
|
+
uri.fragment = nil
|
|
56
|
+
uri.to_s
|
|
57
|
+
rescue URI::InvalidURIError
|
|
58
|
+
nil
|
|
59
|
+
end
|
|
60
|
+
|
|
44
61
|
def require_enabled
|
|
45
62
|
head :forbidden unless Testimonials.enabled?(request)
|
|
46
63
|
end
|
|
@@ -5,34 +5,50 @@ module Testimonials
|
|
|
5
5
|
# need to know about Active Storage. Admins can fetch anything; everyone
|
|
6
6
|
# else only reaches media of publishable (approved + consented)
|
|
7
7
|
# testimonials, and only when the read API is public. The actual bytes are
|
|
8
|
-
#
|
|
9
|
-
#
|
|
8
|
+
# streamed here after authorization. Keeping the browser on this route means
|
|
9
|
+
# an Active Storage URL cannot outlive the gate; Range support remains for
|
|
10
|
+
# video playback and seeking.
|
|
10
11
|
class MediaController < ApplicationController
|
|
12
|
+
include ActiveStorage::Streaming if defined?(::ActiveStorage::Streaming)
|
|
13
|
+
|
|
11
14
|
before_action :set_testimonial
|
|
12
15
|
before_action :authorize_media
|
|
13
16
|
|
|
14
17
|
def video
|
|
15
18
|
head :not_found and return unless @testimonial.video_attached?
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
stream(@testimonial.video_file)
|
|
18
21
|
end
|
|
19
22
|
|
|
20
23
|
def poster
|
|
21
24
|
head :not_found and return unless @testimonial.poster_attached?
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
stream(@testimonial.poster)
|
|
24
27
|
end
|
|
25
28
|
|
|
26
29
|
def avatar
|
|
27
30
|
head :not_found and return unless @testimonial.avatar_attached?
|
|
28
31
|
|
|
29
|
-
|
|
32
|
+
stream(@testimonial.avatar)
|
|
30
33
|
end
|
|
31
34
|
|
|
32
35
|
private
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
def stream(attachment)
|
|
38
|
+
blob = attachment.blob
|
|
39
|
+
response.headers['Cache-Control'] = 'private, no-store'
|
|
40
|
+
if request.headers['Range'].present?
|
|
41
|
+
send_blob_byte_range_data(blob, request.headers['Range'], disposition: disposition)
|
|
42
|
+
else
|
|
43
|
+
response.headers['Accept-Ranges'] = 'bytes'
|
|
44
|
+
response.headers['Content-Length'] = blob.byte_size.to_s
|
|
45
|
+
send_blob_stream(blob, disposition: disposition)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# ?download=1 explicitly requests a file download. Without it we request
|
|
50
|
+
# inline playback; Active Storage may still force attachment disposition
|
|
51
|
+
# for content types outside the host's safe-inline allowlist.
|
|
36
52
|
def disposition
|
|
37
53
|
params[:download].present? ? 'attachment' : 'inline'
|
|
38
54
|
end
|
|
@@ -48,7 +64,11 @@ module Testimonials
|
|
|
48
64
|
# Authors can always see their own media — the widget replays their
|
|
49
65
|
# attached video when they edit their review.
|
|
50
66
|
return if current_author_id.present? && @testimonial.author_id == current_author_id.to_s
|
|
51
|
-
|
|
67
|
+
|
|
68
|
+
if Testimonials.config.public_api && @testimonial.approved? && @testimonial.consent_given?
|
|
69
|
+
response.set_header('Access-Control-Allow-Origin', '*')
|
|
70
|
+
return
|
|
71
|
+
end
|
|
52
72
|
|
|
53
73
|
head :forbidden
|
|
54
74
|
end
|
|
@@ -55,6 +55,7 @@ module Testimonials
|
|
|
55
55
|
end
|
|
56
56
|
testimonial.kind = wants_video?(testimonial) ? 'video' : 'text'
|
|
57
57
|
testimonial.source = 'widget' unless Testimonial::SOURCES.include?(testimonial.source)
|
|
58
|
+
testimonial.page_url = clean_page_url(testimonial.page_url)
|
|
58
59
|
testimonial.consent_text = Testimonials.consent_text_for(testimonial.consent_given?)
|
|
59
60
|
testimonial.locale = I18n.locale.to_s
|
|
60
61
|
testimonial.tenant = current_tenant
|
data/lib/testimonials/version.rb
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Testimonials
|
|
4
|
-
# Included into the host's ActionView.
|
|
5
|
-
# before
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# `data-testimonial-prompt` attribute, or by `window.Testimonials.open()`.
|
|
9
|
-
#
|
|
10
|
-
# `testimonials_button` renders a ready-made opener with a localized
|
|
11
|
-
# label ("Leave a review", or "Update your review" once the user has one),
|
|
12
|
-
# so hosts get a user-facing entry point without any i18n setup.
|
|
4
|
+
# Included into the host's ActionView. This lives under lib and is required
|
|
5
|
+
# before the engine registers its Action View load hook: a host initializer
|
|
6
|
+
# may load Action View before Rails sets up application autoloaders, when an
|
|
7
|
+
# app/helpers constant cannot be resolved yet.
|
|
13
8
|
module WidgetHelper
|
|
14
9
|
def testimonials_tag
|
|
15
10
|
return unless Testimonials.enabled?(request)
|
|
@@ -23,19 +18,12 @@ module Testimonials
|
|
|
23
18
|
).html_safe
|
|
24
19
|
end
|
|
25
20
|
|
|
26
|
-
# A plain, unstyled <button> so it picks up the host's own styles. Put it
|
|
27
|
-
# anywhere on a page that also renders testimonials_tag. Pass label: to
|
|
28
|
-
# override the localized default, and any other options (class:, etc.)
|
|
29
|
-
# through to the tag.
|
|
30
21
|
def testimonials_button(label: nil, **)
|
|
31
22
|
return unless Testimonials.enabled?(request)
|
|
32
23
|
|
|
33
24
|
tag.button(label || testimonials_cta_label, type: 'button', 'data-testimonial-prompt': '', **)
|
|
34
25
|
end
|
|
35
26
|
|
|
36
|
-
# The localized call-to-action on its own — "Leave a review", or "Update
|
|
37
|
-
# your review" once the user has one — for hosts composing their own
|
|
38
|
-
# opener markup (icons, list items) around a data-testimonial-prompt element.
|
|
39
27
|
def testimonials_cta_label
|
|
40
28
|
if testimonials_existing_testimonial
|
|
41
29
|
I18n.t(:update_title, scope: :testimonials, default: 'Update your review')
|
|
@@ -52,8 +40,6 @@ module Testimonials
|
|
|
52
40
|
@testimonials_author = Testimonials.config.current_user.call(request)
|
|
53
41
|
end
|
|
54
42
|
|
|
55
|
-
# One review per signed-in user (per tenant): this is the record the widget
|
|
56
|
-
# opens pre-filled, and the one the create endpoint updates in place.
|
|
57
43
|
def testimonials_existing_testimonial
|
|
58
44
|
return @testimonials_existing_testimonial if defined?(@testimonials_existing_testimonial)
|
|
59
45
|
|
|
@@ -65,16 +51,10 @@ module Testimonials
|
|
|
65
51
|
end
|
|
66
52
|
end
|
|
67
53
|
|
|
68
|
-
# A prompt requested via testimonial_prompt! rides the flash, so it survives
|
|
69
|
-
# the usual redirect-after-create. It only reaches the page if the
|
|
70
|
-
# throttle ledger agrees — call testimonial_prompt! as often as you like.
|
|
71
54
|
def testimonials_auto_open
|
|
72
55
|
kind = flash[:testimonials_prompt].to_s
|
|
73
56
|
return unless Testimonials::PromptEvent::KINDS.include?(kind)
|
|
74
57
|
return if kind == 'nps' && !Testimonials.config.nps
|
|
75
|
-
# Without the ledger there is no record of who has been asked already, so
|
|
76
|
-
# an auto-open would reopen on every page a testimonial_prompt! reaches.
|
|
77
|
-
# An install with --skip-prompt-events opens on a click and nothing else.
|
|
78
58
|
return unless Testimonials.config.prompt_events
|
|
79
59
|
|
|
80
60
|
author = testimonials_author
|
data/lib/testimonials.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: testimonials
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yaroslav Shmarov
|
|
@@ -16,6 +16,9 @@ dependencies:
|
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
18
|
version: '7.1'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '9'
|
|
19
22
|
type: :runtime
|
|
20
23
|
prerelease: false
|
|
21
24
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -23,6 +26,9 @@ dependencies:
|
|
|
23
26
|
- - ">="
|
|
24
27
|
- !ruby/object:Gem::Version
|
|
25
28
|
version: '7.1'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '9'
|
|
26
32
|
description: |
|
|
27
33
|
A mountable Rails engine that collects customer testimonials — text and
|
|
28
34
|
video — and NPS scores from inside your app. An iOS-style "Enjoying this
|
|
@@ -43,6 +49,7 @@ files:
|
|
|
43
49
|
- MIT-LICENSE
|
|
44
50
|
- README.md
|
|
45
51
|
- Rakefile
|
|
52
|
+
- SECURITY.md
|
|
46
53
|
- app/controllers/concerns/testimonials/request_context.rb
|
|
47
54
|
- app/controllers/testimonials/api/base_controller.rb
|
|
48
55
|
- app/controllers/testimonials/api/stats_controller.rb
|
|
@@ -57,7 +64,6 @@ files:
|
|
|
57
64
|
- app/controllers/testimonials/submissions_controller.rb
|
|
58
65
|
- app/controllers/testimonials/testimonials_controller.rb
|
|
59
66
|
- app/controllers/testimonials/widgets_controller.rb
|
|
60
|
-
- app/helpers/testimonials/widget_helper.rb
|
|
61
67
|
- app/models/testimonials/application_record.rb
|
|
62
68
|
- app/models/testimonials/nps_response.rb
|
|
63
69
|
- app/models/testimonials/prompt_event.rb
|
|
@@ -129,12 +135,13 @@ files:
|
|
|
129
135
|
- lib/testimonials/version.rb
|
|
130
136
|
- lib/testimonials/widget.js
|
|
131
137
|
- lib/testimonials/widget.rb
|
|
138
|
+
- lib/testimonials/widget_helper.rb
|
|
132
139
|
homepage: https://github.com/yshmarov/testimonials
|
|
133
140
|
licenses:
|
|
134
141
|
- MIT
|
|
135
142
|
metadata:
|
|
136
143
|
homepage_uri: https://github.com/yshmarov/testimonials
|
|
137
|
-
source_code_uri: https://github.com/yshmarov/testimonials
|
|
144
|
+
source_code_uri: https://github.com/yshmarov/testimonials/tree/main
|
|
138
145
|
changelog_uri: https://github.com/yshmarov/testimonials/blob/main/CHANGELOG.md
|
|
139
146
|
bug_tracker_uri: https://github.com/yshmarov/testimonials/issues
|
|
140
147
|
rubygems_mfa_required: 'true'
|
|
@@ -152,7 +159,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
152
159
|
- !ruby/object:Gem::Version
|
|
153
160
|
version: '0'
|
|
154
161
|
requirements: []
|
|
155
|
-
rubygems_version: 4.0.
|
|
162
|
+
rubygems_version: 4.0.16
|
|
156
163
|
specification_version: 4
|
|
157
164
|
summary: 'Testimonials, reviews and NPS for Rails: in-app collection widget, public
|
|
158
165
|
collection page, triage dashboard, and a read API.'
|