@@ -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
|
@@ -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
|
@@ -0,0 +1,2 @@
|
|
1
|
+
**/test/**/*.js
|
2
|
+
testing/**/spec_helper.js
|
@@ -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
|
+
}
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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
|
@@ -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
|
@@ -0,0 +1 @@
|
|
1
|
+
--files CHANGELOG.md
|
@@ -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.
|
@@ -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>
|
@@ -0,0 +1,3 @@
|
|
1
|
+
View this plugin's contribution guidelines here:
|
2
|
+
|
3
|
+
<https://github.com/workarea-commerce/workarea/blob/master/CONTRIBUTING.md>
|
@@ -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'
|
@@ -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.
|
@@ -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)
|
@@ -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
|
@@ -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>
|
@@ -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>
|
@@ -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>
|
@@ -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>
|
@@ -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
|
+
}()));
|
@@ -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
|
+
}()));
|
@@ -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
|
+
}()));
|
@@ -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>
|
@@ -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
|
+
}
|
@@ -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
|
+
}
|
@@ -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
|
+
}
|
@@ -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
|
+
}
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
+
|