diff --git a/app/assets/stylesheets/_base-variables.scss b/app/assets/stylesheets/_base-variables.scss index e62a646f..db3074c3 100644 --- a/app/assets/stylesheets/_base-variables.scss +++ b/app/assets/stylesheets/_base-variables.scss @@ -2,16 +2,28 @@ $gw-column: 60px; $gw-gutter: 20px; $serif: Georgia, Cambria, serif; -$sans-serif: 'museo-sans', 'proxima-nova', $helvetica; +$sans-serif: "museo-sans", "proxima-nova", $helvetica; -$base-font-color: rgb(55,55,55); -$lighter-base-font-color: rgb(46,46,46); +$base-font-color: rgb(55, 55, 55); +$lighter-base-font-color: rgb(46, 46, 46); $muted-color: #ccc; $pink: rgb(255, 75, 126); $button-pink: rgb(252, 103, 147); -$blue: rgb(34,122,255); //blue +$blue: rgb(34, 122, 255); //blue + +$green-background: #a7f3d0; +$green-text: #065f46; + +$yellow-background: #fef08a; +$yellow-text: #854d0e; + +$red-background: #fecaca; +$red-text: #7f1d1d; + +$base-font-color: rgb(55, 55, 55); +$lighter-base-font-color: rgb(46, 46, 46); /*** * Responsiveness Sizes @@ -22,7 +34,7 @@ $blue: rgb(34,122,255); //blue * mobile-small: for additional changes that accommdate very small screens like iPhone 5S ***/ -$maximum-width: 60em; // Equivalent to 969px; +$maximum-width: 60em; // Equivalent to 969px; $tablet-standard: 60em; $mobile-standard: 30em; // Equivalent to 480px; -$mobile-small: 20em; // Equivalent to 320px; +$mobile-small: 20em; // Equivalent to 320px; diff --git a/app/assets/stylesheets/_project_analyses.scss b/app/assets/stylesheets/_project_analyses.scss new file mode 100644 index 00000000..d7bf7825 --- /dev/null +++ b/app/assets/stylesheets/_project_analyses.scss @@ -0,0 +1,45 @@ +// @import "base_variables"; + +body.project-analysis-show { + h1 { + font-size: 24px; + margin-bottom: 16px; + } + + .scores-table { + width: 100%; + border-collapse: collapse; + margin-bottom: 20px; + + th, + td { + padding: 8px; + border: 1px solid #ddd; + } + + th { + background-color: #f0f0f0; + text-align: left; + font-weight: bold; + } + + // Define classes for score rows using the color variables + tr.score-row--high { + background-color: $green-background; + color: $green-text; + font-weight: 900; + } + + tr.score-row--medium { + background-color: $yellow-background; + color: $yellow-text; + } + + tr.score-row--low { + background-color: $red-background; + color: $red-text; + } + } + + // Additional styles can be defined here, following your SCSS strategies +} diff --git a/app/assets/stylesheets/_projects-index.scss b/app/assets/stylesheets/_projects-index.scss index 6d5ca16f..7c3df572 100644 --- a/app/assets/stylesheets/_projects-index.scss +++ b/app/assets/stylesheets/_projects-index.scss @@ -1,15 +1,13 @@ body.projects-index { - section.container { @extend .clearfix; } - + @media all and (max-width: $maximum-width) { section.container { margin-top: 0.5em !important; } } - } .application__image-wrapper { @@ -32,7 +30,7 @@ body.projects-index { } .admin-panel { - border-right: 1px solid rgb(200,200,200); + border-right: 1px solid rgb(200, 200, 200); float: left; margin-right: 39px; padding: 0px 20px 20px 0px; @@ -76,13 +74,13 @@ body.projects-index { } h2 { - border-bottom: 1px dotted rgb(200,200,200); + border-bottom: 1px dotted rgb(200, 200, 200); font: 300 18px/20px $sans-serif; margin: 15px 0px; padding-bottom: 30px; text-align: center; @media all and (max-width: $maximum-width) { - border-bottom: 1px solid rgb(200,200,200); + border-bottom: 1px solid rgb(200, 200, 200); margin-bottom: 0px; text-align: left; padding-left: 70px; @@ -94,7 +92,7 @@ body.projects-index { margin: 0px; li { - border-bottom: 1px dotted rgb(200,200,200); + border-bottom: 1px dotted rgb(200, 200, 200); list-style: none; margin-bottom: 15px; padding: 0px 0px 15px 0px; @@ -121,7 +119,7 @@ body.projects-index { } a { - color: rgb(55,55,55); + color: rgb(55, 55, 55); display: block; font: normal 12px/14px $sans-serif; padding: 5px 0px 5px 10px; @@ -132,7 +130,7 @@ body.projects-index { &:hover { background: $pink; - color: rgb(255,255,255); + color: rgb(255, 255, 255); } } } @@ -142,7 +140,7 @@ body.projects-index { .applications { float: left; max-width: grid-width(9); - + /* Ensure 'Back to Chapter' link on admin view of project page is the correct font size */ > p:nth-child(2) { font-size: 0.8em; @@ -169,11 +167,12 @@ body.projects-index { } .application-filters { - background: rgb(230,230,230); - border: 1px solid rgb(200,200,200); + background: rgb(230, 230, 230); + border: 1px solid rgb(200, 200, 200); border-radius: 4px; - box-shadow: inset 0 -1px 0 0 rgba(255,255,255, 0.3), inset 0 1px 0 0 rgba(255,255,255, 0.8); - @include linear-gradient(rgb(240,240,240), rgb(220,220,220)); + box-shadow: inset 0 -1px 0 0 rgba(255, 255, 255, 0.3), + inset 0 1px 0 0 rgba(255, 255, 255, 0.8); + @include linear-gradient(rgb(240, 240, 240), rgb(220, 220, 220)); margin-bottom: 10px; position: relative; width: grid-width(9); @@ -187,8 +186,8 @@ body.projects-index { position: relative; a.chapter-selection { - background: rgb(230,230,230); - border: 1px solid rgb(190,190,190); + background: rgb(230, 230, 230); + border: 1px solid rgb(190, 190, 190); border-radius: 3px; display: block; padding: 5px 40px 5px 10px; @@ -196,18 +195,18 @@ body.projects-index { z-index: 2; &:hover { - background: rgb(220,220,220); + background: rgb(220, 220, 220); } span { - color: rgb(55,55,55); + color: rgb(55, 55, 55); font: normal 16px/18px $sans-serif; - text-shadow: 0 1px rgba(255,255,255, 0.75); + text-shadow: 0 1px rgba(255, 255, 255, 0.75); } span.arrow { border: { - top: 7px solid rgb(150,150,150); + top: 7px solid rgb(150, 150, 150); right: 7px solid transparent; bottom: 7px solid transparent; left: 7px solid transparent; @@ -218,18 +217,18 @@ body.projects-index { } &.expanded { - background: rgb(220,220,220); - border: 1px solid rgb(150,150,150); + background: rgb(220, 220, 220); + border: 1px solid rgb(150, 150, 150); border-radius: 3px 3px 0px 0px; cursor: pointer; } } ol.chapter-selector { - background: rgb(255,255,255); - border: 1px solid rgb(150,150,150); + background: rgb(255, 255, 255); + border: 1px solid rgb(150, 150, 150); @include border-bottom-radius(4px); - box-shadow: 0 3px 5px 0 rgba(0,0,0, 0.2); + box-shadow: 0 3px 5px 0 rgba(0, 0, 0, 0.2); opacity: 0; overflow-y: auto; position: relative; @@ -237,13 +236,13 @@ body.projects-index { z-index: 1; margin-top: -1px; margin-bottom: 0px; - + &.bounded { - max-height: 428px; + max-height: 428px; } li { - border-bottom: 1px solid rgb(200,200,200); + border-bottom: 1px solid rgb(200, 200, 200); list-style: none; margin: 0 5px; padding: 3px 0px; @@ -253,14 +252,14 @@ body.projects-index { } a { - color: rgb(55,55,55); + color: rgb(55, 55, 55); display: block; padding: 5px 8px; text-decoration: none; &:hover { background: $pink; - color: rgb(255,255,255); + color: rgb(255, 255, 255); } } } @@ -276,7 +275,9 @@ body.projects-index { float: right; padding: 0px 10px 10px 10px; - div.short-list-toggle, div.funded-toggle, div.toggle { + div.short-list-toggle, + div.funded-toggle, + div.toggle { float: left; margin-right: 10px; margin-top: 20px; @@ -289,28 +290,29 @@ body.projects-index { } label { - background: rgb(230,230,230); - border: 1px solid rgb(190,190,190); + background: rgb(230, 230, 230); + border: 1px solid rgb(190, 190, 190); border-radius: 3px; font: normal 12px $sans-serif; height: 21px; margin: 0px; - text-shadow: 0 1px rgba(255,255,255, 0.8); + text-shadow: 0 1px rgba(255, 255, 255, 0.8); padding: 8px 12px 0px 30px; &:hover { - background: rgb(220,220,220); + background: rgb(220, 220, 220); cursor: pointer; } } input[type="checkbox"]:checked + label { - background: rgb(220,220,220); + background: rgb(220, 220, 220); cursor: pointer; } } - div.date-range, div.search { + div.date-range, + div.search { float: left; margin-right: 10px; margin-top: 20px; @@ -320,12 +322,12 @@ body.projects-index { } input[type="text"] { - color: rgb(180,180,180); + color: rgb(180, 180, 180); height: 30px; width: 90px; &:focus { - color: rgb(55,55,55); + color: rgb(55, 55, 55); } } @@ -334,8 +336,9 @@ body.projects-index { } } - input[type="submit"], button[type="submit"] { - @include button(rgb(230,230,230)); + input[type="submit"], + button[type="submit"] { + @include button(rgb(230, 230, 230)); margin: 0px; margin-top: 20px; width: auto; @@ -351,10 +354,10 @@ body.projects-index { } article.project { - border: 1px solid rgb(200,200,200); - background: rgb(255,255,255); + border: 1px solid rgb(200, 200, 200); + background: rgb(255, 255, 255); border-radius: 4px; - box-shadow: 0 1px 6px 0 rgba(0,0,0, 0.15); + box-shadow: 0 1px 6px 0 rgba(0, 0, 0, 0.15); margin-top: 20px; margin-bottom: 20px; @@ -363,7 +366,7 @@ body.projects-index { div.title { @extend .clearfix; - border-bottom: 1px solid rgb(200,200,200); + border-bottom: 1px solid rgb(200, 200, 200); padding: 10px; a.title { @@ -379,7 +382,7 @@ body.projects-index { .public-link { display: none; - padding-top: .5em; + padding-top: 0.5em; font-size: 0.8em; clear: both; @@ -400,7 +403,7 @@ body.projects-index { a.short-list, a.mark-as-winner, a.project-actions-toggle { - @include button(rgb(240,240,240)); + @include button(rgb(240, 240, 240)); float: right; margin-left: 10px; text-decoration: none; @@ -410,13 +413,28 @@ body.projects-index { } &.mark-as-winner:hover { - @include button(rgb(34,122,255)); + @include button(rgb(34, 122, 255)); } } a.project-actions-toggle { font-size: 1.1em; } + + a.auto-awesome { + @include button(rgb(240, 240, 240)); + float: right; + margin-left: 10px; + text-decoration: none; + + &.auto-awesome:hover { + @include button($pink); + } + + // &.mark-as-winner:hover { + // @include button(rgb(34,122,255)); + // } + } } .contact { @@ -429,7 +447,7 @@ body.projects-index { } .meta-data { - border-bottom: 1px solid rgb(200,200,200); + border-bottom: 1px solid rgb(200, 200, 200); padding: 10px; font-size: 0.8em; @@ -439,7 +457,7 @@ body.projects-index { } .project-actions-toggle { - margin-left: .25em; + margin-left: 0.25em; font-size: 1.2em; float: right; } @@ -474,7 +492,7 @@ body.projects-index { margin-bottom: 0; li { - padding-bottom: .5em; + padding-bottom: 0.5em; a { text-decoration: none; @@ -509,7 +527,8 @@ body.projects-index { font: normal 16px/20px $serif; padding: 10px; - h3, p { + h3, + p { margin: 0; padding: 0; font: normal 14px/20px $serif; @@ -544,7 +563,7 @@ body.projects-index { .see-more { background: rgba(245, 245, 245, 1); - border-top: 1px solid rgb(200,200,200); + border-top: 1px solid rgb(200, 200, 200); @include border-bottom-radius(4px); display: block; text-decoration: none; @@ -567,11 +586,12 @@ body.projects-index { } .filtering { - border-top: 1px solid rgb(200,200,200); + border-top: 1px solid rgb(200, 200, 200); padding: 10px; font-size: 0.8em; - .hiding-form, .unhiding-form { + .hiding-form, + .unhiding-form { p { margin: 0; } @@ -592,7 +612,7 @@ body.projects-index { } input.hide-action { - @include button(rgb(230,230,230)); + @include button(rgb(230, 230, 230)); width: 80px; position: relative; margin: 0; diff --git a/app/assets/stylesheets/application.css.scss b/app/assets/stylesheets/application.css.scss index 042988d8..887fcc64 100644 --- a/app/assets/stylesheets/application.css.scss +++ b/app/assets/stylesheets/application.css.scss @@ -5,37 +5,38 @@ *= require_self */ -@import 'flutie'; -@import 'bourbon'; -@import 'flexbox-ultralight'; -@import 'lightgallery'; +@import "flutie"; +@import "bourbon"; +@import "flexbox-ultralight"; +@import "lightgallery"; -@import 'base-variables'; -@import 'base-extends'; -@import 'base-mixins'; -@import 'base-animations'; -@import 'base'; -@import 'buttons'; -@import 'navigation'; -@import 'shared-feed'; -@import 'shared-forms'; -@import 'chapters'; -@import 'projects'; -@import 'comments'; -@import 'home'; -@import 'invitations-new'; -@import 'users'; -@import 'finalists'; -@import 'shared-header'; -@import 'shared-footer'; -@import 'shared-date-picker'; -@import 'shared-flash-messages'; -@import 'about'; -@import 'faq'; -@import 'winners'; -@import 'funded-projects'; -@import 'pagination'; -@import 'magnific-popup'; +@import "base-variables"; +@import "base-extends"; +@import "base-mixins"; +@import "base-animations"; +@import "base"; +@import "buttons"; +@import "navigation"; +@import "shared-feed"; +@import "shared-forms"; +@import "chapters"; +@import "projects"; +@import "project_analyses"; +@import "comments"; +@import "home"; +@import "invitations-new"; +@import "users"; +@import "finalists"; +@import "shared-header"; +@import "shared-footer"; +@import "shared-date-picker"; +@import "shared-flash-messages"; +@import "about"; +@import "faq"; +@import "winners"; +@import "funded-projects"; +@import "pagination"; +@import "magnific-popup"; -@import 'owl.carousel.min'; -@import 'owl.theme.default.min'; +@import "owl.carousel.min"; +@import "owl.theme.default.min"; diff --git a/app/controllers/project_analyses_controller.rb b/app/controllers/project_analyses_controller.rb new file mode 100644 index 00000000..fed46dc7 --- /dev/null +++ b/app/controllers/project_analyses_controller.rb @@ -0,0 +1,27 @@ +class ProjectAnalysesController < ApplicationController + before_action :require_login + before_action :set_project + + def show_or_create + # Delete existing analysis if force is true + if params[:force].to_s == "true" && @project.project_analysis.present? + @project.project_analysis.destroy + end + + # Use cached analysis if it exists, otherwise generate a new one + @project_analysis = @project.project_analysis || ProjectAnalysisGenerator.new(@project.id).call + + flash[:notice] = if @project_analysis.persisted? + "Project analysis loaded from cache." + else + "New project analysis generated." + end + render :show + end + + private + + def set_project + @project = Project.find(params[:project_id]) + end +end diff --git a/app/views/project_analyses/show.html.erb b/app/views/project_analyses/show.html.erb new file mode 100644 index 00000000..8dfc46f5 --- /dev/null +++ b/app/views/project_analyses/show.html.erb @@ -0,0 +1,51 @@ +<% provide(:body_class, 'project-analysis-show') %> +
+Summary: <%= @project_analysis.summary %>
+Applicant Role: <%= @project_analysis.applicant_role.humanize %>
+Language: <%= @project_analysis.language_code %>
+Tags: <%= @project_analysis.tags.join(', ') %>
+ +Criterion | +Score | +Reason | +
---|---|---|
<%= score_hash[:field] %> | +<%= (score_hash[:score]).round %> | +<%= score_hash[:reason] %> | +
Suggestions: <%= @project_analysis.suggestion %>
+ \ No newline at end of file diff --git a/app/views/projects/_project_details.html.erb b/app/views/projects/_project_details.html.erb index cd31b902..0490553e 100644 --- a/app/views/projects/_project_details.html.erb +++ b/app/views/projects/_project_details.html.erb @@ -12,6 +12,7 @@ <% if display_project_actions?(current_user, project) %> <%= link_to ''.html_safe, "#", class: "project-actions-toggle", data: { action: "click->toggler#toggle" } %> + <%= link_to I18n.t("auto_awesome", scope: "projects.project.buttons"), show_or_create_project_project_analysis_path(project), class: "auto-awesome" %> <% end %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 10963a5c..3a7d10ed 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -168,6 +168,8 @@ en: add-comment: Add a comment confirm-delete-comment: Are you sure you want to remove this comment? delete-comment: Delete comment + buttons: + auto_awesome: Auto-awesome hide_form: hide: Hide unhide: Unhide diff --git a/config/routes.rb b/config/routes.rb index 1a5ae987..31d411b2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,53 +1,53 @@ Rails.application.routes.draw do if ENV["AWS_BUCKET"].present? - mount Shrine.uppy_s3_multipart(:cache) => '/s3/multipart' + mount Shrine.uppy_s3_multipart(:cache) => "/s3/multipart" else - mount Tus::Server => '/uploads' + mount Tus::Server => "/uploads" end constraints(SubdomainConstraint) do get "/apply" => "subdomains#apply" - get "*url" => "subdomains#chapter" - get "/" => "subdomains#chapter" + get "*url" => "subdomains#chapter" + get "/" => "subdomains#chapter" end constraints(DomainConstraint) do get "*url" => "subdomains#canonical" - get "/" => "subdomains#canonical" + get "/" => "subdomains#canonical" end get "/blog/contact/" => redirect("/en/contact") - get "/blog/about/" => redirect("/en/about_us") - get "/blog" => redirect("http://blog.awesomefoundation.org") - get "/blog/*path" => redirect { |params, request| "http://blog.awesomefoundation.org/#{Addressable::URI.escape(params[:path])}" }, :format => false - get "/apply" => redirect("/en/submissions/new") + get "/blog/about/" => redirect("/en/about_us") + get "/blog" => redirect("http://blog.awesomefoundation.org") + get "/blog/*path" => redirect { |params, request| "http://blog.awesomefoundation.org/#{Addressable::URI.escape(params[:path])}" }, :format => false + get "/apply" => redirect("/en/submissions/new") - resources :passwords, :controller => 'clearance/passwords', :only => [:new, :create] + resources :passwords, controller: "clearance/passwords", only: [:new, :create] - resources :users, :shallow => true do - resource :password, :controller => 'clearance/passwords', :only => [:create, :edit, :update] + resources :users, shallow: true do + resource :password, controller: "clearance/passwords", only: [:create, :edit, :update] end scope "(:locale)", locale: /#{I18n.available_locales.join("|")}/ do - resource :session, controller: :sessions, only: [:new, :create, :destroy] + resource :session, controller: :sessions, only: [:new, :create, :destroy] - get "sign_in", :to => "sessions#new" - delete "sign_out", :to => "sessions#destroy" + get "sign_in", to: "sessions#new" + delete "sign_out", to: "sessions#destroy" - resources :users, :only => [:index, :update, :edit] do - resource :admins, :only => [:create, :destroy] + resources :users, only: [:index, :update, :edit] do + resource :admins, only: [:create, :destroy] end - resources :chapters, :only => [:index, :show, :new, :create, :edit, :update] do - resources :finalists, :only => [:index] - resources :projects, :only => [:index, :show] do - resource :winner, :only => [:edit] + resources :chapters, only: [:index, :show, :new, :create, :edit, :update] do + resources :finalists, only: [:index] + resources :projects, only: [:index, :show] do + resource :winner, only: [:edit] end - resources :users, :only => [:index] + resources :users, only: [:index] end - resources :invitations, :only => [:new, :create] do - resources :acceptances, :only => [:new, :create] + resources :invitations, only: [:new, :create] do + resources :acceptances, only: [:new, :create] end resources :funded_projects, path: "projects", only: [:index, :show] @@ -58,8 +58,12 @@ put "unhide" end resources :comments - resource :winner, :only => [:create, :update, :destroy] - resource :vote, :only => [:create, :destroy] + resource :winner, only: [:create, :update, :destroy] + resource :vote, only: [:create, :destroy] + + resource :project_analysis, only: [] do + get "show_or_create", to: "project_analyses#show_or_create" + end end resources :submissions, controller: "projects" do @@ -68,21 +72,21 @@ end end - resources :roles, :only => [:destroy] do - resource :promotions, :only => [:create, :destroy] + resources :roles, only: [:destroy] do + resource :promotions, only: [:create, :destroy] end - %w(about_us faq start_a_chapter).each do |page| - get page, :to => 'high_voltage/pages#show', :id => page + %w[about_us faq start_a_chapter].each do |page| + get page, to: "high_voltage/pages#show", id: page end - root :to => 'home#index' + root to: "home#index" end - get "/404", :to => "errors#not_found" - get "/500", :to => "errors#internal_server_error" + get "/404", to: "errors#not_found" + get "/500", to: "errors#internal_server_error" # All other routes are considered 404s. ActionController::RoutingError # will catch them, but that fills our logs with noisy exceptions. - get '*url', :to => 'errors#not_found' + get "*url", to: "errors#not_found" end diff --git a/spec/factories.rb b/spec/factories.rb index 4b600208..365e8c31 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -1,7 +1,6 @@ -# coding: utf-8 FactoryGirl.define do - sequence(:email) {|n| "user#{n}@example.com" } - sequence(:title) {|n| "Something Awesome ##{n}" } + sequence(:email) { |n| "user#{n}@example.com" } + sequence(:title) { |n| "Something Awesome ##{n}" } sequence(:index) factory :chapter do @@ -14,7 +13,7 @@ end end - factory :user, :aliases => [:inviter, :invitee] do + factory :user, aliases: [:inviter, :invitee] do first_name "Joe" last_name "Schmoe" email @@ -27,13 +26,13 @@ factory :user_with_dean_role do after(:create) do |user| - FactoryGirl.create(:role, :user => user, :name => "dean") + FactoryGirl.create(:role, user: user, name: "dean") user.reload end end factory :user_with_trustee_role do - chapters { [association(:chapter) ] } + chapters { [association(:chapter)] } end end @@ -48,7 +47,7 @@ factory :invitation do email - association :inviter, :factory => :user_with_dean_role + association :inviter, factory: :user_with_dean_role after(:build) do |invitation, proxy| invitation.chapter = invitation.inviter.chapters.first @@ -66,11 +65,11 @@ chapter factory :project_with_rss_feed do - rss_feed_url Rails.root.join('spec', 'support', 'feed.xml').to_s + rss_feed_url Rails.root.join("spec", "support", "feed.xml").to_s end factory :winning_project do - sequence(:funded_on) { |n| (3000-n.to_i).days.ago } + sequence(:funded_on) { |n| (3000 - n.to_i).days.ago } end factory :hidden_project do @@ -107,11 +106,10 @@ # custom storage the way we do. factory :s3_photo, class: "Photo" do project - storage_keys { { store: :s3_store, cache: :s3_cache } } + storage_keys { {store: :s3_store, cache: :s3_cache} } image_data { FakeData.shrine_uploaded_file("1.JPG") } end - factory :comment do project user diff --git a/spec/jobs/project_analysis_job_spec.rb b/spec/jobs/project_analysis_job_spec.rb new file mode 100644 index 00000000..5fcd287f --- /dev/null +++ b/spec/jobs/project_analysis_job_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe ProjectAnalysisJob, type: :job do + pending "add some examples to (or delete) #{__FILE__}" +end