Files changed (166) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +20 -0
  3. data/.eslintignore +2 -0
  4. data/.eslintrc +24 -0
  5. data/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
  6. data/.github/ISSUE_TEMPLATE/documentation-request.md +17 -0
  7. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  8. data/.gitignore +16 -0
  9. data/.scss-lint.yml +188 -0
  10. data/.yardopts +1 -0
  11. data/CHANGELOG.md +763 -0
  12. data/CODE_OF_CONDUCT.md +3 -0
  13. data/CONTRIBUTING.md +3 -0
  14. data/Gemfile +6 -0
  15. data/LICENSE +52 -0
  16. data/README.md +107 -0
  17. data/Rakefile +54 -0
  18. data/app/assets/images/workarea/admin/icons/star.svg +1 -0
  19. data/app/assets/images/workarea/storefront/icons/empty_star.svg +1 -0
  20. data/app/assets/images/workarea/storefront/icons/half_star.svg +1 -0
  21. data/app/assets/images/workarea/storefront/icons/star.svg +1 -0
  22. data/app/assets/javascripts/workarea/storefront/reviews/modules/product_review_ajax_submit.js +39 -0
  23. data/app/assets/javascripts/workarea/storefront/reviews/modules/product_reviews_sort_menus.js +82 -0
  24. data/app/assets/javascripts/workarea/storefront/reviews/modules/rating_buttons.js +103 -0
  25. data/app/assets/javascripts/workarea/storefront/reviews/templates/sort_by_property.jst.ejs +11 -0
  26. data/app/assets/stylesheets/workarea/storefront/reviews/components/_rating.scss +28 -0
  27. data/app/assets/stylesheets/workarea/storefront/reviews/components/_reviews.scss +59 -0
  28. data/app/assets/stylesheets/workarea/storefront/reviews/components/_reviews_aggregate.scss +38 -0
  29. data/app/assets/stylesheets/workarea/storefront/reviews/components/_write_review.scss +63 -0
  30. data/app/controllers/workarea/admin/catalog_products_controller.decorator +9 -0
  31. data/app/controllers/workarea/admin/import_reviews_controller.rb +35 -0
  32. data/app/controllers/workarea/admin/reviews_controller.rb +51 -0
  33. data/app/controllers/workarea/storefront/review_requests_controller.rb +50 -0
  34. data/app/controllers/workarea/storefront/reviews_controller.rb +53 -0
  35. data/app/helpers/workarea/admin/reviews_helper.rb +12 -0
  36. data/app/helpers/workarea/storefront/reviews_helper.rb +24 -0
  37. data/app/mailers/workarea/admin/status_report_mailer.decorator +8 -0
  38. data/app/mailers/workarea/storefront/review_mailer.rb +23 -0
  39. data/app/models/workarea/catalog/product.decorator +15 -0
  40. data/app/models/workarea/fulfillment.decorator +11 -0
  41. data/app/models/workarea/review.rb +135 -0
  42. data/app/models/workarea/review/request.rb +62 -0
  43. data/app/models/workarea/search/admin/review.rb +72 -0
  44. data/app/models/workarea/search/storefront/product.decorator +9 -0
  45. data/app/models/workarea/sort.decorator +17 -0
  46. data/app/queries/workarea/review_request_params.rb +47 -0
  47. data/app/queries/workarea/review_summary.rb +11 -0
  48. data/app/queries/workarea/search/admin_reviews.rb +40 -0
  49. data/app/queries/workarea/search/category_browse.decorator +9 -0
  50. data/app/queries/workarea/search/product_search.decorator +9 -0
  51. data/app/seeds/review_seeds.rb +49 -0
  52. data/app/services/workarea/create_review.rb +99 -0
  53. data/app/view_models/workarea/admin/product_view_model.decorator +7 -0
  54. data/app/view_models/workarea/admin/review_view_model.rb +23 -0
  55. data/app/view_models/workarea/admin/reviews_search_view_model.rb +46 -0
  56. data/app/view_models/workarea/storefront/product_view_model.decorator +13 -0
  57. data/app/view_models/workarea/storefront/review_view_model.rb +14 -0
  58. data/app/views/workarea/admin/activities/_review_create.html.haml +12 -0
  59. data/app/views/workarea/admin/activities/_review_destroy.html.haml +10 -0
  60. data/app/views/workarea/admin/activities/_review_update.html.haml +10 -0
  61. data/app/views/workarea/admin/reviews/_aux_navigation.html.haml +5 -0
  62. data/app/views/workarea/admin/reviews/_catalog_products_aux_link.html.haml +5 -0
  63. data/app/views/workarea/admin/reviews/_dashboard_navigation.html.haml +1 -0
  64. data/app/views/workarea/admin/reviews/_menu.html.haml +3 -0
  65. data/app/views/workarea/admin/reviews/_summary.html.haml +12 -0
  66. data/app/views/workarea/admin/reviews/edit.html.haml +77 -0
  67. data/app/views/workarea/admin/reviews/index.html.haml +74 -0
  68. data/app/views/workarea/admin/status_report_mailer/_reviews.html.haml +5 -0
  69. data/app/views/workarea/storefront/products/_rating.html.haml +10 -0
  70. data/app/views/workarea/storefront/products/_reviews.html.haml +23 -0
  71. data/app/views/workarea/storefront/products/_reviews_aggregate.html.haml +22 -0
  72. data/app/views/workarea/storefront/products/_reviews_summary.html.haml +2 -0
  73. data/app/views/workarea/storefront/review_mailer/review_request.html.haml +22 -0
  74. data/app/views/workarea/storefront/review_requests/show.html.haml +44 -0
  75. data/app/views/workarea/storefront/reviews/new.html.haml +68 -0
  76. data/app/views/workarea/storefront/style_guides/_reviews_product_summary_docs.html.haml +13 -0
  77. data/app/views/workarea/storefront/style_guides/components/_reviews.html.haml +17 -0
  78. data/app/workers/workarea/create_review_requests.rb +33 -0
  79. data/app/workers/workarea/schedule_review_requests.rb +44 -0
  80. data/app/workers/workarea/send_review_requests.rb +12 -0
  81. data/app/workers/workarea/update_product_review_data.rb +26 -0
  82. data/bin/rails +18 -0
  83. data/config/initializers/append_points.rb +70 -0
  84. data/config/initializers/assets.rb +1 -0
  85. data/config/initializers/configuration.rb +29 -0
  86. data/config/initializers/rack_attack.rb +11 -0
  87. data/config/initializers/recaptcha.rb +19 -0
  88. data/config/initializers/scheduled_jobs.rb +8 -0
  89. data/config/locales/en.yml +104 -0
  90. data/config/routes.rb +21 -0
  91. data/lib/tasks/verify.rake +16 -0
  92. data/lib/workarea/mailer_previews/storefront/review_mailer_preview.rb +10 -0
  93. data/lib/workarea/reviews.rb +13 -0
  94. data/lib/workarea/reviews/engine.rb +12 -0
  95. data/lib/workarea/reviews/version.rb +5 -0
  96. data/test/dummy/Rakefile +6 -0
  97. data/test/dummy/app/assets/config/manifest.js +4 -0
  98. data/test/dummy/app/assets/images/.keep +0 -0
  99. data/test/dummy/app/assets/javascripts/application.js +13 -0
  100. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  101. data/test/dummy/app/controllers/application_controller.rb +3 -0
  102. data/test/dummy/app/controllers/concerns/.keep +0 -0
  103. data/test/dummy/app/helpers/application_helper.rb +2 -0
  104. data/test/dummy/app/jobs/application_job.rb +2 -0
  105. data/test/dummy/app/mailers/application_mailer.rb +4 -0
  106. data/test/dummy/app/models/concerns/.keep +0 -0
  107. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  108. data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
  109. data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
  110. data/test/dummy/bin/bundle +3 -0
  111. data/test/dummy/bin/rails +4 -0
  112. data/test/dummy/bin/rake +4 -0
  113. data/test/dummy/bin/setup +34 -0
  114. data/test/dummy/bin/update +29 -0
  115. data/test/dummy/config.ru +5 -0
  116. data/test/dummy/config/application.rb +25 -0
  117. data/test/dummy/config/boot.rb +5 -0
  118. data/test/dummy/config/cable.yml +9 -0
  119. data/test/dummy/config/environment.rb +5 -0
  120. data/test/dummy/config/environments/development.rb +55 -0
  121. data/test/dummy/config/environments/production.rb +86 -0
  122. data/test/dummy/config/environments/test.rb +43 -0
  123. data/test/dummy/config/initializers/application_controller_renderer.rb +6 -0
  124. data/test/dummy/config/initializers/assets.rb +11 -0
  125. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  126. data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
  127. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  128. data/test/dummy/config/initializers/inflections.rb +16 -0
  129. data/test/dummy/config/initializers/mime_types.rb +4 -0
  130. data/test/dummy/config/initializers/new_framework_defaults.rb +21 -0
  131. data/test/dummy/config/initializers/session_store.rb +3 -0
  132. data/test/dummy/config/initializers/workarea.rb +5 -0
  133. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  134. data/test/dummy/config/locales/en.yml +23 -0
  135. data/test/dummy/config/puma.rb +47 -0
  136. data/test/dummy/config/routes.rb +5 -0
  137. data/test/dummy/config/secrets.yml +22 -0
  138. data/test/dummy/config/spring.rb +6 -0
  139. data/test/dummy/db/seeds.rb +2 -0
  140. data/test/dummy/lib/assets/.keep +0 -0
  141. data/test/dummy/log/.keep +0 -0
  142. data/test/factories/reviews.rb +32 -0
  143. data/test/fixtures/reviews.csv +5 -0
  144. data/test/helpers/workarea/admin/reviews_helper_test.rb +17 -0
  145. data/test/helpers/workarea/storefront/reviews_helper_test.rb +19 -0
  146. data/test/integration/workarea/admin/reviews_integration_test.rb +33 -0
  147. data/test/integration/workarea/storefront/review_requests_integration_test.rb +63 -0
  148. data/test/integration/workarea/storefront/reviews_integration_test.rb +82 -0
  149. data/test/javascripts/fixtures/product_review_ajax_submit.html.haml +4 -0
  150. data/test/javascripts/product_review_ajax_submit_spec.js +28 -0
  151. data/test/javascripts/spec_helper.js +23 -0
  152. data/test/models/workarea/review/request_test.rb +18 -0
  153. data/test/models/workarea/review_test.rb +94 -0
  154. data/test/models/workarea/search/storefront/product_test.decorator +14 -0
  155. data/test/queries/workarea/review_request_params_test.rb +64 -0
  156. data/test/services/workarea/create_review_test.rb +131 -0
  157. data/test/system/workarea/admin/review_system_test.rb +96 -0
  158. data/test/system/workarea/storefront/review_request_system_test.rb +34 -0
  159. data/test/system/workarea/storefront/review_system_test.rb +113 -0
  160. data/test/teaspoon_env.rb +10 -0
  161. data/test/test_helper.rb +11 -0
  162. data/test/workers/workarea/create_review_requests_test.rb +38 -0
  163. data/test/workers/workarea/schedule_review_requests_test.rb +115 -0
  164. data/test/workers/workarea/send_review_requests_test.rb +30 -0
  165. data/workarea-reviews.gemspec +22 -0
  166. metadata +241 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: cbc95c26a6972a59da6d0daa765491622783a60cfc8dd0961109b82839b0af77
4
+ data.tar.gz: 9f62f3d6484cdf8002172106970954fa053784844a6fa6084b2aee3baadf8e7b
5
+ SHA512:
6
+ metadata.gz: e6913aca804eeb31ab4bb877b74b2826b50596e7b3a087eb162c49d46dc6df2a162ab8ca683e8251a1c056c798d12597294125acea2f102a91150baef4b31a4c
7
+ data.tar.gz: ab487315635024cb2fcbb3b6cea388188b01ad31e0cfbcaf6e9e9513f35f1ad53c035eeb67927bda4796265bf6459eed6b14028bec6faf22066c3257907df5a1
data/.editorconfig ADDED
@@ -0,0 +1,20 @@
1
+ # editorconfig.org
2
+ root = true
3
+
4
+ [*]
5
+ charset = utf-8
6
+ indent_style = space
7
+ end_of_line = lf
8
+ trim_trailing_whitespace = true
9
+ insert_final_newline = true
10
+
11
+ [{*.rb,*.haml,*.decorator,*.yml,*.yaml,*.jbuilder}]
12
+ indent_size = 2
13
+ indent_style = space
14
+
15
+ [{*.js,*.jst,*.ejs,*.scss}]
16
+ indent_size = 4
17
+
18
+ [*.md]
19
+ indent_size = 4
20
+ trim_trailing_whitespace = false
data/.eslintignore ADDED
@@ -0,0 +1,2 @@
1
+ **/test/**/*.js
2
+ testing/**/spec_helper.js
data/.eslintrc ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "extends": "eslint:recommended",
3
+ "rules": {
4
+ "semi": [1, "always"]
5
+ },
6
+ "globals": {
7
+ "window": true,
8
+ "document": true,
9
+ "WORKAREA": true,
10
+ "quot;: true,
11
+ "jQuery": true,
12
+ "_": true,
13
+ "feature": true,
14
+ "JST": true,
15
+ "Turbolinks": true,
16
+ "I18n": true,
17
+ "Chart": true,
18
+ "Dropzone": true,
19
+ "strftime": true,
20
+ "Waypoint": true,
21
+ "wysihtml": true,
22
+ "LocalTime": true,
23
+ }
24
+ }
data/.github/ISSUE_TEMPLATE/bug_report.md ADDED
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: Bug report
3
+ about: Create a report to help us improve Workarea
4
+ title: ''
5
+ labels: bug
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ ⚠️**Before you create**⚠️
11
+ Please verify the issue you're experiencing is not part of your Workarea project customizations. The best way to do this is with a [vanilla Workarea installation](https://developer.workarea.com/articles/create-a-new-host-application.html). This will help us spend time on fixes/improvements for the whole community. Thank you!
12
+
13
+ **Describe the bug**
14
+ A clear and concise description of what the bug is.
15
+
16
+ **To Reproduce**
17
+ Steps to reproduce the behavior:
18
+ 1. Go to '...'
19
+ 2. Click on '....'
20
+ 3. Scroll down to '....'
21
+ 4. See error
22
+
23
+ **Expected behavior**
24
+ A clear and concise description of what you expected to happen.
25
+
26
+ **Workarea Setup (please complete the following information):**
27
+ - Workarea Version: [e.g. v3.4.6]
28
+ - Plugins [e.g. workarea-blog, workarea-sitemaps]
29
+
30
+ **Attachments**
31
+ If applicable, add any attachments to help explain your problem, things like:
32
+ - screenshots
33
+ - Gemfile.lock
34
+ - test cases
35
+
36
+ **Additional context**
37
+ Add any other context about the problem here.
data/.github/ISSUE_TEMPLATE/documentation-request.md ADDED
@@ -0,0 +1,17 @@
1
+ ---
2
+ name: Documentation request
3
+ about: Suggest documentation
4
+ title: ''
5
+ labels: documentation
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ **Is your documentation related to a problem? Please describe.**
11
+ A clear and concise description of what the problem is. Ex. I'm confused by [...]
12
+
13
+ **Describe the article you'd like**
14
+ A clear and concise description of what would be in the documentation article.
15
+
16
+ **Additional context**
17
+ Add any other context or screenshots about the feature request here.
data/.github/ISSUE_TEMPLATE/feature_request.md ADDED
@@ -0,0 +1,20 @@
1
+ ---
2
+ name: Feature request
3
+ about: Suggest an idea for Workarea
4
+ title: ''
5
+ labels: enhancement
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ **Is your feature request related to a problem? Please describe.**
11
+ A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12
+
13
+ **Describe the solution you'd like**
14
+ A clear and concise description of what you want to happen.
15
+
16
+ **Describe alternatives you've considered**
17
+ A clear and concise description of any alternative solutions or features you've considered.
18
+
19
+ **Additional context**
20
+ Add any other context or screenshots about the feature request here.
data/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ .DS_Store
2
+ .bundle/
3
+ .byebug_history
4
+ Gemfile.lock
5
+ .sass-cache/
6
+ log/*.log
7
+ pkg/
8
+ test/dummy/db/*.sqlite3
9
+ test/dummy/db/*.sqlite3-journal
10
+ test/dummy/log/*.log
11
+ test/dummy/tmp/
12
+ test/dummy/public/
13
+ node_modules
14
+ test/reports
15
+ package.json
16
+ yarn.lock
data/.scss-lint.yml ADDED
@@ -0,0 +1,188 @@
1
+ # Extension of the default configuration:
2
+ # https://github.com/causes/scss-lint/blob/master/config/default.yml
3
+
4
+ linters:
5
+ Comment:
6
+ enabled: false
7
+
8
+ DeclarationOrder:
9
+ enabled: true
10
+
11
+ ElsePlacement:
12
+ enabled: true
13
+ style: new_line
14
+
15
+ EmptyRule:
16
+ enabled: false
17
+
18
+ HexLength:
19
+ enabled: true
20
+ style: long
21
+
22
+ Indentation:
23
+ enabled: true
24
+ allow_non_nested_indentation: true
25
+ character: space
26
+ width: 4
27
+
28
+ LeadingZero:
29
+ enabled: true
30
+ style: include_zero
31
+
32
+ MergeableSelector:
33
+ enabled: true
34
+ force_nesting: false
35
+
36
+ PropertySortOrder:
37
+ enabled: true
38
+ ignore_unspecified: false
39
+ separate_groups: false
40
+ order:
41
+ - display
42
+ -
43
+ - position
44
+ - top
45
+ - right
46
+ - bottom
47
+ - left
48
+ - z-index
49
+ -
50
+ - margin
51
+ - margin-top
52
+ - margin-right
53
+ - margin-bottom
54
+ - margin-left
55
+ -
56
+ - margin-collapse
57
+ - margin-top-collapse
58
+ - margin-right-collapse
59
+ - margin-bottom-collapse
60
+ - margin-left-collapse
61
+ -
62
+ - padding
63
+ - padding-top
64
+ - padding-right
65
+ - padding-bottom
66
+ - padding-left
67
+ -
68
+ - width
69
+ - height
70
+ - max-width
71
+ - max-height
72
+ - min-width
73
+ - min-height
74
+ -
75
+ - float
76
+ - clear
77
+ -
78
+ - color
79
+ -
80
+ - font
81
+ - font-size
82
+ - font-style
83
+ - font-family
84
+ - font-weight
85
+ - font-variant
86
+ - font-smoothing
87
+ -
88
+ - line-height
89
+ - letter-spacing
90
+ - word-spacing
91
+ -
92
+ - text-align
93
+ - text-indent
94
+ - text-shadow
95
+ - text-overflow
96
+ - text-rendering
97
+ - text-transform
98
+ - text-decoration
99
+ - text-size-adjust
100
+ -
101
+ - word-break
102
+ - word-wrap
103
+ -
104
+ - white-space
105
+ -
106
+ - background
107
+ - background-size
108
+ - background-color
109
+ - background-image
110
+ - background-repeat
111
+ - background-position
112
+ - background-attachment
113
+ -
114
+ - border
115
+ - border-top
116
+ - border-right
117
+ - border-bottom
118
+ - border-left
119
+ -
120
+ - border-image
121
+ - border-spacing
122
+ - border-collapse
123
+ -
124
+ - border-color
125
+ - border-top-color
126
+ - border-right-color
127
+ - border-bottom-color
128
+ - border-left-color
129
+ -
130
+ - border-style
131
+ - border-top-style
132
+ - border-right-style
133
+ - border-bottom-style
134
+ - border-left-style
135
+ -
136
+ - border-width
137
+ - border-top-width
138
+ - border-right-width
139
+ - border-bottom-width
140
+ - border-left-width
141
+ -
142
+ - border-radius
143
+ - border-top-right-radius
144
+ - border-bottom-right-radius
145
+ - border-bottom-left-radius
146
+ - border-top-left-radius
147
+ - border-radius-topright
148
+ - border-radius-bottomright
149
+ - border-radius-bottomleft
150
+ - border-radius-topleft
151
+ -
152
+ - box-shadow
153
+
154
+ SelectorFormat:
155
+ enabled: true
156
+ convention: hyphenated_BEM
157
+
158
+ SingleLinePerSelector:
159
+ enabled: false
160
+
161
+ SpaceAfterPropertyColon:
162
+ enabled: true
163
+ style: at_least_one_space
164
+
165
+ SpaceBeforeBrace:
166
+ enabled: true
167
+ style: space
168
+ allow_single_line_padding: true
169
+
170
+ VariableForProperty:
171
+ enabled: true
172
+ properties:
173
+ - color
174
+ - font-family
175
+ - background-color
176
+
177
+ # These default settings may be problematic to implementors. They are not
178
+ # ommitted so that they may be adjusted as needed during an implementation.
179
+ #
180
+ # For documentation:
181
+ # https://github.com/causes/scss-lint/blob/master/lib/scss_lint/linter/README.md
182
+
183
+ DuplicateProperty:
184
+ enabled: true
185
+
186
+ PropertySpelling:
187
+ enabled: true
188
+ extra_properties: [] # Add experimental CSS to this array, if needed
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --files CHANGELOG.md
data/CHANGELOG.md ADDED
@@ -0,0 +1,763 @@
1
+ Workarea Reviews 3.0.9 (2019-08-26)
2
+ --------------------------------------------------------------------------------
3
+
4
+ * Only try to add schedule job if service connections are not skipped
5
+
6
+
7
+
8
+ Workarea Reviews 3.0.8 (2019-08-21)
9
+ --------------------------------------------------------------------------------
10
+
11
+ * Open Source!
12
+
13
+
14
+
15
+ Workarea Reviews 3.0.7 (2019-06-11)
16
+ --------------------------------------------------------------------------------
17
+
18
+ * Add Rake Task for Reconciling Verified Purchasers
19
+
20
+ Reviews can have a `:verified` badge associated with the content, but
21
+ for those upgrading to a newer version some data needs to be changed in
22
+ order to make this happen retroactively for older reviews. Add a Rake
23
+ task for adding the `:verified` field to reviews where the user actually
24
+ bought the product. For any reviews that are made after the upgrade,
25
+ this will be automatically assigned as the review is being created.
26
+
27
+ REVIEWS-146
28
+ Tom Scott
29
+
30
+
31
+
32
+ Workarea Reviews 3.0.6 (2019-04-16)
33
+ --------------------------------------------------------------------------------
34
+
35
+ * Automatically Configure reCAPTCHA Proxy
36
+
37
+ Set the `Recaptcha.config.proxy` to `$HTTP_PROXY` in deployed
38
+ environments so developers don't have to worry about it. Update the
39
+ README to remove references to this configuration setting.
40
+
41
+ REVIEWS-144
42
+ Tom Scott
43
+
44
+ * Point Gemfile to gem server
45
+
46
+ Curt Howard
47
+
48
+
49
+
50
+ Workarea Reviews 3.0.5 (2019-03-19)
51
+ --------------------------------------------------------------------------------
52
+
53
+ * Fix Browsing Controls UI
54
+
55
+ REVIEWS-140
56
+ Curt Howard
57
+
58
+ * Update for workarea v3.4 compatibility
59
+
60
+ REVIEWS-139
61
+ Matt Duffy
62
+
63
+
64
+
65
+ Workarea Reviews 3.0.4 (2019-01-22)
66
+ --------------------------------------------------------------------------------
67
+
68
+ * Improve readme
69
+
70
+ ECOMMERCE-6523
71
+ REVIEWS-138
72
+ Chris Cressman
73
+
74
+
75
+
76
+ Workarea Reviews 3.0.3 (2018-10-30)
77
+ --------------------------------------------------------------------------------
78
+
79
+ * Change DateTime to Time in ScheduleReviewRequestsTests
80
+
81
+ Assertions began failing due to difference in nanoseconds, uncertain why but using Time instead of DateTime is a safe resolution.
82
+
83
+ REVIEWS-137
84
+ Francis Bongiovanni
85
+
86
+ * Extend list-reset trump for review group UI
87
+
88
+ REVIEWS-136
89
+ Curt Howard
90
+
91
+
92
+
93
+ Workarea Reviews 3.0.2 (2018-07-24)
94
+ --------------------------------------------------------------------------------
95
+
96
+ * Add rack attack throttle for review submissions
97
+
98
+ REVIEWS-132
99
+ Matt Duffy
100
+
101
+ * Fix Missing Title When Submitting Review
102
+
103
+ Previously, all reviews had the title and the body equal because the
104
+ title was not being passed through properly in params. We're now passing
105
+ the `title:` attribute into the Review model explicitly.
106
+
107
+ REVIEWS-130
108
+ Tom Scott
109
+
110
+
111
+
112
+ Workarea Reviews 3.0.1 (2018-06-12)
113
+ --------------------------------------------------------------------------------
114
+
115
+ * Translate hard-coded text on new review form
116
+
117
+ - Add `workarea.storefront.reviews.hints.display_name` for viewing hint on
118
+ how names are displayed.
119
+ - Add `workarea.storefront.reviews.hints.email` for viewing hint on how
120
+ email is used.
121
+
122
+ REVIEWS-130
123
+ Tom Scott
124
+
125
+
126
+
127
+ Workarea Reviews 3.0.0 (2018-05-24)
128
+ --------------------------------------------------------------------------------
129
+
130
+ * Add calculated review fields on product to data file ignore fields
131
+
132
+ REVIEWS-129
133
+ Matt Duffy
134
+
135
+ * Cancel review requests after user reviews a product from order
136
+
137
+ If a user reviews a product, either through a review request email or
138
+ directly on the site, other future review requests are canceled for
139
+ orders with that product.
140
+
141
+ REVIEWS-128
142
+ Matt Duffy
143
+
144
+ * Add administrable content to review request emails
145
+
146
+ REVIEWS-128
147
+ Matt Duffy
148
+
149
+ * set verified on a sample of review seeds
150
+
151
+ Matt Duffy
152
+
153
+ * Add importing/exporting from v3.3
154
+
155
+ ECOMMERCE-6010
156
+ Ben Crouse
157
+
158
+ * Remove import, update to work with base DataFile and bulk action changes
159
+
160
+ REVIEWS-127
161
+ Matt Duffy
162
+
163
+ * Leverage Workarea Changelog task
164
+
165
+ ECOMMERCE-5355
166
+ Curt Howard
167
+
168
+ * Update Mailers for Premailer
169
+
170
+ REVIEWS-126
171
+ Curt Howard
172
+
173
+ * Fix CHANGELOG
174
+
175
+ Curt Howard
176
+
177
+ * Update status report mailer partial to fit with new templates
178
+
179
+ Matt Duffy
180
+
181
+ * Update review request template
182
+
183
+ Matt Duffy
184
+
185
+ * Fix issues around reviews of missing products
186
+
187
+ Matt Duffy
188
+
189
+ * Update review import to align with Workarea::Import changes
190
+
191
+ REVIEWS-124
192
+ Matt Duffy
193
+
194
+ * Add verified facet and column to reviews
195
+
196
+ REVIEWS-116
197
+ Matt Duffy
198
+
199
+ * Add reviews import to admin
200
+
201
+ REVIEWS-107
202
+ Matt Duffy
203
+
204
+ * Automatically configure recaptcha for test and development
205
+
206
+ REVIEWS-117
207
+ Matt Duffy
208
+
209
+ * Disable submit button when user leaves a review
210
+
211
+ - This prevents multiple review submissions, which can happen even with recaptcha in place
212
+
213
+ REVIEWS-119
214
+ Dave Barnow
215
+
216
+ * Mark requests as sent after sending email
217
+
218
+ Matt Duffy
219
+
220
+ * Improve write a review form
221
+
222
+ * Remove fieldset and use property/label instead
223
+ * Use inline-list for rating stars wrapper
224
+ * Mark optional fields as such
225
+ * Use a grid for submit/cancel buttons for modal action consistency
226
+ * Use email_field_tag with correct attributes
227
+
228
+ REVIEWS-113
229
+ Dave Barnow
230
+
231
+ * Clean up navigation links
232
+
233
+ * Add link to marketing dashboard
234
+ * Fix link to point back to marketing dashboard
235
+ * Add jump to navigation
236
+
237
+ REVIEWS-115
238
+ Dave Barnow
239
+
240
+ * Send review request emails to users who have recently placed an order
241
+
242
+ REVIEWS-110
243
+ Matt Duffy
244
+
245
+ * Removes appended stylesheet for product_summary
246
+
247
+ REVIEWS-59
248
+ Mansi Pathak
249
+
250
+ * Removes .rating styles extended in product_summary sass
251
+
252
+ REVIEWS-59
253
+ Mansi Pathak
254
+
255
+ * Mark reviews made by users who have purchased the product as verified
256
+
257
+ REVIEWS-108
258
+ Matt Duffy
259
+
260
+ * Allow reviews without logging in. Loosen model validations
261
+
262
+ REVIEWS-112
263
+ Matt Duffy
264
+
265
+ * Cleans up sass to match code standards
266
+
267
+ * add functional vars
268
+
269
+ * extend trumps and objects
270
+
271
+ * resolve sass linter warnings
272
+
273
+ * use spacing unit for marign and padding
274
+
275
+ REVIEWS-98
276
+ Mansi Pathak
277
+
278
+ * Add summary of reviews to status report email
279
+
280
+ REVIEWS-109
281
+ Matt Duffy
282
+
283
+ * Convert index from summaries to table
284
+
285
+ REVIEWS-103
286
+ Curt Howard
287
+
288
+
289
+
290
+ Workarea Reviews 2.1.1 (2018-04-03)
291
+ --------------------------------------------------------------------------------
292
+
293
+ * Default Reviews link in Product Auxiliary Nav to sort by newest
294
+
295
+ REVIEWS-121
296
+ Dave Barnow
297
+
298
+ Workarea Reviews 2.1.0 (2017-09-15)
299
+ --------------------------------------------------------------------------------
300
+
301
+ * Adds conditional to show how many pending reviews compared to all reviews
302
+
303
+ REVIEWS-91
304
+ Ivana Veliskova
305
+
306
+ * Fix 0 Reviews State on PDP
307
+
308
+ * Shows the user there are no reviews
309
+ * lines review count display and write a review in the same row
310
+ ** allows for consistent ui across items with reviews and items without reviews
311
+
312
+ REVIEWS-90
313
+ Lucas Boyd
314
+
315
+ * Fix Review Stars UI in ie11
316
+
317
+ Add a width to write review stars to prevent them from spreading too far apart and breaking the UI
318
+
319
+ REVIEWS-96
320
+ Lucas Boyd
321
+
322
+ * Replace modernizr with feature js in CSS
323
+
324
+ Replaces modernizr selectors with the corresponding featurejs selectors
325
+
326
+ REVIEWS-94
327
+ Lucas Boyd
328
+
329
+ * Update activity with correct workarea helper for restore links
330
+
331
+ REVIEWS-95
332
+ Matt Duffy
333
+
334
+ * Add restore link to reviews for admin trash
335
+
336
+ REVIEWS-95
337
+ Matt Duffy
338
+
339
+
340
+ Workarea Reviews 2.0.5 (2017-08-22)
341
+ --------------------------------------------------------------------------------
342
+
343
+ * Adds conditional to show how many pending reviews compared to all reviews
344
+
345
+ REVIEWS-91
346
+ Ivana Veliskova
347
+
348
+ * Fix 0 Reviews State on PDP
349
+
350
+ * Shows the user there are no reviews
351
+ * lines review count display and write a review in the same row
352
+ ** allows for consistent ui across items with reviews and items without reviews
353
+
354
+ REVIEWS-90
355
+ Lucas Boyd
356
+
357
+ * Fix Review Stars UI in ie11
358
+
359
+ Add a width to write review stars to prevent them from spreading too far apart and breaking the UI
360
+
361
+ REVIEWS-96
362
+ Lucas Boyd
363
+
364
+ * Replace modernizr with feature js in CSS
365
+
366
+ Replaces modernizr selectors with the corresponding featurejs selectors
367
+
368
+ REVIEWS-94
369
+ Lucas Boyd
370
+
371
+
372
+ Workarea Reviews 2.0.4 (2017-07-07)
373
+ --------------------------------------------------------------------------------
374
+
375
+ * Wrap product ID in quotes for more relevant search results
376
+
377
+ REVIEWS-92
378
+ Dave Barnow
379
+
380
+ * Reset review attributes to defaults for product copies
381
+
382
+ REVIEWS-89
383
+ Matt Duffy
384
+
385
+
386
+ Workarea Reviews 2.0.3 (2017-06-08)
387
+ --------------------------------------------------------------------------------
388
+
389
+ * Fix test to match change to class
390
+
391
+ The interface of storefront/product changed but the test wasn't changed
392
+ to match it. Change the test to check if #sorts includes the sorting
393
+ rating.
394
+
395
+ no changelog
396
+
397
+ REVIEWS-88
398
+ Eric Pigeon
399
+
400
+ * Correct storefront product sorting by top rated, clean up admin reviews sorting
401
+
402
+ REVIEWS-83
403
+ Matt Duffy
404
+
405
+ * Expose top rated sort option to review admin. Fix issues with review sorting
406
+
407
+ REVIEWS-83
408
+ Matt Duffy
409
+
410
+ * Rename admin review view model to fit expected naming convention for search results
411
+
412
+ REVIEWS-82
413
+ Matt Duffy
414
+
415
+ * Remove jshint and replace with eslint
416
+
417
+ REVIEWS-81
418
+ Dave Barnow
419
+
420
+
421
+ Workarea Reviews 2.0.2 (2017-05-26)
422
+ --------------------------------------------------------------------------------
423
+
424
+ * Append seeds after others so that all product types are accounted for
425
+
426
+ REVIEWS-80
427
+ Dave Barnow
428
+
429
+ * Don't load helper in initialiser
430
+
431
+ This was causing a rails error when the plugin is installed in the same app as package-products.
432
+
433
+ REVIEWS-78
434
+ Beresford, Jake
435
+
436
+
437
+ Workarea Reviews 2.0.1 (2017-05-19)
438
+ --------------------------------------------------------------------------------
439
+
440
+
441
+ Workarea Reviews 2.0.0 (2017-05-17)
442
+ --------------------------------------------------------------------------------
443
+
444
+ * Simplify admin reviews UI
445
+
446
+ REVIEWS-69
447
+ Matt Duffy
448
+
449
+ * Upgrade Reviews for v3
450
+
451
+ REVIEWS-69
452
+ Eric Pigeon
453
+
454
+ * Reviews upgrade for v3 Frontend work
455
+
456
+ * Updated remaining admin translations
457
+ * Make the UI all nice and that.
458
+ * Updated admin views (summary card, edit etc.)
459
+ * Change review stars implementation to use inline svgs
460
+ * Update write review form star buttons to work with inline_svg
461
+
462
+ REVIEWS-69
463
+ Beresford, Jake
464
+
465
+ * Upgrade Reviews for v3
466
+
467
+ REVIEWS-69
468
+ Eric Pigeon
469
+
470
+
471
+ WebLinc Reviews 1.1.0 (2016-10-12)
472
+ --------------------------------------------------------------------------------
473
+
474
+ * Add activity support for v2.3
475
+
476
+ REVIEWS-66
477
+ Ben Crouse
478
+
479
+ * Improve ajax submit review feature
480
+
481
+ REVIEWS-64
482
+ Curt Howard
483
+
484
+ * Fix malformed link to reviews section on PDP
485
+
486
+ REVIEWS-62
487
+ Curt Howard
488
+
489
+ * Add teaspoon test for ajax submit reviews
490
+
491
+ REVIEWS-56
492
+ Kristen Ward
493
+
494
+ * Set up teaspoon in reviews gem
495
+
496
+ Add necessary files and settings
497
+
498
+ REVIEWS-56
499
+ Kristen Ward
500
+
501
+ * Submit reviews via ajax when submitted from dialog
502
+
503
+ Allow the 'write review' form to be opened in a dialog
504
+ and submitted via ajax to keep the user on the same page.
505
+
506
+ REVIEWS-56
507
+ Kristen Ward
508
+
509
+ * Correct the ordering of reviews on PDP to be based on created_at field.
510
+
511
+ REVIEWS-60
512
+ gharnly
513
+
514
+ * Correct the ordering of reviews on PDP to be based on created_at field.
515
+
516
+ REVIEWS-60
517
+ gharnly
518
+
519
+ * Force loading of the sort decorator before the product browse decorator
520
+
521
+ Because decorator-loading order isn't deterministic, sometimes the product browse decorator gets loaded first. This causes an error because it tries to reference a method added in the sort decorator.
522
+
523
+ REVIEWS-57
524
+ Ben Crouse
525
+
526
+ * Force loading of the sort decorator before the product browse decorator
527
+
528
+ Because decorator-loading order isn't deterministic, sometimes the product browse decorator gets loaded first. This causes an error because it tries to reference a method added in the sort decorator.
529
+
530
+ REVIEWS-57
531
+ Ben Crouse
532
+
533
+ * Add hidden-field honeypot for review bots.
534
+
535
+ Adds a `username` field on the new review form which is hidden with CSS.
536
+ If this field is present on submission we drop the request and redirect
537
+ as if it were successfully created.
538
+
539
+ REVIEWS-53
540
+ Thomas Vendetta
541
+
542
+ * Better logic and test
543
+
544
+ REVIEWS-54
545
+ Thomas Vendetta
546
+
547
+ * Merge branch 'feature/REVIEWS-54-require-users-spend-money-to-post-reviews' of ssh://stash.tools.workarea.com:7999/wl/workarea-reviews into feature/REVIEWS-54-require-users-spend-money-to-post-reviews
548
+ Thomas Vendetta
549
+
550
+ * Add visually hidden class to field
551
+
552
+ REVIEWS-53
553
+ Thomas Vendetta
554
+
555
+ * Config to require user spend money to post reviews
556
+
557
+ Adds a configuration setting that requires users to have spent money
558
+ to post a product review.
559
+
560
+ REVIEWS-54
561
+ Thomas Vendetta
562
+
563
+ * Add hidden-field honeypot for review bots.
564
+
565
+ Adds a `username` field on the new review form which is hidden with CSS.
566
+ If this field is present on submission we drop the request and redirect
567
+ as if it were successfully created.
568
+
569
+ REVIEWS-53
570
+ Thomas Vendetta
571
+
572
+ * Fix read/write review scroll_to_button bug
573
+
574
+ The `anchor` param being passed to some route helpers was being
575
+ improperly merged into the options hash. This commit separates this
576
+ param from the other options passed to the helper.
577
+
578
+ REVIEWS-52
579
+ Curt Howard
580
+
581
+
582
+ WebLinc Reviews 1.0.5 (2016-08-30)
583
+ --------------------------------------------------------------------------------
584
+
585
+ * Fix malformed link to reviews section on PDP
586
+
587
+ REVIEWS-62
588
+ Curt Howard
589
+
590
+
591
+ WebLinc Reviews 1.0.4 (2016-05-09)
592
+ --------------------------------------------------------------------------------
593
+
594
+ * Correct the ordering of reviews on PDP to be based on created_at field.
595
+
596
+ REVIEWS-60
597
+ gharnly
598
+
599
+
600
+ WebLinc Reviews 1.0.3 (2016-04-08)
601
+ --------------------------------------------------------------------------------
602
+
603
+ * Force loading of the sort decorator before the product browse decorator
604
+
605
+ Because decorator-loading order isn't deterministic, sometimes the product browse decorator gets loaded first. This causes an error because it tries to reference a method added in the sort decorator.
606
+
607
+ REVIEWS-57
608
+ Ben Crouse
609
+
610
+
611
+ WebLinc Reviews 1.0.2 (2016-04-05)
612
+ --------------------------------------------------------------------------------
613
+
614
+
615
+ WebLinc Reviews 1.0.1 (January 26, 2016)
616
+ --------------------------------------------------------------------------------
617
+
618
+ * Fix read/write review scroll_to_button bug
619
+
620
+ The `anchor` param being passed to some route helpers was being
621
+ improperly merged into the options hash. This commit separates this
622
+ param from the other options passed to the helper.
623
+
624
+ REVIEWS-52
625
+
626
+
627
+ Unreleased
628
+ --------------------------------------------------------------------------------
629
+
630
+ * Config to require user spend money to post reviews
631
+
632
+ Adds a configuration setting that requires users to have spent money
633
+ to post a product review.
634
+
635
+ REVIEWS-54
636
+
637
+ * Add hidden-field honeypot for review bots.
638
+
639
+ Adds a `username` field on the new review form which is hidden with CSS.
640
+ If this field is present on submission we drop the request and redirect
641
+ as if it were successfully created.
642
+
643
+ REVIEWS-53
644
+
645
+
646
+ WebLinc Reviews 1.0.0 (January 13, 2016)
647
+ --------------------------------------------------------------------------------
648
+
649
+ * Update for compatibility with WebLinc 2.0
650
+
651
+ * Replace absolute URLs with relative paths
652
+
653
+
654
+ WebLinc Reviews 0.10.0 (October 7, 2015)
655
+ --------------------------------------------------------------------------------
656
+
657
+ * Add metadata and update context-menu
658
+
659
+ REVIEWS-50
660
+
661
+ * Update plugin to be compatible with v0.12
662
+
663
+ Update new & edit views, property work
664
+ Add blank row to permissions partial
665
+ Update indexes, add context-menu to summary/edit
666
+
667
+ REVIEWS-50
668
+
669
+ * Update menu for compatibility with ECOMMERCE-1344
670
+
671
+ REVIEWS-45
672
+
673
+ * Fix presentation of dashboard pending reviews count
674
+
675
+ Fix markup within dashboard to do list partial to match the to do list
676
+ markup from workarea.
677
+
678
+ REVIEWS-51
679
+
680
+ * Update sort by property js template with correct class name
681
+
682
+ REVIEWS-46
683
+
684
+
685
+ WebLinc Reviews 0.9.0 (August 21, 2015)
686
+ --------------------------------------------------------------------------------
687
+
688
+ * Allow reviews without a user so that reviews from legacy applications can
689
+ be imported.
690
+
691
+ REVIEWS-44
692
+
693
+ 826ca2a5c96062bc4e0be5adb2887baec76effbd
694
+ 3b578531e3a3d23d292cffb592a3a53b2c63e42b
695
+
696
+ * Rename SCSS blocks `panel` and `panel--buttons` to `index-filters` and
697
+ `form-actions`, respectively, for compatibility with WebLinc 0.11.
698
+
699
+ REVIEWS-43
700
+
701
+ c2a5cbfd09014160afe7ed5039f60ce229e4f096
702
+
703
+ * Use vector images for review stars. Re-write display code accordingly.
704
+
705
+ REVIEWS-34
706
+
707
+ 491e6bad575c921e5e6f57f4962b45f4672c18db (merge)
708
+
709
+
710
+ WebLinc Reviews 0.8.0 (July 12, 2015)
711
+ --------------------------------------------------------------------------------
712
+
713
+ * Add model summaries to Admin indexes.
714
+
715
+ REVIEWS-39
716
+
717
+ d462476e7bd817f505c703c7cf7daa66874e7da3
718
+ 410b6dd1f2590178860421784a9adec0a4520c4d
719
+ 1f52be2a94898d03639b82a0bdeafde146b564d6
720
+ bd21b18c9c65fa6a9010098f63ed877f1ce65389
721
+
722
+ * Update for compatibility with workarea 0.10 and constrain to workarea 0.10.
723
+
724
+ eaa21d516260b6bdd20cffdfdf6f260a314e80c6
725
+ e641252d86ac34cecb696a992cec2976514a2946
726
+ b0a1b40cb10f10dd7b287331d2ef5c0584544dcd
727
+ 5606d9fe4eac382d120266b79bfdf0fcbfc18953
728
+ 7d251907578264886982393b821056f614d8c87c
729
+ 170ba9bdaf8aa60cd47d4a0030e20048e3c347f6
730
+ 5babadcb3270b4d6c2f06e9fd68efe3ad6fdf35b
731
+ dd669d4b31f0f20ff51b100a57274e2d39ef897e
732
+
733
+ * Fix "back" links in Admin.
734
+
735
+ REVIEWS-41
736
+
737
+ 696b1698c97cc08915588db304a67a931ee57a0b
738
+
739
+
740
+ WebLinc Reviews 0.7.0 (June 1, 2015)
741
+ --------------------------------------------------------------------------------
742
+
743
+ * Rename fixtures to factories and clean up factories.
744
+
745
+ * Update for compatibility and consistency with workarea 0.9.0.
746
+
747
+ * Remove pagination from reviews in Store Front. Display all reviews.
748
+
749
+ REVIEWS-20
750
+
751
+
752
+ WebLinc Reviews 0.6.0 (April 10, 2015)
753
+ --------------------------------------------------------------------------------
754
+
755
+ * Update JavaScript modules for compatibility with WebLinc 0.8.0.
756
+
757
+ * Update testing environment for compatibility with WebLinc 0.8.0.
758
+
759
+ * Use new decorator style for consistency with WebLinc 0.8.0.
760
+
761
+ * Remove gems server secrets for consistency with WebLinc 0.8.0.
762
+
763
+ * Update assets for compatibility with WebLinc 0.8.0.
data/CODE_OF_CONDUCT.md ADDED
@@ -0,0 +1,3 @@
1
+ View this plugin's code of conduct here:
2
+
3
+ <https://github.com/workarea-commerce/workarea/blob/master/CODE_OF_CONDUCT.md>
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,3 @@
1
+ View this plugin's contribution guidelines here:
2
+
3
+ <https://github.com/workarea-commerce/workarea/blob/master/CONTRIBUTING.md>
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+ git_source(:github) { |repo| "git@github.com:#{repo}.git" }
3
+
4
+ gemspec
5
+
6
+ gem 'workarea'
data/LICENSE ADDED
@@ -0,0 +1,52 @@
1
+ WebLinc
2
+ Business Source License
3
+
4
+ Licensor: WebLinc Corporation, 22 S. 3rd Street, 2nd Floor, Philadelphia PA 19106
5
+
6
+ Licensed Work: Workarea Commerce Platform
7
+ The Licensed Work is (c) 2019 WebLinc Corporation
8
+
9
+ Additional Use Grant:
10
+ You may make production use of the Licensed Work without an additional license agreement with WebLinc so long as you do not use the Licensed Work for a Commerce Service.
11
+
12
+ A "Commerce Service" is a commercial offering that allows third parties (other than your employees and contractors) to access the functionality of the Licensed Work by creating or managing commerce functionality, the products, taxonomy, assets and/or content of which are controlled by such third parties.
13
+
14
+ For information about obtaining an additional license agreement with WebLinc, contact licensing@workarea.com.
15
+
16
+ Change Date: 2019-08-20
17
+
18
+ Change License: Version 2.0 or later of the GNU General Public License as published by the Free Software Foundation
19
+
20
+ Terms
21
+
22
+ The Licensor hereby grants you the right to copy, modify, create derivative works, redistribute, and make non-production use of the Licensed Work. The Licensor may make an Additional Use Grant, above, permitting limited production use.
23
+
24
+ Effective on the Change Date, or the fourth anniversary of the first publicly available distribution of a specific version of the Licensed Work under this License, whichever comes first, the Licensor hereby grants you rights under the terms of the Change License, and the rights granted in the paragraph above terminate.
25
+
26
+ If your use of the Licensed Work does not comply with the requirements currently in effect as described in this License, you must purchase a commercial license from the Licensor, its affiliated entities, or authorized resellers, or you must refrain from using the Licensed Work.
27
+
28
+ All copies of the original and modified Licensed Work, and derivative works of the Licensed Work, are subject to this License. This License applies separately for each version of the Licensed Work and the Change Date may vary for each version of the Licensed Work released by Licensor.
29
+
30
+ You must conspicuously display this License on each original or modified copy of the Licensed Work. If you receive the Licensed Work in original or modified form from a third party, the terms and conditions set forth in this License apply to your use of that work.
31
+
32
+ Any use of the Licensed Work in violation of this License will automatically terminate your rights under this License for the current and all other versions of the Licensed Work.
33
+
34
+ This License does not grant you any right in any trademark or logo of Licensor or its affiliates (provided that you may use a trademark or logo of Licensor as expressly required by this License). TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND TITLE. MariaDB hereby grants you permission to use this License’s text to license your works and to refer to it using the trademark "Business Source License" as long as you comply with the Covenants of Licensor below.
35
+
36
+ Covenants of Licensor
37
+ In consideration of the right to use this License’s text and the "Business Source License" name and trademark, Licensor covenants to MariaDB, and to all other recipients of the licensed work to be provided by Licensor:
38
+
39
+ To specify as the Change License the GPL Version 2.0 or any later version, or a license that is compatible with GPL Version 2.0 or a later version, where "compatible" means that software provided under the Change License can be included in a program with software provided under GPL Version 2.0 or a later version. Licensor may specify additional Change Licenses without limitation.
40
+
41
+ To either: (a) specify an additional grant of rights to use that does not impose any additional restriction on the right granted in this License, as the Additional Use Grant; or (b) insert the text "None."
42
+
43
+ To specify a Change Date.
44
+
45
+ Not to modify this License in any other way.
46
+
47
+ Notice
48
+ The Business Source License (this document, or the "License") is not an Open Source license. However, the Licensed Work will eventually be made available under an Open Source License, as stated in this License.
49
+
50
+ For more information on the use of the Business Source License generally, please visit the Adopting and Developing Business Source License FAQ.
51
+
52
+ License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved. "Business Source License" is a trademark of MariaDB Corporation Ab.
data/README.md ADDED
@@ -0,0 +1,107 @@
1
+ Workarea Reviews
2
+ ================================================================================
3
+
4
+ A Workarea Commerce plugin that enables product reviews.
5
+
6
+
7
+ Overview
8
+ --------------------------------------------------------------------------------
9
+
10
+ * Consumers can write product reviews in the Storefront
11
+ * Admins can edit those reviews in the Admin, which includes approving them
12
+ * Approved reviews are reflected in product summaries and displayed on product detail pages
13
+ * Consumers can sort products by rating in the Storefront
14
+ * Admins can list, show, edit, and delete reviews in the Admin
15
+ * Admins can view and navigate to a product's reviews when administrating a product in the Admin
16
+ * The Admin daily status report email includes a summary of reviews
17
+ * The system emails consumers who place orders to request they write reviews
18
+ * Developers can seed reviews for all products
19
+ * Developers can preview review-related emails
20
+ * Admins can view a reviews by product report
21
+ * The system generates top rated and most reviewed product insights
22
+ * The system generates most active reviewers insight
23
+
24
+ Getting Started
25
+ --------------------------------------------------------------------------------
26
+
27
+ Add the gem to your application's Gemfile:
28
+
29
+ ```ruby
30
+ # ...
31
+ gem 'workarea-reviews'
32
+ # ...
33
+ ```
34
+
35
+ Update your application's bundle.
36
+
37
+ ```bash
38
+ cd path/to/application
39
+ bundle
40
+ ```
41
+
42
+ Features
43
+ --------------------------------------------------------------------------------
44
+
45
+
46
+ ### Writing Reviews
47
+
48
+ * Reviews are written in the Storefront; users may be logged in or anonymous (requires reCAPTCHA)
49
+ * Developers can set `Workarea.config.require_purchase_to_write_review` to `true` to require the reviewer to have a `total_spent` greater than zero for the review to be valid
50
+ * The "write review" form is presented as a dialog, which closes after the review is submitted
51
+ * The reviewer uses a rating star UI (with underlying values of 1 through 5) to select a rating
52
+ * The reviewer may also provide a title and body, along with their first name, last name, and email
53
+ * The reviewer receives an email to confirm the review was submitted and is awaiting admin approval
54
+
55
+
56
+ ### Displaying Reviews
57
+
58
+ * In the Storefront, reviews are presented without pagination
59
+ * Reviews can be sorted, which is handled client-side (values are oldest, newest, and highest)
60
+ * Each review may include a title, a body, and the following metadata: author, date, verified
61
+ * A review is considered verified if the reviewer was logged in and has the ID of the reviewed product in their order history
62
+
63
+
64
+ ### Administrating Reviews
65
+
66
+ * Admins with marketing permission may access the reviews admin
67
+ * The reviews admin is accessible from the Marketing section of the primary navigation and from the Marketing dashboard
68
+ * Admins can also jump to the reviews admin by searching for "Product Reviews"
69
+ * Products and reviews are cross referenced within the auxillary navigation of each
70
+ * Admins can list, show, edit, and delete reviews, which includes approving them (Admins can edit a review before approving)
71
+
72
+
73
+ ### Requesting Reviews
74
+
75
+ * For each order, the system will send up to _n_ review requests (Workarea.config.review_requests_per_order)
76
+ * Each request is for a different order item (selected by most expensive)
77
+ * Each request expires after a configurable duration (Workarea.config.review_request_ttl)
78
+ * The first request is sent after a configurable duration has passed (Workarea.config.review_request_initial_delivery_delay)
79
+ * Each consecutive request is sent at a configurable duration (Workarea.config.review_request_secondary_delivery_delay)
80
+
81
+ reCAPTCHA Configuration
82
+ --------------------------------------------------------------------------------
83
+
84
+ Workarea Reviews utilizes [reCAPTCHA](https://github.com/ambethia/recaptcha) in the review form to prevent abuse. You'll need to configure each environment's secrets file to provide recaptcha keys. For testing and development, the plugin will use the [google provided](https://developers.google.com/recaptcha/docs/faq) testing keys if none are specified in your secrets.
85
+
86
+ ```yaml
87
+ production:
88
+ recaptcha:
89
+ site_key: YOUR_SITE_KEY
90
+ secret_key: YOUR_SECRET_KEY
91
+ ```
92
+
93
+ reCAPTCHA sends its HTTP requests through the proxy automatically if the
94
+ `$HTTP_PROXY` environment variable is set. This is set for you in hosted
95
+ environments.
96
+
97
+ [Obtain your keys from the reCAPTCHA site](https://www.google.com/recaptcha/admin)
98
+
99
+ Workarea Commerce Documentation
100
+ --------------------------------------------------------------------------------
101
+
102
+ See [https://developer.workarea.com](https://developer.workarea.com) for Workarea Commerce documentation.
103
+
104
+ License
105
+ --------------------------------------------------------------------------------
106
+
107
+ Workarea Reviews is released under the [Business Software License](LICENSE)
data/Rakefile ADDED
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+
8
+ APP_RAKEFILE = File.expand_path('../test/dummy/Rakefile', __FILE__)
9
+ load 'rails/tasks/engine.rake'
10
+ load 'rails/tasks/statistics.rake'
11
+ load 'workarea/changelog.rake'
12
+
13
+ require 'rake/testtask'
14
+
15
+ Rake::TestTask.new(:test) do |t|
16
+ t.libs << 'lib'
17
+ t.libs << 'test'
18
+ t.pattern = 'test/**/*_test.rb'
19
+ t.verbose = false
20
+ end
21
+
22
+ task default: :test
23
+
24
+ $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
25
+ require 'workarea/reviews/version'
26
+
27
+ desc "Release version #{Workarea::Reviews::VERSION} of the gem"
28
+ task :release do
29
+ host = "https://#{ENV['BUNDLE_GEMS__WEBLINC__COM']}@gems.weblinc.com"
30
+
31
+ #Rake::Task['workarea:changelog'].execute
32
+ #system 'git add CHANGELOG.md'
33
+ #system 'git commit -m "Update CHANGELOG"'
34
+ #system 'git push origin HEAD'
35
+
36
+ system "git tag -a v#{Workarea::Reviews::VERSION} -m 'Tagging #{Workarea::Reviews::VERSION}'"
37
+ system 'git push --tags'
38
+
39
+ system 'gem build workarea-reviews.gemspec'
40
+ system "gem push workarea-reviews-#{Workarea::Reviews::VERSION}.gem"
41
+ system "gem push workarea-reviews-#{Workarea::Reviews::VERSION}.gem --host #{host}"
42
+ system "rm workarea-reviews-#{Workarea::Reviews::VERSION}.gem"
43
+ end
44
+
45
+ desc 'Run the JavaScript tests'
46
+ ENV['TEASPOON_RAILS_ENV'] = File.expand_path('../test/dummy/config/environment', __FILE__)
47
+ task teaspoon: 'app:teaspoon'
48
+
49
+ desc 'Start a server at http://localhost:3000/teaspoon for JavaScript tests'
50
+ task :teaspoon_server do
51
+ Dir.chdir('test/dummy')
52
+ teaspoon_env = File.expand_path('../test/teaspoon_env.rb', __FILE__)
53
+ system "RAILS_ENV=test TEASPOON_ENV=#{teaspoon_env} rails s"
54
+ end
data/app/assets/images/workarea/admin/icons/star.svg ADDED
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>star</title><path d="M12 19.41l-7.33 3.84 1.43-8.13L.17 9.36l8.18-1.2L12 .75l3.65 7.41 8.19 1.2-5.94 5.76 1.42 8.13L12 19.41z"/></svg>
data/app/assets/images/workarea/storefront/icons/empty_star.svg ADDED
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>empty_star</title><path d="M12 3l2.75 5.6.23.47.52.08 6.18.91-4.48 4.34-.38.37.09.52L18 21.42l-5.52-2.9-.48-.24-.46.24L6 21.42l1.08-6.13.09-.52-.37-.37-4.48-4.34 6.18-.91.5-.08.23-.47L12 3m0-2.25L8.35 8.16.17 9.36l5.93 5.76-1.43 8.13L12 19.41l7.32 3.84-1.42-8.13 5.94-5.76-8.19-1.2L12 .75z"/></svg>
data/app/assets/images/workarea/storefront/icons/half_star.svg ADDED
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>half_star</title><path d="M12 19.41V.75L8.35 8.16.17 9.36l5.93 5.76-1.43 8.13L12 19.41z"/><path d="M12 3l2.75 5.6.23.47.52.08 6.18.91-4.48 4.34-.38.37.09.52L18 21.42l-5.52-2.9-.48-.24-.46.24L6 21.42l1.08-6.13.09-.52-.37-.37-4.48-4.34 6.18-.91.5-.08.23-.47L12 3m0-2.25L8.35 8.16.17 9.36l5.93 5.76-1.43 8.13L12 19.41l7.32 3.84-1.42-8.13 5.94-5.76-8.19-1.2L12 .75z"/></svg>
data/app/assets/images/workarea/storefront/icons/star.svg ADDED
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>star</title><path d="M12 19.41l-7.33 3.84 1.43-8.13L.17 9.36l8.18-1.2L12 .75l3.65 7.41 8.19 1.2-5.94 5.76 1.42 8.13L12 19.41z"/></svg>
data/app/assets/javascripts/workarea/storefront/reviews/modules/product_review_ajax_submit.js ADDED
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @namespace WORKAREA.productReviewAjaxSubmit
3
+ */
4
+ WORKAREA.registerModule('productReviewAjaxSubmit', (function () {
5
+ 'use strict';
6
+
7
+ var submitForm = function ($form, event) {
8
+ if ($form.valid() === false) { return; }
9
+
10
+ event.preventDefault();
11
+
12
+ $.ajax({
13
+ url: $form.attr('action'),
14
+ type: $form.attr('method'),
15
+ data: $form.serialize()
16
+ }).done(_.partial(WORKAREA.dialog.closeClosest, $form));
17
+ },
18
+
19
+ formNotInDialog = function ($form) {
20
+ return _.isEmpty($form.closest('.ui-dialog'));
21
+ },
22
+
23
+ /**
24
+ * @method
25
+ * @name init
26
+ * @memberof WORKAREA.productReviewAjaxSubmit
27
+ */
28
+ init = function ($scope) {
29
+ var $form = $('[data-product-review-ajax-submit]', $scope);
30
+
31
+ if (formNotInDialog($form)) { return; }
32
+
33
+ $form.on('submit', _.partial(submitForm, $form));
34
+ };
35
+
36
+ return {
37
+ init: init
38
+ };
39
+ }()));
data/app/assets/javascripts/workarea/storefront/reviews/modules/product_reviews_sort_menus.js ADDED
@@ -0,0 +1,82 @@
1
+ /**
2
+ * @namespace WORKAREA.productReviewsSortMenus
3
+ */
4
+ WORKAREA.registerModule('productReviewsSortMenus', (function () {
5
+ 'use strict';
6
+
7
+ var insertSortSelectBox = function (index, section) {
8
+ $('.reviews__header', section).after(
9
+ JST['workarea/storefront/reviews/templates/sort_by_property']()
10
+ );
11
+ },
12
+
13
+ sortNewToOld = function ($reviews) {
14
+ return _.sortBy($reviews, function (review) {
15
+ return -($(review).data('productReviewSectionEntry').createdAt);
16
+ });
17
+ },
18
+
19
+ sortOldToNew = function ($reviews) {
20
+ return _.sortBy($reviews, function (review) {
21
+ return $(review).data('productReviewSectionEntry').createdAt;
22
+ });
23
+ },
24
+
25
+
26
+ sortHighToLow = function ($reviews) {
27
+ return _.sortBy($reviews, function (review) {
28
+ return -($(review).data('productReviewSectionEntry').rating);
29
+ });
30
+ },
31
+
32
+ sortLowToHigh = function ($reviews) {
33
+ return _.sortBy($reviews, function (review) {
34
+ return $(review).data('productReviewSectionEntry').rating;
35
+ });
36
+ },
37
+
38
+ handleSortSelectChange = function (event) {
39
+ var $reviews = $(
40
+ '[data-product-review-section-entry]', event.delegateTarget
41
+ ),
42
+
43
+ action = $(event.currentTarget).val(),
44
+
45
+ actions = {
46
+ oldest: function () {
47
+ return sortOldToNew($reviews);
48
+ },
49
+ newest: function () {
50
+ return sortNewToOld($reviews);
51
+ },
52
+ lowest: function () {
53
+ return sortLowToHigh($reviews);
54
+ },
55
+ highest: function () {
56
+ return sortHighToLow($reviews);
57
+ }
58
+ };
59
+
60
+ $('.reviews__review-group', event.delegateTarget)
61
+ .html(actions[action]());
62
+ },
63
+
64
+ /**
65
+ * @method
66
+ * @name init
67
+ * @memberof WORKAREA.productReviewsSortMenus
68
+ */
69
+ init = function ($scope) {
70
+ $('.reviews', $scope)
71
+ .each(insertSortSelectBox)
72
+ .on(
73
+ 'change',
74
+ '[data-product-reviews-sort-menu]',
75
+ handleSortSelectChange
76
+ );
77
+ };
78
+
79
+ return {
80
+ init: init
81
+ };
82
+ }()));
data/app/assets/javascripts/workarea/storefront/reviews/modules/rating_buttons.js ADDED
@@ -0,0 +1,103 @@
1
+ /**
2
+ * @namespace WORKAREA.ratingButtons
3
+ */
4
+ WORKAREA.registerModule('ratingButtons', (function () {
5
+ 'use strict';
6
+
7
+ var activateLabels = function (radioButton, originalIndex, $scope) {
8
+ var $allButtons = getAllRatingButtons($(document)),
9
+
10
+ updateState = function (index, button) {
11
+ var $label = getAssociatedLabel(button, $scope);
12
+
13
+ $label.removeClass('write-review__star--hovered write-review__star--active');
14
+
15
+ if ($label.data('index') <= originalIndex) {
16
+ $label.addClass('write-review__star--active');
17
+ }
18
+ };
19
+
20
+ if ($(radioButton).is(':checked')) {
21
+ $allButtons.each(updateState);
22
+ }
23
+ },
24
+
25
+ getAllRatingButtons = function ($scope) {
26
+ return $('[data-rating-button]', $scope);
27
+ },
28
+
29
+ getAssociatedLabel = function (button, $scope) {
30
+ return $('label[for=' + $(button).attr('id') + ']', $scope);
31
+ },
32
+
33
+ removeHoverClass = function ($label) {
34
+ $label.removeClass('write-review__star--hovered');
35
+ },
36
+
37
+ addHoverClass = function ($label) {
38
+ $label.addClass('write-review__star--hovered');
39
+ },
40
+
41
+ removeHoverStateFromLabel = function ($scope) {
42
+ var $buttons = getAllRatingButtons($scope),
43
+
44
+ removeState = function (index, button) {
45
+ var $label = getAssociatedLabel(button, $scope);
46
+
47
+ removeHoverClass($label);
48
+ };
49
+
50
+ $buttons.each(removeState);
51
+ },
52
+
53
+ addHoverStateToLabels = function ($scope, originalIndex) {
54
+ var $buttons = getAllRatingButtons($scope),
55
+
56
+ addState = function (index, button) {
57
+ var $label = getAssociatedLabel(button, $scope);
58
+
59
+ if ($label.data('index') <= originalIndex) {
60
+ addHoverClass($label);
61
+ }
62
+ };
63
+
64
+ $buttons.each(addState);
65
+ },
66
+
67
+ setupLabels = function ($scope, index, button) {
68
+ var $label = getAssociatedLabel(button, $scope),
69
+ labelTitle = $('.write-review__star-text', $label).text();
70
+
71
+ /**
72
+ * 1. per http://www.workarea.com/labs/touching-on-html-labels/
73
+ */
74
+
75
+ $label
76
+ .attr('title', labelTitle)
77
+ .data('index', index)
78
+ .on('click', $.noop) /* [1] */
79
+ .on('mouseenter', _.partial(addHoverStateToLabels, $scope, index))
80
+ .on('mouseleave', _.partial(removeHoverStateFromLabel, $scope));
81
+
82
+ activateLabels(button, index, $scope);
83
+
84
+ $(button)
85
+ .on('click', _.partial(activateLabels, button, index, $scope))
86
+ .on('focus', _.partial(addHoverClass, $label))
87
+ .on('blur', _.partial(removeHoverClass, $label));
88
+ },
89
+
90
+ /**
91
+ * @method
92
+ * @name init
93
+ * @memberof WORKAREA.ratingButtons
94
+ */
95
+ init = function ($scope) {
96
+ $('[data-rating-button]', $scope)
97
+ .each(_.partial(setupLabels, $scope));
98
+ };
99
+
100
+ return {
101
+ init: init
102
+ };
103
+ }()));
data/app/assets/javascripts/workarea/storefront/reviews/templates/sort_by_property.jst.ejs ADDED
@@ -0,0 +1,11 @@
1
+ <div class="property property--responsive">
2
+ <label class="property__name"><span class="property__text">Sort By</span></label>
3
+ <div class="value">
4
+ <select data-product-reviews-sort-menu="">
5
+ <option value="newest" selected>Most Recent</option>
6
+ <option value="oldest">Oldest First</option>
7
+ <option value="highest">Rating: High to Low</option>
8
+ <option value="lowest">Rating: Low to High</option>
9
+ </select>
10
+ </div>
11
+ </div>
data/app/assets/stylesheets/workarea/storefront/reviews/components/_rating.scss ADDED
@@ -0,0 +1,28 @@
1
+ /*------------------------------------*\
2
+ #RATING
3
+ \*------------------------------------*/
4
+
5
+ $rating-fill-color: $yellow !default;
6
+ $rating-empty-color: $gray !default;
7
+ $rating-star-size: 16px !default;
8
+
9
+
10
+
11
+ .rating {
12
+ position: relative;
13
+ white-space: nowrap;
14
+ vertical-align: middle;
15
+ user-select: none;
16
+
17
+ .product-summary & {
18
+ margin: $spacing-unit auto 0;
19
+ }
20
+ }
21
+
22
+ .rating__star {
23
+ display: inline-block;
24
+ width: $rating-star-size;
25
+ height: $rating-star-size;
26
+ fill: $rating-fill-color;
27
+ overflow: hidden;
28
+ }
data/app/assets/stylesheets/workarea/storefront/reviews/components/_reviews.scss ADDED
@@ -0,0 +1,59 @@
1
+ /*------------------------------------*\
2
+ #REVIEWS
3
+ \*------------------------------------*/
4
+
5
+ .reviews {
6
+ margin: ($spacing-unit * 3) 0 0;
7
+ }
8
+
9
+ .reviews__header {
10
+ display: table;
11
+ margin: 0 0 ($spacing-unit * 2);
12
+ width: 100%;
13
+ }
14
+
15
+ .reviews__count {
16
+ display: table-cell;
17
+ }
18
+
19
+ .reviews__write-action {
20
+ display: table-cell;
21
+ text-align: right;
22
+ }
23
+
24
+ .reviews__review-group {
25
+ @extend %list-reset;
26
+ }
27
+
28
+ .reviews__review {
29
+ padding: ($spacing-unit * 2) 0;
30
+ }
31
+
32
+ .reviews__review-title {}
33
+
34
+ .reviews__review-body {}
35
+
36
+ .reviews__review-meta {
37
+ @extend %clearfix;
38
+ }
39
+
40
+ .reviews__review-author {
41
+ margin: 0;
42
+ float: left;
43
+ line-height: 1;
44
+ }
45
+
46
+ .reviews__review-verified {
47
+ margin: 0 0 0 $spacing-unit;
48
+ padding-left: $spacing-unit;
49
+ float: left;
50
+ line-height: 1;
51
+ font-weight: bold;
52
+ }
53
+
54
+ .reviews__review-date {
55
+ margin: 0 0 0 $spacing-unit;
56
+ padding-left: $spacing-unit;
57
+ float: left;
58
+ line-height: 1;
59
+ }
data/app/assets/stylesheets/workarea/storefront/reviews/components/_reviews_aggregate.scss ADDED
@@ -0,0 +1,38 @@
1
+ /*------------------------------------*\
2
+ #REVIEWS-AGGREGATE
3
+ \*------------------------------------*/
4
+
5
+ $reviews-aggregate-border: 1px solid $link-color !default;
6
+
7
+
8
+ .reviews-aggregate {
9
+ @extend %clearfix;
10
+ margin: 0 0 12px;
11
+
12
+ /**
13
+ * Extend `.rating {}` in _rating.scss.
14
+ */
15
+
16
+ .rating {
17
+ @extend %inline-list;
18
+ margin: 0 ($spacing-unit / 2) 0 0;
19
+ }
20
+ }
21
+
22
+ .reviews-aggregate__rating-link:hover {
23
+ text-decoration: none;
24
+ }
25
+
26
+ .reviews-aggregate__count {
27
+ @extend %inline-list;
28
+ }
29
+
30
+ .reviews-aggregate__read {}
31
+
32
+ .reviews-aggregate__write-action {
33
+ &::before {
34
+ padding-right: ($spacing-unit / 2);
35
+ border-left: $reviews-aggregate-border;
36
+ content: '';
37
+ }
38
+ }
data/app/assets/stylesheets/workarea/storefront/reviews/components/_write_review.scss ADDED
@@ -0,0 +1,63 @@
1
+ /*------------------------------------*\
2
+ #WRITE-REVIEW
3
+ \*------------------------------------*/
4
+
5
+ $write-review-fill-color: $yellow !default;
6
+ $write-review-empty-color: $transparent !default;
7
+ $write-review-star-size: 16px !default;
8
+
9
+
10
+ .write-review {}
11
+
12
+ .write-review__ratings {
13
+ @extend %inline-list;
14
+
15
+
16
+ .js & {
17
+ @extend %clearfix;
18
+ }
19
+ }
20
+
21
+ .write-review__rating {
22
+ font-size: $write-review-star-size;
23
+
24
+ .js & {
25
+ padding-right: ($spacing-unit / 2);
26
+ float: left;
27
+ }
28
+ }
29
+
30
+ .write-review__rating-input {
31
+ margin: 0 ($spacing-unit / 2) 0 0;
32
+
33
+ .js & {
34
+ @extend %visually-hidden;
35
+ }
36
+ }
37
+
38
+ .write-review__star {}
39
+
40
+ .write-review__star-icon {
41
+ width: $write-review-star-size;
42
+ height: $write-review-star-size;
43
+ fill: $write-review-empty-color;
44
+ stroke: $write-review-fill-color;
45
+
46
+ .write-review__star--hovered &,
47
+ .write-review__star--active & {
48
+ fill: $write-review-fill-color;
49
+ }
50
+ }
51
+
52
+
53
+ .write-review__star--hovered {
54
+ opacity: 0.4;
55
+ }
56
+
57
+ .write-review__star--active {
58
+ opacity: 1;
59
+ }
60
+
61
+ .write-review__star-text {
62
+ @extend %hidden-if-js-enabled;
63
+ }
data/app/controllers/workarea/admin/catalog_products_controller.decorator ADDED
@@ -0,0 +1,9 @@
1
+ module Workarea
2
+ decorate Admin::CatalogProductsController, with: :reviews do
3
+ decorated { helper Admin::ReviewsHelper }
4
+
5
+ def reviews
6
+ @reviews = Review.find_for_product(@product.id, true)
7
+ end
8
+ end
9
+ end
data/app/controllers/workarea/admin/import_reviews_controller.rb ADDED
@@ -0,0 +1,35 @@
1
+ module Workarea
2
+ module Admin
3
+ class ImportReviewsController < Admin::ApplicationController
4
+ required_permissions :marketing
5
+
6
+ def new
7
+ @import = Import::Review.new
8
+ end
9
+
10
+ def create
11
+ @import = Import::Review.new(import_params)
12
+
13
+ if @import.save
14
+ ProcessImport.perform_async(@import.to_global_id)
15
+
16
+ flash[:success] = t('workarea.admin.import_reviews.flash_messages.processing')
17
+ redirect_to reviews_path
18
+ else
19
+ render :new, status: :unprocessable_entity
20
+ end
21
+ end
22
+
23
+ def sample
24
+ send_file Reviews::Engine.root.join('public/workarea/import_samples/reviews.csv')
25
+ end
26
+
27
+ private
28
+
29
+ def import_params
30
+ params.fetch(:import_review, {})
31
+ .merge(created_by_id: current_user.id)
32
+ end
33
+ end
34
+ end
35
+ end
data/app/controllers/workarea/admin/reviews_controller.rb ADDED
@@ -0,0 +1,51 @@
1
+ module Workarea
2
+ class Admin::ReviewsController < Admin::ApplicationController
3
+ required_permissions :marketing
4
+ before_action :find_review, except: :index
5
+
6
+ def index
7
+ search = Search::AdminReviews.new(params.merge(autocomplete: request.xhr?))
8
+ @search = Admin::ReviewsSearchViewModel.new(search, view_model_options)
9
+ end
10
+
11
+ def show
12
+ redirect_to edit_review_path(@review)
13
+ end
14
+
15
+ def edit; end
16
+
17
+ def update
18
+ if @review.update_attributes(review_params)
19
+ flash[:success] = t('workarea.admin.reviews.flash_messages.updated')
20
+ redirect_to reviews_path
21
+ else
22
+ render :edit
23
+ end
24
+ end
25
+
26
+ def destroy
27
+ @review.destroy
28
+ flash[:success] = t('workarea.admin.reviews.flash_messages.destroyed')
29
+ if params[:product].present?
30
+ redirect_to reviews_catalog_product_path(params[:product])
31
+ else
32
+ redirect_to reviews_path
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def find_review
39
+ if params[:id].present?
40
+ review = Review.find(params[:id])
41
+ @review = Admin::ReviewViewModel.wrap(review)
42
+ @user = User.find(review.user_id) rescue nil
43
+ end
44
+ end
45
+
46
+ def review_params
47
+ return {} if params[:review].blank?
48
+ params[:review].permit(:body, :approved, :rating, :title)
49
+ end
50
+ end
51
+ end
data/app/controllers/workarea/storefront/review_requests_controller.rb ADDED
@@ -0,0 +1,50 @@
1
+ module Workarea
2
+ module Storefront
3
+ class ReviewRequestsController < Storefront::ApplicationController
4
+ before_action :validate_request
5
+ before_action :find_product, :set_review
6
+
7
+ def show; end
8
+
9
+ def complete
10
+ if verify_recaptcha(model: @review, env: Rails.env) && @review.save
11
+ @request.complete!
12
+ Review::Request.cancel_for_orders!(@request.order_id)
13
+
14
+ flash[:success] = t('workarea.storefront.reviews.flash_messages.created')
15
+ redirect_to product_path(@product)
16
+ else
17
+ flash[:error] = t('workarea.storefront.reviews.flash_messages.failure')
18
+ render :show
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def validate_request
25
+ @request = Review::Request.find_by_token(params[:id])
26
+
27
+ if @request.nil? || @request.completed?
28
+ flash[:error] = t('workarea.storefront.review_requests.flash_messages.already_submitted')
29
+ redirect_to(root_path) && (return)
30
+ end
31
+ end
32
+
33
+ def find_product
34
+ model = Catalog::Product.find(@request.product_id)
35
+ @product = ProductViewModel.wrap(model, view_model_options)
36
+ end
37
+
38
+ def set_review
39
+ @review = ReviewViewModel.wrap(Review.new(review_params))
40
+ end
41
+
42
+ def review_params
43
+ ReviewRequestParams.new(
44
+ @request,
45
+ params[:review]&.permit(:rating, :title, :body)&.to_h || {}
46
+ ).to_h
47
+ end
48
+ end
49
+ end
50
+ end
data/app/controllers/workarea/storefront/reviews_controller.rb ADDED
@@ -0,0 +1,53 @@
1
+ module Workarea
2
+ module Storefront
3
+ class ReviewsController < Storefront::ApplicationController
4
+ before_action :find_product
5
+ before_action :set_review
6
+
7
+ def new; end
8
+
9
+ def create
10
+ if review_can_be_saved? && @create_review.save
11
+ redirect_to product_path(params[:product_id])
12
+ flash[:success] = t('workarea.storefront.reviews.flash_messages.created')
13
+ else
14
+ render :new
15
+ flash[:error] = t('workarea.storefront.reviews.flash_messages.failure')
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def set_review
22
+ @create_review = CreateReview.for(
23
+ product: @product,
24
+ user: current_user,
25
+ params: review_params.to_h
26
+ )
27
+
28
+ @review = ReviewViewModel.wrap(@create_review.review)
29
+ end
30
+
31
+ def find_product
32
+ @product ||=
33
+ begin
34
+ model = Catalog::Product.find_by(slug: params[:product_id])
35
+ raise InvalidDisplay unless model.active? || current_user.try(:admin?)
36
+ ProductViewModel.wrap(model)
37
+ end
38
+ end
39
+
40
+ def review_params
41
+ return {} unless params[:review].present?
42
+
43
+ params[:review].permit(
44
+ :product_id, :rating, :title, :body, :first_name, :last_name, :email
45
+ )
46
+ end
47
+
48
+ def review_can_be_saved?
49
+ verify_recaptcha(model: @create_review, env: Rails.env)
50
+ end
51
+ end
52
+ end
53
+ end
data/app/helpers/workarea/admin/reviews_helper.rb ADDED
@@ -0,0 +1,12 @@
1
+ module Workarea
2
+ module Admin
3
+ module ReviewsHelper
4
+ def reviewer_info(model)
5
+ return model.user_info unless model.user_id.present?
6
+
7
+ link_to model.user_info,
8
+ edit_user_path(model.user_id)
9
+ end
10
+ end
11
+ end
12
+ end
data/app/helpers/workarea/storefront/reviews_helper.rb ADDED
@@ -0,0 +1,24 @@
1
+ module Workarea
2
+ module Storefront
3
+ module ReviewsHelper
4
+ def rating_stars(rating, options = {})
5
+ full_star_count = rating.floor
6
+ empty_star_count = 5 - rating.ceil
7
+ half_star_size = (rating % 1).round(2) * 100
8
+ half_star_width = 20 + (half_star_size - 0) * (80.0 - 20) / (100.0 - 0)
9
+ itemprop = options[:aggregate] ? 'aggregateRating' : 'reviewRating'
10
+ itemtype = options[:aggregate] ? 'http://schema.org/AggregateRating' : 'http://schema.org/Rating'
11
+
12
+ render 'workarea/storefront/products/rating', rating: rating, full_star_count: full_star_count, empty_star_count: empty_star_count, half_star_width: half_star_width, half_star_size: half_star_size, itemprop: itemprop, itemtype: itemtype
13
+ end
14
+
15
+ def display_purchase_requirement_message
16
+ if current_user && Workarea.config.require_purchase_to_post_review && current_user.total_spent.to_f.zero?
17
+ true
18
+ else
19
+ false
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
data/app/mailers/workarea/admin/status_report_mailer.decorator ADDED
@@ -0,0 +1,8 @@
1
+ module Workarea
2
+ decorate Admin::StatusReportMailer, with: :reviews do
3
+ def report(email, date)
4
+ @reviews_summary = ReviewSummary.new
5
+ super
6
+ end
7
+ end
8
+ end
data/app/mailers/workarea/storefront/review_mailer.rb ADDED
@@ -0,0 +1,23 @@
1
+ module Workarea
2
+ module Storefront
3
+ class ReviewMailer < Storefront::ApplicationMailer
4
+ include TransactionalMailer
5
+
6
+ def review_request(request_id)
7
+ @request = Review::Request.find(request_id)
8
+ @content = Content::Email.find_content('review_request')
9
+ @product = Storefront::ProductViewModel.wrap(
10
+ Catalog::Product.find(@request.product_id)
11
+ )
12
+
13
+ mail(
14
+ to: @request.email,
15
+ subject: t(
16
+ 'workarea.storefront.email.review_request.subject',
17
+ product: @product.name
18
+ )
19
+ )
20
+ end
21
+ end
22
+ end
23
+ end
data/app/models/workarea/catalog/product.decorator ADDED
@@ -0,0 +1,15 @@
1
+ module Workarea
2
+ decorate Catalog::Product, with: :reviews do
3
+ decorated do
4
+ # @!attribute total_reviews
5
+ # @return [Integer] the total number of approved reviews
6
+ #
7
+ field :total_reviews, type: Integer, default: 0
8
+
9
+ # @!attribute average_rating
10
+ # @return [Float] the average rating of approved reviews
11
+ #
12
+ field :average_rating, type: Float
13
+ end
14
+ end
15
+ end
data/app/models/workarea/fulfillment.decorator ADDED
@@ -0,0 +1,11 @@
1
+ module Workarea
2
+ decorate Fulfillment, with: :reviews do
3
+ def ship_items(tracking_number, shipped_items)
4
+ super.tap do |result|
5
+ if result && Workarea.config.send_transactional_emails
6
+ ScheduleReviewRequests.perform_async(id, shipped_items)
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
data/app/models/workarea/review.rb ADDED
@@ -0,0 +1,135 @@
1
+ module Workarea
2
+ class Review
3
+ include ApplicationDocument
4
+
5
+ field :user_id, type: String
6
+ field :user_info, type: String
7
+ field :first_name, type: String
8
+ field :last_name, type: String
9
+ field :email, type: String
10
+ field :product_id, type: String
11
+ field :rating, type: Integer
12
+ field :title, type: String
13
+ field :body, type: String
14
+ field :approved, type: Boolean, default: false
15
+ field :verified, type: Boolean, default: false
16
+
17
+ index(product_id: 1, approved: 1)
18
+ index(approved: 1)
19
+ index(created_at: 1)
20
+
21
+ validates :product_id, presence: true
22
+ validates :user_info, presence: true
23
+ validates :body, presence: true
24
+ validates :rating, presence: true,
25
+ numericality: {
26
+ greater_than_or_equal_to: 1,
27
+ less_than_or_equal_to: 5,
28
+ only_integer: true
29
+ }
30
+
31
+ validate :user_must_have_spent_money_to_create_review
32
+
33
+ before_save :ensure_title
34
+
35
+ scope :by_product, ->(id) { where(product_id: id) }
36
+ scope :approved, -> { where(approved: true) }
37
+ scope :pending, -> { where(approved: false) }
38
+
39
+ def self.sorts
40
+ [ Workarea::Sort.pending,
41
+ Workarea::Sort.newest,
42
+ Workarea::Sort.modified,
43
+ Workarea::Sort.title,
44
+ Workarea::Sort.rating ]
45
+ end
46
+
47
+ # Get {Mongoid::Criteria} for the reviews for a certain product.
48
+ # Optionally allow including unapproved reviews (for the admin).
49
+ #
50
+ # @param [String] product_id
51
+ # @param [optional, Boolean] allow_unapproved
52
+ # @return [Mongoid::Criteria]
53
+ #
54
+ def self.find_for_product(product_id, allow_unapproved = false)
55
+ criteria = by_product(product_id)
56
+ allow_unapproved ? criteria : criteria.approved
57
+ end
58
+
59
+ # Lookup aggregate stats on product reviews for a product.
60
+ #
61
+ # @param [String] product_id
62
+ # @return [Hash]
63
+ #
64
+ def self.find_single_aggregates(product_id)
65
+ stats = by_product(product_id).approved.aggregates(:rating)
66
+ { count: stats['count'] || 0, average: stats['avg'] || 0 }
67
+ end
68
+
69
+ # Lookup aggregate review stats on multiple products.
70
+ # Used for show stats on browse/detail.
71
+ #
72
+ # @param [Array<String>] product_ids
73
+ # @return [Hash] results Keys are product IDs, values are stats Hash
74
+ #
75
+ def self.find_aggregates(*product_ids)
76
+ product_ids = Array(product_ids).flatten
77
+
78
+ if product_ids.one?
79
+ find_single_aggregates(product_ids.first)
80
+ else
81
+ product_ids.inject({}) do |memo, product_id|
82
+ memo[product_id] = find_single_aggregates(product_id)
83
+ memo
84
+ end
85
+ end
86
+ end
87
+
88
+ # Find sort score for a product. Used in the search index for sorting by
89
+ # best reviewed.
90
+ #
91
+ # Balances a small number of ratings with the uncertainty of only having a few ratings.
92
+ # See: http://masanjin.net/blog/how-to-rank-products-based-on-user-input
93
+ #
94
+ # @param [String] product_id
95
+ # @return [Float]
96
+ #
97
+ def self.find_sorting_score(product_id)
98
+ reviews = find_for_product(product_id)
99
+
100
+ votes = [ reviews.select { |r| r.rating == 1 }.length,
101
+ reviews.select { |r| r.rating == 2 }.length,
102
+ reviews.select { |r| r.rating == 3 }.length,
103
+ reviews.select { |r| r.rating == 4 }.length,
104
+ reviews.select { |r| r.rating == 5 }.length ]
105
+
106
+ prior = [2, 2, 2, 2, 2]
107
+
108
+ posterior = votes.zip(prior).map { |a, b| a + b }
109
+ sum = posterior.inject { |a, b| a + b }
110
+
111
+ posterior.
112
+ map.with_index { |v, i| (i + 1) * v }.
113
+ inject { |a, b| a + b }.
114
+ to_f / sum
115
+ end
116
+
117
+ def anonymous?
118
+ !user_id.present?
119
+ end
120
+
121
+ def ensure_title
122
+ self.title = body.truncate(50) if title.blank?
123
+ end
124
+
125
+ def user_must_have_spent_money_to_create_review
126
+ if Workarea.config.require_purchase_to_post_review
127
+ user = User.find(user_id)
128
+ if user.total_spent.to_f.zero?
129
+ err_msg = I18n.t('workarea.storefront.reviews.total_spent_validation')
130
+ errors.add(:user, err_msg)
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
data/app/models/workarea/review/request.rb ADDED
@@ -0,0 +1,62 @@
1
+ module Workarea
2
+ class Review
3
+ class Request
4
+ include ApplicationDocument
5
+ include UrlToken
6
+
7
+ field :order_id, type: String
8
+ field :user_id, type: String
9
+ field :product_id, type: String
10
+
11
+ field :email, type: String
12
+
13
+ field :send_after, type: DateTime
14
+ field :sent_at, type: DateTime
15
+ field :completed_at, type: DateTime
16
+ field :canceled_at, type: DateTime
17
+
18
+ index(order_id: 1)
19
+ index(send_after: 1, sent_at: 1, canceled_at: 1)
20
+
21
+ index(
22
+ { created_at: 1 },
23
+ { expire_after_seconds: Workarea.config.review_request_ttl }
24
+ )
25
+
26
+ scope :by_order, ->(order_id) { where(order_id: order_id) }
27
+ scope :ready_to_send, ->(from = Time.current) do
28
+ where(:send_after.lte => from, :sent_at => nil, :canceled_at => nil)
29
+ end
30
+
31
+ def self.cancel_for_orders!(order_ids)
32
+ self.in(order_id: Array.wrap(order_ids))
33
+ .where(completed_at: nil)
34
+ .update_all(canceled_at: Time.current)
35
+ end
36
+
37
+ def scheduled?
38
+ send_after.present?
39
+ end
40
+
41
+ def sent?
42
+ sent_at.present?
43
+ end
44
+
45
+ def completed?
46
+ completed_at.present? || canceled?
47
+ end
48
+
49
+ def canceled?
50
+ canceled_at.present?
51
+ end
52
+
53
+ def complete!
54
+ update_attributes(completed_at: Time.current)
55
+ end
56
+
57
+ def send!
58
+ update_attributes(sent_at: Time.current)
59
+ end
60
+ end
61
+ end
62
+ end
data/app/models/workarea/search/admin/review.rb ADDED
@@ -0,0 +1,72 @@
1
+ module Workarea
2
+ module Search
3
+ class Admin
4
+ class Review < Search::Admin
5
+ def type
6
+ 'review'
7
+ end
8
+
9
+ def status
10
+ 'active'
11
+ end
12
+
13
+ def facets
14
+ super.merge(
15
+ state: state,
16
+ rating: model.rating,
17
+ verification: verification
18
+ )
19
+ end
20
+
21
+ def jump_to_text
22
+ nil
23
+ end
24
+
25
+ def name
26
+ nil
27
+ end
28
+
29
+ def jump_to_position
30
+ 6
31
+ end
32
+
33
+ def search_text
34
+ [model.product_id, model.user_id]
35
+ end
36
+
37
+ def review_text
38
+ [model.title, model.body, product&.name].compact
39
+ end
40
+
41
+ def state
42
+ if model.approved?
43
+ I18n.t('workarea.admin.reviews.approved')
44
+ else
45
+ I18n.t('workarea.admin.reviews.pending')
46
+ end
47
+ end
48
+
49
+ def verification
50
+ if model.verified?
51
+ I18n.t('workarea.admin.reviews.verified')
52
+ else
53
+ I18n.t('workarea.admin.reviews.unverified')
54
+ end
55
+ end
56
+
57
+ def as_document
58
+ super.merge(
59
+ review_text: review_text,
60
+ rating: model.rating
61
+ )
62
+ end
63
+
64
+ private
65
+
66
+ def product
67
+ @product ||= Catalog::Product.find(model.product_id) rescue nil
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
data/app/models/workarea/search/storefront/product.decorator ADDED
@@ -0,0 +1,9 @@
1
+ module Workarea
2
+ decorate Search::Storefront::Product, with: :reviews do
3
+ def sorts
4
+ super.merge(
5
+ rating: Review.find_sorting_score(model.id)
6
+ )
7
+ end
8
+ end
9
+ end
data/app/models/workarea/sort.decorator ADDED
@@ -0,0 +1,17 @@
1
+ module Workarea
2
+ decorate Sort, with: :reviews do
3
+ class_methods do
4
+ def top_rated
5
+ new(I18n.t('workarea.sorts.top_rated'), :top_rated, :'sorts.rating', :desc)
6
+ end
7
+
8
+ def highest_rating
9
+ new(I18n.t('workarea.sorts.highest_rating'), :highest_rating, :rating, :desc)
10
+ end
11
+
12
+ def lowest_rating
13
+ new(I18n.t('workarea.sorts.lowest_rating'), :lowest_rating, :rating, :asc)
14
+ end
15
+ end
16
+ end
17
+ end
data/app/queries/workarea/review_request_params.rb ADDED
@@ -0,0 +1,47 @@
1
+ module Workarea
2
+ class ReviewRequestParams
3
+ attr_reader :request, :params
4
+
5
+ def initialize(request, params)
6
+ @request = request
7
+ @params = params
8
+ end
9
+
10
+ def to_h
11
+ params.merge(
12
+ product_id: @request.product_id,
13
+ user_id: @request.user_id,
14
+ email: @request.email,
15
+ user_info: user_info,
16
+ first_name: first_name,
17
+ last_name: last_name,
18
+ verified: true
19
+ )
20
+ end
21
+
22
+ private
23
+
24
+ def user_info
25
+ return user.public_info if user&.public_info.present?
26
+
27
+ [first_name.first, last_name.first].join
28
+ end
29
+
30
+ def first_name
31
+ user&.first_name || billing_address.first_name
32
+ end
33
+
34
+ def last_name
35
+ user&.last_name || billing_address.last_name
36
+ end
37
+
38
+ def user
39
+ return nil unless @request.user_id.present?
40
+ @user ||= User.find(@request.user_id)
41
+ end
42
+
43
+ def billing_address
44
+ @billing_address ||= Payment.find(@request.order_id).address
45
+ end
46
+ end
47
+ end
data/app/queries/workarea/review_summary.rb ADDED
@@ -0,0 +1,11 @@
1
+ module Workarea
2
+ class ReviewSummary
3
+ def pending_reviews_count
4
+ @pending_reviews_count ||= Review.pending.count
5
+ end
6
+
7
+ def recent_reviews_count
8
+ @recent_reviews_count ||= Review.where(:created_at.gte => 1.day.ago).count
9
+ end
10
+ end
11
+ end
data/app/queries/workarea/search/admin_reviews.rb ADDED
@@ -0,0 +1,40 @@
1
+ module Workarea
2
+ module Search
3
+ class AdminReviews
4
+ include Query
5
+ include AdminIndexSearch
6
+ include AdminSorting
7
+ include Pagination
8
+
9
+ document Search::Admin
10
+
11
+ def initialize(params = {})
12
+ super(params.merge(type: 'review'))
13
+ end
14
+
15
+ def fields
16
+ super + %w(review_text)
17
+ end
18
+
19
+ def facets
20
+ super + [
21
+ TermsFacet.new(self, 'state'),
22
+ TermsFacet.new(self, 'rating'),
23
+ TermsFacet.new(self, 'verification')
24
+ ]
25
+ end
26
+
27
+ def sort
28
+ result = super || []
29
+
30
+ if params[:sort] == Sort.highest_rating.to_s
31
+ result.prepend(Sort.highest_rating.field => Sort.highest_rating.direction)
32
+ elsif params[:sort] == Sort.lowest_rating.to_s
33
+ result.prepend(Sort.lowest_rating.field => Sort.lowest_rating.direction)
34
+ end
35
+
36
+ result
37
+ end
38
+ end
39
+ end
40
+ end
data/app/queries/workarea/search/category_browse.decorator ADDED
@@ -0,0 +1,9 @@
1
+ module Workarea
2
+ decorate Search::CategoryBrowse, with: :reviews do
3
+ class_methods do
4
+ def available_sorts
5
+ super.tap { |sorts| sorts << Sort.top_rated }
6
+ end
7
+ end
8
+ end
9
+ end
data/app/queries/workarea/search/product_search.decorator ADDED
@@ -0,0 +1,9 @@
1
+ module Workarea
2
+ decorate Search::ProductSearch, with: :reviews do
3
+ class_methods do
4
+ def available_sorts
5
+ super.tap { |sorts| sorts << Sort.top_rated }
6
+ end
7
+ end
8
+ end
9
+ end
data/app/seeds/review_seeds.rb ADDED
@@ -0,0 +1,49 @@
1
+ module Workarea
2
+ class ReviewSeeds
3
+ def perform
4
+ puts 'Adding reviews...'
5
+
6
+ Sidekiq::Callbacks.disable do
7
+ create_reviews_for_catalog
8
+ create_review_request_email_content
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ def create_reviews_for_catalog
15
+ Workarea::Catalog::Product.all.each_by(100) do |product|
16
+ reviews = Array.new(rand(10)) { create_review(product) }
17
+ next unless reviews.size > 0
18
+
19
+ Workarea::Review.collection.insert_many(reviews.map(&:as_document))
20
+ UpdateProductReviewData.perform_async(product.id)
21
+ end
22
+ end
23
+
24
+ def create_review(product)
25
+ Workarea::Review.new(
26
+ product_id: product.id,
27
+ user_id: BSON::ObjectId.new,
28
+ rating: rand(5) + 1,
29
+ title: Faker::Lorem.sentence,
30
+ body: Faker::Lorem.paragraph,
31
+ approved: [true, false].sample,
32
+ user_info: Faker::Internet.user_name,
33
+ verified: [true, false].sample,
34
+ created_at: rand(45).days.ago,
35
+ updated_at: Time.current
36
+ )
37
+ end
38
+
39
+ def create_review_request_email_content
40
+ Content::Email.create!(
41
+ type: 'review_request',
42
+ content: <<~HTML
43
+ <h1>Let us know what you think!</h1>
44
+ <p>Your feedback is important to us.</p>
45
+ HTML
46
+ )
47
+ end
48
+ end
49
+ end
data/app/services/workarea/create_review.rb ADDED
@@ -0,0 +1,99 @@
1
+ module Workarea
2
+ class CreateReview
3
+ include ActiveModel::Validations
4
+
5
+ validate :valid_review
6
+ validates :first_name, :last_name, presence: true
7
+
8
+ attr_reader :review, :user, :params
9
+
10
+ def self.for(product: nil, user: nil, params: {})
11
+ review = Review.new(
12
+ product_id: product.try(:id),
13
+ user_id: user.try(:id)
14
+ )
15
+
16
+