workarea-reviews 3.0.10 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.eslintrc.json +35 -0
- data/.github/workflows/ci.yml +60 -0
- data/.gitignore +1 -2
- data/.rubocop.yml +3 -0
- data/.stylelintrc.json +8 -0
- data/CHANGELOG.md +5 -23
- data/Gemfile +4 -2
- data/Rakefile +4 -5
- data/app/controllers/workarea/admin/reports_controller.decorator +10 -0
- data/app/helpers/workarea/admin/reviews_helper.rb +6 -5
- data/app/helpers/workarea/storefront/reviews_helper.rb +1 -3
- data/app/helpers/workarea/storefront/reviews_schema_org_helper.rb +34 -0
- data/app/mailers/workarea/admin/status_report_mailer.decorator +2 -2
- data/app/models/workarea/insights/most_active_reviewers.rb +34 -0
- data/app/models/workarea/insights/most_reviewed_products.rb +33 -0
- data/app/models/workarea/insights/top_rated_products.rb +33 -0
- data/app/models/workarea/review.rb +5 -16
- data/app/queries/workarea/reports/reviews_by_product.rb +86 -0
- data/app/queries/workarea/reports/reviews_by_user.rb +71 -0
- data/app/seeds/{review_seeds.rb → workarea/review_seeds.rb} +0 -0
- data/app/view_models/workarea/admin/dashboards/reports_view_model.decorator +11 -0
- data/app/view_models/workarea/admin/reports/reviews_by_product_view_model.rb +27 -0
- data/app/views/workarea/admin/dashboards/_reviews_by_product_card.html.haml +17 -0
- data/app/views/workarea/admin/insights/_most_active_reviewers.html.haml +22 -0
- data/app/views/workarea/admin/insights/_most_reviewed_products.html.haml +22 -0
- data/app/views/workarea/admin/insights/_top_rated_products.html.haml +22 -0
- data/app/views/workarea/admin/reports/reviews_by_product.html.haml +44 -0
- data/app/views/workarea/storefront/products/_rating.html.haml +1 -2
- data/app/views/workarea/storefront/products/_reviews.html.haml +5 -5
- data/app/views/workarea/storefront/products/_reviews_aggregate.html.haml +2 -2
- data/app/views/workarea/storefront/review_mailer/review_request.html.haml +2 -0
- data/app/views/workarea/storefront/review_requests/show.html.haml +2 -1
- data/config/initializers/append_points.rb +5 -0
- data/config/initializers/configuration.rb +3 -1
- data/config/locales/en.yml +46 -0
- data/config/routes.rb +4 -0
- data/lib/workarea/reviews/engine.rb +4 -0
- data/lib/workarea/reviews/version.rb +1 -1
- data/package.json +9 -0
- data/test/dummy/config/initializers/session_store.rb +3 -1
- data/test/helpers/workarea/storefront/reviews_helper_test.rb +1 -0
- data/test/integration/workarea/admin/reviews_integration_test.rb +5 -1
- data/test/models/workarea/insights/most_active_reviewers_test.rb +32 -0
- data/test/models/workarea/insights/most_reviewed_products_test.rb +32 -0
- data/test/models/workarea/insights/top_rated_products_test.rb +49 -0
- data/test/models/workarea/review_test.rb +29 -18
- data/test/queries/workarea/reports/reviews_by_product_test.rb +104 -0
- data/test/queries/workarea/reports/reviews_by_user_test.rb +102 -0
- data/test/system/workarea/admin/reviews_by_product_system_test.rb +60 -0
- data/workarea-reviews.gemspec +1 -1
- data/yarn.lock +3265 -0
- metadata +31 -7
- data/app/controllers/workarea/admin/import_reviews_controller.rb +0 -35
- data/test/helpers/workarea/admin/reviews_helper_test.rb +0 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7135d1659f6d6564addc0f472fbf72a6225098a29a9de3e9a3c16af4724b364e
|
4
|
+
data.tar.gz: fdfe10e7e4e59e7bfb4b4c08f05dc2a8190acc36c2bd5602277c2298a2f4072e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 294e2feacac1639da7c4b927fd9580f8c5e000bbd93885893a1c647cd980327ff8dd6d01d83db044cfc56da5dc71f2c62825deb07f71f85e042eeb59a54babb9
|
7
|
+
data.tar.gz: 014a4986c50d89dad1f932d8506d3171c776c91aedf83e959d6bab7e9a0d365255d31ce99aa960229189a959c1e744bb2c2ba889f46863677dd3abe4acfb2bb0
|
data/.eslintrc.json
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
{
|
2
|
+
"extends": "eslint:recommended",
|
3
|
+
"rules": {
|
4
|
+
"semi": ["error", "always"],
|
5
|
+
"eqeqeq": ["error", "always"]
|
6
|
+
},
|
7
|
+
"globals": {
|
8
|
+
"window": true,
|
9
|
+
"document": true,
|
10
|
+
"WORKAREA": true,
|
11
|
+
"$": true,
|
12
|
+
"jQuery": true,
|
13
|
+
"_": true,
|
14
|
+
"feature": true,
|
15
|
+
"JST": true,
|
16
|
+
"Turbolinks": true,
|
17
|
+
"I18n": true,
|
18
|
+
"Chart": true,
|
19
|
+
"Dropzone": true,
|
20
|
+
"strftime": true,
|
21
|
+
"Waypoint": true,
|
22
|
+
"wysihtml": true,
|
23
|
+
"LocalTime": true,
|
24
|
+
"describe": true,
|
25
|
+
"after": true,
|
26
|
+
"afterEach": true,
|
27
|
+
"before": true,
|
28
|
+
"beforeEach": true,
|
29
|
+
"it": true,
|
30
|
+
"expect": true,
|
31
|
+
"sinon": true,
|
32
|
+
"fixture": true,
|
33
|
+
"chai": true
|
34
|
+
}
|
35
|
+
}
|
@@ -0,0 +1,60 @@
|
|
1
|
+
name: CI
|
2
|
+
on: [push]
|
3
|
+
|
4
|
+
jobs:
|
5
|
+
static_analysis:
|
6
|
+
runs-on: ubuntu-latest
|
7
|
+
steps:
|
8
|
+
- uses: actions/checkout@v1
|
9
|
+
- uses: workarea-commerce/ci/bundler-audit@v1
|
10
|
+
- uses: workarea-commerce/ci/rubocop@v1
|
11
|
+
- uses: workarea-commerce/ci/eslint@v1
|
12
|
+
with:
|
13
|
+
args: '**/*.js'
|
14
|
+
- uses: workarea-commerce/ci/stylelint@v1
|
15
|
+
with:
|
16
|
+
args: '**/*.scss'
|
17
|
+
|
18
|
+
admin_tests:
|
19
|
+
runs-on: ubuntu-latest
|
20
|
+
steps:
|
21
|
+
- uses: actions/checkout@v1
|
22
|
+
- uses: actions/setup-ruby@v1
|
23
|
+
with:
|
24
|
+
ruby-version: 2.6.x
|
25
|
+
- uses: workarea-commerce/ci/test@v1
|
26
|
+
with:
|
27
|
+
command: bin/rails app:workarea:test:admin
|
28
|
+
|
29
|
+
core_tests:
|
30
|
+
runs-on: ubuntu-latest
|
31
|
+
steps:
|
32
|
+
- uses: actions/checkout@v1
|
33
|
+
- uses: actions/setup-ruby@v1
|
34
|
+
with:
|
35
|
+
ruby-version: 2.6.x
|
36
|
+
- uses: workarea-commerce/ci/test@v1
|
37
|
+
with:
|
38
|
+
command: bin/rails app:workarea:test:core
|
39
|
+
|
40
|
+
storefront_tests:
|
41
|
+
runs-on: ubuntu-latest
|
42
|
+
steps:
|
43
|
+
- uses: actions/checkout@v1
|
44
|
+
- uses: actions/setup-ruby@v1
|
45
|
+
with:
|
46
|
+
ruby-version: 2.6.x
|
47
|
+
- uses: workarea-commerce/ci/test@v1
|
48
|
+
with:
|
49
|
+
command: bin/rails app:workarea:test:storefront
|
50
|
+
|
51
|
+
plugins_tests:
|
52
|
+
runs-on: ubuntu-latest
|
53
|
+
steps:
|
54
|
+
- uses: actions/checkout@v1
|
55
|
+
- uses: actions/setup-ruby@v1
|
56
|
+
with:
|
57
|
+
ruby-version: 2.6.x
|
58
|
+
- uses: workarea-commerce/ci/test@v1
|
59
|
+
with:
|
60
|
+
command: bin/rails app:workarea:test:plugins
|
data/.gitignore
CHANGED
data/.rubocop.yml
ADDED
data/.stylelintrc.json
ADDED
data/CHANGELOG.md
CHANGED
@@ -1,31 +1,13 @@
|
|
1
|
-
Workarea Reviews 3.0
|
1
|
+
Workarea Reviews 3.1.0 (2019-11-26)
|
2
2
|
--------------------------------------------------------------------------------
|
3
3
|
|
4
|
-
*
|
4
|
+
* Updates for v3.5 compatibility
|
5
5
|
|
6
|
+
Ben Crouse
|
6
7
|
|
8
|
+
* Initial commit on master
|
7
9
|
|
8
|
-
|
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
|
10
|
+
Curt Howard
|
29
11
|
|
30
12
|
|
31
13
|
|
data/Gemfile
CHANGED
data/Rakefile
CHANGED
@@ -28,16 +28,15 @@ desc "Release version #{Workarea::Reviews::VERSION} of the gem"
|
|
28
28
|
task :release do
|
29
29
|
host = "https://#{ENV['BUNDLE_GEMS__WEBLINC__COM']}@gems.weblinc.com"
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
35
|
|
36
36
|
system "git tag -a v#{Workarea::Reviews::VERSION} -m 'Tagging #{Workarea::Reviews::VERSION}'"
|
37
37
|
system 'git push --tags'
|
38
38
|
|
39
39
|
system 'gem build workarea-reviews.gemspec'
|
40
|
-
system "gem push workarea-reviews-#{Workarea::Reviews::VERSION}.gem"
|
41
40
|
system "gem push workarea-reviews-#{Workarea::Reviews::VERSION}.gem --host #{host}"
|
42
41
|
system "rm workarea-reviews-#{Workarea::Reviews::VERSION}.gem"
|
43
42
|
end
|
@@ -1,11 +1,12 @@
|
|
1
1
|
module Workarea
|
2
2
|
module Admin
|
3
3
|
module ReviewsHelper
|
4
|
-
def
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
4
|
+
def reviews_report_filter_options
|
5
|
+
[
|
6
|
+
[t('workarea.admin.reports.reviews_by_product.filters.all'), nil],
|
7
|
+
[t('workarea.admin.reports.reviews_by_product.filters.approved'), 'approved'],
|
8
|
+
[t('workarea.admin.reports.reviews_by_product.filters.unapproved'), 'unapproved']
|
9
|
+
]
|
9
10
|
end
|
10
11
|
end
|
11
12
|
end
|
@@ -6,10 +6,8 @@ module Workarea
|
|
6
6
|
empty_star_count = 5 - rating.ceil
|
7
7
|
half_star_size = (rating % 1).round(2) * 100
|
8
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
9
|
|
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
|
10
|
+
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
|
13
11
|
end
|
14
12
|
|
15
13
|
def display_purchase_requirement_message
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Workarea
|
2
|
+
module Storefront
|
3
|
+
module ReviewsSchemaOrgHelper
|
4
|
+
def product_schema(product, related_products: nil)
|
5
|
+
schema = super.merge(
|
6
|
+
'review': product.reviews.map do |review|
|
7
|
+
{
|
8
|
+
'@type': 'Review',
|
9
|
+
'author': review.user_info,
|
10
|
+
'datePublished': review.created_at.strftime('%Y-%m-%d'),
|
11
|
+
'description': review.body,
|
12
|
+
'name': review.title,
|
13
|
+
'reviewRating': {
|
14
|
+
'@type': 'Rating',
|
15
|
+
'bestRating': '5',
|
16
|
+
'worstRating': '1',
|
17
|
+
'ratingValue': review.rating.round(2).to_s
|
18
|
+
}
|
19
|
+
}
|
20
|
+
end
|
21
|
+
)
|
22
|
+
|
23
|
+
if product.total_reviews > 0 && product.average_rating.present?
|
24
|
+
schema['aggregateRating'] = {
|
25
|
+
'reviewCount': product.total_reviews.to_s,
|
26
|
+
'ratingValue': product.average_rating.round(2).to_s
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
schema
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Workarea
|
2
|
+
module Insights
|
3
|
+
class MostActiveReviewers < Base
|
4
|
+
class << self
|
5
|
+
def dashboards
|
6
|
+
%w(people marketing)
|
7
|
+
end
|
8
|
+
|
9
|
+
def generate_monthly!
|
10
|
+
results = generate_results
|
11
|
+
create!(results: results) if results.present?
|
12
|
+
end
|
13
|
+
|
14
|
+
def generate_results
|
15
|
+
report
|
16
|
+
.results
|
17
|
+
.select { |r| r['reviews'] > 0 }
|
18
|
+
.take(Workarea.config.insights_users_list_max_results)
|
19
|
+
.map { |result| result.merge(email: result['_id']) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def report
|
23
|
+
Reports::ReviewsByUser.new(
|
24
|
+
starts_at: beginning_of_last_month,
|
25
|
+
ends_at: end_of_last_month,
|
26
|
+
sort_by: 'activity_score',
|
27
|
+
sort_direction: 'desc',
|
28
|
+
results_filter: 'approved'
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Workarea
|
2
|
+
module Insights
|
3
|
+
class MostReviewedProducts < Base
|
4
|
+
class << self
|
5
|
+
def dashboards
|
6
|
+
%w(marketing)
|
7
|
+
end
|
8
|
+
|
9
|
+
def generate_monthly!
|
10
|
+
results = generate_results
|
11
|
+
create!(results: results) if results.present?
|
12
|
+
end
|
13
|
+
|
14
|
+
def generate_results
|
15
|
+
report
|
16
|
+
.results
|
17
|
+
.select { |r| r['reviews'] > 0 }
|
18
|
+
.take(Workarea.config.insights_products_list_max_results)
|
19
|
+
.map { |result| result.merge(product_id: result['_id']) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def report
|
23
|
+
Reports::ReviewsByProduct.new(
|
24
|
+
starts_at: beginning_of_last_month,
|
25
|
+
ends_at: end_of_last_month,
|
26
|
+
sort_by: 'reviews',
|
27
|
+
sort_direction: 'desc'
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Workarea
|
2
|
+
module Insights
|
3
|
+
class TopRatedProducts < Base
|
4
|
+
class << self
|
5
|
+
def dashboards
|
6
|
+
%w(marketing)
|
7
|
+
end
|
8
|
+
|
9
|
+
def generate_monthly!
|
10
|
+
results = generate_results
|
11
|
+
create!(results: results) if results.present?
|
12
|
+
end
|
13
|
+
|
14
|
+
def generate_results
|
15
|
+
report
|
16
|
+
.results
|
17
|
+
.select { |r| r['reviews'] > 0 }
|
18
|
+
.take(Workarea.config.insights_products_list_max_results)
|
19
|
+
.map { |result| result.merge(product_id: result['_id']) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def report
|
23
|
+
Reports::ReviewsByProduct.new(
|
24
|
+
starts_at: beginning_of_last_month,
|
25
|
+
ends_at: end_of_last_month,
|
26
|
+
sort_by: 'weighted_average_rating',
|
27
|
+
sort_direction: 'desc'
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -95,23 +95,12 @@ module Workarea
|
|
95
95
|
# @return [Float]
|
96
96
|
#
|
97
97
|
def self.find_sorting_score(product_id)
|
98
|
-
reviews = find_for_product(product_id)
|
99
|
-
|
100
|
-
|
101
|
-
|
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 }
|
98
|
+
reviews = find_for_product(product_id).to_a
|
99
|
+
count = (1..5).each_with_object({}) do |i, memo|
|
100
|
+
memo[i] = reviews.select { |r| r.rating == i }.length + 2
|
101
|
+
end
|
110
102
|
|
111
|
-
|
112
|
-
map.with_index { |v, i| (i + 1) * v }.
|
113
|
-
inject { |a, b| a + b }.
|
114
|
-
to_f / sum
|
103
|
+
count.sum { |rating, count| rating * count }.to_f / count.values.sum
|
115
104
|
end
|
116
105
|
|
117
106
|
def anonymous?
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module Workarea
|
2
|
+
module Reports
|
3
|
+
class ReviewsByProduct
|
4
|
+
include Report
|
5
|
+
|
6
|
+
self.reporting_class = Review
|
7
|
+
self.sort_fields = %w(reviews verified average_rating weighted_average_rating)
|
8
|
+
|
9
|
+
def aggregation
|
10
|
+
[filter_results, project_used_fields, group_by_product, project_averages]
|
11
|
+
end
|
12
|
+
|
13
|
+
def filter_results
|
14
|
+
{
|
15
|
+
'$match' => {
|
16
|
+
'created_at' => { '$gte' => starts_at, '$lte' => ends_at },
|
17
|
+
**approval_query
|
18
|
+
}
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def project_used_fields
|
23
|
+
{
|
24
|
+
'$project' => {
|
25
|
+
'product_id' => 1,
|
26
|
+
'rating' => 1,
|
27
|
+
'verified' => 1
|
28
|
+
}
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def group_by_product
|
33
|
+
{
|
34
|
+
'$group' => {
|
35
|
+
'_id' => '$product_id',
|
36
|
+
'reviews' => { '$sum' => 1 },
|
37
|
+
'verified' => { '$sum' => { '$cond' => { 'if' => '$verified', 'then' => 1, 'else' => 0 } } },
|
38
|
+
'rating_tally' => { '$sum' => '$rating' },
|
39
|
+
'rated_5' => { '$sum' => { '$cond' => { 'if' => { '$eq' => ['$rating', 5] }, 'then' => 1, 'else' => 0 } } },
|
40
|
+
'rated_4' => { '$sum' => { '$cond' => { 'if' => { '$eq' => ['$rating', 4] }, 'then' => 1, 'else' => 0 } } },
|
41
|
+
'rated_3' => { '$sum' => { '$cond' => { 'if' => { '$eq' => ['$rating', 3] }, 'then' => 1, 'else' => 0 } } },
|
42
|
+
'rated_2' => { '$sum' => { '$cond' => { 'if' => { '$eq' => ['$rating', 2] }, 'then' => 1, 'else' => 0 } } },
|
43
|
+
'rated_1' => { '$sum' => { '$cond' => { 'if' => { '$eq' => ['$rating', 1] }, 'then' => 1, 'else' => 0 } } }
|
44
|
+
}
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
def project_averages
|
49
|
+
{
|
50
|
+
'$project' => {
|
51
|
+
'product_id' => 1,
|
52
|
+
'reviews' => 1,
|
53
|
+
'verified' => 1,
|
54
|
+
'average_rating' => { '$divide' => ['$rating_tally', '$reviews'] },
|
55
|
+
'weighted_average_rating' => {
|
56
|
+
'$divide' => [
|
57
|
+
{
|
58
|
+
'$sum' => [
|
59
|
+
{ '$multiply' => [{ '$sum' => [2, '$rated_5'] }, 5] },
|
60
|
+
{ '$multiply' => [{ '$sum' => [2, '$rated_4'] }, 4] },
|
61
|
+
{ '$multiply' => [{ '$sum' => [2, '$rated_3'] }, 3] },
|
62
|
+
{ '$multiply' => [{ '$sum' => [2, '$rated_2'] }, 2] },
|
63
|
+
{ '$multiply' => [{ '$sum' => [2, '$rated_1'] }, 1] }
|
64
|
+
]
|
65
|
+
},
|
66
|
+
{ '$sum' => [10, '$rated_5', '$rated_4', '$rated_3', '$rated_2', '$rated_1'] }
|
67
|
+
]
|
68
|
+
}
|
69
|
+
}
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def approval_query
|
76
|
+
if params[:results_filter] == 'approved'
|
77
|
+
{ approved: true }
|
78
|
+
elsif params[:results_filter] == 'unapproved'
|
79
|
+
{ approved: false }
|
80
|
+
else
|
81
|
+
{}
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|