[deploy] Allow org admins to delete their organizations (#9357)

* Add destroy organization backend

* Add view for deleting org

* Add new line to end of file

* Refactor and move destroyable? to model

* Use a nice card to distinguish the message

* Prevent organizations from being deleted if they have credits

* Fix codeclimate issues

* Remove let_it_be_changeable for flakiness
This commit is contained in:
Andy Zhao 2020-07-28 12:00:15 -04:00 committed by GitHub
parent c9ff792497
commit a589529269
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 173 additions and 11 deletions

View file

@ -49,6 +49,25 @@ class OrganizationsController < ApplicationController
end
end
def destroy
organization = Organization.find_by(id: params[:id])
authorize organization
if organization.destroy
current_user.touch(:organization_info_updated_at)
CacheBuster.bust_user(current_user)
flash[:settings_notice] = "Your organization: \"#{organization.name}\" was successfully deleted."
redirect_to user_settings_path(:organization)
else
flash[:settings_notice] = "#{organization.errors.full_messages.to_sentence}.
Please email #{SiteConfig.email_addresses['default']} for assistance."
redirect_to user_settings_path(:organization, id: organization.id)
end
rescue Pundit::NotAuthorizedError
flash[:error] = "Your organization was not deleted; you must be an admin, the only member in the organization,
and have no articles connected to the organization."
redirect_to user_settings_path(:organization, id: organization.id)
end
def generate_new_secret
set_organization
@organization.secret = @organization.generated_random_secret

View file

@ -15,7 +15,7 @@ class Organization < ApplicationRecord
has_many :articles
has_many :listings
has_many :collections
has_many :credits
has_many :credits, dependent: :restrict_with_error
has_many :display_ads
has_many :notifications
has_many :organization_memberships, dependent: :delete_all
@ -58,6 +58,7 @@ class Organization < ApplicationRecord
before_validation :evaluate_markdown
after_commit :sync_related_elasticsearch_docs, on: %i[update destroy]
after_commit :bust_cache, on: :destroy
mount_uploader :profile_image, ProfileImageUploader
mount_uploader :nav_image, ProfileImageUploader
@ -104,6 +105,10 @@ class Organization < ApplicationRecord
false
end
def destroyable?
organization_memberships.count == 1 && articles.count.zero?
end
private
def evaluate_markdown

View file

@ -7,6 +7,10 @@ class OrganizationPolicy < ApplicationPolicy
user.org_admin?(record)
end
def destroy?
user.org_admin?(record) && record.destroyable?
end
def leave_org?
part_of_org?
end

View file

@ -198,3 +198,45 @@
<div><button class="crayons-btn" type="submit">Save</button></div>
<% end %>
</div>
<div class="crayons-card grid gap-6 p-6 mb-6">
<header>
<h2 class="color-accent-danger">Danger Zone</h2>
</header>
<div class="crayons-field">
<div class="grid gap-2">
<header>
<h3>Delete Organization</h3>
</header>
<p>
You can delete your organization if you:
<ul>
<li>are the only member in the organization</li>
<li>are an admin of the organization</li>
<li>the organization has no posts</li>
<li>and the organization has no credits</li>
</ul>
</p>
<% if @org_organization_memberships.size === 1 && @organization.articles_count.zero? %>
<% onsubmit = "return confirm('Are you absolutely sure you want to delete your organization: #{@organization.name}?');" %>
<%= form_tag organization_path(@organization.id), method: :delete, onsubmit: onsubmit do %>
<button class="crayons-btn crayons-btn--danger crayons-btn--icon-left" type="submit">
Delete "<%= @organization.name %>"
</button>
<% end %>
<% else %>
<div class="crayons-card crayons-card--secondary">
<div class="crayons-card__body">
Your organization currently does not meet the above requirements.
Please remove all other members and organization posts first.
</div>
</div>
<% end %>
<p>
Questions? Email us at
<a href="<%= SiteConfig.email_addresses[:default] %>"><%= SiteConfig.email_addresses[:default] %></a>
for help.
</p>
</div>
</div>
</div>

View file

@ -199,7 +199,7 @@ Rails.application.routes.draw do
resources :reactions, only: %i[index create]
resources :response_templates, only: %i[index create edit update destroy]
resources :feedback_messages, only: %i[index create]
resources :organizations, only: %i[update create]
resources :organizations, only: %i[update create destroy]
resources :followed_articles, only: [:index]
resources :follows, only: %i[show create update] do
collection do

View file

@ -1,7 +1,7 @@
require "rails_helper"
RSpec.describe UserDecorator, type: :decorator do
let_it_be_changeable(:saved_user) { create(:user) }
let(:saved_user) { create(:user) }
let(:user) { build(:user) }
context "with serialization" do

View file

@ -165,4 +165,55 @@ RSpec.describe "UserOrganization", type: :request do
.to raise_error Pundit::NotAuthorizedError
end
end
context "when deleting an organization" do
let(:org_admin) { create(:user, :org_admin) }
let(:org_member) { create(:user, :org_member) }
let(:user) { create(:user) }
it "deletes the organization" do
org_id = org_admin.organizations.first.id
sign_in org_admin
delete "/organizations/#{org_id}"
expect { Organization.find(org_id) }.to raise_error(ActiveRecord::RecordNotFound)
end
it "does not delete the organization if the user is only an org member" do
org_id = org_member.organizations.first.id
sign_in org_member
delete "/organizations/#{org_id}"
expect(Organization.find(org_id).persisted?).to eq true
end
it "does not delete the organization if the user is not a part of the org" do
org = create(:organization)
sign_in user
delete "/organizations/#{org.id}"
expect(org.persisted?).to eq true
end
it "does not delete the organization if the organization has an article associated to it" do
org_id = org_admin.organizations.first.id
create(:article, user: org_admin, organization_id: org_id)
sign_in org_admin
delete "/organizations/#{org_id}"
expect(Organization.find(org_id).persisted?).to eq true
end
it "does not delete the organization if the organization has more than one member" do
org_id = org_admin.organizations.first.id
create(:organization_membership, user: user, organization_id: org_id, type_of_user: "member")
sign_in org_admin
delete "/organizations/#{org_id}"
expect(Organization.find(org_id).persisted?).to eq true
end
it "does not delete the organization if the organization has credits" do
org = org_admin.organizations.first
sign_in org_admin
Credit.add_to(org, 1)
delete "/organizations/#{org.id}"
expect(org.persisted?).to eq true
end
end
end

View file

@ -48,14 +48,6 @@ RSpec.describe "UserSettings", type: :request do
expect(response.body).to include error_message
end
it "renders the proper organization page" do
first_org, second_org = create_list(:organization, 2)
create(:organization_membership, user: user, organization: first_org)
create(:organization_membership, user: user, organization: second_org, type_of_user: "admin")
get user_settings_path(tab: "organization", org_id: second_org.id) # /settings/organization/:org_id
expect(response.body).to include "Grow the team"
end
it "renders the proper response template" do
response_template = create(:response_template, user: user)
get user_settings_path(tab: "response-templates", id: response_template.id)

View file

@ -0,0 +1,49 @@
require "rails_helper"
RSpec.describe "users/edit", type: :view do
let(:user) { create(:user) }
let(:org) { create(:organization) }
describe "/settings/organization" do
before do
sign_in user
assign(:user, user)
assign(:tab_list, user.settings_tab_list)
assign(:tab, "organization")
end
context "when the user is an org admin" do
before do
org_membership = create(:organization_membership, user: user, organization: org, type_of_user: "admin")
assign(:organizations, user.organizations)
assign(:organization, org)
assign(:organization_membership, org_membership)
assign(:org_organization_memberships, org.organization_memberships)
end
it "shows the org admin page" do
render
expect(rendered).to have_text("Grow the team")
end
it "shows the destroy button if the org has one admin and no content" do
render
expect(rendered).to have_css(".crayons-btn--danger")
end
it "shows the proper message if the org has more than one member" do
second_user = create(:user)
create(:organization_membership, user: second_user, organization: org)
assign(:org_organization_memberships, org.organization_memberships)
render
expect(rendered).to have_text("Your organization currently does not meet the above requirements.")
end
it "shows the proper message if the org has an article" do
allow(org).to receive(:articles_count).and_return(1)
render
expect(rendered).to have_text("Your organization currently does not meet the above requirements.")
end
end
end
end