From 5f0236f92cc6dec484eb5cf2517f94afe7fcc93f Mon Sep 17 00:00:00 2001 From: MAC Date: Sun, 18 Jul 2021 11:37:27 +0100 Subject: [PATCH 01/36] modified projects controller create method to take a default pending status --- app/controllers/projects_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 5c2403410..c633f5cfa 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -33,7 +33,7 @@ def new end def create - @project = Project.new(project_params.merge('user_id' => current_user.id)) + @project = Project.new(project_params.merge('user_id' => current_user.id, 'status' => 'Pending')) # Create new project with default status of "Pending" if @project.save add_to_feed(:create) redirect_to project_path(@project), notice: 'Project was successfully created.' From 664c439416850eae90862a1fb56d2d418c9368ae Mon Sep 17 00:00:00 2001 From: MAC Date: Sun, 18 Jul 2021 11:43:38 +0100 Subject: [PATCH 02/36] created pending_projects conroller method with corresponding view and route --- app/controllers/projects_controller.rb | 4 ++ app/views/projects/pending_projects.html.erb | 40 ++++++++++++++++++++ config/routes.rb | 3 ++ 3 files changed, 47 insertions(+) create mode 100644 app/views/projects/pending_projects.html.erb diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index c633f5cfa..e1aa800be 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -43,6 +43,10 @@ def create end end + def pending_projects # view only projects that are of status 'pending' + @projects = Project.where(status: 'Pending').order('created_at DESC').paginate(page: params[:page], per_page: 10) + end + def edit; end def update diff --git a/app/views/projects/pending_projects.html.erb b/app/views/projects/pending_projects.html.erb new file mode 100644 index 000000000..d1c45ecd3 --- /dev/null +++ b/app/views/projects/pending_projects.html.erb @@ -0,0 +1,40 @@ +<% provide :title, 'Projects' %> +
+
+

List of Pending Projects

+
+
+ <% if user_signed_in? %> +
    +
  • <%= custom_css_btn 'new project', 'fa-2x fa fa-plus', new_project_path %>
  • +
+ <% end %> +
+
+ + <% if @projects.empty? %> +

We have no projects right now…

+ <% else %> +

+ To get involved in any of the projects, join one of the + <%= link_to 'scrums', events_path %> + and reach out to us, or send us an email at + info@agileventures.org. +

+ + <% end %> +
+
    + <%= render 'listing' %> +
+
+ <%= will_paginate @projects %> +<% end %> \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 329b6b6c4..b48904675 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -77,6 +77,9 @@ def loaderio_token end end + get '/pending_projects' => 'projects#pending_projects' + + get '/mentors' => 'users#index', defaults: { title: 'Mentor' } get '/premium_members' => 'users#index', defaults: { title: 'Premium' } From 6830c964ab85669638da70e68e03052f7e998389 Mon Sep 17 00:00:00 2001 From: MAC Date: Sun, 18 Jul 2021 11:47:48 +0100 Subject: [PATCH 03/36] created activate_project controller method with form in project view and corresponding route --- app/controllers/projects_controller.rb | 7 +++++++ app/views/projects/show.html.erb | 9 +++++++++ config/routes.rb | 1 + 3 files changed, 17 insertions(+) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index e1aa800be..36d966763 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -47,6 +47,13 @@ def pending_projects # view only projects that are of status 'pending' @projects = Project.where(status: 'Pending').order('created_at DESC').paginate(page: params[:page], per_page: 10) end + def activate_project # changes project status from 'Pending' to 'Active' making them visible on the projects index page + @project = Project.friendly.find(params[:id]) + @project.status = 'Active' + @project.save + redirect_to project_path, notice: 'project active' + end + def edit; end def update diff --git a/app/views/projects/show.html.erb b/app/views/projects/show.html.erb index aa21e22b7..60532a1a8 100644 --- a/app/views/projects/show.html.erb +++ b/app/views/projects/show.html.erb @@ -55,6 +55,15 @@ Start Jitsi Meet <% end %> + + <% if @project.status != 'Active' %> + <%= form_tag(activate_project_path(@project)) do %> + <%= button_tag(type: "submit" , :class => 'btn btn-primary') do %> + Activate Project + <% end %> + <% end %> + <% end %> +
diff --git a/config/routes.rb b/config/routes.rb index b48904675..5910341f4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -78,6 +78,7 @@ def loaderio_token end get '/pending_projects' => 'projects#pending_projects' + post '/activate_project/:id' => 'projects#activate_project', as: 'activate_project' get '/mentors' => 'users#index', defaults: { title: 'Mentor' } From b7b16a594df3dd6df15a6fb37a60d22c2340a0ff Mon Sep 17 00:00:00 2001 From: MAC Date: Sun, 18 Jul 2021 11:51:02 +0100 Subject: [PATCH 04/36] created deactivate_project controller method with form in project view and corresponding route --- app/controllers/projects_controller.rb | 7 +++++++ app/views/projects/show.html.erb | 9 ++++++++- config/routes.rb | 1 + 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 36d966763..05376d3f8 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -54,6 +54,13 @@ def activate_project # changes project status from 'Pending' to 'Active' making redirect_to project_path, notice: 'project active' end + def deactivate_project # changes project status from 'Active' to 'Pending' making them visible on the projects index page + @project = Project.friendly.find(params[:id]) + @project.status = 'Pending' + @project.save + redirect_to project_path, notice: 'project deactived' + end + def edit; end def update diff --git a/app/views/projects/show.html.erb b/app/views/projects/show.html.erb index 60532a1a8..cbf298e4e 100644 --- a/app/views/projects/show.html.erb +++ b/app/views/projects/show.html.erb @@ -63,7 +63,14 @@ <% end %> <% end %> <% end %> - + + <% if @project.status == 'Active' %> + <%= form_tag(deactivate_project_path(@project)) do %> + <%= button_tag(type: "submit" , :class => 'btn btn-primary') do %> + Deactivate Project + <% end %> + <% end %> + <% end %>
diff --git a/config/routes.rb b/config/routes.rb index 5910341f4..f26207742 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -79,6 +79,7 @@ def loaderio_token get '/pending_projects' => 'projects#pending_projects' post '/activate_project/:id' => 'projects#activate_project', as: 'activate_project' + post '/deactivate_project/:id' => 'projects#deactivate_project', as: 'deactivate_project' get '/mentors' => 'users#index', defaults: { title: 'Mentor' } From 290f01fca39de95d18bfbd79d3dc0f88240c5249 Mon Sep 17 00:00:00 2001 From: MAC Date: Sun, 18 Jul 2021 19:31:20 +0100 Subject: [PATCH 05/36] added admin attribute to User and created valid_admin method in project controller to check that currentuser is admin or not --- app/controllers/projects_controller.rb | 7 +++++++ db/schema.rb | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 05376d3f8..9ad99f94d 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -5,6 +5,7 @@ class ProjectsController < ApplicationController before_action :authenticate_user!, except: %i(index show) before_action :set_project, only: %i(show edit update) before_action :get_current_stories, only: [:show] + before_action :valid_admin, only: [:pending_projects, :activate_project, :deactivate_project] include DocumentsHelper # TODO: YA Add controller specs for all the code @@ -61,6 +62,12 @@ def deactivate_project # changes project status from 'Active' to 'Pending' makin redirect_to project_path, notice: 'project deactived' end + def valid_admin + if !current_user.admin? + redirect_to root_path, notice: 'You do not have permission to perform that operation' + end + end + def edit; end def update diff --git a/db/schema.rb b/db/schema.rb index c1c407950..7272097d4 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_04_12_143519) do +ActiveRecord::Schema.define(version: 2021_07_18_174049) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -324,6 +324,7 @@ t.datetime "deleted_at" t.integer "event_participation_count", default: 0 t.boolean "can_see_dashboard", default: false + t.boolean "admin" t.index ["deleted_at"], name: "index_users_on_deleted_at" t.index ["email"], name: "index_users_on_email", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true From c5f383bc2566a2698f5b400339f2d59d1965ec07 Mon Sep 17 00:00:00 2001 From: MAC Date: Sun, 18 Jul 2021 19:33:37 +0100 Subject: [PATCH 06/36] modified index method in projects controller to only display projects with status Active --- app/controllers/projects_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 9ad99f94d..ec28733e6 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -14,7 +14,7 @@ def index initialze_projects @projects_languages_array = Language.pluck(:name) filter_projects_list_by_language if params[:project] - @projects = @projects.search(params[:search], params[:page]) + @projects = @projects.where(status: 'Active').search(params[:search], params[:page]) # Selects on ACTIVE status projects render layout: 'with_sidebar_sponsor_right' end From 10eb6ba171b32368b18633c09631e03a4d29c0c4 Mon Sep 17 00:00:00 2001 From: MAC Date: Mon, 19 Jul 2021 07:24:43 +0100 Subject: [PATCH 07/36] added access_to_edit method inproject controller to resctrict edit of project to only admin or project creator. Also if statements in project form and show page to make changing of status only visible to admin --- app/controllers/projects_controller.rb | 13 +++++++++++-- app/views/projects/_form.html.erb | 17 ++++++++++++----- app/views/projects/show.html.erb | 24 +++++++++++++----------- 3 files changed, 36 insertions(+), 18 deletions(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index ec28733e6..5cbc9f071 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -3,9 +3,10 @@ class ProjectsController < ApplicationController layout 'with_sidebar' before_action :authenticate_user!, except: %i(index show) - before_action :set_project, only: %i(show edit update) + before_action :set_project, only: %i(show edit update access_to_edit) before_action :get_current_stories, only: [:show] before_action :valid_admin, only: [:pending_projects, :activate_project, :deactivate_project] + before_action :access_to_edit, only: [:edit] include DocumentsHelper # TODO: YA Add controller specs for all the code @@ -62,12 +63,20 @@ def deactivate_project # changes project status from 'Active' to 'Pending' makin redirect_to project_path, notice: 'project deactived' end - def valid_admin + def valid_admin # Check to see if user is admin if !current_user.admin? redirect_to root_path, notice: 'You do not have permission to perform that operation' end end + def access_to_edit # Check to see if user is admin or project creator + unless (current_user.admin?) || (current_user == @project.user) + redirect_to root_path, notice: 'You do not have permission to perform that operation' + end + end + + + def edit; end def update diff --git a/app/views/projects/_form.html.erb b/app/views/projects/_form.html.erb index bdb3d384d..84eeb44a5 100644 --- a/app/views/projects/_form.html.erb +++ b/app/views/projects/_form.html.erb @@ -11,11 +11,18 @@ <%= awesome_text_field f, :image_url, placeholder: 'Paste a link to your image here' %> <%= awesome_text_area f, :description, rows: 10, placeholder: 'Description' %> - -
- <%= f.label :status %> - <%= f.select :status, %w( Active Closed Pending ), {}, :class => 'form-control input-lg' %> -
+ + <% if current_user.admin? %> +
+ <%= f.label :status %> + <%= f.select :status, %w( Active Closed Pending ), {}, :class => 'form-control input-lg' %> +
+ <% elsif @project.user %> +
+ <%= f.label :status %> + <%= f.select :status, %w( Closed Pending ), {}, :class => 'form-control input-lg' %> +
+ <% end %>
<%= f.fields_for :source_repositories do |source_repository| %> diff --git a/app/views/projects/show.html.erb b/app/views/projects/show.html.erb index cbf298e4e..1974f0d5c 100644 --- a/app/views/projects/show.html.erb +++ b/app/views/projects/show.html.erb @@ -55,19 +55,21 @@ Start Jitsi Meet <% end %> - - <% if @project.status != 'Active' %> - <%= form_tag(activate_project_path(@project)) do %> - <%= button_tag(type: "submit" , :class => 'btn btn-primary') do %> - Activate Project + <% if current_user.admin? %> + + <% if @project.status != 'Active' %> + <%= form_tag(activate_project_path(@project)) do %> + <%= button_tag(type: "submit" , :class => 'btn btn-primary') do %> + Activate Project + <% end %> <% end %> <% end %> - <% end %> - - <% if @project.status == 'Active' %> - <%= form_tag(deactivate_project_path(@project)) do %> - <%= button_tag(type: "submit" , :class => 'btn btn-primary') do %> - Deactivate Project + + <% if @project.status == 'Active' %> + <%= form_tag(deactivate_project_path(@project)) do %> + <%= button_tag(type: "submit" , :class => 'btn btn-primary') do %> + Deactivate Project + <% end %> <% end %> <% end %> <% end %> From 3f96a117c1a8bc7f878c32e41aff1de6567aa046 Mon Sep 17 00:00:00 2001 From: MAC Date: Mon, 19 Jul 2021 07:36:07 +0100 Subject: [PATCH 08/36] made deactivate project button class btn-danger --- app/views/projects/show.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/show.html.erb b/app/views/projects/show.html.erb index 1974f0d5c..e21a71950 100644 --- a/app/views/projects/show.html.erb +++ b/app/views/projects/show.html.erb @@ -67,7 +67,7 @@ <% if @project.status == 'Active' %> <%= form_tag(deactivate_project_path(@project)) do %> - <%= button_tag(type: "submit" , :class => 'btn btn-primary') do %> + <%= button_tag(type: "submit" , :class => 'btn btn-danger') do %> Deactivate Project <% end %> <% end %> From 48dfd1f3bb3690e867fcaa4b411afd5b42218440 Mon Sep 17 00:00:00 2001 From: MAC Date: Wed, 21 Jul 2021 07:00:11 +0100 Subject: [PATCH 09/36] created a sub facory called :signed_in_user --- spec/factories/users.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/factories/users.rb b/spec/factories/users.rb index 2e1073bce..e5c544141 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -24,5 +24,9 @@ after(:save) do |user, evaluator| create(:authentication, provider: 'gplus', uid: evaluator.gplus, user_id: user.id) end + + factory :signed_in_user do + email {'go@go.com'} + end end end From 0e9aa478ece31ab389fc542eb35c1f5c2ecbc89f Mon Sep 17 00:00:00 2001 From: MAC Date: Wed, 21 Jul 2021 07:04:23 +0100 Subject: [PATCH 10/36] created NewUserForm class for page object pattern for feature spec --- spec/support/new_user_form.rb | 38 +++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 spec/support/new_user_form.rb diff --git a/spec/support/new_user_form.rb b/spec/support/new_user_form.rb new file mode 100644 index 000000000..01e201955 --- /dev/null +++ b/spec/support/new_user_form.rb @@ -0,0 +1,38 @@ +class NewUserForm + include Capybara::DSL + + def user_sign_in + StaticPage.create!(title: 'getting started', body: 'remote pair programming') + email = "go@go.com" + password = "12345678" + @current_user = @user = FactoryBot.create(:signed_in_user, + email: email, password: password, password_confirmation: password) + + + visit('/users/sign_in') + within('#main') do + fill_in 'user_email', with: email + fill_in 'user_password', with: password + click_button 'Sign in' + end + self + end + + def admin_user_sign_in + StaticPage.create!(title: 'getting started', body: 'remote pair programming') + email = "go@go.com" + password = "12345678" + @current_user = @user = FactoryBot.create(:signed_in_user, + email: email, password: password, password_confirmation: password, admin: true) + + + visit('/users/sign_in') + within('#main') do + fill_in 'user_email', with: email + fill_in 'user_password', with: password + click_button 'Sign in' + end + self + end + + end \ No newline at end of file From 83b6e6933178b77f7061c6d532134c18bb1a4668 Mon Sep 17 00:00:00 2001 From: MAC Date: Wed, 21 Jul 2021 07:06:07 +0100 Subject: [PATCH 11/36] created features folder and projects_spec.rb --- spec/features/projects_spec.rb | 102 +++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 spec/features/projects_spec.rb diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb new file mode 100644 index 000000000..38cb13895 --- /dev/null +++ b/spec/features/projects_spec.rb @@ -0,0 +1,102 @@ +require 'rails_helper' +require_relative '../support/new_user_form' + +feature 'user cannot access new project page if signed out' do + let(:new_user_form) {NewUserForm.new} + + + scenario "newly created project status is 'Pending' by default " do + new_user_form.user_sign_in + + visit('/projects/new') + expect(page).to have_content('Creating a new Project') + + fill_in('Title', with: 'Read a book') + fill_in('Description', with: 'Excellent read') + + click_button('Submit') + + expect(Project.last.title).to eq("Read a book") + expect(Project.last.status).to eq("Pending") + + end + + scenario 'Non-admin user cannot access pending projects page' do + new_user_form.user_sign_in + + visit('/pending_projects') + expect(page).to have_content('You do not have permission to perform that operation') + end + + + + scenario 'Non-admin user cannot edit projects they did not create' do + new_user_form.user_sign_in + @project = FactoryBot.create(:project) + + visit("/projects/#{@project.id}/edit") + expect(page).to have_content('You do not have permission to perform that operation') + end + + scenario 'Non-admin user cannot see activate button on project page' do + new_user_form.user_sign_in + + visit('/projects/new') + expect(page).to have_content('Creating a new Project') + + fill_in('Title', with: 'Read a book') + fill_in('Description', with: 'Excellent read') + + click_button('Submit') + + expect(Project.last.title).to eq("Read a book") + expect(Project.last.status).to eq("Pending") + + visit("/projects/#{Project.last.id}") + expect(page).to have_no_content('Activate Project') + end + + scenario 'Non-admin user cannot see deactivate button on project page' do + new_user_form.user_sign_in + @project = FactoryBot.create(:project, status: "Active") + + + visit("/projects/#{@project.id}") + expect(page).to have_no_content('Deactivate Project') + end + + scenario 'Admin user can access pending projects page' do + new_user_form.admin_user_sign_in + + visit('/pending_projects') + expect(page).to have_content('List of Pending Projects') + end + + + scenario 'Admin user can see activate button on project page' do + new_user_form.admin_user_sign_in + + visit('/projects/new') + expect(page).to have_content('Creating a new Project') + + fill_in('Title', with: 'Read a book') + fill_in('Description', with: 'Excellent read') + + click_button('Submit') + + expect(Project.last.title).to eq("Read a book") + expect(Project.last.status).to eq("Pending") + + visit("/projects/#{Project.last.id}") + expect(page).to have_content('Activate Project') + end + + scenario 'Admin user can see deactivate button on project page' do + new_user_form.admin_user_sign_in + @project = FactoryBot.create(:project, status: "Active") + + + visit("/projects/#{@project.id}") + expect(page).to have_content('Deactivate Project') + end +end \ No newline at end of file From d9e01a3ceb68731358677d30f222bbb10ab35079 Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 21 Jul 2021 11:55:03 +0200 Subject: [PATCH 12/36] refactors project create and approve feature --- .../20210721093118_add_admin_to_users.rb | 5 + db/schema.rb | 47 +------- spec/factories/users.rb | 6 +- .../project_create_and_approval_spec.rb | 107 ++++++++++++++++++ spec/features/projects_spec.rb | 102 ----------------- spec/support/new_user_form.rb | 38 ------- 6 files changed, 116 insertions(+), 189 deletions(-) create mode 100644 db/migrate/20210721093118_add_admin_to_users.rb create mode 100644 spec/features/project_create_and_approval_spec.rb delete mode 100644 spec/features/projects_spec.rb delete mode 100644 spec/support/new_user_form.rb diff --git a/db/migrate/20210721093118_add_admin_to_users.rb b/db/migrate/20210721093118_add_admin_to_users.rb new file mode 100644 index 000000000..3406c6a66 --- /dev/null +++ b/db/migrate/20210721093118_add_admin_to_users.rb @@ -0,0 +1,5 @@ +class AddAdminToUsers < ActiveRecord::Migration[6.1] + def change + add_column :users, :admin, :boolean + end +end diff --git a/db/schema.rb b/db/schema.rb index 7272097d4..5a6eb28f1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2021_07_18_174049) do +ActiveRecord::Schema.define(version: 2021_07_21_093118) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -331,51 +331,6 @@ t.index ["slug"], name: "index_users_on_slug", unique: true end - create_table "vanity_conversions", force: :cascade do |t| - t.integer "vanity_experiment_id" - t.integer "alternative" - t.integer "conversions" - t.index ["vanity_experiment_id", "alternative"], name: "by_experiment_id_and_alternative", unique: true - end - - create_table "vanity_experiments", force: :cascade do |t| - t.string "experiment_id" - t.integer "outcome" - t.boolean "enabled" - t.datetime "created_at" - t.datetime "completed_at" - t.index ["experiment_id"], name: "index_vanity_experiments_on_experiment_id", unique: true - end - - create_table "vanity_metric_values", force: :cascade do |t| - t.integer "vanity_metric_id" - t.integer "index" - t.integer "value" - t.string "date" - t.index ["vanity_metric_id", "date"], name: "index_vanity_metric_values_on_vanity_metric_id_and_date" - end - - create_table "vanity_metrics", force: :cascade do |t| - t.string "metric_id" - t.datetime "updated_at" - t.index ["metric_id"], name: "index_vanity_metrics_on_metric_id" - end - - create_table "vanity_participants", force: :cascade do |t| - t.string "experiment_id" - t.string "identity" - t.integer "shown" - t.integer "seen" - t.integer "converted" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["experiment_id", "converted"], name: "by_experiment_id_and_converted" - t.index ["experiment_id", "identity"], name: "by_experiment_id_and_identity", unique: true - t.index ["experiment_id", "seen"], name: "by_experiment_id_and_seen" - t.index ["experiment_id", "shown"], name: "by_experiment_id_and_shown" - t.index ["experiment_id"], name: "index_vanity_participants_on_experiment_id" - end - create_table "versions", id: :serial, force: :cascade do |t| t.string "item_type", null: false t.integer "item_id", null: false diff --git a/spec/factories/users.rb b/spec/factories/users.rb index e5c544141..19414579b 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -25,8 +25,8 @@ create(:authentication, provider: 'gplus', uid: evaluator.gplus, user_id: user.id) end - factory :signed_in_user do - email {'go@go.com'} - end + # factory :signed_in_user do + # email {'go@go.com'} + # end end end diff --git a/spec/features/project_create_and_approval_spec.rb b/spec/features/project_create_and_approval_spec.rb new file mode 100644 index 000000000..62a198c70 --- /dev/null +++ b/spec/features/project_create_and_approval_spec.rb @@ -0,0 +1,107 @@ +# require_relative '../support/new_user_form' + +feature 'user cannot access new project page if signed out' do + let!(:admin) { create(:user, admin: true) } + let!(:user) { create(:user, admin: false) } + + scenario "is expected to set newly created project status to 'Pending'" do + login_as user, scope: :user + visit('/projects/new') + fill_in('Title', with: 'Read a book') + fill_in('Description', with: 'Excellent read') + + click_button('Submit') + expect(Project.last.status).to eq('Pending') + end + + feature 'Non-admin user attempts to access pending projects page' do + before do + login_as user, scope: :user + visit('/pending_projects') + end + + it do + expect(page).to have_content('You do not have permission to perform that operation') + end + end + + feature 'Non-admin user cannot edit projects they did not create' do + before do + login_as user, scope: :user + project = create(:project) + visit("/projects/#{project.id}/edit") + end + + it do + expect(page).to have_content('You do not have permission to perform that operation') + end + end + + feature 'Non-admin user cannot see activate button on project page' do + before do + login_as user, scope: :user + visit('/projects/new') + end + + it 'is expected to....' do + expect(page).to have_content('Creating a new Project') + + fill_in('Title', with: 'Read a book') + fill_in('Description', with: 'Excellent read') + + click_button('Submit') + + expect(Project.last.title).to eq('Read a book') + expect(Project.last.status).to eq('Pending') + + visit("/projects/#{Project.last.id}") + expect(page).to have_no_content('Activate Project') + end + end + + scenario 'Non-admin user cannot see deactivate button on project page' do + # new_user_form.user_sign_in + login_as user, scope: :user + @project = create(:project, status: 'Active') + + visit("/projects/#{@project.id}") + expect(page).to have_no_content('Deactivate Project') + end + + scenario 'Admin user can access pending projects page' do + # new_user_form.admin_user_sign_in + login_as admin, scope: :user + + visit('/pending_projects') + expect(page).to have_content('List of Pending Projects') + end + + scenario 'Admin user can see activate button on project page' do + # new_user_form.admin_user_sign_in + login_as admin, scope: :user + + visit('/projects/new') + expect(page).to have_content('Creating a new Project') + + fill_in('Title', with: 'Read a book') + fill_in('Description', with: 'Excellent read') + + click_button('Submit') + + expect(Project.last.title).to eq('Read a book') + expect(Project.last.status).to eq('Pending') + + visit("/projects/#{Project.last.id}") + expect(page).to have_content('Activate Project') + end + + scenario 'Admin user can see deactivate button on project page' do + # new_user_form.admin_user_sign_in + login_as admin, scope: :user + + @project = create(:project, status: 'Active') + + visit("/projects/#{@project.id}") + expect(page).to have_content('Deactivate Project') + end +end diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb deleted file mode 100644 index 38cb13895..000000000 --- a/spec/features/projects_spec.rb +++ /dev/null @@ -1,102 +0,0 @@ -require 'rails_helper' -require_relative '../support/new_user_form' - -feature 'user cannot access new project page if signed out' do - let(:new_user_form) {NewUserForm.new} - - - scenario "newly created project status is 'Pending' by default " do - new_user_form.user_sign_in - - visit('/projects/new') - expect(page).to have_content('Creating a new Project') - - fill_in('Title', with: 'Read a book') - fill_in('Description', with: 'Excellent read') - - click_button('Submit') - - expect(Project.last.title).to eq("Read a book") - expect(Project.last.status).to eq("Pending") - - end - - scenario 'Non-admin user cannot access pending projects page' do - new_user_form.user_sign_in - - visit('/pending_projects') - expect(page).to have_content('You do not have permission to perform that operation') - end - - - - scenario 'Non-admin user cannot edit projects they did not create' do - new_user_form.user_sign_in - @project = FactoryBot.create(:project) - - visit("/projects/#{@project.id}/edit") - expect(page).to have_content('You do not have permission to perform that operation') - end - - scenario 'Non-admin user cannot see activate button on project page' do - new_user_form.user_sign_in - - visit('/projects/new') - expect(page).to have_content('Creating a new Project') - - fill_in('Title', with: 'Read a book') - fill_in('Description', with: 'Excellent read') - - click_button('Submit') - - expect(Project.last.title).to eq("Read a book") - expect(Project.last.status).to eq("Pending") - - visit("/projects/#{Project.last.id}") - expect(page).to have_no_content('Activate Project') - end - - scenario 'Non-admin user cannot see deactivate button on project page' do - new_user_form.user_sign_in - @project = FactoryBot.create(:project, status: "Active") - - - visit("/projects/#{@project.id}") - expect(page).to have_no_content('Deactivate Project') - end - - scenario 'Admin user can access pending projects page' do - new_user_form.admin_user_sign_in - - visit('/pending_projects') - expect(page).to have_content('List of Pending Projects') - end - - - scenario 'Admin user can see activate button on project page' do - new_user_form.admin_user_sign_in - - visit('/projects/new') - expect(page).to have_content('Creating a new Project') - - fill_in('Title', with: 'Read a book') - fill_in('Description', with: 'Excellent read') - - click_button('Submit') - - expect(Project.last.title).to eq("Read a book") - expect(Project.last.status).to eq("Pending") - - visit("/projects/#{Project.last.id}") - expect(page).to have_content('Activate Project') - end - - scenario 'Admin user can see deactivate button on project page' do - new_user_form.admin_user_sign_in - @project = FactoryBot.create(:project, status: "Active") - - - visit("/projects/#{@project.id}") - expect(page).to have_content('Deactivate Project') - end -end \ No newline at end of file diff --git a/spec/support/new_user_form.rb b/spec/support/new_user_form.rb deleted file mode 100644 index 01e201955..000000000 --- a/spec/support/new_user_form.rb +++ /dev/null @@ -1,38 +0,0 @@ -class NewUserForm - include Capybara::DSL - - def user_sign_in - StaticPage.create!(title: 'getting started', body: 'remote pair programming') - email = "go@go.com" - password = "12345678" - @current_user = @user = FactoryBot.create(:signed_in_user, - email: email, password: password, password_confirmation: password) - - - visit('/users/sign_in') - within('#main') do - fill_in 'user_email', with: email - fill_in 'user_password', with: password - click_button 'Sign in' - end - self - end - - def admin_user_sign_in - StaticPage.create!(title: 'getting started', body: 'remote pair programming') - email = "go@go.com" - password = "12345678" - @current_user = @user = FactoryBot.create(:signed_in_user, - email: email, password: password, password_confirmation: password, admin: true) - - - visit('/users/sign_in') - within('#main') do - fill_in 'user_email', with: email - fill_in 'user_password', with: password - click_button 'Sign in' - end - self - end - - end \ No newline at end of file From 7eb06eb722bd55ee47dab75c2dfb6caa42cfbfb0 Mon Sep 17 00:00:00 2001 From: MAC Date: Wed, 21 Jul 2021 13:10:15 +0100 Subject: [PATCH 13/36] refactors project create and approve feature some more --- .../project_create_and_approval_spec.rb | 93 +++++++++---------- 1 file changed, 42 insertions(+), 51 deletions(-) diff --git a/spec/features/project_create_and_approval_spec.rb b/spec/features/project_create_and_approval_spec.rb index 62a198c70..4fa48e69a 100644 --- a/spec/features/project_create_and_approval_spec.rb +++ b/spec/features/project_create_and_approval_spec.rb @@ -20,7 +20,7 @@ visit('/pending_projects') end - it do + it "is expected to have text 'You do not have permission to perform that operation' " do expect(page).to have_content('You do not have permission to perform that operation') end end @@ -32,7 +32,7 @@ visit("/projects/#{project.id}/edit") end - it do + it "is expected to have text 'You do not have permission to perform that operation' " do expect(page).to have_content('You do not have permission to perform that operation') end end @@ -40,68 +40,59 @@ feature 'Non-admin user cannot see activate button on project page' do before do login_as user, scope: :user - visit('/projects/new') + project = create(:project, status: 'Pending') + visit("/projects/#{project.id}") end - it 'is expected to....' do - expect(page).to have_content('Creating a new Project') - - fill_in('Title', with: 'Read a book') - fill_in('Description', with: 'Excellent read') - - click_button('Submit') - - expect(Project.last.title).to eq('Read a book') - expect(Project.last.status).to eq('Pending') - - visit("/projects/#{Project.last.id}") + it "is expected to not have text 'Activate Project' " do expect(page).to have_no_content('Activate Project') end end - scenario 'Non-admin user cannot see deactivate button on project page' do - # new_user_form.user_sign_in - login_as user, scope: :user - @project = create(:project, status: 'Active') - - visit("/projects/#{@project.id}") - expect(page).to have_no_content('Deactivate Project') + feature 'Non-admin user cannot see deactivate button on project page' do + before do + login_as user, scope: :user + project = create(:project, status: 'Active') + visit("/projects/#{project.id}") + end + + it "is expected to not have text 'Deactivate Project' " do + expect(page).to have_no_content('Deactivate Project') + end end - scenario 'Admin user can access pending projects page' do - # new_user_form.admin_user_sign_in - login_as admin, scope: :user - - visit('/pending_projects') - expect(page).to have_content('List of Pending Projects') + feature 'Admin user can access pending projects page' do + before do + login_as admin, scope: :user + visit('/pending_projects') + end + + it "is expected to have text 'List of Pending Projects' " do + expect(page).to have_content('List of Pending Projects') + end end - scenario 'Admin user can see activate button on project page' do - # new_user_form.admin_user_sign_in - login_as admin, scope: :user - - visit('/projects/new') - expect(page).to have_content('Creating a new Project') - - fill_in('Title', with: 'Read a book') - fill_in('Description', with: 'Excellent read') - - click_button('Submit') - - expect(Project.last.title).to eq('Read a book') - expect(Project.last.status).to eq('Pending') + feature 'Admin user can see activate button on project page' do + before do + login_as admin, scope: :user + project = create(:project, status: 'Pending') + visit("/projects/#{project.id}") + end - visit("/projects/#{Project.last.id}") - expect(page).to have_content('Activate Project') + it "is expected to have text 'Activate Project'" do + expect(page).to have_content('Activate Project') + end end - scenario 'Admin user can see deactivate button on project page' do - # new_user_form.admin_user_sign_in - login_as admin, scope: :user - - @project = create(:project, status: 'Active') + feature 'Admin user can see deactivate button on project page' do + before do + login_as admin, scope: :user + @project = create(:project, status: 'Active') + visit("/projects/#{@project.id}") + end - visit("/projects/#{@project.id}") - expect(page).to have_content('Deactivate Project') + it "is expected to have text 'Deactivate Project' " do + expect(page).to have_content('Deactivate Project') + end end end From bdb00f410741116ed35fdc44a2fbb87df1d3c9b0 Mon Sep 17 00:00:00 2001 From: Thomas Date: Sat, 24 Jul 2021 09:25:47 +0200 Subject: [PATCH 14/36] refactors feature structure moves routes to project resource as members and keeps pending_projects as custom route pointing to index action --- config/routes.rb | 15 ++-- spec/factories/users.rb | 4 - .../project_create_and_approval_spec.rb | 76 +++++++++++-------- 3 files changed, 53 insertions(+), 42 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index f26207742..37b57f997 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -55,15 +55,18 @@ def loaderio_token get :mercury_saved get :follow get :unfollow + post :activate_project, action: :update, defaults: { command: 'activate' } + post :deactivate_project, action: :update, defaults: { command: 'deactivate' } end - + resources :documents, except: %i(edit update), format: false do put :mercury_update get :mercury_saved end - + resources :events, only: [:index] end + get :pending_projects, controller: :projects, action: :index, defaults: { status: 'pending' } resources :events do member do @@ -77,10 +80,10 @@ def loaderio_token end end - get '/pending_projects' => 'projects#pending_projects' - post '/activate_project/:id' => 'projects#activate_project', as: 'activate_project' - post '/deactivate_project/:id' => 'projects#deactivate_project', as: 'deactivate_project' - + # get '/pending_projects' => 'projects#pending_projects' + # post '/activate_project/:id', controller: :projects, action: :activate_project + # post '/activate_project/:id' => 'projects#activate_project', as: 'activate_project' + # post '/deactivate_project/:id' => 'projects#deactivate_project', as: 'deactivate_project' get '/mentors' => 'users#index', defaults: { title: 'Mentor' } get '/premium_members' => 'users#index', defaults: { title: 'Premium' } diff --git a/spec/factories/users.rb b/spec/factories/users.rb index 19414579b..2e1073bce 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -24,9 +24,5 @@ after(:save) do |user, evaluator| create(:authentication, provider: 'gplus', uid: evaluator.gplus, user_id: user.id) end - - # factory :signed_in_user do - # email {'go@go.com'} - # end end end diff --git a/spec/features/project_create_and_approval_spec.rb b/spec/features/project_create_and_approval_spec.rb index 4fa48e69a..1d7742fd0 100644 --- a/spec/features/project_create_and_approval_spec.rb +++ b/spec/features/project_create_and_approval_spec.rb @@ -1,28 +1,32 @@ # require_relative '../support/new_user_form' -feature 'user cannot access new project page if signed out' do +describe 'Project is subject to approval' do let!(:admin) { create(:user, admin: true) } let!(:user) { create(:user, admin: false) } + subject { page } - scenario "is expected to set newly created project status to 'Pending'" do - login_as user, scope: :user - visit('/projects/new') - fill_in('Title', with: 'Read a book') - fill_in('Description', with: 'Excellent read') + feature 'upon creation by a communiy member (user without admin rights)' do + scenario "is expected to set newly created project status to 'Pending'" do + login_as user, scope: :user + visit('/projects/new') + fill_in('Title', with: 'Read a book') + fill_in('Description', with: 'Excellent read') - click_button('Submit') - expect(Project.last.status).to eq('Pending') + click_button('Submit') + expect(Project.last.status).to eq('Pending') + end end - feature 'Non-admin user attempts to access pending projects page' do + feature 'communiy member (user without admin rights) attempts to access pending projects page' do before do login_as user, scope: :user visit('/pending_projects') end - it "is expected to have text 'You do not have permission to perform that operation' " do - expect(page).to have_content('You do not have permission to perform that operation') - end + it { + is_expected + .to have_content('You do not have permission to perform that operation') + } end feature 'Non-admin user cannot edit projects they did not create' do @@ -32,9 +36,10 @@ visit("/projects/#{project.id}/edit") end - it "is expected to have text 'You do not have permission to perform that operation' " do - expect(page).to have_content('You do not have permission to perform that operation') - end + it { + is_expected + .to have_content('You do not have permission to perform that operation') + } end feature 'Non-admin user cannot see activate button on project page' do @@ -44,9 +49,10 @@ visit("/projects/#{project.id}") end - it "is expected to not have text 'Activate Project' " do - expect(page).to have_no_content('Activate Project') - end + it { + is_expected + .to have_no_content('Activate Project') + } end feature 'Non-admin user cannot see deactivate button on project page' do @@ -55,10 +61,12 @@ project = create(:project, status: 'Active') visit("/projects/#{project.id}") end - - it "is expected to not have text 'Deactivate Project' " do - expect(page).to have_no_content('Deactivate Project') - end + + it { + is_expected + .to have_no_content('Deactivate Project') + } + end feature 'Admin user can access pending projects page' do @@ -66,10 +74,12 @@ login_as admin, scope: :user visit('/pending_projects') end - - it "is expected to have text 'List of Pending Projects' " do - expect(page).to have_content('List of Pending Projects') - end + + it { + is_expected + .to have_content('List of Pending Projects') + } + end feature 'Admin user can see activate button on project page' do @@ -79,9 +89,10 @@ visit("/projects/#{project.id}") end - it "is expected to have text 'Activate Project'" do - expect(page).to have_content('Activate Project') - end + it { + is_expected + .to have_content('Activate Project') + } end feature 'Admin user can see deactivate button on project page' do @@ -91,8 +102,9 @@ visit("/projects/#{@project.id}") end - it "is expected to have text 'Deactivate Project' " do - expect(page).to have_content('Deactivate Project') - end + it { + is_expected + .to have_content('Deactivate Project') + } end end From 21f77de2491ebe5f2b248386125f01313387529a Mon Sep 17 00:00:00 2001 From: Thomas Date: Sat, 24 Jul 2021 10:05:39 +0200 Subject: [PATCH 15/36] refactors projects controller to deal with activation/deactivation of projecs in REST actions --- app/controllers/projects_controller.rb | 49 ++++++++++--------- config/routes.rb | 4 +- .../project_create_and_approval_spec.rb | 10 ++-- 3 files changed, 33 insertions(+), 30 deletions(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 5cbc9f071..df32207f6 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -5,18 +5,22 @@ class ProjectsController < ApplicationController before_action :authenticate_user!, except: %i(index show) before_action :set_project, only: %i(show edit update access_to_edit) before_action :get_current_stories, only: [:show] - before_action :valid_admin, only: [:pending_projects, :activate_project, :deactivate_project] - before_action :access_to_edit, only: [:edit] + before_action :valid_admin, only: %i(index), if: -> { params[:status] = 'pending' } + # before_action :access_to_edit, only: [:edit] include DocumentsHelper # TODO: YA Add controller specs for all the code def index - initialze_projects + initialze_projects(params[:status]) @projects_languages_array = Language.pluck(:name) filter_projects_list_by_language if params[:project] @projects = @projects.where(status: 'Active').search(params[:search], params[:page]) # Selects on ACTIVE status projects - render layout: 'with_sidebar_sponsor_right' + if params[:status] = 'pending' + render :pending_projects, layout: 'with_sidebar_sponsor_right' + else + render layout: 'with_sidebar_sponsor_right' + end end def show @@ -35,7 +39,7 @@ def new end def create - @project = Project.new(project_params.merge('user_id' => current_user.id, 'status' => 'Pending')) # Create new project with default status of "Pending" + @project = Project.new(project_params.merge('user_id' => current_user.id, 'status' => 'pending')) # Create new project with default status of "Pending" if @project.save add_to_feed(:create) redirect_to project_path(@project), notice: 'Project was successfully created.' @@ -45,38 +49,36 @@ def create end end - def pending_projects # view only projects that are of status 'pending' - @projects = Project.where(status: 'Pending').order('created_at DESC').paginate(page: params[:page], per_page: 10) - end - - def activate_project # changes project status from 'Pending' to 'Active' making them visible on the projects index page + # changes project status from 'Pending' to 'Active' making them visible on the projects index page + def activate_project @project = Project.friendly.find(params[:id]) - @project.status = 'Active' + # refactor this to use #update + @project.status = 'active' @project.save redirect_to project_path, notice: 'project active' end - def deactivate_project # changes project status from 'Active' to 'Pending' making them visible on the projects index page + # changes project status from 'Active' to 'Pending' making them visible on the projects index page + def deactivate_project @project = Project.friendly.find(params[:id]) - @project.status = 'Pending' + # refactor this to use #update + @project.status = 'pending' @project.save redirect_to project_path, notice: 'project deactived' end - def valid_admin # Check to see if user is admin - if !current_user.admin? - redirect_to root_path, notice: 'You do not have permission to perform that operation' - end + # Check to see if user is admin + def valid_admin + redirect_to root_path, notice: 'You do not have permission to perform that operation' unless current_user.admin? end - def access_to_edit # Check to see if user is admin or project creator - unless (current_user.admin?) || (current_user == @project.user) + # Check to see if user is admin or project creator + def access_to_edit + unless current_user.admin? || (current_user == @project.user) redirect_to root_path, notice: 'You do not have permission to perform that operation' end end - - def edit; end def update @@ -127,8 +129,9 @@ def set_project @project = Project.friendly.find(params[:id]) end - def initialze_projects - @projects = Project.order('status ASC') + def initialze_projects(status) + status ||= 'active' + @projects = Project.where(status: status) .order('last_github_update DESC NULLS LAST') .order('commit_count DESC NULLS LAST') .includes(:user) diff --git a/config/routes.rb b/config/routes.rb index 37b57f997..783e91ead 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -55,8 +55,8 @@ def loaderio_token get :mercury_saved get :follow get :unfollow - post :activate_project, action: :update, defaults: { command: 'activate' } - post :deactivate_project, action: :update, defaults: { command: 'deactivate' } + post :activate, action: :update, defaults: { command: 'activate' } + post :deactivate, action: :update, defaults: { command: 'deactivate' } end resources :documents, except: %i(edit update), format: false do diff --git a/spec/features/project_create_and_approval_spec.rb b/spec/features/project_create_and_approval_spec.rb index 1d7742fd0..7267577c8 100644 --- a/spec/features/project_create_and_approval_spec.rb +++ b/spec/features/project_create_and_approval_spec.rb @@ -13,7 +13,7 @@ fill_in('Description', with: 'Excellent read') click_button('Submit') - expect(Project.last.status).to eq('Pending') + expect(Project.last.status).to eq('pending') end end @@ -45,7 +45,7 @@ feature 'Non-admin user cannot see activate button on project page' do before do login_as user, scope: :user - project = create(:project, status: 'Pending') + project = create(:project, status: 'pending') visit("/projects/#{project.id}") end @@ -58,7 +58,7 @@ feature 'Non-admin user cannot see deactivate button on project page' do before do login_as user, scope: :user - project = create(:project, status: 'Active') + project = create(:project, status: 'active') visit("/projects/#{project.id}") end @@ -85,7 +85,7 @@ feature 'Admin user can see activate button on project page' do before do login_as admin, scope: :user - project = create(:project, status: 'Pending') + project = create(:project, status: 'pending') visit("/projects/#{project.id}") end @@ -98,7 +98,7 @@ feature 'Admin user can see deactivate button on project page' do before do login_as admin, scope: :user - @project = create(:project, status: 'Active') + @project = create(:project, status: 'active') visit("/projects/#{@project.id}") end From 2fc4f0e2e88864f28962924f0848c22bb8492937 Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 25 Jul 2021 08:25:31 +0200 Subject: [PATCH 16/36] refactors specs and reactivates the #access_to_edit method --- app/controllers/projects_controller.rb | 4 +- app/views/projects/show.html.erb | 5 +-- .../project_create_and_approval_spec.rb | 41 ++++++++++++++----- 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index df32207f6..d48664bf5 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -6,11 +6,9 @@ class ProjectsController < ApplicationController before_action :set_project, only: %i(show edit update access_to_edit) before_action :get_current_stories, only: [:show] before_action :valid_admin, only: %i(index), if: -> { params[:status] = 'pending' } - # before_action :access_to_edit, only: [:edit] + before_action :access_to_edit, only: [:edit] include DocumentsHelper - # TODO: YA Add controller specs for all the code - def index initialze_projects(params[:status]) @projects_languages_array = Language.pluck(:name) diff --git a/app/views/projects/show.html.erb b/app/views/projects/show.html.erb index e21a71950..64600078d 100644 --- a/app/views/projects/show.html.erb +++ b/app/views/projects/show.html.erb @@ -57,15 +57,14 @@ <% end %> <% if current_user.admin? %> - <% if @project.status != 'Active' %> + <% if @project.status == 'pending' %> <%= form_tag(activate_project_path(@project)) do %> <%= button_tag(type: "submit" , :class => 'btn btn-primary') do %> Activate Project <% end %> <% end %> - <% end %> + <% else %> - <% if @project.status == 'Active' %> <%= form_tag(deactivate_project_path(@project)) do %> <%= button_tag(type: "submit" , :class => 'btn btn-danger') do %> Deactivate Project diff --git a/spec/features/project_create_and_approval_spec.rb b/spec/features/project_create_and_approval_spec.rb index 7267577c8..cda152c41 100644 --- a/spec/features/project_create_and_approval_spec.rb +++ b/spec/features/project_create_and_approval_spec.rb @@ -3,9 +3,10 @@ describe 'Project is subject to approval' do let!(:admin) { create(:user, admin: true) } let!(:user) { create(:user, admin: false) } + subject { page } - feature 'upon creation by a communiy member (user without admin rights)' do + feature 'upon creation by a user without admin rights' do scenario "is expected to set newly created project status to 'Pending'" do login_as user, scope: :user visit('/projects/new') @@ -17,7 +18,7 @@ end end - feature 'communiy member (user without admin rights) attempts to access pending projects page' do + feature 'user without admin rights attempts to access pending projects page' do before do login_as user, scope: :user visit('/pending_projects') @@ -29,10 +30,10 @@ } end - feature 'Non-admin user cannot edit projects they did not create' do + feature 'user without admin rights cannot access edit projects view for a project they did not create' do before do login_as user, scope: :user - project = create(:project) + project = create(:project, user: admin) visit("/projects/#{project.id}/edit") end @@ -42,7 +43,27 @@ } end - feature 'Non-admin user cannot see activate button on project page' do + # Add spec for Admin user being okay to access? + feature 'user with admin rights can access edit projects view for a project they did not create' do + let!(:project) { create(:project, user: user) } + + before do + login_as admin, scope: :user + visit("/projects/#{project.id}/edit") + save_and_open_page + end + + it { + is_expected.to have_current_path("/projects/#{project.id}/edit") + } + + it { + is_expected + .to have_no_content('You do not have permission to perform that operation') + } + end + + feature 'user without admin rights cannot see activate button on project page' do before do login_as user, scope: :user project = create(:project, status: 'pending') @@ -55,7 +76,7 @@ } end - feature 'Non-admin user cannot see deactivate button on project page' do + feature 'user without admin rights cannot see deactivate button on project page' do before do login_as user, scope: :user project = create(:project, status: 'active') @@ -66,10 +87,9 @@ is_expected .to have_no_content('Deactivate Project') } - end - feature 'Admin user can access pending projects page' do + feature 'user with admin rights can access pending projects page' do before do login_as admin, scope: :user visit('/pending_projects') @@ -82,7 +102,7 @@ end - feature 'Admin user can see activate button on project page' do + feature 'user with admin rights can see activate button on project page' do before do login_as admin, scope: :user project = create(:project, status: 'pending') @@ -95,11 +115,12 @@ } end - feature 'Admin user can see deactivate button on project page' do + feature 'user with admin rights can see deactivate button on project page' do before do login_as admin, scope: :user @project = create(:project, status: 'active') visit("/projects/#{@project.id}") + save_and_open_page end it { From eda9f129ae194dad5fd163ca8ab13bb74ba2ee8b Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 25 Jul 2021 09:02:55 +0200 Subject: [PATCH 17/36] adds more scenarios to feature refactors controller actions --- app/controllers/projects_controller.rb | 5 ++- .../project_create_and_approval_spec.rb | 39 +++++++++++-------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index d48664bf5..70b0ed3df 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -13,8 +13,9 @@ def index initialze_projects(params[:status]) @projects_languages_array = Language.pluck(:name) filter_projects_list_by_language if params[:project] - @projects = @projects.where(status: 'Active').search(params[:search], params[:page]) # Selects on ACTIVE status projects - if params[:status] = 'pending' + projects_status = params[:status] || 'active' + @projects = @projects.where(status: projects_status).search(params[:search], params[:page]) # Selects on ACTIVE status projects + if params[:status] == 'pending' render :pending_projects, layout: 'with_sidebar_sponsor_right' else render layout: 'with_sidebar_sponsor_right' diff --git a/spec/features/project_create_and_approval_spec.rb b/spec/features/project_create_and_approval_spec.rb index cda152c41..d7de7bae2 100644 --- a/spec/features/project_create_and_approval_spec.rb +++ b/spec/features/project_create_and_approval_spec.rb @@ -6,6 +6,7 @@ subject { page } + # move to model spec? feature 'upon creation by a user without admin rights' do scenario "is expected to set newly created project status to 'Pending'" do login_as user, scope: :user @@ -30,6 +31,23 @@ } end + feature 'user with admin rights can access pending projects page' do + let!(:projects) { 5.times { create(:project, status: 'pending') } } + before do + login_as admin, scope: :user + visit('/pending_projects') + end + + it { + is_expected + .to have_content('List of Pending Projects') + } + + it { + is_expected.to have_selector('.project_card', count: 5) + } + end + feature 'user without admin rights cannot access edit projects view for a project they did not create' do before do login_as user, scope: :user @@ -43,10 +61,9 @@ } end - # Add spec for Admin user being okay to access? feature 'user with admin rights can access edit projects view for a project they did not create' do let!(:project) { create(:project, user: user) } - + before do login_as admin, scope: :user visit("/projects/#{project.id}/edit") @@ -86,21 +103,9 @@ it { is_expected .to have_no_content('Deactivate Project') - } + } end - feature 'user with admin rights can access pending projects page' do - before do - login_as admin, scope: :user - visit('/pending_projects') - end - - it { - is_expected - .to have_content('List of Pending Projects') - } - - end feature 'user with admin rights can see activate button on project page' do before do @@ -112,7 +117,7 @@ it { is_expected .to have_content('Activate Project') - } + } end feature 'user with admin rights can see deactivate button on project page' do @@ -126,6 +131,6 @@ it { is_expected .to have_content('Deactivate Project') - } + } end end From 890619faa33a90dd2a623a96023f1de508f8fcef Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 25 Jul 2021 09:06:37 +0200 Subject: [PATCH 18/36] cleans up debugging code runs migrations --- db/schema.rb | 45 +++++++++++++++++++ .../project_create_and_approval_spec.rb | 1 - spec/requests/authentications_spec.rb | 1 - 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index 5a6eb28f1..2286f0b25 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -331,6 +331,51 @@ t.index ["slug"], name: "index_users_on_slug", unique: true end + create_table "vanity_conversions", force: :cascade do |t| + t.integer "vanity_experiment_id" + t.integer "alternative" + t.integer "conversions" + t.index ["vanity_experiment_id", "alternative"], name: "by_experiment_id_and_alternative", unique: true + end + + create_table "vanity_experiments", force: :cascade do |t| + t.string "experiment_id" + t.integer "outcome" + t.boolean "enabled" + t.datetime "created_at" + t.datetime "completed_at" + t.index ["experiment_id"], name: "index_vanity_experiments_on_experiment_id", unique: true + end + + create_table "vanity_metric_values", force: :cascade do |t| + t.integer "vanity_metric_id" + t.integer "index" + t.integer "value" + t.string "date" + t.index ["vanity_metric_id", "date"], name: "index_vanity_metric_values_on_vanity_metric_id_and_date" + end + + create_table "vanity_metrics", force: :cascade do |t| + t.string "metric_id" + t.datetime "updated_at" + t.index ["metric_id"], name: "index_vanity_metrics_on_metric_id" + end + + create_table "vanity_participants", force: :cascade do |t| + t.string "experiment_id" + t.string "identity" + t.integer "shown" + t.integer "seen" + t.integer "converted" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["experiment_id", "converted"], name: "by_experiment_id_and_converted" + t.index ["experiment_id", "identity"], name: "by_experiment_id_and_identity", unique: true + t.index ["experiment_id", "seen"], name: "by_experiment_id_and_seen" + t.index ["experiment_id", "shown"], name: "by_experiment_id_and_shown" + t.index ["experiment_id"], name: "index_vanity_participants_on_experiment_id" + end + create_table "versions", id: :serial, force: :cascade do |t| t.string "item_type", null: false t.integer "item_id", null: false diff --git a/spec/features/project_create_and_approval_spec.rb b/spec/features/project_create_and_approval_spec.rb index d7de7bae2..c6d999c68 100644 --- a/spec/features/project_create_and_approval_spec.rb +++ b/spec/features/project_create_and_approval_spec.rb @@ -67,7 +67,6 @@ before do login_as admin, scope: :user visit("/projects/#{project.id}/edit") - save_and_open_page end it { diff --git a/spec/requests/authentications_spec.rb b/spec/requests/authentications_spec.rb index b81412911..de37b59ea 100644 --- a/spec/requests/authentications_spec.rb +++ b/spec/requests/authentications_spec.rb @@ -42,7 +42,6 @@ visit new_user_session_path expect do expect do - # save_and_open_page page.click_on "with #{name}" end.to change(User, :count).by(0) end.to change(Authentication, :count).by(0) From 051971833d26e4d7c2f4d557f8e8c8297e4e28ca Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 25 Jul 2021 09:20:25 +0200 Subject: [PATCH 19/36] WIP projects controller spec is failing --- app/controllers/projects_controller.rb | 1 + spec/controllers/projects_controller_spec.rb | 2 +- spec/factories/projects.rb | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 70b0ed3df..44407c168 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -14,6 +14,7 @@ def index @projects_languages_array = Language.pluck(:name) filter_projects_list_by_language if params[:project] projects_status = params[:status] || 'active' + binding.pry @projects = @projects.where(status: projects_status).search(params[:search], params[:page]) # Selects on ACTIVE status projects if params[:status] == 'pending' render :pending_projects, layout: 'with_sidebar_sponsor_right' diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index bac527038..5275a54d4 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -5,7 +5,7 @@ { id: 1, title: 'WebTwentyFive', description: 'My project description', - status: 'Active', + status: 'active', pitch: 'My project pitch ...', pivotaltracker_url: 'https://www.pivotaltracker.com/s/projects/982890', slug: 'my-project' } diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index f6f23d57b..f0d565d88 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -8,7 +8,7 @@ sequence(:slug) { |n| "title-#{n}" } description { 'Warp fields stabilize.' } pitch { "'I AM the greatest!' - M. Ali" } - status { 'We feel your presence.' } + status { 'active' } factory :project_with_tags do transient do From 23f0e55a43e2ed2e31f89820fc571349d5dcfa15 Mon Sep 17 00:00:00 2001 From: Thomas Date: Sat, 31 Jul 2021 09:14:45 +0200 Subject: [PATCH 20/36] deletes prjects_controller spec --- app/controllers/projects_controller.rb | 1 - spec/controllers/projects_controller_spec.rb | 233 ------------------- 2 files changed, 234 deletions(-) delete mode 100644 spec/controllers/projects_controller_spec.rb diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 44407c168..70b0ed3df 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -14,7 +14,6 @@ def index @projects_languages_array = Language.pluck(:name) filter_projects_list_by_language if params[:project] projects_status = params[:status] || 'active' - binding.pry @projects = @projects.where(status: projects_status).search(params[:search], params[:page]) # Selects on ACTIVE status projects if params[:status] == 'pending' render :pending_projects, layout: 'with_sidebar_sponsor_right' diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb deleted file mode 100644 index 5275a54d4..000000000 --- a/spec/controllers/projects_controller_spec.rb +++ /dev/null @@ -1,233 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe ProjectsController, type: :controller do - let(:valid_attributes) do - { id: 1, - title: 'WebTwentyFive', - description: 'My project description', - status: 'active', - pitch: 'My project pitch ...', - pivotaltracker_url: 'https://www.pivotaltracker.com/s/projects/982890', - slug: 'my-project' } - end - let(:valid_session) { {} } - - before :each do - @user = build_stubbed(:user, id: 1, slug: 'some-id') - allow(request.env['warden']).to receive(:authenticate!).and_return(@user) - allow(controller).to receive(:current_user).and_return(@user) - allow(@user).to receive(:touch) - end - - let(:user) { @user } - - describe '#index' do - before(:each) do - @project = mock_model(Project, title: 'Carrier has arrived.', commit_count: 200, last_github_update: '2000-01-01') - @project2 = mock_model(Project, title: 'Carrier has left.', commit_count: 100, last_github_update: '2000-02-01') - @projects = [@project, @project2] - end - - it 'is expected to render index page for projects' do - get :index - expect(response).to render_template 'index' - end - - it 'is expected to assign variables to be rendered by view' do - allow(Project).to receive(:search).and_return(@project) - @project.stub(:includes).and_return(@project) - get :index - expect(assigns(:projects).title).to eq 'Carrier has arrived.' - end - - describe '#show' do - let(:pivotal_tracker) { instance_double(PivotalAPI::Project) } - - before(:each) do - @project = build_stubbed(:project, valid_attributes) - allow(@project).to receive(:tag_list).and_return ['WSO'] - Project.stub_chain(:friendly, :find).and_return @project - @project.stub_chain(:user, :display_name).and_return 'Happy User' - @users = [build_stubbed(:user, slug: 'my-friendly-id', display_profile: true)] - expect(@project).to receive(:members).and_return @users - event_instances = double('event_instances') - ordered_event_instances = double('ordered_event_instances') - expect(EventInstance).to receive(:where).with(project_id: @project.id).and_return(event_instances) - expect(event_instances).to receive(:order).with(created_at: :desc).and_return(ordered_event_instances) - expect(event_instances).to receive(:count).and_return('count') - expect(ordered_event_instances).to receive(:limit).with(25).and_return('videos') - allow(PivotalAPI::Project).to receive(:retrieve).and_return(pivotal_tracker) - allow(pivotal_tracker).to receive_messages(current_iteration: pivotal_tracker, stories: 'stories') - end - - it 'assigns the requested project as @project' do - get :show, params: { id: @project.friendly_id }.merge(valid_session) - expect(assigns(:project)).to eq(@project) - end - - it 'renders the show template' do - get :show, params: { id: @project.friendly_id }.merge(valid_session) - expect(response).to render_template 'show' - end - - it 'assigns the list of members with public profiles to @members' do - get :show, params: { id: @project.friendly_id }.merge(valid_session) - expect(assigns(:members)).to eq @users - end - - it 'assigns the list of related YouTube videos in created_at descending order' do - get :show, params: { id: @project.friendly_id }.merge(valid_session) - expect(assigns(:event_instances)).to eq 'videos' - end - - it 'assigns the count of related YouTube videos' do - get :show, params: { id: @project.friendly_id }.merge(valid_session) - expect(assigns(:event_instances_count)).to eq 'count' - end - - it 'assigns the list of related PivotalTracker stories' do - get :show, params: { id: @project.friendly_id }.merge(valid_session) - expect(assigns(:stories)).to eq 'stories' - end - end - - describe '#new' do - it 'is expected to render a new project page' do - get :new - expect(assigns(:project)).to be_a_new(Project) - expect(response).to render_template 'new' - end - end - - describe '#create' do - before(:each) do - @params = { - project: { - id: 1, - title: 'Title 1', - description: 'Description 1', - status: 'Status 1' - } - } - @project = mock_model(Project, friendly_id: 'some-project') - allow(Project).to receive(:new).and_return(@project) - allow(controller).to receive(:current_user).and_return(@user) - allow(@project).to receive(:create_activity) - end - - it 'assigns a newly created project as @project' do - allow(@project).to receive(:save) - post :create, params: @params - expect(assigns(:project)).to eq @project - end - - context 'successful save' do - before(:each) do - allow(@project).to receive(:save).and_return(true) - post :create, params: @params - end - it 'redirects to index' do - expect(response).to redirect_to(project_path(@project)) - end - - it 'received :create_activity with :create' do - expect(@project).to have_received(:create_activity).with(:create, { owner: @user }) - end - - it 'assigns successful message' do - # TODO: YA add a show view_spec to check if flash is actually displayed - expect(flash[:notice]).to eq('Project was successfully created.') - end - - it 'passes current_user id into new' do - expect(Project).to receive(:new).with({ 'title' => 'Title 1', 'description' => 'Description 1', - 'status' => 'Status 1', 'user_id' => @user.id }) - allow(@project).to receive(:save).and_return(true) - post :create, params: @params - end - end - - context 'unsuccessful save' do - it 'renders new template' do - allow(@project).to receive(:save).and_return(false) - - post :create, params: @params - - expect(response).to render_template :new - end - - it 'assigns failure message' do - allow(@project).to receive(:save).and_return(false) - - post :create, params: @params - - expect(flash[:alert]).to eql('Project was not saved. Please check the input.') - end - end - end - - describe '#edit' do - before(:each) do - @project = Project.new - Project.stub_chain(:friendly, :find).with(an_instance_of(String)).and_return(@project) - get :edit, params: { id: 'some-random-thing' } - end - - it 'renders the edit template' do - expect(response).to render_template 'edit' - end - - it 'assigns the requested project as @project' do - expect(assigns(:project)).to eq(@project) - end - end - - describe '#update' do - before(:each) do - @project = FactoryBot.create(:project) - allow(@project).to receive(:create_activity) - Project.stub_chain(:friendly, :find).with(an_instance_of(String)).and_return(@project) - end - - it 'assigns the requested project as @project' do - expect(@project).to receive(:update) - put :update, params: { id: 'update', project: { title: '' } } - expect(assigns(:project)).to eq(@project) - end - - context 'successful update' do - before(:each) do - allow(@project).to receive(:update).and_return(true) - put :update, params: { id: 'update', project: { title: '' } } - end - - it 'received :create_activity with :update' do - expect(@project).to have_received(:create_activity) - .with(:update, { owner: @user }) - end - - it 'redirects to the project' do - expect(response).to redirect_to(project_path(@project)) - end - - it 'shows a success message' do - expect(flash[:notice]).to eq('Project was successfully updated.') - end - end - - context 'unsuccessful save' do - before(:each) do - allow(@project).to receive(:update).and_return(false) - put :update, params: { id: 'update', project: { title: '' } } - end - it 'renders edit' do - expect(response).to render_template(:edit) - end - - it 'shows an unsuccessful message' do - expect(flash[:alert]).to eq('Project was not updated.') - end - end - end - end -end From 9f2e1f628573e9658bc91a48ecb591b38a23aec7 Mon Sep 17 00:00:00 2001 From: Thomas Date: Sat, 31 Jul 2021 10:06:03 +0200 Subject: [PATCH 21/36] updates project controller and activate/deactivate feature --- app/controllers/projects_controller.rb | 48 +++++++---------- .../project_create_and_approval_spec.rb | 51 ++++++++++++++----- 2 files changed, 55 insertions(+), 44 deletions(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 70b0ed3df..49381dc42 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -48,39 +48,10 @@ def create end end - # changes project status from 'Pending' to 'Active' making them visible on the projects index page - def activate_project - @project = Project.friendly.find(params[:id]) - # refactor this to use #update - @project.status = 'active' - @project.save - redirect_to project_path, notice: 'project active' - end - - # changes project status from 'Active' to 'Pending' making them visible on the projects index page - def deactivate_project - @project = Project.friendly.find(params[:id]) - # refactor this to use #update - @project.status = 'pending' - @project.save - redirect_to project_path, notice: 'project deactived' - end - - # Check to see if user is admin - def valid_admin - redirect_to root_path, notice: 'You do not have permission to perform that operation' unless current_user.admin? - end - - # Check to see if user is admin or project creator - def access_to_edit - unless current_user.admin? || (current_user == @project.user) - redirect_to root_path, notice: 'You do not have permission to perform that operation' - end - end - def edit; end def update + params[:command].present? && update_project_status(params[:command]) and return if @project.update(project_params) add_to_feed(:update) redirect_to project_path(@project), notice: 'Project was successfully updated.' @@ -171,4 +142,21 @@ def project_params name_ids: [], source_repositories_attributes: %i(id url _destroy), issue_trackers_attributes: %i(id url _destroy)) end + + def valid_admin + redirect_to root_path, notice: 'You do not have permission to perform that operation' unless current_user.admin? + end + + def access_to_edit + unless current_user.admin? || (current_user == @project.user) + redirect_to root_path, notice: 'You do not have permission to perform that operation' + end + end + + def update_project_status(command) + status = command == 'activate' ? 'active' : 'pending' + @project = Project.friendly.find(params[:id]) + @project.update(status: status) + redirect_to project_path, notice: "Project was #{command}d" + end end diff --git a/spec/features/project_create_and_approval_spec.rb b/spec/features/project_create_and_approval_spec.rb index c6d999c68..26a629911 100644 --- a/spec/features/project_create_and_approval_spec.rb +++ b/spec/features/project_create_and_approval_spec.rb @@ -1,14 +1,11 @@ -# require_relative '../support/new_user_form' - -describe 'Project is subject to approval' do +RSpec.describe 'Project is subject to approval' do let!(:admin) { create(:user, admin: true) } let!(:user) { create(:user, admin: false) } subject { page } - # move to model spec? - feature 'upon creation by a user without admin rights' do - scenario "is expected to set newly created project status to 'Pending'" do + feature 'upon creation by a user without admin rights a project' do + scenario "is expected to have status set to 'Pending'" do login_as user, scope: :user visit('/projects/new') fill_in('Title', with: 'Read a book') @@ -19,7 +16,7 @@ end end - feature 'user without admin rights attempts to access pending projects page' do + feature 'user without admin rights attempts to access pending projects view' do before do login_as user, scope: :user visit('/pending_projects') @@ -31,7 +28,7 @@ } end - feature 'user with admin rights can access pending projects page' do + feature 'user with admin rights can access pending projects view' do let!(:projects) { 5.times { create(:project, status: 'pending') } } before do login_as admin, scope: :user @@ -93,9 +90,9 @@ end feature 'user without admin rights cannot see deactivate button on project page' do + let(:project) { create(:project, status: 'active') } before do login_as user, scope: :user - project = create(:project, status: 'active') visit("/projects/#{project.id}") end @@ -105,11 +102,11 @@ } end - feature 'user with admin rights can see activate button on project page' do + let(:project) { create(:project, status: 'pending') } + before do login_as admin, scope: :user - project = create(:project, status: 'pending') visit("/projects/#{project.id}") end @@ -117,19 +114,45 @@ is_expected .to have_content('Activate Project') } + + feature 'and clicking the activate project button' do + before { click_on('Activate Project') } + + it 'is expected to set project status to "active"' do + expect(project.reload.status).to eq 'active' + end + + it { + is_expected + .to have_content('Project was activated') + } + end end feature 'user with admin rights can see deactivate button on project page' do + let(:project) { create(:project, status: 'active') } + before do login_as admin, scope: :user - @project = create(:project, status: 'active') - visit("/projects/#{@project.id}") - save_and_open_page + visit("/projects/#{project.id}") end it { is_expected .to have_content('Deactivate Project') } + + feature 'and clicking the activate project button' do + before { click_on('Deactivate Project') } + + it 'is expected to set project status to "active"' do + expect(project.reload.status).to eq 'pending' + end + + it { + is_expected + .to have_content('Project was deactivated') + } + end end end From a49f993a5bec1f6930bef45e63808e3ebf386600 Mon Sep 17 00:00:00 2001 From: Thomas Date: Sat, 31 Jul 2021 10:20:00 +0200 Subject: [PATCH 22/36] triggers ci --- spec/factories/projects.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index f0d565d88..6d443e0c9 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -9,7 +9,6 @@ description { 'Warp fields stabilize.' } pitch { "'I AM the greatest!' - M. Ali" } status { 'active' } - factory :project_with_tags do transient do tags { [generate(:tag), generate(:tag)] } From 9f4566423559b4361cf564f5436046cbfa70c139 Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 1 Aug 2021 08:59:49 +0200 Subject: [PATCH 23/36] fixes project edit feature --- features/projects/edit_project.feature | 33 ++-- ...epos_shows_them_correctly_in_edit_form.yml | 163 +++++++++++++++++- features/support/helpers.rb | 2 + 3 files changed, 178 insertions(+), 20 deletions(-) diff --git a/features/projects/edit_project.feature b/features/projects/edit_project.feature index 30a3913a8..a8c16e645 100644 --- a/features/projects/edit_project.feature +++ b/features/projects/edit_project.feature @@ -5,42 +5,43 @@ Feature: Edit Project I would like to update a project Background: + + Given the following users exist + | first_name | last_name | email | admin | + | Thomas | Admin | thomas@admin.com | true | Given the following projects exist: - | title | description | pitch | status | github_url | pivotaltracker_url | commit_count | - | hello world | greetings earthlings | | active | https://github.com/AgileVentures/WebsiteOne | https://www.pivotaltracker.com/s/projects/742821 | 2795 | - | hello mars | greetings aliens | | inactive | | | 2000 | - | hello jupiter | greetings jupiter folks | | active | | https://jira.atlassian.com/projects/CONFEXT | 2000 | - | hello mercury | greetings mercury folks | | inactive | | | 1900 | - | hello saturn | greetings saturn folks | My pitch... | active | | | 1900 | - | hello sun | greetings sun folks | | active | | | | - | hello venus | greetings venus folks | | active | | | | - | hello terra | greetings terra folks | | active | | | | - | hello pluto | greetings pluto folks | | inactive | | | 2000 | + | title | description | author | pitch | status | github_url | pivotaltracker_url | commit_count | + | hello world | greetings earthlings | Thomas | | active | https://github.com/AgileVentures/WebsiteOne | https://www.pivotaltracker.com/s/projects/742821 | 2795 | + | hello mars | greetings aliens | Thomas | | inactive | | | 2000 | + | hello jupiter | greetings jupiter folks | | | active | | https://jira.atlassian.com/projects/CONFEXT | 2000 | + | hello mercury | greetings mercury folks | | | inactive | | | 1900 | + | hello saturn | greetings saturn folks | | My pitch... | active | | | 1900 | + | hello sun | greetings sun folks | | | active | | | | + | hello venus | greetings venus folks | | | active | | | | + | hello terra | greetings terra folks | | | active | | | | + | hello pluto | greetings pluto folks | | | inactive | | | 2000 | And there are no videos + And I am logged in as "Thomas" Scenario: Edit page has a link to upload an image - Given I am logged in And I am on the "Edit" page for projects "hello mars" Then I should see link "imgur.com/upload" with "https://imgur.com/upload" @javascript Scenario: Existing project with multiple repos shows them correctly in edit form - Given I have logged in And that project "hello world" has an extra repository "https://github.com/AgileVentures/WebsiteOne" When I am on the "Edit" page for projects "hello world" Then I should see "GitHub url (primary)" And I should see "GitHub url (2)" Scenario: Edit page has a return link - Given I have logged in And I am on the "Edit" page for projects "hello mars" When I click "Back" Then I should be on the "Show" page for project "hello mars" @javascript Scenario: Updating a project: success - Given I have logged in And I am on the "Edit" page for project "hello mars" And I fill in "Description" with "Hello, Uranus!" And I click "Add more repos" @@ -58,7 +59,6 @@ Feature: Edit Project And I should see a link "hello mars" that connects to the "slack_channel" Scenario: Saving a project: failure - Given I have logged in And I am on the "Edit" page for project "hello mars" When I fill in "Title" with "" And I click the "Submit" button @@ -66,7 +66,6 @@ Feature: Edit Project @javascript Scenario: Update GitHub url if valid - Given I have logged in And I am on the "Edit" page for project "hello mars" And I click "Add more repos" And I fill in "GitHub url (primary)" with "https://github.com/google/instant-hangouts" @@ -76,7 +75,6 @@ Feature: Edit Project @javascript Scenario: Update Issue Tracker url if valid pivotal tracker link - Given I have logged in And I am on the "Edit" page for project "hello mars" And I click "Add more trackers" And I fill in "Issue Tracker (primary)" with "https://www.pivotaltracker.com/s/projects/853345" @@ -87,7 +85,6 @@ Feature: Edit Project @javascript Scenario: Reject GitHub url update if invalid - Given I have logged in And I am on the "Edit" page for project "hello mars" And I click "Add more repos" And I fill in "GitHub url (primary)" with "https:/github.com/google/instant-hangouts" diff --git a/features/support/fixtures/cassettes/Edit_Project/Existing_project_with_multiple_repos_shows_them_correctly_in_edit_form.yml b/features/support/fixtures/cassettes/Edit_Project/Existing_project_with_multiple_repos_shows_them_correctly_in_edit_form.yml index 40038c966..dc575ee2e 100644 --- a/features/support/fixtures/cassettes/Edit_Project/Existing_project_with_multiple_repos_shows_them_correctly_in_edit_form.yml +++ b/features/support/fixtures/cassettes/Edit_Project/Existing_project_with_multiple_repos_shows_them_correctly_in_edit_form.yml @@ -62,6 +62,165 @@ http_interactions: body: encoding: UTF-8 string: '{"noinspectlet":"crawlbot"}' - http_version: recorded_at: Wed, 05 Feb 2014 06:58:23 GMT -recorded_with: VCR 5.0.0 +- request: + method: get + uri: http://cdn.inspectlet.com/inspectlet.js?r=452166&wid=1674848404 + body: + encoding: UTF-8 + string: '' + headers: + Accept: + - "*/*" + Referer: + - http://127.0.0.1:50628/users/sign_in + User-Agent: + - Mozilla/5.0 (Macintosh; Intel Mac OS X) AppleWebKit/538.1 (KHTML, like Gecko) + PhantomJS/2.1.1 Safari/538.1 + Connection: + - close + Accept-Encoding: + - '' + Accept-Language: + - en-US,* + Host: + - cdn.inspectlet.com + response: + status: + code: 200 + message: OK + headers: + Date: + - Sun, 01 Aug 2021 06:58:56 GMT + Content-Type: + - text/javascript;charset=UTF-8 + Transfer-Encoding: + - chunked + Connection: + - close + Cache-Control: + - s-maxage=60, max-age=14400 + Via: + - 1.1 vegur + Cf-Cache-Status: + - MISS + Vary: + - Accept-Encoding + Server: + - cloudflare + Cf-Ray: + - 677d2e4ceb9f736b-CPH + Alt-Svc: + - h3-27=":443"; ma=86400, h3-28=":443"; ma=86400, h3-29=":443"; ma=86400, h3=":443"; + ma=86400 + body: + encoding: UTF-8 + string: |- + if(!window.__insp || typeof window.__insp.loaded != 'boolean'){ + + !function(e,t){"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(e,t){var n=[],r=e.document,i=n.slice,o=n.concat,s=n.push,a=n.indexOf,u={},l=u.toString,c=u.hasOwnProperty,f={},p=function(e,t){return new p.fn.init(e,t)},d=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,h=/^-ms-/,g=/-([\da-z])/gi,v=function(e,t){return t.toUpperCase()};function m(e){var t=!!e&&"length"in e&&e.length,n=p.type(e);return"function"!==n&&!p.isWindow(e)&&("array"===n||0===t||"number"==typeof t&&t>0&&t-1 in e)}p.fn=p.prototype={jquery:"2.2.4",constructor:p,selector:"",length:0,toArray:function(){return i.call(this)},get:function(e){return null!=e?e<0?this[e+this.length]:this[e]:i.call(this)},pushStack:function(e){var t=p.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e){return p.each(this,e)},map:function(e){return this.pushStack(p.map(this,function(t,n){return e.call(t,n,t)}))},slice:function(){return this.pushStack(i.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(n>=0&&n=0},isPlainObject:function(e){var t;if("object"!==p.type(e)||e.nodeType||p.isWindow(e))return!1;if(e.constructor&&!c.call(e,"constructor")&&!c.call(e.constructor.prototype||{},"isPrototypeOf"))return!1;for(t in e);return void 0===t||c.call(e,t)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?u[l.call(e)]||"object":typeof e},globalEval:function(e){var t,n=eval;(e=p.trim(e))&&(1===e.indexOf("use strict")?((t=r.createElement("script")).text=e,r.head.appendChild(t).parentNode.removeChild(t)):n(e))},camelCase:function(e){return e.replace(h,"ms-").replace(g,v)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t){var n,r=0;if(m(e))for(n=e.length;r+~]|"+R+")"+R+"*"),z=new RegExp("="+R+"*([^\\]'\"]*?)"+R+"*\\]","g"),U=new RegExp(W),V=new RegExp("^"+M+"$"),Y={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M+"|[*])"),ATTR:new RegExp("^"+I),PSEUDO:new RegExp("^"+W),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+R+"*(even|odd|(([+-]|)(\\d*)n|)"+R+"*(?:([+-]|)"+R+"*(\\d+)|))"+R+"*\\)|)","i"),bool:new RegExp("^(?:"+P+")$","i"),needsContext:new RegExp("^"+R+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+R+"*((?:-\\d)?\\d*)"+R+"*\\)|)(?=[^-]|$)","i")},G=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Q=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,Z=/[+~]/,ee=/'|\\/g,te=new RegExp("\\\\([\\da-f]{1,6}"+R+"?|("+R+")|.)","ig"),ne=function(e,t,n){var r="0x"+t-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},re=function(){p()};try{H.apply(A=O.call(w.childNodes),w.childNodes),A[w.childNodes.length].nodeType}catch(e){H={apply:A.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){for(var n=e.length,r=0;e[n++]=t[r++];);e.length=n-1}}}function ie(e,t,r,i){var o,a,l,c,f,h,m,y,T=t&&t.ownerDocument,C=t?t.nodeType:9;if(r=r||[],"string"!=typeof e||!e||1!==C&&9!==C&&11!==C)return r;if(!i&&((t?t.ownerDocument||t:w)!==d&&p(t),t=t||d,g)){if(11!==C&&(h=Q.exec(e)))if(o=h[1]){if(9===C){if(!(l=t.getElementById(o)))return r;if(l.id===o)return r.push(l),r}else if(T&&(l=T.getElementById(o))&&x(t,l)&&l.id===o)return r.push(l),r}else{if(h[2])return H.apply(r,t.getElementsByTagName(e)),r;if((o=h[3])&&n.getElementsByClassName&&t.getElementsByClassName)return H.apply(r,t.getElementsByClassName(o)),r}if(n.qsa&&!N[e+" "]&&(!v||!v.test(e))){if(1!==C)T=t,y=e;else if("object"!==t.nodeName.toLowerCase()){for((c=t.getAttribute("id"))?c=c.replace(ee,"\\$&"):t.setAttribute("id",c=b),a=(m=s(e)).length,f=V.test(c)?"#"+c:"[id='"+c+"']";a--;)m[a]=f+" "+ge(m[a]);y=m.join(","),T=Z.test(e)&&de(t.parentNode)||t}if(y)try{return H.apply(r,T.querySelectorAll(y)),r}catch(e){}finally{c===b&&t.removeAttribute("id")}}}return u(e.replace(B,"$1"),t,r,i)}function oe(){var e=[];return function t(n,i){return e.push(n+" ")>r.cacheLength&&delete t[e.shift()],t[n+" "]=i}}function se(e){return e[b]=!0,e}function ae(e){var t=d.createElement("div");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function ue(e,t){for(var n=e.split("|"),i=n.length;i--;)r.attrHandle[n[i]]=t}function le(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)for(;n=n.nextSibling;)if(n===t)return-1;return e?1:-1}function ce(e){return function(t){return"input"===t.nodeName.toLowerCase()&&t.type===e}}function fe(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function pe(e){return se(function(t){return t=+t,se(function(n,r){for(var i,o=e([],n.length,t),s=o.length;s--;)n[i=o[s]]&&(n[i]=!(r[i]=n[i]))})})}function de(e){return e&&void 0!==e.getElementsByTagName&&e}for(t in n=ie.support={},o=ie.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return!!t&&"HTML"!==t.nodeName},p=ie.setDocument=function(e){var t,i,s=e?e.ownerDocument||e:w;return s!==d&&9===s.nodeType&&s.documentElement?(h=(d=s).documentElement,g=!o(d),(i=d.defaultView)&&i.top!==i&&(i.addEventListener?i.addEventListener("unload",re,!1):i.attachEvent&&i.attachEvent("onunload",re)),n.attributes=ae(function(e){return e.className="i",!e.getAttribute("className")}),n.getElementsByTagName=ae(function(e){return e.appendChild(d.createComment("")),!e.getElementsByTagName("*").length}),n.getElementsByClassName=K.test(d.getElementsByClassName),n.getById=ae(function(e){return h.appendChild(e).id=b,!d.getElementsByName||!d.getElementsByName(b).length}),n.getById?(r.find.ID=function(e,t){if(void 0!==t.getElementById&&g){var n=t.getElementById(e);return n?[n]:[]}},r.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}}):(delete r.find.ID,r.filter.ID=function(e){var t=e.replace(te,ne);return function(e){var n=void 0!==e.getAttributeNode&&e.getAttributeNode("id");return n&&n.value===t}}),r.find.TAG=n.getElementsByTagName?function(e,t){return void 0!==t.getElementsByTagName?t.getElementsByTagName(e):n.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){for(;n=o[i++];)1===n.nodeType&&r.push(n);return r}return o},r.find.CLASS=n.getElementsByClassName&&function(e,t){if(void 0!==t.getElementsByClassName&&g)return t.getElementsByClassName(e)},m=[],v=[],(n.qsa=K.test(d.querySelectorAll))&&(ae(function(e){h.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+R+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+R+"*(?:value|"+P+")"),e.querySelectorAll("[id~="+b+"-]").length||v.push("~="),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+b+"+*").length||v.push(".#.+[+~]")}),ae(function(e){var t=d.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+R+"*[*^$|!~]?="),e.querySelectorAll(":enabled").length||v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(n.matchesSelector=K.test(y=h.matches||h.webkitMatchesSelector||h.mozMatchesSelector||h.oMatchesSelector||h.msMatchesSelector))&&ae(function(e){n.disconnectedMatch=y.call(e,"div"),y.call(e,"[s!='']:x"),m.push("!=",W)}),v=v.length&&new RegExp(v.join("|")),m=m.length&&new RegExp(m.join("|")),t=K.test(h.compareDocumentPosition),x=t||K.test(h.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)for(;t=t.parentNode;)if(t===e)return!0;return!1},S=t?function(e,t){if(e===t)return f=!0,0;var r=!e.compareDocumentPosition-!t.compareDocumentPosition;return r||(1&(r=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!n.sortDetached&&t.compareDocumentPosition(e)===r?e===d||e.ownerDocument===w&&x(w,e)?-1:t===d||t.ownerDocument===w&&x(w,t)?1:c?F(c,e)-F(c,t):0:4&r?-1:1)}:function(e,t){if(e===t)return f=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,s=[e],a=[t];if(!i||!o)return e===d?-1:t===d?1:i?-1:o?1:c?F(c,e)-F(c,t):0;if(i===o)return le(e,t);for(n=e;n=n.parentNode;)s.unshift(n);for(n=t;n=n.parentNode;)a.unshift(n);for(;s[r]===a[r];)r++;return r?le(s[r],a[r]):s[r]===w?-1:a[r]===w?1:0},d):d},ie.matches=function(e,t){return ie(e,null,null,t)},ie.matchesSelector=function(e,t){if((e.ownerDocument||e)!==d&&p(e),t=t.replace(z,"='$1']"),n.matchesSelector&&g&&!N[t+" "]&&(!m||!m.test(t))&&(!v||!v.test(t)))try{var r=y.call(e,t);if(r||n.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(e){}return ie(t,d,null,[e]).length>0},ie.contains=function(e,t){return(e.ownerDocument||e)!==d&&p(e),x(e,t)},ie.attr=function(e,t){(e.ownerDocument||e)!==d&&p(e);var i=r.attrHandle[t.toLowerCase()],o=i&&j.call(r.attrHandle,t.toLowerCase())?i(e,t,!g):void 0;return void 0!==o?o:n.attributes||!g?e.getAttribute(t):(o=e.getAttributeNode(t))&&o.specified?o.value:null},ie.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},ie.uniqueSort=function(e){var t,r=[],i=0,o=0;if(f=!n.detectDuplicates,c=!n.sortStable&&e.slice(0),e.sort(S),f){for(;t=e[o++];)t===e[o]&&(i=r.push(o));for(;i--;)e.splice(r[i],1)}return c=null,e},i=ie.getText=function(e){var t,n="",r=0,o=e.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=i(e)}else if(3===o||4===o)return e.nodeValue}else for(;t=e[r++];)n+=i(t);return n},(r=ie.selectors={cacheLength:50,createPseudo:se,match:Y,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||ie.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&ie.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return Y.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&U.test(n)&&(t=s(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=k[e+" "];return t||(t=new RegExp("(^|"+R+")"+e+"("+R+"|$)"))&&k(e,function(e){return t.test("string"==typeof e.className&&e.className||void 0!==e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=ie.attr(r,e);return null==i?"!="===t:!t||(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i.replace($," ")+" ").indexOf(n)>-1:"|="===t&&(i===n||i.slice(0,n.length+1)===n+"-"))}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),s="last"!==e.slice(-4),a="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,p,d,h,g=o!==s?"nextSibling":"previousSibling",v=t.parentNode,m=a&&t.nodeName.toLowerCase(),y=!u&&!a,x=!1;if(v){if(o){for(;g;){for(p=t;p=p[g];)if(a?p.nodeName.toLowerCase()===m:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[s?v.firstChild:v.lastChild],s&&y){for(x=(d=(l=(c=(f=(p=v)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1])&&l[2],p=d&&v.childNodes[d];p=++d&&p&&p[g]||(x=d=0)||h.pop();)if(1===p.nodeType&&++x&&p===t){c[e]=[T,d,x];break}}else if(y&&(x=d=(l=(c=(f=(p=t)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1]),!1===x)for(;(p=++d&&p&&p[g]||(x=d=0)||h.pop())&&((a?p.nodeName.toLowerCase()!==m:1!==p.nodeType)||!++x||(y&&((c=(f=p[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]=[T,x]),p!==t)););return(x-=i)===r||x%r==0&&x/r>=0}}},PSEUDO:function(e,t){var n,i=r.pseudos[e]||r.setFilters[e.toLowerCase()]||ie.error("unsupported pseudo: "+e);return i[b]?i(t):i.length>1?(n=[e,e,"",t],r.setFilters.hasOwnProperty(e.toLowerCase())?se(function(e,n){for(var r,o=i(e,t),s=o.length;s--;)e[r=F(e,o[s])]=!(n[r]=o[s])}):function(e){return i(e,0,n)}):i}},pseudos:{not:se(function(e){var t=[],n=[],r=a(e.replace(B,"$1"));return r[b]?se(function(e,t,n,i){for(var o,s=r(e,null,i,[]),a=e.length;a--;)(o=s[a])&&(e[a]=!(t[a]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),t[0]=null,!n.pop()}}),has:se(function(e){return function(t){return ie(e,t).length>0}}),contains:se(function(e){return e=e.replace(te,ne),function(t){return(t.textContent||t.innerText||i(t)).indexOf(e)>-1}}),lang:se(function(e){return V.test(e||"")||ie.error("unsupported lang: "+e),e=e.replace(te,ne).toLowerCase(),function(t){var n;do{if(n=g?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return(n=n.toLowerCase())===e||0===n.indexOf(e+"-")}while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===h},focus:function(e){return e===d.activeElement&&(!d.hasFocus||d.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return!1===e.disabled},disabled:function(e){return!0===e.disabled},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!r.pseudos.empty(e)},header:function(e){return J.test(e.nodeName)},input:function(e){return G.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:pe(function(){return[0]}),last:pe(function(e,t){return[t-1]}),eq:pe(function(e,t,n){return[n<0?n+t:n]}),even:pe(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:pe(function(e,t,n){for(var r=n<0?n+t:n;++r1?function(t,n,r){for(var i=e.length;i--;)if(!e[i](t,n,r))return!1;return!0}:e[0]}function ye(e,t,n,r,i){for(var o,s=[],a=0,u=e.length,l=null!=t;a-1&&(o[l]=!(s[l]=f))}}else m=ye(m===s?m.splice(h,m.length):m),i?i(null,s,m,u):H.apply(s,m)})}function be(e){for(var t,n,i,o=e.length,s=r.relative[e[0].type],a=s||r.relative[" "],u=s?1:0,c=ve(function(e){return e===t},a,!0),f=ve(function(e){return F(t,e)>-1},a,!0),p=[function(e,n,r){var i=!s&&(r||n!==l)||((t=n).nodeType?c(e,n,r):f(e,n,r));return t=null,i}];u1&&me(p),u>1&&ge(e.slice(0,u-1).concat({value:" "===e[u-2].type?"*":""})).replace(B,"$1"),n,u0,i=e.length>0,o=function(o,s,a,u,c){var f,h,v,m=0,y="0",x=o&&[],b=[],w=l,C=o||i&&r.find.TAG("*",c),k=T+=null==w?1:Math.random()||.1,E=C.length;for(c&&(l=s===d||s||c);y!==E&&null!=(f=C[y]);y++){if(i&&f){for(h=0,s||f.ownerDocument===d||(p(f),a=!g);v=e[h++];)if(v(f,s||d,a)){u.push(f);break}c&&(T=k)}n&&((f=!v&&f)&&m--,o&&x.push(f))}if(m+=y,n&&y!==m){for(h=0;v=t[h++];)v(x,b,s,a);if(o){if(m>0)for(;y--;)x[y]||b[y]||(b[y]=q.call(u));b=ye(b)}H.apply(u,b),c&&!o&&b.length>0&&m+t.length>1&&ie.uniqueSort(u)}return c&&(T=k,l=w),x};return n?se(o):o}(o,i))).selector=e}return a},u=ie.select=function(e,t,i,o){var u,l,c,f,p,d="function"==typeof e&&e,h=!o&&s(e=d.selector||e);if(i=i||[],1===h.length){if((l=h[0]=h[0].slice(0)).length>2&&"ID"===(c=l[0]).type&&n.getById&&9===t.nodeType&&g&&r.relative[l[1].type]){if(!(t=(r.find.ID(c.matches[0].replace(te,ne),t)||[])[0]))return i;d&&(t=t.parentNode),e=e.slice(l.shift().value.length)}for(u=Y.needsContext.test(e)?0:l.length;u--&&(c=l[u],!r.relative[f=c.type]);)if((p=r.find[f])&&(o=p(c.matches[0].replace(te,ne),Z.test(l[0].type)&&de(t.parentNode)||t))){if(l.splice(u,1),!(e=o.length&&ge(l)))return H.apply(i,o),i;break}}return(d||a(e,h))(o,t,!g,i,!t||Z.test(e)&&de(t.parentNode)||t),i},n.sortStable=b.split("").sort(S).join("")===b,n.detectDuplicates=!!f,p(),n.sortDetached=ae(function(e){return 1&e.compareDocumentPosition(d.createElement("div"))}),ae(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||ue("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),n.attributes&&ae(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||ue("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),ae(function(e){return null==e.getAttribute("disabled")})||ue(P,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),ie}(e);p.find=y,p.expr=y.selectors,p.expr[":"]=p.expr.pseudos,p.uniqueSort=p.unique=y.uniqueSort,p.text=y.getText,p.isXMLDoc=y.isXML,p.contains=y.contains;var x=function(e,t,n){for(var r=[],i=void 0!==n;(e=e[t])&&9!==e.nodeType;)if(1===e.nodeType){if(i&&p(e).is(n))break;r.push(e)}return r},b=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},w=p.expr.match.needsContext,T=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,C=/^.[^:#\[\.,]*$/;function k(e,t,n){if(p.isFunction(t))return p.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return p.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(C.test(t))return p.filter(t,e,n);t=p.filter(t,e)}return p.grep(e,function(e){return a.call(t,e)>-1!==n})}p.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?p.find.matchesSelector(r,e)?[r]:[]:p.find.matches(e,p.grep(t,function(e){return 1===e.nodeType}))},p.fn.extend({find:function(e){var t,n=this.length,r=[],i=this;if("string"!=typeof e)return this.pushStack(p(e).filter(function(){for(t=0;t1?p.unique(r):r)).selector=this.selector?this.selector+" "+e:e,r},filter:function(e){return this.pushStack(k(this,e||[],!1))},not:function(e){return this.pushStack(k(this,e||[],!0))},is:function(e){return!!k(this,"string"==typeof e&&w.test(e)?p(e):e||[],!1).length}});var E,N=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/;(p.fn.init=function(e,t,n){var i,o;if(!e)return this;if(n=n||E,"string"==typeof e){if(!(i="<"===e[0]&&">"===e[e.length-1]&&e.length>=3?[null,e,null]:N.exec(e))||!i[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(i[1]){if(t=t instanceof p?t[0]:t,p.merge(this,p.parseHTML(i[1],t&&t.nodeType?t.ownerDocument||t:r,!0)),T.test(i[1])&&p.isPlainObject(t))for(i in t)p.isFunction(this[i])?this[i](t[i]):this.attr(i,t[i]);return this}return(o=r.getElementById(i[2]))&&o.parentNode&&(this.length=1,this[0]=o),this.context=r,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):p.isFunction(e)?void 0!==n.ready?n.ready(e):e(p):(void 0!==e.selector&&(this.selector=e.selector,this.context=e.context),p.makeArray(e,this))}).prototype=p.fn,E=p(r);var S=/^(?:parents|prev(?:Until|All))/,D={children:!0,contents:!0,next:!0,prev:!0};function j(e,t){for(;(e=e[t])&&1!==e.nodeType;);return e}p.fn.extend({has:function(e){var t=p(e,this),n=t.length;return this.filter(function(){for(var e=0;e-1:1===n.nodeType&&p.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(o.length>1?p.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?a.call(p(e),this[0]):a.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(p.uniqueSort(p.merge(this.get(),p(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),p.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x(e,"parentNode")},parentsUntil:function(e,t,n){return x(e,"parentNode",n)},next:function(e){return j(e,"nextSibling")},prev:function(e){return j(e,"previousSibling")},nextAll:function(e){return x(e,"nextSibling")},prevAll:function(e){return x(e,"previousSibling")},nextUntil:function(e,t,n){return x(e,"nextSibling",n)},prevUntil:function(e,t,n){return x(e,"previousSibling",n)},siblings:function(e){return b((e.parentNode||{}).firstChild,e)},children:function(e){return b(e.firstChild)},contents:function(e){return e.contentDocument||p.merge([],e.childNodes)}},function(e,t){p.fn[e]=function(n,r){var i=p.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=p.filter(r,i)),this.length>1&&(D[e]||p.uniqueSort(i),S.test(e)&&i.reverse()),this.pushStack(i)}});var A,q=/\S+/g;function L(){r.removeEventListener("DOMContentLoaded",L),e.removeEventListener("load",L),p.ready()}p.Callbacks=function(e){e="string"==typeof e?function(e){var t={};return p.each(e.match(q)||[],function(e,n){t[n]=!0}),t}(e):p.extend({},e);var t,n,r,i,o=[],s=[],a=-1,u=function(){for(i=e.once,r=t=!0;s.length;a=-1)for(n=s.shift();++a-1;)o.splice(n,1),n<=a&&a--}),this},has:function(e){return e?p.inArray(e,o)>-1:o.length>0},empty:function(){return o&&(o=[]),this},disable:function(){return i=s=[],o=n="",this},disabled:function(){return!o},lock:function(){return i=s=[],n||(o=n=""),this},locked:function(){return!!i},fireWith:function(e,n){return i||(n=[e,(n=n||[]).slice?n.slice():n],s.push(n),t||u()),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!r}};return l},p.extend({Deferred:function(e){var t=[["resolve","done",p.Callbacks("once memory"),"resolved"],["reject","fail",p.Callbacks("once memory"),"rejected"],["notify","progress",p.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return p.Deferred(function(n){p.each(t,function(t,o){var s=p.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&p.isFunction(e.promise)?e.promise().progress(n.notify).done(n.resolve).fail(n.reject):n[o[0]+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?p.extend(e,r):r}},i={};return r.pipe=r.then,p.each(t,function(e,o){var s=o[2],a=o[3];r[o[1]]=s.add,a&&s.add(function(){n=a},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=s.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t,n,r,o=0,s=i.call(arguments),a=s.length,u=1!==a||e&&p.isFunction(e.promise)?a:0,l=1===u?e:p.Deferred(),c=function(e,n,r){return function(o){n[e]=this,r[e]=arguments.length>1?i.call(arguments):o,r===t?l.notifyWith(n,r):--u||l.resolveWith(n,r)}};if(a>1)for(t=new Array(a),n=new Array(a),r=new Array(a);o0||(A.resolveWith(r,[p]),p.fn.triggerHandler&&(p(r).triggerHandler("ready"),p(r).off("ready"))))}}),p.ready.promise=function(t){return A||(A=p.Deferred(),"complete"===r.readyState||"loading"!==r.readyState&&!r.documentElement.doScroll?e.setTimeout(p.ready):(r.addEventListener("DOMContentLoaded",L),e.addEventListener("load",L))),A.promise(t)},p.ready.promise();var H=function(e,t,n,r,i,o,s){var a=0,u=e.length,l=null==n;if("object"===p.type(n))for(a in i=!0,n)H(e,t,a,n[a],!0,o,s);else if(void 0!==r&&(i=!0,p.isFunction(r)||(s=!0),l&&(s?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(p(e),n)})),t))for(;a-1&&void 0!==n&&R.set(this,e,t)})},null,t,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){R.remove(this,e)})}}),p.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=P.get(e,t),n&&(!r||p.isArray(n)?r=P.access(e,t,p.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=p.queue(e,t),r=n.length,i=n.shift(),o=p._queueHooks(e,t);"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,function(){p.dequeue(e,t)},o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return P.get(e,n)||P.access(e,n,{empty:p.Callbacks("once memory").add(function(){P.remove(e,[t+"queue",n])})})}}),p.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),arguments.length",""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function J(e,t){var n=void 0!==e.getElementsByTagName?e.getElementsByTagName(t||"*"):void 0!==e.querySelectorAll?e.querySelectorAll(t||"*"):[];return void 0===t||t&&p.nodeName(e,t)?p.merge([e],n):n}function K(e,t){for(var n=0,r=e.length;n-1)i&&i.push(o);else if(l=p.contains(o.ownerDocument,o),s=J(f.appendChild(o),"script"),l&&K(s),n)for(c=0;o=s[c++];)Y.test(o.type||"")&&n.push(o);return f}Q=r.createDocumentFragment().appendChild(r.createElement("div")),(Z=r.createElement("input")).setAttribute("type","radio"),Z.setAttribute("checked","checked"),Z.setAttribute("name","t"),Q.appendChild(Z),f.checkClone=Q.cloneNode(!0).cloneNode(!0).lastChild.checked,Q.innerHTML="",f.noCloneChecked=!!Q.cloneNode(!0).lastChild.defaultValue;var ne=/^key/,re=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,ie=/^([^.]*)(?:\.(.+)|)/;function oe(){return!0}function se(){return!1}function ae(){try{return r.activeElement}catch(e){}}function ue(e,t,n,r,i,o){var s,a;if("object"==typeof t){for(a in"string"!=typeof n&&(r=r||n,n=void 0),t)ue(e,a,n,r,t[a],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=se;else if(!i)return e;return 1===o&&(s=i,(i=function(e){return p().off(e),s.apply(this,arguments)}).guid=s.guid||(s.guid=p.guid++)),e.each(function(){p.event.add(this,t,i,r,n)})}p.event={global:{},add:function(e,t,n,r,i){var o,s,a,u,l,c,f,d,h,g,v,m=P.get(e);if(m)for(n.handler&&(n=(o=n).handler,i=o.selector),n.guid||(n.guid=p.guid++),(u=m.events)||(u=m.events={}),(s=m.handle)||(s=m.handle=function(t){return void 0!==p&&p.event.triggered!==t.type?p.event.dispatch.apply(e,arguments):void 0}),l=(t=(t||"").match(q)||[""]).length;l--;)h=v=(a=ie.exec(t[l])||[])[1],g=(a[2]||"").split(".").sort(),h&&(f=p.event.special[h]||{},h=(i?f.delegateType:f.bindType)||h,f=p.event.special[h]||{},c=p.extend({type:h,origType:v,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&p.expr.match.needsContext.test(i),namespace:g.join(".")},o),(d=u[h])||((d=u[h]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(e,r,g,s)||e.addEventListener&&e.addEventListener(h,s)),f.add&&(f.add.call(e,c),c.handler.guid||(c.handler.guid=n.guid)),i?d.splice(d.delegateCount++,0,c):d.push(c),p.event.global[h]=!0)},remove:function(e,t,n,r,i){var o,s,a,u,l,c,f,d,h,g,v,m=P.hasData(e)&&P.get(e);if(m&&(u=m.events)){for(l=(t=(t||"").match(q)||[""]).length;l--;)if(h=v=(a=ie.exec(t[l])||[])[1],g=(a[2]||"").split(".").sort(),h){for(f=p.event.special[h]||{},d=u[h=(r?f.delegateType:f.bindType)||h]||[],a=a[2]&&new RegExp("(^|\\.)"+g.join("\\.(?:.*\\.|)")+"(\\.|$)"),s=o=d.length;o--;)c=d[o],!i&&v!==c.origType||n&&n.guid!==c.guid||a&&!a.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(d.splice(o,1),c.selector&&d.delegateCount--,f.remove&&f.remove.call(e,c));s&&!d.length&&(f.teardown&&!1!==f.teardown.call(e,g,m.handle)||p.removeEvent(e,h,m.handle),delete u[h])}else for(h in u)p.event.remove(e,h+t[l],n,r,!0);p.isEmptyObject(u)&&P.remove(e,"handle events")}},dispatch:function(e){e=p.event.fix(e);var t,n,r,o,s,a,u=i.call(arguments),l=(P.get(this,"events")||{})[e.type]||[],c=p.event.special[e.type]||{};if(u[0]=e,e.delegateTarget=this,!c.preDispatch||!1!==c.preDispatch.call(this,e)){for(a=p.event.handlers.call(this,e,l),t=0;(o=a[t++])&&!e.isPropagationStopped();)for(e.currentTarget=o.elem,n=0;(s=o.handlers[n++])&&!e.isImmediatePropagationStopped();)e.rnamespace&&!e.rnamespace.test(s.namespace)||(e.handleObj=s,e.data=s.data,void 0!==(r=((p.event.special[s.origType]||{}).handle||s.handler).apply(o.elem,u))&&!1===(e.result=r)&&(e.preventDefault(),e.stopPropagation()));return c.postDispatch&&c.postDispatch.call(this,e),e.result}},handlers:function(e,t){var n,r,i,o,s=[],a=t.delegateCount,u=e.target;if(a&&u.nodeType&&("click"!==e.type||isNaN(e.button)||e.button<1))for(;u!==this;u=u.parentNode||this)if(1===u.nodeType&&(!0!==u.disabled||"click"!==e.type)){for(r=[],n=0;n-1:p.find(i,this,null,[u]).length),r[i]&&r.push(o);r.length&&s.push({elem:u,handlers:r})}return a]*)\/>/gi,ce=/\s*$/g;function he(e,t){return p.nodeName(e,"table")&&p.nodeName(11!==t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function ge(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function ve(e){var t=pe.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function me(e,t){var n,r,i,o,s,a,u,l;if(1===t.nodeType){if(P.hasData(e)&&(o=P.access(e),s=P.set(t,o),l=o.events))for(i in delete s.handle,s.events={},l)for(n=0,r=l[i].length;n1&&"string"==typeof v&&!f.checkClone&&fe.test(v))return e.each(function(i){var o=e.eq(i);m&&(t[0]=v.call(this,i,o.html())),ye(o,t,n,r)});if(h&&(s=(i=te(t,e[0].ownerDocument,!1,e,r)).firstChild,1===i.childNodes.length&&(i=s),s||r)){for(u=(a=p.map(J(i,"script"),ge)).length;d")},clone:function(e,t,n){var r,i,o,s,a,u,l,c=e.cloneNode(!0),d=p.contains(e.ownerDocument,e);if(!(f.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||p.isXMLDoc(e)))for(s=J(c),r=0,i=(o=J(e)).length;r0&&K(s,!d&&J(e,"script")),c},cleanData:function(e){for(var t,n,r,i=p.event.special,o=0;void 0!==(n=e[o]);o++)if(O(n)){if(t=n[P.expando]){if(t.events)for(r in t.events)i[r]?p.event.remove(n,r):p.removeEvent(n,r,t.handle);n[P.expando]=void 0}n[R.expando]&&(n[R.expando]=void 0)}}}),p.fn.extend({domManip:ye,detach:function(e){return xe(this,e,!0)},remove:function(e){return xe(this,e)},text:function(e){return H(this,function(e){return void 0===e?p.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return ye(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||he(this,e).appendChild(e)})},prepend:function(){return ye(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=he(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return ye(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return ye(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(p.cleanData(J(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return p.clone(this,e,t)})},html:function(e){return H(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!ce.test(e)&&!G[(V.exec(e)||["",""])[1].toLowerCase()]){e=p.htmlPrefilter(e);try{for(;n")).appendTo(t.documentElement))[0].contentDocument).write(),t.close(),n=Te(e,t),be.detach()),we[e]=n),n}var ke=/^margin/,Ee=new RegExp("^("+$+")(?!px)[a-z%]+$","i"),Ne=function(t){var n=t.ownerDocument.defaultView;return n&&n.opener||(n=e),n.getComputedStyle(t)},Se=function(e,t,n,r){var i,o,s={};for(o in t)s[o]=e.style[o],e.style[o]=t[o];for(o in i=n.apply(e,r||[]),t)e.style[o]=s[o];return i},De=r.documentElement;function je(e,t,n){var r,i,o,s,a=e.style;return""!==(s=(n=n||Ne(e))?n.getPropertyValue(t)||n[t]:void 0)&&void 0!==s||p.contains(e.ownerDocument,e)||(s=p.style(e,t)),n&&!f.pixelMarginRight()&&Ee.test(s)&&ke.test(t)&&(r=a.width,i=a.minWidth,o=a.maxWidth,a.minWidth=a.maxWidth=a.width=s,s=n.width,a.width=r,a.minWidth=i,a.maxWidth=o),void 0!==s?s+"":s}function Ae(e,t){return{get:function(){if(!e())return(this.get=t).apply(this,arguments);delete this.get}}}!function(){var t,n,i,o,s=r.createElement("div"),a=r.createElement("div");function u(){a.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;position:relative;display:block;margin:auto;border:1px;padding:1px;top:1%;width:50%",a.innerHTML="",De.appendChild(s);var r=e.getComputedStyle(a);t="1%"!==r.top,o="2px"===r.marginLeft,n="4px"===r.width,a.style.marginRight="50%",i="4px"===r.marginRight,De.removeChild(s)}a.style&&(a.style.backgroundClip="content-box",a.cloneNode(!0).style.backgroundClip="",f.clearCloneStyle="content-box"===a.style.backgroundClip,s.style.cssText="border:0;width:8px;height:0;top:0;left:-9999px;padding:0;margin-top:1px;position:absolute",s.appendChild(a),p.extend(f,{pixelPosition:function(){return u(),t},boxSizingReliable:function(){return null==n&&u(),n},pixelMarginRight:function(){return null==n&&u(),i},reliableMarginLeft:function(){return null==n&&u(),o},reliableMarginRight:function(){var t,n=a.appendChild(r.createElement("div"));return n.style.cssText=a.style.cssText="-webkit-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:0",n.style.marginRight=n.style.width="0",a.style.width="1px",De.appendChild(s),t=!parseFloat(e.getComputedStyle(n).marginRight),De.removeChild(s),a.removeChild(n),t}}))}();var qe=/^(none|table(?!-c[ea]).+)/,Le={position:"absolute",visibility:"hidden",display:"block"},He={letterSpacing:"0",fontWeight:"400"},Oe=["Webkit","O","Moz","ms"],Fe=r.createElement("div").style;function Pe(e){if(e in Fe)return e;for(var t=e[0].toUpperCase()+e.slice(1),n=Oe.length;n--;)if((e=Oe[n]+t)in Fe)return e}function Re(e,t,n){var r=B.exec(t);return r?Math.max(0,r[2]-(n||0))+(r[3]||"px"):t}function Me(e,t,n,r,i){for(var o=n===(r?"border":"content")?4:"width"===t?1:0,s=0;o<4;o+=2)"margin"===n&&(s+=p.css(e,n+_[o],!0,i)),r?("content"===n&&(s-=p.css(e,"padding"+_[o],!0,i)),"margin"!==n&&(s-=p.css(e,"border"+_[o]+"Width",!0,i))):(s+=p.css(e,"padding"+_[o],!0,i),"padding"!==n&&(s+=p.css(e,"border"+_[o]+"Width",!0,i)));return s}function Ie(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Ne(e),s="border-box"===p.css(e,"boxSizing",!1,o);if(i<=0||null==i){if(((i=je(e,t,o))<0||null==i)&&(i=e.style[t]),Ee.test(i))return i;r=s&&(f.boxSizingReliable()||i===e.style[t]),i=parseFloat(i)||0}return i+Me(e,t,n||(s?"border":"content"),r,o)+"px"}function We(e,t){for(var n,r,i,o=[],s=0,a=e.length;s1)},show:function(){return We(this,!0)},hide:function(){return We(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){X(this)?p(this).show():p(this).hide()})}}),p.Tween=$e,$e.prototype={constructor:$e,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||p.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(p.cssNumber[n]?"":"px")},cur:function(){var e=$e.propHooks[this.prop];return e&&e.get?e.get(this):$e.propHooks._default.get(this)},run:function(e){var t,n=$e.propHooks[this.prop];return this.options.duration?this.pos=t=p.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):$e.propHooks._default.set(this),this}},$e.prototype.init.prototype=$e.prototype,$e.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=p.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){p.fx.step[e.prop]?p.fx.step[e.prop](e):1!==e.elem.nodeType||null==e.elem.style[p.cssProps[e.prop]]&&!p.cssHooks[e.prop]?e.elem[e.prop]=e.now:p.style(e.elem,e.prop,e.now+e.unit)}}},$e.propHooks.scrollTop=$e.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},p.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},p.fx=$e.prototype.init,p.fx.step={};var Be,_e,Xe=/^(?:toggle|show|hide)$/,ze=/queueHooks$/;function Ue(){return e.setTimeout(function(){Be=void 0}),Be=p.now()}function Ve(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i["margin"+(n=_[r])]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function Ye(e,t,n){for(var r,i=(Ge.tweeners[t]||[]).concat(Ge.tweeners["*"]),o=0,s=i.length;o1)},removeAttr:function(e){return this.each(function(){p.removeAttr(this,e)})}}),p.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return void 0===e.getAttribute?p.prop(e,t,n):(1===o&&p.isXMLDoc(e)||(t=t.toLowerCase(),i=p.attrHooks[t]||(p.expr.match.bool.test(t)?Je:void 0)),void 0!==n?null===n?void p.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=p.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!f.radioValue&&"radio"===t&&p.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(q);if(o&&1===e.nodeType)for(;n=o[i++];)r=p.propFix[n]||n,p.expr.match.bool.test(n)&&(e[r]=!1),e.removeAttribute(n)}}),Je={set:function(e,t,n){return!1===t?p.removeAttr(e,n):e.setAttribute(n,n),n}},p.each(p.expr.match.bool.source.match(/\w+/g),function(e,t){var n=Ke[t]||p.find.attr;Ke[t]=function(e,t,r){var i,o;return r||(o=Ke[t],Ke[t]=i,i=null!=n(e,t,r)?t.toLowerCase():null,Ke[t]=o),i}});var Qe=/^(?:input|select|textarea|button)$/i,Ze=/^(?:a|area)$/i;p.fn.extend({prop:function(e,t){return H(this,p.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[p.propFix[e]||e]})}}),p.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&p.isXMLDoc(e)||(t=p.propFix[t]||t,i=p.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=p.find.attr(e,"tabindex");return t?parseInt(t,10):Qe.test(e.nodeName)||Ze.test(e.nodeName)&&e.href?0:-1}}},propFix:{for:"htmlFor",class:"className"}}),f.optSelected||(p.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),p.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){p.propFix[this.toLowerCase()]=this});var et=/[\t\r\n\f]/g;function tt(e){return e.getAttribute&&e.getAttribute("class")||""}p.fn.extend({addClass:function(e){var t,n,r,i,o,s,a,u=0;if(p.isFunction(e))return this.each(function(t){p(this).addClass(e.call(this,t,tt(this)))});if("string"==typeof e&&e)for(t=e.match(q)||[];n=this[u++];)if(i=tt(n),r=1===n.nodeType&&(" "+i+" ").replace(et," ")){for(s=0;o=t[s++];)r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(a=p.trim(r))&&n.setAttribute("class",a)}return this},removeClass:function(e){var t,n,r,i,o,s,a,u=0;if(p.isFunction(e))return this.each(function(t){p(this).removeClass(e.call(this,t,tt(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof e&&e)for(t=e.match(q)||[];n=this[u++];)if(i=tt(n),r=1===n.nodeType&&(" "+i+" ").replace(et," ")){for(s=0;o=t[s++];)for(;r.indexOf(" "+o+" ")>-1;)r=r.replace(" "+o+" "," ");i!==(a=p.trim(r))&&n.setAttribute("class",a)}return this},toggleClass:function(e,t){var n=typeof e;return"boolean"==typeof t&&"string"===n?t?this.addClass(e):this.removeClass(e):p.isFunction(e)?this.each(function(n){p(this).toggleClass(e.call(this,n,tt(this),t),t)}):this.each(function(){var t,r,i,o;if("string"===n)for(r=0,i=p(this),o=e.match(q)||[];t=o[r++];)i.hasClass(t)?i.removeClass(t):i.addClass(t);else void 0!==e&&"boolean"!==n||((t=tt(this))&&P.set(this,"__className__",t),this.setAttribute&&this.setAttribute("class",t||!1===e?"":P.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;for(t=" "+e+" ";n=this[r++];)if(1===n.nodeType&&(" "+tt(n)+" ").replace(et," ").indexOf(t)>-1)return!0;return!1}});var nt=/\r/g,rt=/[\x20\t\r\n\f]+/g;p.fn.extend({val:function(e){var t,n,r,i=this[0];return arguments.length?(r=p.isFunction(e),this.each(function(n){var i;1===this.nodeType&&(null==(i=r?e.call(this,n,p(this).val()):e)?i="":"number"==typeof i?i+="":p.isArray(i)&&(i=p.map(i,function(e){return null==e?"":e+""})),(t=p.valHooks[this.type]||p.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))})):i?(t=p.valHooks[i.type]||p.valHooks[i.nodeName.toLowerCase()])&&"get"in t&&void 0!==(n=t.get(i,"value"))?n:"string"==typeof(n=i.value)?n.replace(nt,""):null==n?"":n:void 0}}),p.extend({valHooks:{option:{get:function(e){var t=p.find.attr(e,"value");return null!=t?t:p.trim(p.text(e)).replace(rt," ")}},select:{get:function(e){for(var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||i<0,s=o?null:[],a=o?i+1:r.length,u=i<0?a:o?i:0;u-1)&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),p.each(["radio","checkbox"],function(){p.valHooks[this]={set:function(e,t){if(p.isArray(t))return e.checked=p.inArray(p(e).val(),t)>-1}},f.checkOn||(p.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var it=/^(?:focusinfocus|focusoutblur)$/;p.extend(p.event,{trigger:function(t,n,i,o){var s,a,u,l,f,d,h,g=[i||r],v=c.call(t,"type")?t.type:t,m=c.call(t,"namespace")?t.namespace.split("."):[];if(a=u=i=i||r,3!==i.nodeType&&8!==i.nodeType&&!it.test(v+p.event.triggered)&&(v.indexOf(".")>-1&&(m=v.split("."),v=m.shift(),m.sort()),f=v.indexOf(":")<0&&"on"+v,(t=t[p.expando]?t:new p.Event(v,"object"==typeof t&&t)).isTrigger=o?2:3,t.namespace=m.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=i),n=null==n?[t]:p.makeArray(n,[t]),h=p.event.special[v]||{},o||!h.trigger||!1!==h.trigger.apply(i,n))){if(!o&&!h.noBubble&&!p.isWindow(i)){for(l=h.delegateType||v,it.test(l+v)||(a=a.parentNode);a;a=a.parentNode)g.push(a),u=a;u===(i.ownerDocument||r)&&g.push(u.defaultView||u.parentWindow||e)}for(s=0;(a=g[s++])&&!t.isPropagationStopped();)t.type=s>1?l:h.bindType||v,(d=(P.get(a,"events")||{})[t.type]&&P.get(a,"handle"))&&d.apply(a,n),(d=f&&a[f])&&d.apply&&O(a)&&(t.result=d.apply(a,n),!1===t.result&&t.preventDefault());return t.type=v,o||t.isDefaultPrevented()||h._default&&!1!==h._default.apply(g.pop(),n)||!O(i)||f&&p.isFunction(i[v])&&!p.isWindow(i)&&((u=i[f])&&(i[f]=null),p.event.triggered=v,i[v](),p.event.triggered=void 0,u&&(i[f]=u)),t.result}},simulate:function(e,t,n){var r=p.extend(new p.Event,n,{type:e,isSimulated:!0});p.event.trigger(r,null,t)}}),p.fn.extend({trigger:function(e,t){return this.each(function(){p.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return p.event.trigger(e,t,n,!0)}}),p.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(e,t){p.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),p.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),f.focusin="onfocusin"in e,f.focusin||p.each({focus:"focusin",blur:"focusout"},function(e,t){var n=function(e){p.event.simulate(t,e.target,p.event.fix(e))};p.event.special[t]={setup:function(){var r=this.ownerDocument||this,i=P.access(r,t);i||r.addEventListener(e,n,!0),P.access(r,t,(i||0)+1)},teardown:function(){var r=this.ownerDocument||this,i=P.access(r,t)-1;i?P.access(r,t,i):(r.removeEventListener(e,n,!0),P.remove(r,t))}}});var ot=e.location,st=p.now(),at=/\?/;p.parseJSON=function(e){return JSON.parse(e+"")},p.parseXML=function(t){var n;if(!t||"string"!=typeof t)return null;try{n=(new e.DOMParser).parseFromString(t,"text/xml")}catch(e){n=void 0}return n&&!n.getElementsByTagName("parsererror").length||p.error("Invalid XML: "+t),n};var ut=/#.*$/,lt=/([?&])_=[^&]*/,ct=/^(.*?):[ \t]*([^\r\n]*)$/gm,ft=/^(?:GET|HEAD)$/,pt=/^\/\//,dt={},ht={},gt="*/".concat("*"),vt=r.createElement("a");function mt(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(q)||[];if(p.isFunction(n))for(;r=o[i++];)"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function yt(e,t,n,r){var i={},o=e===ht;function s(a){var u;return i[a]=!0,p.each(e[a]||[],function(e,a){var l=a(t,n,r);return"string"!=typeof l||o||i[l]?o?!(u=l):void 0:(t.dataTypes.unshift(l),s(l),!1)}),u}return s(t.dataTypes[0])||!i["*"]&&s("*")}function xt(e,t){var n,r,i=p.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&p.extend(!0,e,r),e}vt.href=ot.href,p.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:ot.href,type:"GET",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(ot.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":gt,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":p.parseJSON,"text xml":p.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?xt(xt(e,p.ajaxSettings),t):xt(p.ajaxSettings,e)},ajaxPrefilter:mt(dt),ajaxTransport:mt(ht),ajax:function(t,n){"object"==typeof t&&(n=t,t=void 0),n=n||{};var i,o,s,a,u,l,c,f,d=p.ajaxSetup({},n),h=d.context||d,g=d.context&&(h.nodeType||h.jquery)?p(h):p.event,v=p.Deferred(),m=p.Callbacks("once memory"),y=d.statusCode||{},x={},b={},w=0,T="canceled",C={readyState:0,getResponseHeader:function(e){var t;if(2===w){if(!a)for(a={};t=ct.exec(s);)a[t[1].toLowerCase()]=t[2];t=a[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return 2===w?s:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return w||(e=b[n]=b[n]||e,x[e]=t),this},overrideMimeType:function(e){return w||(d.mimeType=e),this},statusCode:function(e){var t;if(e)if(w<2)for(t in e)y[t]=[y[t],e[t]];else C.always(e[C.status]);return this},abort:function(e){var t=e||T;return i&&i.abort(t),k(0,t),this}};if(v.promise(C).complete=m.add,C.success=C.done,C.error=C.fail,d.url=((t||d.url||ot.href)+"").replace(ut,"").replace(pt,ot.protocol+"//"),d.type=n.method||n.type||d.method||d.type,d.dataTypes=p.trim(d.dataType||"*").toLowerCase().match(q)||[""],null==d.crossDomain){l=r.createElement("a");try{l.href=d.url,l.href=l.href,d.crossDomain=vt.protocol+"//"+vt.host!=l.protocol+"//"+l.host}catch(e){d.crossDomain=!0}}if(d.data&&d.processData&&"string"!=typeof d.data&&(d.data=p.param(d.data,d.traditional)),yt(dt,d,n,C),2===w)return C;for(f in(c=p.event&&d.global)&&0==p.active++&&p.event.trigger("ajaxStart"),d.type=d.type.toUpperCase(),d.hasContent=!ft.test(d.type),o=d.url,d.hasContent||(d.data&&(o=d.url+=(at.test(o)?"&":"?")+d.data,delete d.data),!1===d.cache&&(d.url=lt.test(o)?o.replace(lt,"$1_="+st++):o+(at.test(o)?"&":"?")+"_="+st++)),d.ifModified&&(p.lastModified[o]&&C.setRequestHeader("If-Modified-Since",p.lastModified[o]),p.etag[o]&&C.setRequestHeader("If-None-Match",p.etag[o])),(d.data&&d.hasContent&&!1!==d.contentType||n.contentType)&&C.setRequestHeader("Content-Type",d.contentType),C.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+("*"!==d.dataTypes[0]?", "+gt+"; q=0.01":""):d.accepts["*"]),d.headers)C.setRequestHeader(f,d.headers[f]);if(d.beforeSend&&(!1===d.beforeSend.call(h,C,d)||2===w))return C.abort();for(f in T="abort",{success:1,error:1,complete:1})C[f](d[f]);if(i=yt(ht,d,n,C)){if(C.readyState=1,c&&g.trigger("ajaxSend",[C,d]),2===w)return C;d.async&&d.timeout>0&&(u=e.setTimeout(function(){C.abort("timeout")},d.timeout));try{w=1,i.send(x,k)}catch(e){if(!(w<2))throw e;k(-1,e)}}else k(-1,"No Transport");function k(t,n,r,a){var l,f,x,b,T,k=n;2!==w&&(w=2,u&&e.clearTimeout(u),i=void 0,s=a||"",C.readyState=t>0?4:0,l=t>=200&&t<300||304===t,r&&(b=function(e,t,n){for(var r,i,o,s,a=e.contents,u=e.dataTypes;"*"===u[0];)u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in a)if(a[i]&&a[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}s||(s=i)}o=o||s}if(o)return o!==u[0]&&u.unshift(o),n[o]}(d,C,r)),b=function(e,t,n,r){var i,o,s,a,u,l={},c=e.dataTypes.slice();if(c[1])for(s in e.converters)l[s.toLowerCase()]=e.converters[s];for(o=c.shift();o;)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(!(s=l[u+" "+o]||l["* "+o]))for(i in l)if((a=i.split(" "))[1]===o&&(s=l[u+" "+a[0]]||l["* "+a[0]])){!0===s?s=l[i]:!0!==l[i]&&(o=a[0],c.unshift(a[1]));break}if(!0!==s)if(s&&e.throws)t=s(t);else try{t=s(t)}catch(e){return{state:"parsererror",error:s?e:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}(d,b,C,l),l?(d.ifModified&&((T=C.getResponseHeader("Last-Modified"))&&(p.lastModified[o]=T),(T=C.getResponseHeader("etag"))&&(p.etag[o]=T)),204===t||"HEAD"===d.type?k="nocontent":304===t?k="notmodified":(k=b.state,f=b.data,l=!(x=b.error))):(x=k,!t&&k||(k="error",t<0&&(t=0))),C.status=t,C.statusText=(n||k)+"",l?v.resolveWith(h,[f,k,C]):v.rejectWith(h,[C,k,x]),C.statusCode(y),y=void 0,c&&g.trigger(l?"ajaxSuccess":"ajaxError",[C,d,l?f:x]),m.fireWith(h,[C,k]),c&&(g.trigger("ajaxComplete",[C,d]),--p.active||p.event.trigger("ajaxStop")))}return C},getJSON:function(e,t,n){return p.get(e,t,n,"json")},getScript:function(e,t){return p.get(e,void 0,t,"script")}}),p.each(["get","post"],function(e,t){p[t]=function(e,n,r,i){return p.isFunction(n)&&(i=i||r,r=n,n=void 0),p.ajax(p.extend({url:e,type:t,dataType:i,data:n,success:r},p.isPlainObject(e)&&e))}}),p._evalUrl=function(e){return p.ajax({url:e,type:"GET",dataType:"script",async:!1,global:!1,throws:!0})},p.fn.extend({wrapAll:function(e){var t;return p.isFunction(e)?this.each(function(t){p(this).wrapAll(e.call(this,t))}):(this[0]&&(t=p(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){for(var e=this;e.firstElementChild;)e=e.firstElementChild;return e}).append(this)),this)},wrapInner:function(e){return p.isFunction(e)?this.each(function(t){p(this).wrapInner(e.call(this,t))}):this.each(function(){var t=p(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=p.isFunction(e);return this.each(function(n){p(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){p.nodeName(this,"body")||p(this).replaceWith(this.childNodes)}).end()}}),p.expr.filters.hidden=function(e){return!p.expr.filters.visible(e)},p.expr.filters.visible=function(e){return e.offsetWidth>0||e.offsetHeight>0||e.getClientRects().length>0};var bt=/%20/g,wt=/\[\]$/,Tt=/\r?\n/g,Ct=/^(?:submit|button|image|reset|file)$/i,kt=/^(?:input|select|textarea|keygen)/i;function Et(e,t,n,r){var i;if(p.isArray(t))p.each(t,function(t,i){n||wt.test(e)?r(e,i):Et(e+"["+("object"==typeof i&&null!=i?t:"")+"]",i,n,r)});else if(n||"object"!==p.type(t))r(e,t);else for(i in t)Et(e+"["+i+"]",t[i],n,r)}p.param=function(e,t){var n,r=[],i=function(e,t){t=p.isFunction(t)?t():null==t?"":t,r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};if(void 0===t&&(t=p.ajaxSettings&&p.ajaxSettings.traditional),p.isArray(e)||e.jquery&&!p.isPlainObject(e))p.each(e,function(){i(this.name,this.value)});else for(n in e)Et(n,e[n],t,i);return r.join("&").replace(bt,"+")},p.fn.extend({serialize:function(){return p.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=p.prop(this,"elements");return e?p.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!p(this).is(":disabled")&&kt.test(this.nodeName)&&!Ct.test(e)&&(this.checked||!U.test(e))}).map(function(e,t){var n=p(this).val();return null==n?null:p.isArray(n)?p.map(n,function(e){return{name:t.name,value:e.replace(Tt,"\r\n")}}):{name:t.name,value:n.replace(Tt,"\r\n")}}).get()}}),p.ajaxSettings.xhr=function(){try{return new e.XMLHttpRequest}catch(e){}};var Nt={0:200,1223:204},St=p.ajaxSettings.xhr();f.cors=!!St&&"withCredentials"in St,f.ajax=St=!!St,p.ajaxTransport(function(t){var n,r;if(f.cors||St&&!t.crossDomain)return{send:function(i,o){var s,a=t.xhr();if(a.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(s in t.xhrFields)a[s]=t.xhrFields[s];for(s in t.mimeType&&a.overrideMimeType&&a.overrideMimeType(t.mimeType),t.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest"),i)a.setRequestHeader(s,i[s]);n=function(e){return function(){n&&(n=r=a.onload=a.onerror=a.onabort=a.onreadystatechange=null,"abort"===e?a.abort():"error"===e?"number"!=typeof a.status?o(0,"error"):o(a.status,a.statusText):o(Nt[a.status]||a.status,a.statusText,"text"!==(a.responseType||"text")||"string"!=typeof a.responseText?{binary:a.response}:{text:a.responseText},a.getAllResponseHeaders()))}},a.onload=n(),r=a.onerror=n("error"),void 0!==a.onabort?a.onabort=r:a.onreadystatechange=function(){4===a.readyState&&e.setTimeout(function(){n&&r()})},n=n("abort");try{a.send(t.hasContent&&t.data||null)}catch(e){if(n)throw e}},abort:function(){n&&n()}}}),p.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return p.globalEval(e),e}}}),p.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),p.ajaxTransport("script",function(e){var t,n;if(e.crossDomain)return{send:function(i,o){t=p("