Adds ability for admins to create new tags via /admin/tags (#11240)

* Adds ability for admins to create new tags via /admin/tags

* Merge branch 'master' of github.com:forem/forem into fdoxyz/admin-new-tags-11026

* Fix rubocop pre-hook

* UI Tweaks

* replaces show with edit action & other minor changes

* Reverse unrelated changes

* Removed more unrelated edits

* Fix specs

* Fix moderator link
This commit is contained in:
Fernando Valverde 2020-12-02 08:12:57 -06:00 committed by GitHub
parent 201492c95b
commit c740b4e6dd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 146 additions and 65 deletions

View file

@ -18,7 +18,7 @@ module Admin
flash[:error] = "Error: User ID ##{tag_params[:user_id]} was not found,
or their account has errors: #{user&.errors_as_sentence}"
end
redirect_to admin_tag_path(params[:tag_id])
redirect_to edit_admin_tag_path(params[:tag_id])
end
def destroy
@ -31,7 +31,7 @@ module Admin
flash[:error] = "Error: User ID ##{tag_params[:user_id]} was not found,
or their account has errors: #{user&.errors_as_sentence}"
end
redirect_to admin_tag_path(tag.id)
redirect_to edit_admin_tag_path(tag.id)
end
private

View file

@ -2,6 +2,7 @@ module Admin
class TagsController < Admin::ApplicationController
layout "admin"
before_action :badges_for_options, only: %i[new create edit update]
after_action only: [:update] do
Audit::Logger.log(:moderator, current_user, params.dup)
end
@ -18,10 +19,26 @@ module Admin
@tags = @tags.where("tags.name ILIKE :search", search: "%#{params[:search]}%") if params[:search].present?
end
def show
def new
@tag = Tag.new
end
def create
@tag = Tag.new(tag_params)
@tag.name = params[:tag][:name].downcase
if @tag.save
flash[:success] = "Tag has been created!"
redirect_to edit_admin_tag_path(@tag)
else
flash[:danger] = @tag.errors_as_sentence
render :new
end
end
def edit
@tag = Tag.find(params[:id])
@tag_moderators = User.with_role(:tag_moderator, @tag).select(:id, :username)
@badges_for_options = Badge.pluck(:title, :id)
end
def update
@ -31,16 +48,20 @@ module Admin
else
flash[:error] = "The tag update failed: #{@tag.errors_as_sentence}"
end
redirect_to admin_tag_path(@tag.id)
redirect_to edit_admin_tag_path(@tag.id)
end
private
def badges_for_options
@badges_for_options = Badge.pluck(:title, :id)
end
def tag_params
allowed_params = %i[
id supported rules_markdown short_summary pretty_name bg_color_hex
text_color_hex user_id alias_for badge_id
category social_preview_template wiki_body_markdown
text_color_hex user_id alias_for badge_id requires_approval
category social_preview_template wiki_body_markdown submission_template
]
params.require(:tag).permit(allowed_params)
end

View file

@ -1,57 +1,70 @@
<div class="crayons-card crayons-card--content-rows">
<h4 class="mb-4">Edit details</h4>
<%= form_with model: [:admin, tag], data: { remote: false } do |f| %>
<div class="crayons-field crayons-field--checkbox">
<%= form_with model: [:admin, tag], data: { remote: false } do |f| %>
<% if !tag.persisted? %>
<div class="crayons-field">
<%= f.label :name, class: "crayons-field__label" %>
<%= f.text_field :name, class: "crayons-textfield" %>
</div>
<% end %>
<div class="crayons-field crayons-field--checkbox mt-3">
<%= f.check_box :supported, class: "crayons-checkbox" %>
<label class="crayons-field__label" for="tag_supported">
Supported
<p class="crayons-field__description">Allows the tag to be a searchable result when writing a post or a listing</p>
</label>
</div>
<div class="crayons-field">
<div class="crayons-field mt-3">
<%= f.label :badge_id, class: "crayons-field__label" %>
<%= f.select(:badge_id, options_for_select(badges_for_options, tag.badge_id), { include_blank: true }, { class: "crayons-select" }) %>
</div>
<div class="crayons-field">
<div class="crayons-field mt-3">
<%= f.label :category, class: "crayons-field__label" %>
<%= f.select(:category, options_for_select(Tag.valid_categories, tag.category), {}, { class: "crayons-select" }) %>
</div>
<div class="crayons-field">
<div class="crayons-field mt-3">
<%= f.label :social_preview_template, class: "crayons-field__label" %>
<%= f.select(:social_preview_template, Tag.social_preview_templates, {}, { class: "crayons-select" }) %>
</div>
<div class="crayons-field">
<div class="crayons-field mt-3">
<%= f.label :alias_for, class: "crayons-field__label" %>
<%= f.text_field :alias_for, value: tag.alias_for, class: "crayons-textfield" %>
</div>
<div class="crayons-field">
<div class="crayons-field mt-3">
<%= f.label :pretty_name, class: "crayons-field__label" %>
<%= f.text_field :pretty_name, value: tag.pretty_name, class: "crayons-textfield" %>
</div>
<div class="crayons-field">
<div class="crayons-field mt-3">
<%= f.label :requires_approval, class: "crayons-field__label" %>
<%= f.check_box :requires_approval %>
</div>
<div class="crayons-field mt-3">
<%= f.label :short_summary, class: "crayons-field__label" %>
<%= f.text_field :short_summary, value: tag.short_summary, class: "crayons-textfield" %>
</div>
<div class="crayons-field">
<div class="crayons-field mt-3">
<%= f.label :rules_markdown, class: "crayons-field__label" %>
<br>
<%= f.text_area :rules_markdown, value: tag.rules_markdown, class: "crayons-textfield" %>
</div>
<div class="crayons-field">
<div class="crayons-field mt-3">
<%= f.label :submission_template, class: "crayons-field__label" %>
<%= f.text_area :submission_template, class: "crayons-textfield" %>
</div>
<div class="crayons-field mt-3">
<%= f.label :wiki_body_markdown, class: "crayons-field__label" %>
<br>
<%= f.text_area :wiki_body_markdown, value: tag.wiki_body_markdown, class: "crayons-textfield" %>
</div>
<%= f.label :bg_color_hex, class: "crayons-field__label" %>
<div class="flex items-center w-100 m:w-50">
<%= f.text_field :bg_color_hex, class: "crayons-textfield", value: tag.bg_color_hex.presence || "#000000", placeholder: "#000000" %>
<%= f.color_field :bg_color_hex, class: "crayons-color-selector ml-2", required: true, value: tag.bg_color_hex.presence || "#000000", placeholder: "#000000" %>
<div class="crayons-field mt-3">
<%= f.label :bg_color_hex, class: "crayons-field__label" %>
<div class="flex items-center w-100 m:w-50">
<%= f.text_field :bg_color_hex, class: "crayons-textfield", value: tag.bg_color_hex.presence || "#000000", placeholder: "#000000" %>
<%= f.color_field :bg_color_hex, class: "crayons-color-selector ml-2", required: true, value: tag.bg_color_hex.presence || "#000000", placeholder: "#000000" %>
</div>
</div>
<%= f.label :text_color_hex, class: "crayons-field__label" %>
<div class="flex items-center w-100 m:w-50">
<%= f.text_field :text_color_hex, class: "crayons-textfield", value: tag.text_color_hex.presence || "#ffffff", placeholder: "#ffffff" %>
<%= f.color_field :text_color_hex, class: "crayons-color-selector ml-2", required: true, value: tag.text_color_hex.presence || "#ffffff", placeholder: "#ffffff" %>
<div class="crayons-field mt-3">
<%= f.label :text_color_hex, class: "crayons-field__label" %>
<div class="flex items-center w-100 m:w-50">
<%= f.text_field :text_color_hex, class: "crayons-textfield", value: tag.text_color_hex.presence || "#ffffff", placeholder: "#ffffff" %>
<%= f.color_field :text_color_hex, class: "crayons-color-selector ml-2", required: true, value: tag.text_color_hex.presence || "#ffffff", placeholder: "#ffffff" %>
</div>
</div>
<%= f.submit class: "crayons-btn mt-2" %>
<% end %>
</div>
<%= f.submit class: "crayons-btn mt-3" %>
<% end %>

View file

@ -5,7 +5,7 @@
</div>
<div class="ml-auto">
<a href="/t/<%= @tag.name %>">View</a>
<a href="<%= tag_path(@tag.name) %>" class="crayons-btn crayons-btn--outlined" target="_blank" rel="noopener noreferer">View</a>
</div>
</header>
@ -18,8 +18,8 @@
<% if @tag_moderators.exists? %>
<ul class="list-none">
<% @tag_moderators.each do |user| %>
<li class="flex justify-between">
<a href="/<%= URL.user(user) %>">@<%= user.username %></a>
<li class="flex justify-between mb-2">
<a href="<%= URL.user(user) %>" target="_blank" rel="noopener noreferer">@<%= user.username %></a>
<%= form_with url: admin_tag_moderator_path(@tag.id), model: [:admin, @tag], method: :delete, data: { remote: false } do |f| %>
<%= f.hidden_field :user_id, value: user.id %>
<%= f.submit "Remove", class: "crayons-btn crayons-btn--danger crayons-btn--s" %>
@ -41,5 +41,9 @@
<% end %>
</div>
</div>
<%= render "form", tag: @tag, badges_for_options: @badges_for_options %>
<div class="crayons-card crayons-card--content-rows">
<h4>Edit details</h4>
<%= render "form", tag: @tag, badges_for_options: @badges_for_options %>
</div>
</div>

View file

@ -1,27 +1,29 @@
<div class="flex items-center mb-6">
<ul class="nav nav-pills">
<li class="nav-item">
<a href="/admin/tags" class="nav-link <%= "active" if params[:state].blank? %>">All</a>
</li>
<li class="nav-item">
<a href="/admin/tags?state=supported" class="nav-link <%= "active" if params[:state] == "supported" %>">Supported</a>
</li>
<li class="nav-item">
<a href="/admin/tags?state=unsupported" class="nav-link <%= "active" if params[:state] == "unsupported" %>">Unsupported</a>
</li>
</ul>
<div class="crayons-tabs">
<div class="nav-item">
<a href="/admin/tags" class="crayons-tabs__item <%= "crayons-tabs__item--current" if params[:state].blank? %>">All</a>
</div>
<div class="nav-item">
<a href="/admin/tags?state=supported" class="crayons-tabs__item <%= "crayons-tabs__item--current" if params[:state] == "supported" %>">Supported</a>
</div>
<div class="nav-item">
<a href="/admin/tags?state=unsupported" class="crayons-tabs__item <%= "crayons-tabs__item--current" if params[:state] == "unsupported" %>">Unsupported</a>
</div>
</div>
<div class="ml-auto">
<%= form_tag(admin_tags_path, method: "get") do %>
<div class="form-group">
<%= text_field_tag(:search, params[:search], aria: { label: "Search" }) %>
<% if params[:state].present? %>
<%= hidden_field_tag(:state, params[:state]) %>
<% end %>
<%= submit_tag("Search") %>
</div>
<div class="m-auto">
<%= form_tag(admin_tags_path, method: "get", class: "inline-flex") do %>
<%= text_field_tag(:search, params[:search], aria: { label: "Search" }, class: "crayons-textfield") %>
<% if params[:state].present? %>
<%= hidden_field_tag(:state, params[:state]) %>
<% end %>
<%= submit_tag("Search", class: "crayons-btn ml-2") %>
<% end %>
</div>
<div class="justify-content-end ml-auto">
<%= link_to "Make A Tag", new_admin_tag_path, class: "crayons-btn" %>
</div>
</div>
<%= paginate @tags %>
@ -40,7 +42,7 @@
<tbody class="crayons-card">
<% @tags.each do |tag| %>
<tr>
<td><%= link_to tag.name, admin_tag_path(tag.id) %></td>
<td><%= link_to tag.name, edit_admin_tag_path(tag.id) %></td>
<td><%= tag.id %></td>
<td><%= tag.alias_for %></td>
<td><%= tag.taggings_count %></td>

View file

@ -0,0 +1,4 @@
<div class="crayons-card p-6">
<h4 class="mb-4">New Tag</h4>
<%= render "form", tag: @tag, badges_for_options: @badges_for_options %>
</div>

View file

@ -72,7 +72,7 @@
<% end %>
<%= yield %>
<% if @help_url %>
<a class="admin-help-button crayons-btn crayons-btn--icon-rounded crayons-btn--s" href="<%= @help_url %>" target="_blank" rel="noopener noreferer" >
<a class="admin-help-button crayons-btn crayons-btn--icon-rounded crayons-btn--s" href="<%= @help_url %>" target="_blank" rel="noopener noreferer">
<%= inline_svg_tag("circle-question.svg", aria: true, title: "Forem Admin Guide") %>
</a>
<% end %>

View file

@ -103,7 +103,7 @@ Rails.application.routes.draw do
post "save_status"
end
end
resources :tags, only: %i[index update show] do
resources :tags, only: %i[index new create update edit] do
resource :moderator, only: %i[create destroy], module: "tags"
end
resources :users, only: %i[index show edit update] do

View file

@ -2,8 +2,23 @@ require "rails_helper"
RSpec.describe "/admin/tags", type: :request do
let(:super_admin) { create(:user, :super_admin) }
let(:tag) { create(:tag) }
let(:badge) { create(:badge) }
let(:user) { create(:user) }
let!(:tag) { create(:tag) }
let(:params) do
{
name: "WWW", supported: true, requires_approval: true,
wiki_body_markdown: "## In here you'll see WWW posts",
short_summary: "Everything WWW related ", rules_markdown: "## NO SPAM",
submission_template: "# <TITLE>\n\n<ARTICLE_BODY>",
pretty_name: "dubdubdub", bg_color_hex: "#333333",
text_color_hex: "#ffffff", badge_id: badge.id, category: "site_mechanic",
social_preview_template: "article"
}
end
let(:post_resource) { post "/admin/tags", params: { tag: params } }
let(:put_resource) { put "/admin/tags/#{tag.id}", params: { tag: params } }
before do
sign_in super_admin
@ -18,8 +33,32 @@ RSpec.describe "/admin/tags", type: :request do
describe "GET /admin/tags/:id" do
it "responds with 200 OK" do
get admin_tag_path(tag.id)
get edit_admin_tag_path(tag.id)
expect(response.status).to eq 200
end
end
describe "POST /admin/tags" do
it "creates a new tag" do
expect do
post_resource
end.to change { Tag.all.count }.by(1)
expect(response.body).to redirect_to edit_admin_tag_path(Tag.last)
end
end
describe "PUT /admin/tags" do
it "updates Tag" do
put_resource
tag.reload
expect(tag.requires_approval).to eq(params[:requires_approval])
expect(tag.wiki_body_markdown).to eq(params[:wiki_body_markdown])
expect(tag.short_summary).to eq(params[:short_summary])
expect(tag.rules_markdown).to eq(params[:rules_markdown])
expect(tag.submission_template).to eq(params[:submission_template])
expect(tag.pretty_name).to eq(params[:pretty_name])
expect(tag.bg_color_hex).to eq(params[:bg_color_hex])
expect(tag.text_color_hex).to eq(params[:text_color_hex])
end
end
end

View file

@ -22,7 +22,6 @@ RSpec.describe "StoriesShow", type: :request do
it "preserves internal nav param (i=i) upon redirect" do
old_path = article.path
article.update(organization: org)
get "#{old_path}?i=i"
expect(response.body).to redirect_to "#{article.path}?i=i"
expect(response).to have_http_status(:moved_permanently)
@ -39,7 +38,6 @@ RSpec.describe "StoriesShow", type: :request do
it "does not have ?i=i on redirects without that precise param" do
old_path = article.path
article.update(organization: org)
get "#{old_path}?i=j"
expect(response.body).to redirect_to article.path
expect(response.body).not_to redirect_to "#{article.path}?i=j"

View file

@ -7,7 +7,7 @@ RSpec.describe "Admin updates a tag", type: :system do
context "when no colors have been choosen for the tag" do
before do
sign_in super_admin
visit admin_tag_path(tag.id)
visit edit_admin_tag_path(tag.id)
end
it "defaults to white text for the color picker" do
@ -26,7 +26,7 @@ RSpec.describe "Admin updates a tag", type: :system do
context "when colors have already been choosen for the tag" do
before do
sign_in super_admin
visit "/admin/tags/#{tag.id}"
visit edit_admin_tag_path(tag)
end
it "remains the same color it was unless otherwise updated via the color picker" do