diff --git a/Gemfile b/Gemfile index 61dc001..f223eed 100644 --- a/Gemfile +++ b/Gemfile @@ -26,6 +26,7 @@ gem 'redcarpet' gem 'pygments.rb' gem "twitter-bootstrap-rails" +gem 'bootstrap-glyphicons' gem 'asset_sync' gem 'unicorn' gem 'newrelic_rpm' diff --git a/Gemfile.lock b/Gemfile.lock index 777fd81..8461318 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -34,6 +34,9 @@ GEM better_errors (1.0.1) coderay (>= 1.0.0) erubis (>= 2.6.6) + bootstrap-glyphicons (0.0.1) + railties (>= 3.0) + sass (>= 3.2) builder (3.1.4) cancan (1.6.10) capistrano (2.15.5) @@ -265,6 +268,7 @@ PLATFORMS DEPENDENCIES asset_sync better_errors + bootstrap-glyphicons cancan capistrano capybara diff --git a/app/assets/javascripts/meeting_requests.js b/app/assets/javascripts/meeting_requests.js new file mode 100644 index 0000000..83a7f93 --- /dev/null +++ b/app/assets/javascripts/meeting_requests.js @@ -0,0 +1,23 @@ +$(function() { + $('.concept-search').click(function() { + $(this).toggleClass('badge-success'); + if ($('.badge-success').length === 0) { + $('.request').show(); + } else { + $('#open-requests').children('.request').hide(); + showRelated(getSelectedConceptNames()); + } + }); +}); + +function getSelectedConceptNames() { + return $('.badge-success').map(function() { + return $(this).attr('data-name').toLowerCase(); + }); +} + +function showRelated(tagNames) { + tagNames.each(function() { + $('[data-' + this + '="true"]').show(); + }); +} diff --git a/app/assets/stylesheets/application.css.scss b/app/assets/stylesheets/application.css.scss index a4324e5..3bb0756 100644 --- a/app/assets/stylesheets/application.css.scss +++ b/app/assets/stylesheets/application.css.scss @@ -12,6 +12,8 @@ *= require_tree . */ + @import 'bootstrap/glyphicons'; + footer { position: relative; width: 370px; diff --git a/app/assets/stylesheets/layouts.css.scss b/app/assets/stylesheets/layouts.css.scss index 94be697..b300963 100644 --- a/app/assets/stylesheets/layouts.css.scss +++ b/app/assets/stylesheets/layouts.css.scss @@ -72,3 +72,28 @@ $mercury: #d1d1d1; #location-map { display: none; } + +.request { + border: solid; + border-color: grey; + margin-bottom: 5px; + padding: 2px; +} + +.no-left-margin { + margin-left: 0px; +} + +.concept-tag { + cursor: pointer; + &:hover { + background-color: blue; + } +} + +.concept-search { + cursor: pointer; + &:hover { + background-color: green; + } +} diff --git a/app/controllers/meeting_requests_controller.rb b/app/controllers/meeting_requests_controller.rb index d84002b..55a9b10 100644 --- a/app/controllers/meeting_requests_controller.rb +++ b/app/controllers/meeting_requests_controller.rb @@ -1,6 +1,7 @@ class MeetingRequestsController < ApplicationController def new @meeting_request = MeetingRequest.new(member_uuid: current_user.uuid) + @concepts = Concept.all authorize! :create, @meeting_request end @@ -17,6 +18,7 @@ def create def edit @meeting_request = MeetingRequest.find(params[:id]) + @concepts = Concept.all authorize! :update, @meeting_request end @@ -37,6 +39,7 @@ def index claimed_uuids = current_user.claimed_meeting_requests.map(&:member_uuid) @open_users = User.fetch_from_uuids(open_uuids) @claimed_users = User.fetch_from_uuids(claimed_uuids) + @concepts = Concept.all end def show @@ -58,6 +61,6 @@ def destroy private def meeting_request_params - params.require(:meeting_request).permit(:title, :content, :contact_info, :member_uuid) + params.require(:meeting_request).permit(:title, :content, :contact_info, :member_uuid, :concept_ids => []) end end \ No newline at end of file diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 2103104..e1a4a4d 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -8,6 +8,10 @@ def sponsors ] end + def escape_attribute_name(string) + string.gsub(/\W|_/, '-').downcase + end + class HTMLwithPygments < Redcarpet::Render::HTML def block_code(code, language) Pygments.highlight(code, lexer:language) diff --git a/app/views/meeting_requests/_concept.html.erb b/app/views/meeting_requests/_concept.html.erb new file mode 100644 index 0000000..e592cdc --- /dev/null +++ b/app/views/meeting_requests/_concept.html.erb @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/views/meeting_requests/_mentor_requests_index.html.erb b/app/views/meeting_requests/_mentor_requests_index.html.erb index a2da7b6..1a8aac1 100644 --- a/app/views/meeting_requests/_mentor_requests_index.html.erb +++ b/app/views/meeting_requests/_mentor_requests_index.html.erb @@ -1,17 +1,18 @@ -
-
-

My Claimed Requests

- <%= render 'requests_list', meeting_requests: current_user.claimed_meeting_requests, users: @claimed_users %> -
-
- -
-
+
+

My Claimed Requests

+ <%= render 'requests_list', meeting_requests: current_user.claimed_meeting_requests, users: @claimed_users %> +
+
+

Open Requests

<%= render 'requests_list', meeting_requests: current_user.open_meeting_requests, users: @open_users %>
- -
-

Concepts

+
+

Concepts

+

Click on a concept to see related requests

+
    + <%= render partial: "concept", collection: @concepts %> +
-
\ No newline at end of file +
+ diff --git a/app/views/meeting_requests/_request_form.html.erb b/app/views/meeting_requests/_request_form.html.erb index c745498..431abd1 100644 --- a/app/views/meeting_requests/_request_form.html.erb +++ b/app/views/meeting_requests/_request_form.html.erb @@ -12,16 +12,32 @@ <%= render 'shared/form_errors', object: @meeting_request %>
-
- <%= f.label :title %> +
+ <%= f.label :title, 'Title' %> +
<%= f.text_field :title, class: "form-control" %>
-
- <%= f.label :contact_method, "What is the best way to contact you?" %> - <%= f.text_area :contact_info, class: "form-control meeting-contact-form" %> +
+ <%= f.label :contact_info, 'What is the best way to contact you?' %> +
+ <%= f.text_area :contact_info, class: "meeting-contact-form" %> +
+
+ +
+
+
Concepts Involved
+
+
+ <% @concepts.each do |concept| %> + + <% end %>
diff --git a/app/views/meeting_requests/_request_status.html.erb b/app/views/meeting_requests/_request_status.html.erb index 53b2827..dd0d4ac 100644 --- a/app/views/meeting_requests/_request_status.html.erb +++ b/app/views/meeting_requests/_request_status.html.erb @@ -1,6 +1,7 @@ <% if current_user.is_mentor? && meeting_request.mentor_uuid.nil? %>

+ Unclaimed

@@ -41,6 +42,7 @@ <% elsif meeting_request.mentor_uuid.nil? %>

+ Unclaimed

diff --git a/app/views/meeting_requests/_requests_list.html.erb b/app/views/meeting_requests/_requests_list.html.erb index cbcd25f..abda7bd 100644 --- a/app/views/meeting_requests/_requests_list.html.erb +++ b/app/views/meeting_requests/_requests_list.html.erb @@ -1,16 +1,18 @@ -

    - <% meeting_requests.each do |meeting_request| %> -
  • -
    - <%= link_to meeting_request.title, meeting_request_path(meeting_request) %> -
    - -
    - <%= meeting_request.created_at.strftime("%B %d, %Y") %> - <% if current_user.is_mentor? %> - | <%= users[meeting_request.member_uuid].name %> - <% end %> -
    -
  • +<% meeting_requests.each do |meeting_request| %> + <% if meeting_request.concepts.any? %> +
    + data-<%= escape_attribute_name(concept.name) %>='true' + <% end %> + > + <% else %> +
    <% end %> -
\ No newline at end of file + <%= link_to meeting_request.title, meeting_request_path(meeting_request) %> +

+ <%= meeting_request.created_at.strftime("%b %d") %> + - <%= users[meeting_request.member_uuid].name %> + <%= render 'shared/concept_tags', object: meeting_request %> +

+
+<% end %> diff --git a/app/views/meeting_requests/show.html.erb b/app/views/meeting_requests/show.html.erb index 3740ec2..1bf96e4 100644 --- a/app/views/meeting_requests/show.html.erb +++ b/app/views/meeting_requests/show.html.erb @@ -6,8 +6,15 @@
- <%= @meeting_request.created_at.strftime("%B %d, %Y") %> - - <%= @member.name %> +
+ <%= @meeting_request.created_at.strftime("%b %d ") %> + - <%= @member.name %> +
+
+ +
<%= simple_format(@meeting_request.content) %> @@ -40,7 +47,6 @@ <%= link_to 'Delete Request', meeting_request_path(@meeting_request), method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-wide' %>
<% end %> -
\ No newline at end of file diff --git a/app/views/meeting_requests/untitled b/app/views/meeting_requests/untitled new file mode 100644 index 0000000..e69de29 diff --git a/app/views/shared/_concept_tags.html.erb b/app/views/shared/_concept_tags.html.erb new file mode 100644 index 0000000..0a0f9f6 --- /dev/null +++ b/app/views/shared/_concept_tags.html.erb @@ -0,0 +1,7 @@ +<% if object.concepts.any? %> + +<% end %> diff --git a/spec/features/request_features_spec.rb b/spec/features/request_features_spec.rb index 975a024..9cd3d6a 100644 --- a/spec/features/request_features_spec.rb +++ b/spec/features/request_features_spec.rb @@ -17,6 +17,17 @@ page.should have_content 'successfully' end + scenario 'a member creates a valid request and tags it with concepts' do + concept = FactoryGirl.create(:concept) + click_link 'Create a request' + fill_in 'meeting_request_title', with: 'need help with rails' + fill_in 'meeting_request_content', with: 'understanding the basics' + fill_in 'meeting_request_contact_info', with: 'my cell phone for sure' + check(concept.name) + click_button 'Submit' + within('#related-concepts') { page.should have_content concept.name } + end + scenario 'a member attempts to create an invalid request' do click_link 'Create a request' click_button 'Submit' @@ -47,14 +58,22 @@ scenario 'a member edits a request with valid information' do click_link 'Edit Request' - fill_in 'Title', with: 'new request title' + fill_in 'meeting_request_title', with: 'new request title' click_button 'Submit' page.should have_content 'new request title' end + scenario 'they add a concept tag' do + concept = FactoryGirl.create(:concept) + click_link 'Edit Request' + check(concept.name) + click_button 'Submit' + within('#related-concepts') { page.should have_content concept.name } + end + scenario 'a member attempts to edit a request with invalid information' do click_link 'Edit Request' - fill_in 'Title', with: '' + fill_in 'meeting_request_title', with: '' click_button 'Submit' page.should have_content 'error' end @@ -191,30 +210,65 @@ @meeting_request4 = FactoryGirl.create(:meeting_request, member_uuid: @user.uuid) end - scenario 'a member visits the page' do - stub_user_fetch_from_uuid - stub_user_fetch_from_uuids - stub_application_controller - visit meeting_requests_path - page.should_not have_content @meeting_request1.title - page.should_not have_content @meeting_request2.title - page.should have_content @meeting_request3.title - page.should have_content @meeting_request4.title + context 'as a member' do + scenario 'a member visits the page' do + stub_user_fetch_from_uuid + stub_user_fetch_from_uuids + stub_application_controller + visit meeting_requests_path + page.should_not have_content @meeting_request1.title + page.should_not have_content @meeting_request2.title + page.should have_content @meeting_request3.title + page.should have_content @meeting_request4.title + end end - scenario 'a mentor visits the page' do - @user = new_mentor - @user2 = new_member - @meeting_request1.update(mentor_uuid: @user.uuid) - @meeting_request3.update(mentor_uuid: 'other-mentor-uuid') - stub_user_fetch_from_uuid - User.stub(:fetch_from_uuids).and_return({@meeting_request3.member_uuid => @user2, @user.uuid => @user, 'member-uuid' => @user2}) - stub_application_controller - visit meeting_requests_path - page.should have_content @meeting_request1.title - page.should have_content @meeting_request2.title - page.should_not have_content @meeting_request3.title - page.should have_content @meeting_request4.title + context 'as a mentor' do + before do + @user = new_mentor + @user2 = new_member + @meeting_request1.update(mentor_uuid: @user.uuid) + @meeting_request3.update(mentor_uuid: 'other-mentor-uuid') + stub_user_fetch_from_uuid + User.stub(:fetch_from_uuids).and_return({@meeting_request3.member_uuid => @user2, @user.uuid => @user, 'member-uuid' => @user2}) + stub_application_controller + end + + scenario 'a mentor visits the page' do + visit meeting_requests_path + page.should have_content @meeting_request1.title + page.should have_content @meeting_request2.title + page.should_not have_content @meeting_request3.title + page.should have_content @meeting_request4.title + end + + scenario 'they filter requests by concepts', js: true do + concept = FactoryGirl.create(:concept) + @meeting_request4.concepts << concept + visit meeting_requests_path + within('.concepts') { find('.concept-search').click } + within('#open-requests') { page.should_not have_content @meeting_request2.title } + end + + scenario 'they unfilter requests', js: true do + concept = FactoryGirl.create(:concept) + @meeting_request4.concepts << concept + visit meeting_requests_path + within('.concepts') { find('.concept-search').click } + within('.concepts') { find('.concept-search').click } + within('#open-requests') { page.should have_content @meeting_request2.title } + end + + scenario 'they filter multiple concepts', js: true do + concept = FactoryGirl.create(:concept) + concept2 = FactoryGirl.create(:concept, name: 'Javascript') + @meeting_request4.concepts << concept + @meeting_request3.concepts << concept2 + visit meeting_requests_path + within('.concepts') { find('.concept-search:first-of-type').click } + within('.concepts') { find('.concept-search:nth-of-type(2)').click } + within('#open-requests') { page.should have_content @meeting_request4.title } + end end end