Skip to content
This repository was archived by the owner on Jun 24, 2024. It is now read-only.

Commit 6c11489

Browse files
authored
Merge pull request #2394 from education/pr/839
Continuation of #839
2 parents da374ec + 88eca49 commit 6c11489

File tree

25 files changed

+189
-10
lines changed

25 files changed

+189
-10
lines changed

Gemfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,5 +121,5 @@ group :test do
121121
gem "shoulda-matchers", "4.0.0.rc1"
122122
gem "simplecov", "~> 0.15.0", require: false
123123
gem "vcr", "~> 3.0", ">= 3.0.3"
124-
gem "webmock", "~> 3.0", ">= 3.0.1"
124+
gem "webmock", "~> 3.5"
125125
end

Gemfile.lock

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ GEM
194194
guard (~> 2.1)
195195
guard-compat (~> 1.1)
196196
rspec (>= 2.99.0, < 4.0)
197-
hashdiff (0.3.6)
197+
hashdiff (1.0.0)
198198
hashie (3.6.0)
199199
httpclient (2.8.3)
200200
i18n (0.9.5)
@@ -435,7 +435,7 @@ GEM
435435
unicode-display_width (~> 1.0, >= 1.0.1)
436436
ruby-progressbar (1.8.1)
437437
ruby_dep (1.5.0)
438-
safe_yaml (1.0.4)
438+
safe_yaml (1.0.5)
439439
sass (3.4.25)
440440
sass-rails (5.0.6)
441441
railties (>= 4.0.0, < 6)
@@ -504,10 +504,10 @@ GEM
504504
activemodel (>= 5.0)
505505
bindex (>= 0.4.0)
506506
railties (>= 5.0)
507-
webmock (3.0.1)
507+
webmock (3.7.5)
508508
addressable (>= 2.3.6)
509509
crack (>= 0.3.2)
510-
hashdiff
510+
hashdiff (>= 0.4.0, < 2.0.0)
511511
websocket-driver (0.7.1)
512512
websocket-extensions (>= 0.1.0)
513513
websocket-extensions (0.1.4)
@@ -600,7 +600,7 @@ DEPENDENCIES
600600
unicode-emoji (~> 1.1)
601601
vcr (~> 3.0, >= 3.0.3)
602602
web-console (~> 3.5, >= 3.5.1)
603-
webmock (~> 3.0, >= 3.0.1)
603+
webmock (~> 3.5)
604604

605605
RUBY VERSION
606606
ruby 2.6.4p104

app/controllers/stafftools/groupings_controller.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,20 @@ class GroupingsController < StafftoolsController
66

77
def show; end
88

9+
def destroy
10+
org = @grouping.organization
11+
12+
GroupAssignment.where(grouping: @grouping).destroy_all
13+
14+
if @grouping.destroy
15+
flash[:success] = "Grouping was destroyed"
16+
redirect_to stafftools_organization_path(org.id)
17+
else
18+
flash[:error] = "Grouping was not destroyed"
19+
render :show
20+
end
21+
end
22+
923
private
1024

1125
def set_grouping

app/controllers/stafftools/groups_controller.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,18 @@ class GroupsController < StafftoolsController
66

77
def show; end
88

9+
def destroy
10+
grouping = @group.grouping
11+
12+
if @group.destroy
13+
flash[:success] = "Group was destroyed"
14+
redirect_to stafftools_grouping_path(grouping.id)
15+
else
16+
flash[:error] = "Group was not destroyed"
17+
render :show
18+
end
19+
end
20+
921
private
1022

1123
def set_group

app/views/stafftools/groupings/show.html.erb

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,39 @@
4949
</div>
5050
</div>
5151
<% end %>
52+
53+
<div class="boxed-group dangerzone">
54+
<h3>Dangerzone</h3>
55+
56+
<div class="boxed-group-inner">
57+
<p>
58+
<a data-remodal-target="delete-grouping" class="btn btn-danger">Delete this grouping</a>
59+
</p>
60+
61+
<div class="remodal text-left" data-remodal-id="delete-grouping">
62+
<button data-remodal-action="close" class="remodal-close"><%= octicon 'x' %></button>
63+
<h2 class="remodal-header">Are you ABSOLUTELY sure?</h2>
64+
65+
<div class="remodal-warning">
66+
Unexpected things will happen if you don't read this!
67+
</div>
68+
69+
<div class="remodal-description">
70+
<p>
71+
This action <strong>CANNOT</strong> be undone.<br>
72+
Please note this will delete the grouping as well as all groups and group-assignments belonging to the grouping.
73+
</p>
74+
</div>
75+
76+
<%= form_for @grouping, url: stafftools_grouping_path(@grouping.id), html: { "data-name" => @grouping.id, method: 'delete' } do |f| %>
77+
<dl class="form js-normalize-submit">
78+
<dt>Please type the ID (<%= @grouping.id %>) of the grouping to confirm</dt>
79+
<dd><input type="text" class="js-input-block" autofocus></dd>
80+
</dl>
81+
82+
<%= f.submit 'Delete this Grouping', class: 'btn btn-danger btn-block js-submit', disabled: true %>
83+
<% end %>
84+
</div>
85+
</div>
86+
</div>
5287
</div>

app/views/stafftools/groups/show.html.erb

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,39 @@
5959
</div>
6060
</div>
6161
<% end %>
62+
63+
<div class="boxed-group dangerzone">
64+
<h3>Dangerzone</h3>
65+
66+
<div class="boxed-group-inner">
67+
<p>
68+
<a data-remodal-target="delete-group" class="btn btn-danger">Delete this group</a>
69+
</p>
70+
71+
<div class="remodal text-left" data-remodal-id="delete-group">
72+
<button data-remodal-action="close" class="remodal-close"><%= octicon 'x' %></button>
73+
<h2 class="remodal-header">Are you ABSOLUTELY sure?</h2>
74+
75+
<div class="remodal-warning">
76+
Unexpected things will happen if you don't read this!
77+
</div>
78+
79+
<div class="remodal-description">
80+
<p>
81+
This action <strong>CANNOT</strong> be undone.<br>
82+
Please note this will delete the record on GitHub Classroom and the team on GitHub, but not the repository on GitHub.
83+
</p>
84+
</div>
85+
86+
<%= form_for @group, url: stafftools_group_path(@group.id), html: { "data-name" => @group.id, method: 'delete' } do |f| %>
87+
<dl class="form js-normalize-submit">
88+
<dt>Please type the ID (<%= @group.id %>) of the group to confirm</dt>
89+
<dd><input type="text" class="js-input-block" autofocus></dd>
90+
</dl>
91+
92+
<%= f.submit 'Delete this Group', class: 'btn btn-danger btn-block js-submit', disabled: true %>
93+
<% end %>
94+
</div>
95+
</div>
96+
</div>
6297
</div>

config/routes.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,8 @@
174174
resources :group_assignment_repos, path: "group-assignment-repos", only: %i[show destroy]
175175
resources :group_assignments, path: "group-assignments", only: [:show]
176176

177-
resources :groupings, only: [:show]
178-
resources :groups, only: [:show]
177+
resources :groupings, only: %i[show destroy]
178+
resources :groups, only: %i[show destroy]
179179
end
180180

181181
namespace :api, defaults: { format: :json } do

spec/controllers/stafftools/groupings_controller_spec.rb

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@
33
require "rails_helper"
44

55
RSpec.describe Stafftools::GroupingsController, type: :controller do
6-
let(:user) { classroom_teacher }
7-
let(:grouping) { create(:grouping) }
6+
let(:organization) { classroom_org }
7+
let(:user) { organization.users.first }
8+
let(:grouping) { Grouping.create(organization: organization, title: "Grouping 1") }
9+
10+
let(:group_assignment) do
11+
create(:group_assignment, creator: user, organization: organization, grouping: grouping)
12+
end
813

914
before(:each) do
1015
sign_in_as(user)
@@ -33,4 +38,41 @@
3338
end
3439
end
3540
end
41+
42+
describe "DELETE #destroy", :vcr do
43+
context "as an unauthorized user" do
44+
before do
45+
delete :destroy, params: { id: grouping.id }
46+
end
47+
48+
it "returns a 404" do
49+
expect(response.status).to eq(404)
50+
end
51+
end
52+
53+
context "as an authorized user" do
54+
before do
55+
group_assignment.save
56+
user.update_attributes(site_admin: true)
57+
58+
delete :destroy, params: { id: grouping.id }
59+
end
60+
61+
it "destroys grouping" do
62+
expect(Grouping.find_by(id: grouping.id)).to be_nil
63+
end
64+
65+
it "destroys group assignments" do
66+
expect(GroupAssignment.find_by(id: group_assignment.id)).to be_nil
67+
end
68+
69+
it "shows a success message" do
70+
expect(flash[:success]).to eq("Grouping was destroyed")
71+
end
72+
73+
it "redirects to org path" do
74+
expect(response).to redirect_to(stafftools_organization_path(organization.id))
75+
end
76+
end
77+
end
3678
end

spec/controllers/stafftools/groups_controller_spec.rb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,35 @@
4141
end
4242
end
4343
end
44+
45+
describe "DELETE #destroy", :vcr do
46+
context "as an unauthorized user" do
47+
before do
48+
delete :destroy, params: { id: group.id }
49+
end
50+
51+
it "returns a 404" do
52+
expect(response.status).to eq(404)
53+
end
54+
end
55+
56+
context "as an authorized user" do
57+
before do
58+
user.update_attributes(site_admin: true)
59+
delete :destroy, params: { id: group.id }
60+
end
61+
62+
it "deletes the group" do
63+
expect(Group.find_by(id: group.id)).to be_nil
64+
end
65+
66+
it "shows an informative message" do
67+
expect(flash[:success]).to eq("Group was destroyed")
68+
end
69+
70+
it "redirects to grouping page" do
71+
expect(response).to redirect_to(stafftools_grouping_path(group.grouping.id))
72+
end
73+
end
74+
end
4475
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"http_interactions":[{"request":{"method":"get","uri":"https://api.github.com/user","body":{"encoding":"US-ASCII","base64_string":""},"headers":{"Accept":["application/vnd.github.v3+json"],"User-Agent":["Octokit Ruby Gem 4.6.2"],"Content-Type":["application/json"],"Cache-Control":["no-cache, no-store"],"Accept-Encoding":["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"]}},"response":{"status":{"code":200,"message":"OK"},"headers":{"Server":["GitHub.com"],"Date":["Sun, 26 Feb 2017 21:36:43 GMT"],"Content-Type":["application/json; charset=utf-8"],"Transfer-Encoding":["chunked"],"Status":["200 OK"],"X-Ratelimit-Limit":["5000"],"X-Ratelimit-Remaining":["4996"],"X-Ratelimit-Reset":["1488145639"],"Cache-Control":["private, max-age=60, s-maxage=60"],"Vary":["Accept, Authorization, Cookie, X-GitHub-OTP","Accept-Encoding"],"Etag":["W/\"d65473901cafa38adc46b81547c5e903\""],"Last-Modified":["Thu, 16 Feb 2017 08:44:30 GMT"],"X-Oauth-Scopes":["admin:gpg_key, admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user:email"],"X-Accepted-Oauth-Scopes":[""],"X-Github-Media-Type":["github.v3; format=json"],"Access-Control-Expose-Headers":["ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval"],"Access-Control-Allow-Origin":["*"],"Content-Security-Policy":["default-src 'none'"],"Strict-Transport-Security":["max-age=31536000; includeSubdomains; preload"],"X-Content-Type-Options":["nosniff"],"X-Frame-Options":["deny"],"X-Xss-Protection":["1; mode=block"],"X-Served-By":["5aeb3f30c9e3ef6ef7bcbcddfd9a68f7"],"X-Github-Request-Id":["A400:231B5:212B444:2B2DA1D:58B34A6B"]},"body":{"encoding":"ASCII-8BIT","base64_string":"eyJsb2dpbiI6Im53b29kdGhvcnBlIiwiaWQiOjxURVNUX0NMQVNTUk9PTV9P\nV05FUl9HSVRIVUJfSUQ+LCJhdmF0YXJfdXJsIjoiaHR0cHM6Ly9hdmF0YXJz\nLmdpdGh1YnVzZXJjb250ZW50LmNvbS91LzxURVNUX0NMQVNTUk9PTV9PV05F\nUl9HSVRIVUJfSUQ+P3Y9MyIsImdyYXZhdGFyX2lkIjoiIiwidXJsIjoiaHR0\ncHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9ud29vZHRob3JwZSIsImh0bWxf\ndXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL253b29kdGhvcnBlIiwiZm9sbG93\nZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvbndvb2R0\naG9ycGUvZm9sbG93ZXJzIiwiZm9sbG93aW5nX3VybCI6Imh0dHBzOi8vYXBp\nLmdpdGh1Yi5jb20vdXNlcnMvbndvb2R0aG9ycGUvZm9sbG93aW5ney9vdGhl\ncl91c2VyfSIsImdpc3RzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20v\ndXNlcnMvbndvb2R0aG9ycGUvZ2lzdHN7L2dpc3RfaWR9Iiwic3RhcnJlZF91\ncmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL253b29kdGhvcnBl\nL3N0YXJyZWR7L293bmVyfXsvcmVwb30iLCJzdWJzY3JpcHRpb25zX3VybCI6\nImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvbndvb2R0aG9ycGUvc3Vi\nc2NyaXB0aW9ucyIsIm9yZ2FuaXphdGlvbnNfdXJsIjoiaHR0cHM6Ly9hcGku\nZ2l0aHViLmNvbS91c2Vycy9ud29vZHRob3JwZS9vcmdzIiwicmVwb3NfdXJs\nIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9ud29vZHRob3JwZS9y\nZXBvcyIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3Vz\nZXJzL253b29kdGhvcnBlL2V2ZW50c3svcHJpdmFjeX0iLCJyZWNlaXZlZF9l\ndmVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9ud29v\nZHRob3JwZS9yZWNlaXZlZF9ldmVudHMiLCJ0eXBlIjoiVXNlciIsInNpdGVf\nYWRtaW4iOmZhbHNlLCJuYW1lIjoiTmF0aGFuaWVsIFdvb2R0aG9ycGUiLCJj\nb21wYW55IjoiQHRpbmZvaWwgIiwiYmxvZyI6Imh0dHA6Ly9ud29vZHRob3Jw\nZS5jb20iLCJsb2NhdGlvbiI6bnVsbCwiZW1haWwiOiJOSldvb2R0aG9ycGVA\nZ21haWwuY29tIiwiaGlyZWFibGUiOnRydWUsImJpbyI6IlN0dWR5aW5nIENv\nbXB1dGVyIFNjaWVuY2UgYXQgdGhlIFVuaXZlcnNpdHkgb2YgV2F0ZXJsb28u\nXHJcblxyXG5DdXJyZW50bHkgd29ya2luZyBhdCBAdGluZm9pbCBhcyBhIFNv\nZnR3YXJlIEVuZ2luZWVyaW5nIEludGVybiIsInB1YmxpY19yZXBvcyI6MzIs\nInB1YmxpY19naXN0cyI6MiwiZm9sbG93ZXJzIjozNiwiZm9sbG93aW5nIjoy\nMCwiY3JlYXRlZF9hdCI6IjIwMTMtMDQtMTRUMDA6MDY6MzhaIiwidXBkYXRl\nZF9hdCI6IjIwMTctMDItMTZUMDg6NDQ6MzBaIn0=\n"},"http_version":null},"recorded_at":"Sun, 26 Feb 2017 21:36:43 GMT"},{"request":{"method":"get","uri":"https://api.github.com/applications/\u003cTEST_APPLICATION_GITHUB_CLIENT_ID\u003e/tokens/\u003cTEST_CLASSROOM_OWNER_GITHUB_TOKEN\u003e","body":{"encoding":"US-ASCII","base64_string":""},"headers":{"Accept":["application/vnd.github.v3+json"],"User-Agent":["GitHub Classroom"],"Content-Type":["application/json"],"Cache-Control":["no-cache, no-store"],"Accept-Encoding":["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"]}},"response":{"status":{"code":200,"message":"OK"},"headers":{"Server":["GitHub.com"],"Date":["Fri, 27 Sep 2019 22:01:10 GMT"],"Content-Type":["application/json; charset=utf-8"],"Transfer-Encoding":["chunked"],"Status":["200 OK"],"X-Ratelimit-Limit":["5000"],"X-Ratelimit-Remaining":["4995"],"X-Ratelimit-Reset":["1569625267"],"Cache-Control":["public, max-age=60, s-maxage=60"],"Vary":["Accept"],"Etag":["W/\"00ded75e66d887a308e34274045fe8e4\""],"X-Github-Media-Type":["github.v3; format=json"],"Access-Control-Expose-Headers":["ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type"],"Access-Control-Allow-Origin":["*"],"Strict-Transport-Security":["max-age=31536000; includeSubdomains; preload"],"X-Frame-Options":["deny"],"X-Content-Type-Options":["nosniff"],"X-Xss-Protection":["1; mode=block"],"Referrer-Policy":["origin-when-cross-origin, strict-origin-when-cross-origin"],"Content-Security-Policy":["default-src 'none'"],"X-Github-Request-Id":["F624:7A19:20CBCD:3D93CA:5D8E86A6"]},"body":{"encoding":"ASCII-8BIT","base64_string":"eyJpZCI6MzE5OTg1MDkxLCJ1cmwiOiJodHRwczovL2FwaS5naXRodWIuY29t\nL2F1dGhvcml6YXRpb25zLzMxOTk4NTA5MSIsImFwcCI6eyJuYW1lIjoibG9j\nYWwgY2xhc3Nyb29tIiwidXJsIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwIiwi\nY2xpZW50X2lkIjoiPFRFU1RfQVBQTElDQVRJT05fR0lUSFVCX0NMSUVOVF9J\nRD4ifSwidG9rZW4iOiI8VEVTVF9DTEFTU1JPT01fT1dORVJfR0lUSFVCX1RP\nS0VOPiIsImhhc2hlZF90b2tlbiI6IjI2MTBlOTA1NmMyODY0NGY5MTkzNzdl\nMmY1MDkzMzQ5NmQxMDZmZTIxNzM3YjY5ODc5MzYwMzJkMzYyYzc3M2EiLCJ0\nb2tlbl9sYXN0X2VpZ2h0IjoiYzE1ZDU0MGEiLCJub3RlIjpudWxsLCJub3Rl\nX3VybCI6bnVsbCwiY3JlYXRlZF9hdCI6IjIwMTktMDgtMjBUMTY6MzI6MDRa\nIiwidXBkYXRlZF9hdCI6IjIwMTktMDgtMjBUMTY6MzI6MDRaIiwic2NvcGVz\nIjpbImFkbWluOm9yZyIsImFkbWluOm9yZ19ob29rIiwiZGVsZXRlX3JlcG8i\nLCJyZXBvIiwidXNlcjplbWFpbCJdLCJmaW5nZXJwcmludCI6bnVsbCwidXNl\nciI6eyJsb2dpbiI6InNwaW5lY29uZSIsImlkIjo8VEVTVF9DTEFTU1JPT01f\nT1dORVJfR0lUSFVCX0lEPiwibm9kZV9pZCI6Ik1EUTZWWE5sY2pjM056STRN\namM9IiwiYXZhdGFyX3VybCI6Imh0dHBzOi8vYXZhdGFyczEuZ2l0aHVidXNl\ncmNvbnRlbnQuY29tL3UvPFRFU1RfQ0xBU1NST09NX09XTkVSX0dJVEhVQl9J\nRD4/dj00IiwiZ3JhdmF0YXJfaWQiOiIiLCJ1cmwiOiJodHRwczovL2FwaS5n\naXRodWIuY29tL3VzZXJzL3NwaW5lY29uZSIsImh0bWxfdXJsIjoiaHR0cHM6\nLy9naXRodWIuY29tL3NwaW5lY29uZSIsImZvbGxvd2Vyc191cmwiOiJodHRw\nczovL2FwaS5naXRodWIuY29tL3VzZXJzL3NwaW5lY29uZS9mb2xsb3dlcnMi\nLCJmb2xsb3dpbmdfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vy\ncy9zcGluZWNvbmUvZm9sbG93aW5ney9vdGhlcl91c2VyfSIsImdpc3RzX3Vy\nbCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvc3BpbmVjb25lL2dp\nc3Rzey9naXN0X2lkfSIsInN0YXJyZWRfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0\naHViLmNvbS91c2Vycy9zcGluZWNvbmUvc3RhcnJlZHsvb3duZXJ9ey9yZXBv\nfSIsInN1YnNjcmlwdGlvbnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNv\nbS91c2Vycy9zcGluZWNvbmUvc3Vic2NyaXB0aW9ucyIsIm9yZ2FuaXphdGlv\nbnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9zcGluZWNv\nbmUvb3JncyIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20v\ndXNlcnMvc3BpbmVjb25lL3JlcG9zIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8v\nYXBpLmdpdGh1Yi5jb20vdXNlcnMvc3BpbmVjb25lL2V2ZW50c3svcHJpdmFj\neX0iLCJyZWNlaXZlZF9ldmVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHVi\nLmNvbS91c2Vycy9zcGluZWNvbmUvcmVjZWl2ZWRfZXZlbnRzIiwidHlwZSI6\nIlVzZXIiLCJzaXRlX2FkbWluIjp0cnVlfX0=\n"},"http_version":null},"recorded_at":"Fri, 27 Sep 2019 22:01:10 GMT"}],"recorded_with":"VCR 3.0.3"}

0 commit comments

Comments
 (0)