Update data exporter to handle admin send (#10274)
* Update data exporter to handle admin send * Match button with everything else * Use proper redirect path * Stub SiteConfig definition instead of setting it Co-authored-by: Mac Siri <krairit.siri@gmail.com> * Removed if statement by accident oops * Remove non-functional boolean param and pass email directly * Use refinement to conv to boolean instead of JSON.parse * Rename to StringToBoolean * Use 'using' in proper scope (not in method) * Rename to_bool to to_boolean * Refactor if statement, thanks rhymes! * Fix small bugs in tests * Remove tracking for export_email b/c no @user Co-authored-by: Mac Siri <krairit.siri@gmail.com>
This commit is contained in:
parent
c817737748
commit
b80dae1436
17 changed files with 161 additions and 33 deletions
|
|
@ -1,6 +1,7 @@
|
|||
module Admin
|
||||
class UsersController < Admin::ApplicationController
|
||||
layout "admin"
|
||||
using StringToBoolean
|
||||
|
||||
after_action only: %i[update user_status banish full_delete merge] do
|
||||
Audit::Logger.log(:moderator, current_user, params.dup)
|
||||
|
|
@ -50,6 +51,21 @@ module Admin
|
|||
redirect_to "/admin/users/#{@user.id}/edit"
|
||||
end
|
||||
|
||||
def export_data
|
||||
user = User.find(params[:id])
|
||||
send_to_admin = params[:send_to_admin].to_boolean
|
||||
if send_to_admin
|
||||
email = SiteConfig.email_addresses[:default]
|
||||
receiver = "admin"
|
||||
else
|
||||
email = user.email
|
||||
receiver = "user"
|
||||
end
|
||||
ExportContentWorker.perform_async(user.id, email)
|
||||
flash[:success] = "Data exported to the #{receiver}. The job will complete momentarily."
|
||||
redirect_to edit_admin_user_path(user.id)
|
||||
end
|
||||
|
||||
def banish
|
||||
Moderator::BanishUserWorker.perform_async(current_user.id, params[:id].to_i)
|
||||
flash[:success] = "This user is being banished in the background. The job will complete soon."
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class UsersController < ApplicationController
|
|||
end
|
||||
if @user.export_requested?
|
||||
notice += " The export will be emailed to you shortly."
|
||||
ExportContentWorker.perform_async(@user.id)
|
||||
ExportContentWorker.perform_async(@user.id, @user.email)
|
||||
end
|
||||
cookies.permanent[:user_experience_level] = @user.experience_level.to_s if @user.experience_level.present?
|
||||
flash[:settings_notice] = notice
|
||||
|
|
|
|||
|
|
@ -116,12 +116,11 @@ class NotifyMailer < ApplicationMailer
|
|||
end
|
||||
|
||||
def export_email
|
||||
@user = params[:user]
|
||||
attachment = params[:attachment]
|
||||
|
||||
export_filename = "devto-export-#{Date.current.iso8601}.zip"
|
||||
attachments[export_filename] = attachment
|
||||
mail(to: @user.email, subject: "The export of your content is ready")
|
||||
mail(to: params[:email], subject: "The export of your content is ready")
|
||||
end
|
||||
|
||||
def tag_moderator_confirmation_email
|
||||
|
|
|
|||
7
app/refinements/string_to_boolean.rb
Normal file
7
app/refinements/string_to_boolean.rb
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
module StringToBoolean
|
||||
refine String do
|
||||
def to_boolean
|
||||
ActiveModel::Type::Boolean.new.cast(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -13,7 +13,7 @@ module Exporter
|
|||
@user = user
|
||||
end
|
||||
|
||||
def export(send_email: false, config: {})
|
||||
def export(email, config: {})
|
||||
exports = {}
|
||||
|
||||
# export content with filenames
|
||||
|
|
@ -26,7 +26,7 @@ module Exporter
|
|||
|
||||
zipped_exports = zip_exports(exports)
|
||||
|
||||
send_exports_by_email(zipped_exports) if send_email
|
||||
send_exports_by_email(zipped_exports, email)
|
||||
|
||||
update_user_export_fields
|
||||
|
||||
|
|
@ -53,9 +53,9 @@ module Exporter
|
|||
buffer
|
||||
end
|
||||
|
||||
def send_exports_by_email(zipped_exports)
|
||||
def send_exports_by_email(zipped_exports, email)
|
||||
zipped_exports.rewind
|
||||
NotifyMailer.with(user: user, attachment: zipped_exports.read).export_email.deliver_now
|
||||
NotifyMailer.with(email: email, attachment: zipped_exports.read).export_email.deliver_now
|
||||
end
|
||||
|
||||
def update_user_export_fields
|
||||
|
|
|
|||
55
app/views/admin/events/new.html.erb
Normal file
55
app/views/admin/events/new.html.erb
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<h2 class="crayons-title mb-6">Create Event</h2>
|
||||
|
||||
<div class="crayons-card p-6">
|
||||
<%# form_with url: admin_ %>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<%= f.label :cover_image %>:
|
||||
<%= f.file_field :cover_image, class: "form-control" %>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<%= f.label :profile_image %> (for live notification):
|
||||
<%= f.file_field :profile_image, class: "form-control" %>
|
||||
<img src="<%= event.profile_image_url %>" style="width: 25%;" alt="event profile image">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<%= f.label :title %>
|
||||
<%= f.text_field :title, maxlength: 90, size: 40, required: true, class: "form-control" %>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<%= f.label :host_name %>
|
||||
<%= f.text_field :host_name, size: 40, required: true, class: "form-control" %>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<%= f.label :category %>
|
||||
<%= f.select :category, ["AMA", "Workshop", "Talk", "Town Hall"], required: true %>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<%= f.label :starts_at %>
|
||||
<%= f.datetime_select :starts_at, required: true, include_blank: true, start_year: Time.current.year, end_year: Time.current.year + 2, class: "form-control" %> UTC Time Only (4 hours ahead of eastern time)
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<%= f.label :ends_at %>
|
||||
<%= f.datetime_select :ends_at, required: true, include_blank: true, start_year: Time.current.year, end_year: Time.current.year + 2, class: "form-control" %> UTC Time Only (4 hours ahead of eastern time)
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<%= f.label :location_name %>
|
||||
<%= f.text_field :location_name, required: true, class: "form-control" %>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<%= f.label :location_url %>
|
||||
<%= f.text_field :location_url, required: true, class: "form-control" %>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<%= f.label :description_markdown %>
|
||||
<%= f.text_area :description_markdown, size: "45x10", required: true, class: "form-control" %>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<%= f.label :published %>
|
||||
<%= f.check_box :published %>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<%= f.label :live_now %>
|
||||
<%= f.check_box :live_now %>
|
||||
</div>
|
||||
<%= f.submit class: "btn btn-primary" %>
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
<%= paginate @moderator_actions %>
|
||||
|
||||
<table class="crayons-table">
|
||||
<table class="crayons-table" width="100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">ID</th>
|
||||
|
|
|
|||
21
app/views/admin/users/_data_export.html.erb
Normal file
21
app/views/admin/users/_data_export.html.erb
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<div class="crayons-card p-6">
|
||||
<div>
|
||||
<h2 class="d-inline">Data Export</h2>
|
||||
<button type="button" data-toggle="collapse" data-target="#data-export-row" class="crayons-btn float-right">Toggle</button>
|
||||
</div>
|
||||
<div class="crayons-card__body collapse" id="data-export-row">
|
||||
<p>
|
||||
You can export a user's data here. Currently we only support exporting a user's posts and comments.
|
||||
</p>
|
||||
<p>
|
||||
You have the option of exporting to your admin account to send to the user, or exporting to the user directly.
|
||||
</p>
|
||||
<p>
|
||||
Exporting the data to your admin account will send it to your default admin email (<%= SiteConfig.email_addresses[:default] %>), and exporting to the user will send it to their email (<%= @user.email %>).
|
||||
</p>
|
||||
<div style="display: flex;">
|
||||
<%= button_to "Export to Admin", export_data_admin_user_path(@user.id), data: { confirm: "Are you sure you want to export this user's content to the ADMIN?" }, class: "crayons-btn", params: { send_to_admin: true }, style: "margin-right: 8px;" %>
|
||||
<%= button_to "Export to User", export_data_admin_user_path(@user.id), data: { confirm: "Are you sure you want to export this user's content to the USER?" }, class: "crayons-btn", params: { send_to_admin: false } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<div class="crayons-card p-6">
|
||||
<h2 class="mb-6">Recent Notes (last 10)</h2>
|
||||
|
||||
|
||||
<% unless @notes.load.empty? %>
|
||||
<% @notes.each do |note| %>
|
||||
<p>
|
||||
|
|
@ -21,6 +21,6 @@
|
|||
<%= f.label "Add new note: ", class: "d-block" %>
|
||||
<%= f.text_area :new_note, class: "form-control" %>
|
||||
</div>
|
||||
<%= f.submit "Submit Note", class: "btn btn-primary float-right" %>
|
||||
<%= f.submit "Submit Note", class: "crayons-btn float-right" %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
<%= link_to "Unlock access", unlock_access_admin_user_path(@user), method: :patch, class: "btn btn-success" %>
|
||||
</div>
|
||||
<% end %>
|
||||
<a href="<%= admin_user_path(@user) %>" class="btn btn-primary float-right">Admin Profile</a>
|
||||
<a href="<%= admin_user_path(@user) %>" class="crayons-btn float-right">Admin Profile</a>
|
||||
<p class="font-italic">Member since <%= @user.created_at.strftime("%b %e '%y") %></p>
|
||||
</div>
|
||||
|
||||
|
|
@ -43,18 +43,19 @@
|
|||
<%= f.label "Reason for action:" %>
|
||||
<%= f.text_area :note_for_current_role, required: true, class: "form-control" %>
|
||||
</div>
|
||||
<%= f.submit "Update User Status", class: "btn btn-primary float-right" %>
|
||||
<%= f.submit "Update User Status", class: "crayons-btn float-right" %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<%= render "notes" %>
|
||||
<%= render "negative_reactions" %>
|
||||
<%= render "reports" %>
|
||||
<%= render "data_export" %>
|
||||
|
||||
<div class="crayons-card p-6">
|
||||
<div>
|
||||
<h2 class="d-inline">Remove Identity</h2>
|
||||
<button type="button" data-toggle="collapse" data-target="#identity-row" class="btn btn-secondary float-right">Toggle</button>
|
||||
<button type="button" data-toggle="collapse" data-target="#identity-row" class="crayons-btn float-right">Toggle</button>
|
||||
</div>
|
||||
<div class="pt-6 collapse" id="identity-row">
|
||||
<p>Removing a social account identity can solve certain sign in issues, for example:</p>
|
||||
|
|
@ -92,7 +93,7 @@
|
|||
<div class="crayons-card p-6">
|
||||
<div>
|
||||
<h2 class="d-inline">Destructive Actions</h2>
|
||||
<button type="button" data-toggle="collapse" data-target="#destructive-actions" class="btn btn-secondary float-right">Toggle</button>
|
||||
<button type="button" data-toggle="collapse" data-target="#destructive-actions" class="crayons-btn float-right">Toggle</button>
|
||||
</div>
|
||||
|
||||
<div id="destructive-actions" class="pt-6 collapse">
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ class ExportContentWorker
|
|||
|
||||
sidekiq_options queue: :medium_priority, retry: 10, lock: :until_executed
|
||||
|
||||
def perform(user_id)
|
||||
def perform(user_id, email)
|
||||
user = User.find_by(id: user_id)
|
||||
|
||||
Exporter::Service.new(user).export(send_email: true) if user
|
||||
Exporter::Service.new(user).export(email) if user
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ Rails.application.routes.draw do
|
|||
resources :users, only: %i[index show edit update] do
|
||||
member do
|
||||
post "banish"
|
||||
post "export_data"
|
||||
post "full_delete"
|
||||
patch "user_status"
|
||||
post "merge"
|
||||
|
|
|
|||
|
|
@ -403,7 +403,7 @@ RSpec.describe NotifyMailer, type: :mailer do
|
|||
end
|
||||
|
||||
describe "#export_email" do
|
||||
let(:email) { described_class.with(user: user, attachment: "attachment").export_email }
|
||||
let(:email) { described_class.with(email: user.email, attachment: "attachment").export_email }
|
||||
|
||||
it "renders proper subject" do
|
||||
expect(email.subject).to include("export of your content is ready")
|
||||
|
|
@ -431,12 +431,6 @@ RSpec.describe NotifyMailer, type: :mailer do
|
|||
it "includes the tracking pixel" do
|
||||
expect(email.html_part.body).to include("open.gif")
|
||||
end
|
||||
|
||||
it "includes UTM params" do
|
||||
expect(email.html_part.body).to include(CGI.escape("utm_medium=email"))
|
||||
expect(email.html_part.body).to include(CGI.escape("utm_source=notify_mailer"))
|
||||
expect(email.html_part.body).to include(CGI.escape("utm_campaign=export_email"))
|
||||
end
|
||||
end
|
||||
|
||||
describe "#tag_moderator_confirmation_email" do
|
||||
|
|
|
|||
10
spec/refinements/string_to_boolean_spec.rb
Normal file
10
spec/refinements/string_to_boolean_spec.rb
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
require "rails_helper"
|
||||
|
||||
RSpec.describe StringToBoolean, type: :refinement do
|
||||
describe "#to_boolean" do
|
||||
using described_class
|
||||
it "converts a string to a boolean" do
|
||||
expect("true".to_boolean).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -11,7 +11,7 @@ RSpec.describe "admin/users", type: :request do
|
|||
sign_in(admin)
|
||||
end
|
||||
|
||||
describe "GETS /admin/users" do
|
||||
describe "GET /admin/users" do
|
||||
it "renders to appropriate page" do
|
||||
get "/admin/users"
|
||||
expect(response.body).to include(user.username)
|
||||
|
|
@ -149,4 +149,12 @@ RSpec.describe "admin/users", type: :request do
|
|||
end.to change { user.reload.access_locked? }.from(true).to(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST admin/users/:id/export_data" do
|
||||
it "redirects properly to the user edit page" do
|
||||
sign_in admin
|
||||
post export_data_admin_user_path(user), params: { send_to_admin: "true" }
|
||||
expect(response).to redirect_to edit_admin_user_path(user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -47,14 +47,14 @@ RSpec.describe Exporter::Service, type: :service do
|
|||
describe "#export" do
|
||||
it "exports a zip file with files" do
|
||||
service = valid_instance(article.user)
|
||||
zipped_exports = service.export
|
||||
zipped_exports = service.export(article.user.email)
|
||||
exports = extract_zipped_exports(zipped_exports)
|
||||
expect(exports.keys).to eq(["articles.json", "comments.json"])
|
||||
end
|
||||
|
||||
it "passes configuration to an exporter" do
|
||||
service = valid_instance(article.user)
|
||||
zipped_exports = service.export(config: { articles: { slug: article.slug } })
|
||||
zipped_exports = service.export(article.user.email, config: { articles: { slug: article.slug } })
|
||||
exports = extract_zipped_exports(zipped_exports)
|
||||
expect(exports.length).to eq(described_class::EXPORTERS.size)
|
||||
end
|
||||
|
|
@ -64,20 +64,26 @@ RSpec.describe Exporter::Service, type: :service do
|
|||
config = double
|
||||
allow(config).to receive(:fetch).with(:articles, {}).and_return(slug: article.slug)
|
||||
allow(config).to receive(:fetch).with(:comments, {}).and_return({})
|
||||
service.export(config: config)
|
||||
service.export(article.user.email, config: config)
|
||||
expect(config).to have_received(:fetch).with(:articles, {})
|
||||
end
|
||||
|
||||
context "when emailing the user" do
|
||||
it "delivers one email" do
|
||||
service = valid_instance(article.user)
|
||||
service.export(send_email: true)
|
||||
service.export(article.user.email)
|
||||
expect(ActionMailer::Base.deliveries.count).to eq(1)
|
||||
end
|
||||
|
||||
it "delivers one email to the user's email" do
|
||||
service = valid_instance(article.user)
|
||||
service.export(article.user.email)
|
||||
expect(ActionMailer::Base.deliveries.last.to.first).to eq article.user.email
|
||||
end
|
||||
|
||||
it "delivers an email with the export" do
|
||||
service = valid_instance(article.user)
|
||||
zipped_export = service.export(send_email: true)
|
||||
zipped_export = service.export(article.user.email)
|
||||
attachment = ActionMailer::Base.deliveries.last.attachments[0].decoded
|
||||
|
||||
exports = extract_zipped_exports(zipped_export)
|
||||
|
|
@ -85,16 +91,26 @@ RSpec.describe Exporter::Service, type: :service do
|
|||
end
|
||||
end
|
||||
|
||||
context "when emailing an admin" do
|
||||
it "delivers one email to the default admin email" do
|
||||
admin_email = "admin@example.com"
|
||||
allow(SiteConfig).to receive(:email_addresses).and_return({ default: admin_email })
|
||||
service = valid_instance(article.user)
|
||||
service.export(admin_email)
|
||||
expect(ActionMailer::Base.deliveries.last.to.first).to eq admin_email
|
||||
end
|
||||
end
|
||||
|
||||
it "sets the requested flag as false" do
|
||||
service = valid_instance(article.user)
|
||||
service.export
|
||||
service.export(article.user.email)
|
||||
expect(user.export_requested).to be(false)
|
||||
end
|
||||
|
||||
it "sets the exported at datetime as the current one" do
|
||||
Timecop.freeze(Time.current) do
|
||||
service = valid_instance(article.user)
|
||||
service.export
|
||||
service.export(article.user.email)
|
||||
expect(user.exported_at).to eq(Time.current)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15,12 +15,12 @@ RSpec.describe ExportContentWorker, type: :worker do
|
|||
end
|
||||
|
||||
it "calls the service" do
|
||||
worker.perform(user.id)
|
||||
worker.perform(user.id, user.email)
|
||||
expect(exporter_service).to have_received(:export).once
|
||||
end
|
||||
|
||||
it "doesn't call the service if non existent user ID is given" do
|
||||
worker.perform(9999)
|
||||
worker.perform(9999, user.email)
|
||||
expect(exporter_service).not_to have_received(:export)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue