[deploy] [Admin Tooling] Allow admins unpublish articles from the Mod Actions Panel (#10038)

This commit is contained in:
Arit Amana 2020-09-02 17:08:08 -04:00 committed by GitHub
parent 7f928555a5
commit b79766f697
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 117 additions and 7 deletions

View file

@ -419,6 +419,16 @@
border-top: 1px solid var(--base-20);
overflow-y: auto;
div.unpublish-article {
background: inherit;
border: none;
padding: var(--su-3) var(--su-4);
display: flex;
justify-content: center;
align-items: center;
cursor: auto;
}
button.other-things-btn {
background: inherit;
border: none;

View file

@ -2,7 +2,7 @@ class ArticlesController < ApplicationController
include ApplicationHelper
before_action :authenticate_user!, except: %i[feed new]
before_action :set_article, only: %i[edit manage update destroy stats]
before_action :set_article, only: %i[edit manage update destroy stats admin_unpublish]
before_action :raise_suspended, only: %i[new create update]
before_action :set_cache_control_headers, only: %i[feed]
after_action :verify_authorized
@ -190,6 +190,21 @@ class ArticlesController < ApplicationController
@organization_id = @article.organization_id
end
def admin_unpublish
authorize @article
if @article.has_frontmatter?
@article.body_markdown.sub!(/\npublished:\s*true\s*\n/, "\npublished: false\n")
else
@article.published = false
end
if @article.save
render json: { message: "success", path: @article.current_state_path }, status: :ok
else
render json: { message: @article.errors.full_messages }, status: :unprocessable_entity
end
end
private
def base_editor_assigments

View file

@ -17,9 +17,9 @@ export function addCloseListener() {
}
export function initializeHeight() {
document.documentElement.style.height = '100%';
document.body.style.cssText = 'height: 100%; margin: 0; padding-top: 0; overflow-y: hidden';
document.body.style.cssText =
'height: 100%; margin: 0; padding-top: 0; overflow-y: hidden';
document.getElementById('page-content').style.cssText =
'margin-top: 0 !important; margin-bottom: 0;';
}
@ -34,7 +34,6 @@ function toggleDropdown(type) {
}
}
function applyReactedClass(category) {
const upVote = document.querySelector("[data-category='thumbsup']");
const downVote = document.querySelector("[data-category='thumbsdown']");
@ -143,6 +142,33 @@ async function updateExperienceLevel(currentUserId, articleId, rating, group) {
}
}
const adminUnpublishArticle = async (id, username, slug) => {
try {
const response = await request(`/articles/${id}/admin_unpublish`, {
method: 'PATCH',
body: JSON.stringify({ id, username, slug }),
credentials: 'same-origin',
});
const outcome = await response.json();
/* eslint-disable no-restricted-globals */
if (outcome.message == 'success') {
window.top.location.assign(`${window.location.origin}${outcome.path}`);
} else {
top.addSnackbarItem({
message: `Error: ${outcome.message}`,
addCloseButton: true,
});
}
} catch (error) {
top.addSnackbarItem({
message: `Error: ${error}`,
addCloseButton: true,
});
}
};
function toggleSubmitContainer() {
document
.getElementById('adjustment-reason-container')
@ -362,6 +388,21 @@ export function addBottomActionsListeners() {
);
});
});
const unpublishArticleBtn = document.querySelector('#unpublish-article-btn');
if (unpublishArticleBtn) {
unpublishArticleBtn.addEventListener('click', () => {
const {
articleId: id,
articleAuthor: username,
articleSlug: slug,
} = unpublishArticleBtn.dataset;
if (confirm('You are unpublishing this article; are you sure?')) {
adminUnpublishArticle(id, username, slug);
}
});
}
}
export function initializeActionsPanel() {

View file

@ -3,6 +3,10 @@ class ArticlePolicy < ApplicationPolicy
user_is_author? || user_admin? || user_org_admin? || minimal_admin?
end
def admin_unpublish?
minimal_admin?
end
def new?
true
end

View file

@ -161,6 +161,20 @@
</span>
</a>
</div>
<% if current_user.any_admin? && @moderatable.published %>
<div class="unpublish-article">
<button
class="crayons-btn crayons-btn--danger"
id="unpublish-article-btn"
data-article-id="<%= @moderatable.id %>"
data-article-author="<%= @moderatable.username %>"
data-article-slug="<%= @moderatable.slug %>"
>
Unpublish Article
</button>
</div>
<% end %>
</div>
<div class="bottom-actions">

View file

@ -195,7 +195,9 @@ Rails.application.routes.draw do
resources :messages, only: [:create]
resources :chat_channels, only: %i[index show create update]
resources :chat_channel_memberships, only: %i[index create edit update destroy]
resources :articles, only: %i[update create destroy]
resources :articles, only: %i[update create destroy] do
patch "/admin_unpublish", to: "articles#admin_unpublish"
end
resources :article_mutes, only: %i[update]
resources :comments, only: %i[create update destroy] do
patch "/hide", to: "comments#hide"

View file

@ -22,13 +22,13 @@ RSpec.describe ArticlePolicy do
context "when user is not the author" do
it { is_expected.to permit_actions(%i[new create preview]) }
it { is_expected.to forbid_actions(%i[update edit manage delete_confirm destroy]) }
it { is_expected.to forbid_actions(%i[update edit manage delete_confirm destroy admin_unpublish]) }
context "with banned status" do
before { user.add_role(:banned) }
it { is_expected.to permit_actions(%i[new preview]) }
it { is_expected.to forbid_actions(%i[create edit manage update delete_confirm destroy]) }
it { is_expected.to forbid_actions(%i[create edit manage update delete_confirm destroy admin_unpublish]) }
end
end
@ -49,5 +49,6 @@ RSpec.describe ArticlePolicy do
let(:user) { build(:user, :super_admin) }
it { is_expected.to permit_actions(%i[update new edit manage create delete_confirm destroy preview]) }
it { is_expected.to permit_actions(%i[admin_unpublish]) }
end
end

View file

@ -0,0 +1,23 @@
require "rails_helper"
RSpec.describe "ArticlesAdminUnpublish", type: :request do
let(:user) { create(:user) }
let(:article) { create(:article, user_id: user.id) }
let(:super_admin) { create(:user, :super_admin) }
before do
sign_in super_admin
end
it "unpublishes an article" do
expect(article.published).to be true
patch "/articles/#{article.id}/admin_unpublish", params: {
id: article.id,
username: user.username,
slug: article.slug
}
article.reload
expect(article.published).to be false
end
end