From 4451e5eb384dcae1ce508bc27fbff2e6d523c74f Mon Sep 17 00:00:00 2001 From: Jess Lee Date: Tue, 11 Sep 2018 15:59:25 -0400 Subject: [PATCH] Update User Dashboard With Mentorship Admin Features (#440) * create mentor relationship table and begin dashboard * add missing files from previous commit * add mentorship relationship status * remove uniqueness validation * add multiple notes * ban from mentorship deactives all relationships * add untracked files * fix logic issue and add specs * add model specs * add request specs * add request specs and fix linting * fix internal users spec * remove phantom columns * remove bizzaro character * move all attr_accessor to one line --- app/controllers/internal/users_controller.rb | 91 +++++++++++++++- app/models/mentor_relationship.rb | 14 +++ app/models/role.rb | 1 + app/models/user.rb | 37 ++++--- app/services/user_role_service.rb | 30 +++--- app/views/internal/users/edit.html.erb | 4 +- app/views/internal/users/index.html.erb | 33 +++--- app/views/internal/users/show.html.erb | 100 ++++++++++++++++++ config/routes.rb | 7 +- ...80816165158_create_mentor_relationships.rb | 13 +++ db/schema.rb | 11 ++ spec/models/mentor_relationship_spec.rb | 23 ++++ spec/requests/internal_users_spec.rb | 100 ++++++++++++++++++ yarn.lock | 34 +++--- 14 files changed, 438 insertions(+), 60 deletions(-) create mode 100644 app/models/mentor_relationship.rb create mode 100644 app/views/internal/users/show.html.erb create mode 100644 db/migrate/20180816165158_create_mentor_relationships.rb create mode 100644 spec/models/mentor_relationship_spec.rb create mode 100644 spec/requests/internal_users_spec.rb diff --git a/app/controllers/internal/users_controller.rb b/app/controllers/internal/users_controller.rb index db8ec59e3..716528f2d 100644 --- a/app/controllers/internal/users_controller.rb +++ b/app/controllers/internal/users_controller.rb @@ -2,15 +2,91 @@ class Internal::UsersController < Internal::ApplicationController layout "internal" def index - @users = User.where.not(feed_url: nil) + @users = case params[:state] + when "mentors" + User.where(offering_mentorship: true).page(params[:page]).per(20) + when "mentees" + User.where(seeking_mentorship: true).page(params[:page]).per(20) + else + User.order("created_at DESC").page(params[:page]).per(20) + end end def edit @user = User.find(params[:id]) end + def show + @user = User.find(params[:id]) + @user_mentee_relationships = MentorRelationship.where(mentor_id: @user.id) + @user_mentor_relationships = MentorRelationship.where(mentee_id: @user.id) + end + def update - # Only used for stripping user right now. + @user = User.find(params[:id]) + @new_mentee = user_params[:add_mentee] + @new_mentor = user_params[:add_mentor] + handle_mentorship + add_note + @user.update!(user_params) + redirect_to "/internal/users/#{@user.id}" + end + + def handle_mentorship + if user_params[:ban_from_mentorship] == "1" + ban_from_mentorship + end + + if @new_mentee.blank? && @new_mentor.blank? + return + end + make_matches + end + + def make_matches + if !@new_mentee.blank? + mentee = User.find(@new_mentee) + MentorRelationship.new(mentee_id: mentee.id, mentor_id: @user.id).save! + end + + if !@new_mentor.blank? + mentor = User.find(@new_mentor) + MentorRelationship.new(mentee_id: @user.id, mentor_id: mentor.id).save! + end + end + + def add_note + if user_params[:mentorship_note] + Note.create( + author_id: @current_user.id, + noteable_id: @user.id, + noteable_type: "User", + reason: "mentorship", + content: user_params[:mentorship_note], + ) + end + end + + def inactive_mentorship(mentor, mentee) + relationship = MentorRelationship.where(mentor_id: mentor.id, mentee_id: mentee.id) + relationship.update(active: false) + end + + def ban_from_mentorship + @user.add_role :banned_from_mentorship + mentee_relationships = MentorRelationship.where(mentor_id: @user.id) + mentor_relationships = MentorRelationship.where(mentee_id: @user.id) + deactivate_mentorship(mentee_relationships) + deactivate_mentorship(mentor_relationships) + end + + def deactivate_mentorship(relationships) + relationships.each do |relationship| + relationship.update(active: false) + end + end + + def banish @user = User.find(params[:id]) strip_user(@user) redirect_to "/internal/users/#{@user.id}/edit" @@ -62,4 +138,15 @@ class Internal::UsersController < Internal::ApplicationController rescue StandardError => e flash[:error] = e.message end + + private + + def user_params + params.require(:user).permit(:seeking_mentorship, + :offering_mentorship, + :add_mentor, + :add_mentee, + :mentorship_note, + :ban_from_mentorship) + end end diff --git a/app/models/mentor_relationship.rb b/app/models/mentor_relationship.rb new file mode 100644 index 000000000..b50c52b7c --- /dev/null +++ b/app/models/mentor_relationship.rb @@ -0,0 +1,14 @@ +class MentorRelationship < ApplicationRecord + belongs_to :mentor, class_name: "User" + belongs_to :mentee, class_name: "User" + validates :mentor, presence: true + validates :mentee, presence: true + validate :check_for_same_user + validates_uniqueness_of :mentor_id, scope: :mentee_id + + def check_for_same_user + if mentor_id == mentee_id + errors.add(:mentor_relationship, "Mentor and Mentee cannot be the same person") + end + end +end diff --git a/app/models/role.rb b/app/models/role.rb index 4879394e0..f1f330626 100644 --- a/app/models/role.rb +++ b/app/models/role.rb @@ -27,6 +27,7 @@ class Role < ApplicationRecord workshop_pass video_permission chatroom_beta_tester + banned_from_mentorship comment_banned ), } diff --git a/app/models/user.rb b/app/models/user.rb index 55fadd0d8..4a8af3217 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,7 +1,8 @@ class User < ApplicationRecord include CloudinaryHelper - attr_accessor :scholar_email + attr_accessor :scholar_email, :add_mentor, :add_mentee, :mentorship_note, :ban_from_mentorship + rolify include AlgoliaSearch include Storext.model @@ -31,6 +32,16 @@ class User < ApplicationRecord has_many :chat_channels, through: :chat_channel_memberships has_many :push_notification_subscriptions, dependent: :destroy has_many :feedback_messages + has_many :mentor_relationships_as_mentee, + class_name: "MentorRelationship", foreign_key: "mentee_id" + has_many :mentor_relationships_as_mentor, + class_name: "MentorRelationship", foreign_key: "mentor_id" + has_many :mentors, + through: :mentor_relationships_as_mentee, + source: :mentor + has_many :mentees, + through: :mentor_relationships_as_mentor, + source: :mentee mount_uploader :profile_image, ProfileImageUploader @@ -91,13 +102,14 @@ class User < ApplicationRecord allow_blank: true validates :website_url, url: { allow_blank: true, no_local: true, schemes: ["https", "http"] } validates :website_url, :employer_name, :employer_url, - :employment_title, :education, :location, - length: { maximum: 100 } - validates :mostly_work_with, :currently_learning, :currently_hacking_on, - :available_for, - length: { maximum: 500 } + length: { maximum: 100 } + validates :employment_title, :education, :location, + length: { maximum: 100 } + validates :mostly_work_with, :currently_learning, + :currently_hacking_on, :available_for, + length: { maximum: 500 } validates :mentee_description, :mentor_description, - length: { maximum: 1000 } + length: { maximum: 1000 } validate :conditionally_validate_summary validate :validate_feed_url validate :unique_including_orgs @@ -107,8 +119,7 @@ class User < ApplicationRecord after_save :subscribe_to_mailchimp_newsletter after_save :conditionally_resave_articles after_create :estimate_default_language! - before_update :mentor_status_update - before_update :mentee_status_update + before_update :mentorship_status_update before_validation :set_username before_validation :downcase_email before_validation :check_for_username_change @@ -255,6 +266,10 @@ class User < ApplicationRecord has_role? :warned end + def banned_from_mentorship + has_role? :banned_from_mentorship + end + def admin? has_role?(:super_admin) end @@ -515,13 +530,11 @@ class User < ApplicationRecord follows.destroy_all end - def mentor_status_update + def mentorship_status_update if mentor_description_changed? || offering_mentorship_changed? self.mentor_form_updated_at = Time.now end - end - def mentee_status_update if mentee_description_changed? || seeking_mentorship_changed? self.mentee_form_updated_at = Time.now end diff --git a/app/services/user_role_service.rb b/app/services/user_role_service.rb index 80b9f1fa0..ef61148e8 100644 --- a/app/services/user_role_service.rb +++ b/app/services/user_role_service.rb @@ -31,6 +31,21 @@ class UserRoleService true end + def create_or_update_note(reason, content) + note = Note.find_by(noteable_id: @user.id, noteable_type: "User", reason: reason) + if note.present? + note.update(content: content) + else + Note.create( + author_id: @current_user_id, + noteable_id: @user.id, + noteable_type: "User", + reason: reason, + content: content, + ) + end + end + private def new_roles?(params) @@ -71,21 +86,6 @@ class UserRoleService end end - def create_or_update_note(reason, content) - note = Note.find_by(noteable_id: @user.id, noteable_type: "User", reason: reason) - if note.nil? - Note.create( - author_id: @current_user_id, - noteable_id: @user.id, - noteable_type: "User", - reason: reason, - content: content, - ) - else - note.update(content: content) - end - end - def ban(content) @user.add_role :banned create_or_update_note("banned", content) diff --git a/app/views/internal/users/edit.html.erb b/app/views/internal/users/edit.html.erb index e82b0e0b1..516ef839b 100644 --- a/app/views/internal/users/edit.html.erb +++ b/app/views/internal/users/edit.html.erb @@ -12,10 +12,10 @@ <% else %>
<% if @user.comments.where("created_at < ?", 7.days.ago).empty? %> - <%= form_for([:internal, @user],confirm: "Are you absolutely sure?") do %> + <%= form_for(@user, :url => banish_internal_user_path(@user), :html => {:method => :post, :onsubmit => "confirm('Are you sure? This is extremely destructive and will change their username to @spam_###. Do not do it lightly.')"}) do %> <% end %> - This is extremely destructive. Do not do it lightly + This is extremely destructive and will change their username to @spam_###. Do not do it lightly. <% else %> You cannot take admin action from this view because this is not a newish user. For safety. <% end %> diff --git a/app/views/internal/users/index.html.erb b/app/views/internal/users/index.html.erb index 70fdd87a2..1bdcccbb1 100644 --- a/app/views/internal/users/index.html.erb +++ b/app/views/internal/users/index.html.erb @@ -1,15 +1,20 @@ -

RSS Users

- +

+ All Users || + Mentors Only || + Mentees Only +

+<% if params[:state] == "mentors" %> +

Mentors

+<% elsif params[:state] == "mentees" %> +

Mentees

+<% else %> +

All Users

+<% end %> +<%= paginate @users %> <% @users.each do |user| %> -
-

<%= user.name%>

-

@<%= user.username%>

-

View dashboard

-
-

<%= user.feed_url%>

- <% if user.feed_admin_publish_permission %> -

Admin Publish Permission 👌

- <% end%> - -
-<% end%> \ No newline at end of file +
+

<%= user.name%>

+

@<%= user.username%>

+
+<% end %> +<%= paginate @users %> \ No newline at end of file diff --git a/app/views/internal/users/show.html.erb b/app/views/internal/users/show.html.erb new file mode 100644 index 000000000..861a92d7a --- /dev/null +++ b/app/views/internal/users/show.html.erb @@ -0,0 +1,100 @@ +
+

+ <%= @user.name%> (@<%= @user.username %>) +

+

Member since <%= @user.created_at.strftime("%d %B %Y %H:%M UTC") %>

+ <% if @user.banned %> +

BANNED USER

+

Reason for banning: <%= @user.reason_for_ban %>

+ <% elsif @user.warned %> +

User Status: Warned

+

Reason for warning: <%= @user.reason_for_warning %>

+ <% else %> +

Member in good standing

+ <% end %> +
+

Basic Info

+

Email: <%= @user.email %>

+ <% if @user.feed_admin_publish_permission %> +

Admins can publish from RSS ✅

+ <% else %> +

Admins cannot publish from RSS ❌

+ <% end %> +

Badges

+ <% if @user.badges.count > 0 %> + <% @user.badges.each do |badge| %> + + <% end %> + <% end %> +

Mentorship Details

+ <%= form_for ([:internal, @user]) do |f| %> + <% if @user.banned_from_mentorship %> +

USER IS BANNED FROM MENTORSHIP, SEE NOTES BELOW

+ <% else %> +

Ban user from participating in mentorship program <%= f.check_box :ban_from_mentorship %>

+ <% end %> + <%= f.label :offering_mentorship %> + <%= f.check_box :offering_mentorship %> +
+ <% if @user.mentor_description %> +

"<%= @user.mentor_description %>"

+ <% end %> + <%= f.label :seeking_mentorship %> + <%= f.check_box :seeking_mentorship %> +
+ <% if @user.mentee_description %> +

"<%= @user.mentee_description %>"

+ <% end %> +

Mentorship Matches

+ <% if @user.mentors.count > 0 %> +

Mentor(s):

+ + <% end %> +
+ <%= f.label :add_mentor %> + <%= f.text_field :add_mentor, placeholder:"user id#" %> + <% if @user.mentees.count > 0 %> +

Mentee(s):

+ + <% end %> +
+ <%= f.label :add_mentee %> + <%= f.text_field :add_mentee, placeholder:"user id#" %> +

Mentorship Notes

+ <% if @user.notes.count > 0 %> + <% @user.notes.each do |note| %> +

<%= note.created_at.strftime("%d %B %Y %H:%M UTC") %> by <%= User.find(note.author_id).username %> - <%= note.content %>

+ <% end %> +
+

Add new mentorship note

+ <%= f.text_area :mentorship_note %> + <% else %> + <%= f.text_area :mentorship_note %> + <% end %> +
+ <%= f.submit %> + <% end %> + +
+ diff --git a/config/routes.rb b/config/routes.rb index b9788b43d..76d01e755 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -27,7 +27,11 @@ Rails.application.routes.draw do resources :tags resources :welcome, only: %i[index create] resources :broadcasts - resources :users + resources :users do + member do + post "banish" + end + end resources :events resources :dogfood, only: [:index] resources :buffer_updates, only: [:create] @@ -43,6 +47,7 @@ Rails.application.routes.draw do post "save_status", to: :save_status, on: :collection end mount Flipflop::Engine => "/features" + end namespace :api, defaults: { format: "json" } do diff --git a/db/migrate/20180816165158_create_mentor_relationships.rb b/db/migrate/20180816165158_create_mentor_relationships.rb new file mode 100644 index 000000000..506613a17 --- /dev/null +++ b/db/migrate/20180816165158_create_mentor_relationships.rb @@ -0,0 +1,13 @@ +class CreateMentorRelationships < ActiveRecord::Migration[5.1] + def change + create_table :mentor_relationships do |t| + t.integer :mentor_id, null: false + t.integer :mentee_id, null: false + t.boolean :active, default: true + t.timestamps + end + add_index :mentor_relationships, :mentee_id + add_index :mentor_relationships, :mentor_id + add_index :mentor_relationships, %i[mentee_id mentor_id], unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 906eebf09..8483bfd1e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -374,6 +374,17 @@ ActiveRecord::Schema.define(version: 20180826174411) do t.integer "user_id" end + create_table "mentor_relationships", force: :cascade do |t| + t.boolean "active", default: true + t.datetime "created_at", null: false + t.integer "mentee_id", null: false + t.integer "mentor_id", null: false + t.datetime "updated_at", null: false + t.index ["mentee_id", "mentor_id"], name: "index_mentor_relationships_on_mentee_id_and_mentor_id", unique: true + t.index ["mentee_id"], name: "index_mentor_relationships_on_mentee_id" + t.index ["mentor_id"], name: "index_mentor_relationships_on_mentor_id" + end + create_table "messages", force: :cascade do |t| t.bigint "chat_channel_id", null: false t.datetime "created_at", null: false diff --git a/spec/models/mentor_relationship_spec.rb b/spec/models/mentor_relationship_spec.rb new file mode 100644 index 000000000..5cc0568fc --- /dev/null +++ b/spec/models/mentor_relationship_spec.rb @@ -0,0 +1,23 @@ +require "rails_helper" + +RSpec.describe MentorRelationship, type: :model do + let(:mentor) { create(:user) } + let(:mentee) { create(:user) } + let(:relationship) { MentorRelationship.create(mentor_id: mentor.id, mentee_id: mentee.id) } + + it "is active" do + expect(relationship.active).to eq(true) + end + + it { is_expected.to belong_to(:mentor) } + it { is_expected.to belong_to(:mentee) } + + it "is not the same user" do + expect(MentorRelationship.create(mentor_id: mentor.id, mentee_id: mentor.id)).to be_invalid + end + + it "cannot be a duplicate record" do + relationship + expect(MentorRelationship.create(mentor_id: mentor.id, mentee_id: mentee.id)).to be_invalid + end +end diff --git a/spec/requests/internal_users_spec.rb b/spec/requests/internal_users_spec.rb new file mode 100644 index 000000000..64e1afa56 --- /dev/null +++ b/spec/requests/internal_users_spec.rb @@ -0,0 +1,100 @@ +require "rails_helper" + +RSpec.describe "internal/users", type: :request do + let(:mentor) { create(:user) } + let(:mentee) { create(:user) } + let(:admin) { create(:user, :super_admin) } + + before do + sign_in(admin) + mentor + mentee + end + + describe "GETS /internal/users" do + it "renders to appropriate page" do + get "/internal/users" + expect(response.body).to include(mentor.username) + end + + it "only displays mentors on ?state=mentors" do + get "/internal/users?state=mentors" + expect(response.body).not_to include(mentee.username) + end + + it "only displays mentees on ?state=mentees" do + get "/internal/users?state=mentees" + expect(response.body).not_to include(mentor.username) + end + end + + describe "GET /internal/users/:id" do + it "renders to appropriate page" do + get "/internal/users/#{mentor.id}" + expect(response.body).to include(mentor.username) + end + end + + describe "PUT internal/users/:id" do + it "updates user to offer mentorship" do + put "/internal/users/#{mentor.id}", + params: { user: { offering_mentorship: true } } + expect(mentor.reload.offering_mentorship).to eq(true) + end + + it "updates user to seek mentorship" do + put "/internal/users/#{mentee.id}", params: { user: { seeking_mentorship: true } } + expect(mentee.reload.seeking_mentorship).to eq(true) + end + + it "bans user from mentorship" do + put "/internal/users/#{mentor.id}", params: { user: { ban_from_mentorship: "1" } } + expect(mentor.reload.banned_from_mentorship).to eq(true) + end + + it "pairs mentor with a mentee" do + put "/internal/users/#{mentor.id}", params: { user: { add_mentee: mentee.id } } + expect(mentee.mentors[0].id).to eq(mentor.id) + end + + it "pairs mentee with a mentor" do + put "/internal/users/#{mentee.id}", params: { user: { add_mentor: mentor.id } } + expect(mentor.mentees[0].id).to eq(mentee.id) + end + + it "deactivates existing mentorships when user is banned" do + put "/internal/users/#{mentor.id}", params: { user: { add_mentee: mentee.id } } + put "/internal/users/#{mentor.id}", params: { user: { ban_from_mentorship: "1" } } + expect(MentorRelationship.where(mentor_id: mentor.id)[0].active).to eq(false) + end + + it "creates mentorship note" do + put "/internal/users/#{mentor.id}", params: { user: { mentorship_note: "some note" } } + expect(mentor.notes[0].content).to eq("some note") + end + end + + describe "GET internal/users/:id/edit" do + it "redirects from /username/moderate" do + get "/#{mentor.username}/moderate" + expect(response).to redirect_to("/internal/users/#{mentor.id}/edit") + end + + it "shows banish button for new users" do + get "/internal/users/#{mentor.id}/edit" + expect(response.body).to include("Banish User for Spam!") + end + + it "does not show banish button for non-admins" do + sign_out(admin) + expect { get "/internal/users/#{mentor.id}/edit" }.to raise_error(Pundit::NotAuthorizedError) + end + end + + describe "PUT internal/users/:id/edit" do + it "bans user for spam" do + post "/internal/users/#{mentor.id}/banish" + expect(mentor.reload.username).to include("spam") + end + end +end diff --git a/yarn.lock b/yarn.lock index 8ab6242f6..6c1cca3f4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2280,9 +2280,9 @@ commander@^2.11.0, commander@^2.14.1, commander@^2.15.0, commander@^2.9.0: version "2.18.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.18.0.tgz#2bf063ddee7c7891176981a2cc798e5754bc6970" -commander@~2.13.0: - version "2.13.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" +commander@~2.14.1: + version "2.14.1" + resolved "http://registry.npmjs.org/commander/-/commander-2.14.1.tgz#2235123e37af8ca3c65df45b026dbd357b01b9aa" common-tags@^1.7.2: version "1.8.0" @@ -2680,12 +2680,18 @@ debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6. dependencies: ms "2.0.0" -debug@^3.1.0: +debug@=3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" dependencies: ms "2.0.0" +debug@^3.1.0: + version "3.2.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.4.tgz#82123737c51afbe9609a2b5dfe9664e7487171f0" + dependencies: + ms "^2.1.1" + decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -2864,7 +2870,7 @@ doctrine@^2.0.0, doctrine@^2.1.0: dom-converter@~0.1: version "0.1.4" - resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.1.4.tgz#a45ef5727b890c9bffe6d7c876e7b19cb0e17f3b" + resolved "http://registry.npmjs.org/dom-converter/-/dom-converter-0.1.4.tgz#a45ef5727b890c9bffe6d7c876e7b19cb0e17f3b" dependencies: utila "~0.3" @@ -3741,10 +3747,10 @@ flush-write-stream@^1.0.0: readable-stream "^2.0.4" follow-redirects@^1.0.0: - version "1.5.7" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.7.tgz#a39e4804dacb90202bca76a9e2ac10433ca6a69a" + version "1.5.8" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.8.tgz#1dbfe13e45ad969f813e86c00e5296f525c885a1" dependencies: - debug "^3.1.0" + debug "=3.1.0" for-in@^0.1.3: version "0.1.8" @@ -5946,7 +5952,7 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" -ms@2.1.1: +ms@2.1.1, ms@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" @@ -7637,8 +7643,8 @@ realpath-native@^1.0.0: util.promisify "^1.0.0" recast@^0.15.0: - version "0.15.4" - resolved "https://registry.yarnpkg.com/recast/-/recast-0.15.4.tgz#67d51fded6f9ba9afb5e826309816b7cb6452f36" + version "0.15.5" + resolved "https://registry.yarnpkg.com/recast/-/recast-0.15.5.tgz#6871177ee26720be80d7624e4283d5c855a5cb0b" dependencies: ast-types "0.11.5" esprima "~4.0.0" @@ -8913,10 +8919,10 @@ ua-parser-js@^0.7.18: resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.18.tgz#a7bfd92f56edfb117083b69e31d2aa8882d4b1ed" uglify-es@^3.3.4: - version "3.3.9" - resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677" + version "3.3.10" + resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.10.tgz#8b0b7992cebe20edc26de1bf325cef797b8f3fa5" dependencies: - commander "~2.13.0" + commander "~2.14.1" source-map "~0.6.1" uglify-js@3.4.x, uglify-js@^3.1.4: