Skip to content

Commit

Permalink
Merge pull request #408 from rubytoolbox/co-comparison
Browse files Browse the repository at this point in the history
Project Comparisons
  • Loading branch information
colszowka committed Feb 14, 2019
2 parents 44e878a + e83e4ed commit a28c539
Show file tree
Hide file tree
Showing 20 changed files with 431 additions and 18 deletions.
1 change: 1 addition & 0 deletions app/assets/javascripts/application.js
Expand Up @@ -14,6 +14,7 @@
//= require turbolinks
//= require chart.js/dist/Chart.bundle.js
//= require headroom.js/dist/headroom
//= require javascript-autocomplete/auto-complete.js
//= require_tree .

document.addEventListener("turbolinks:load", function () {
Expand Down
27 changes: 25 additions & 2 deletions app/assets/stylesheets/application.css.sass
Expand Up @@ -11,6 +11,7 @@
* It is generally better to create a new file per style scope.
*
*= require_self
*= require javascript-autocomplete/auto-complete.css
*/

@import "font-awesome"
Expand Down Expand Up @@ -40,6 +41,14 @@ $navbar-item-hover-background-color: transparent
$navbar-item-active-background-color: transparent
$navbar-tab-hover-background-color: transparent
$navbar-tab-active-background-color: transparent
// Need to set these variables despite them being bulma defaults
// so we can customize the navbar breakpoint to the
// default bulma widescreen breakpoint before importing bulma itself
$gap: 64px
$widescreen: 1152px + (2 * $gap)
// Navbar burger toggles on desktop <-> widescreen (default: tablet <-> desktop)
// since we have too many menu items
$navbar-breakpoint: $widescreen

@import "bulma/bulma"
@import "components/**/*"
Expand Down Expand Up @@ -75,8 +84,10 @@ header.main
.wrap
border-color: $primary

// Do not show red active border on mobile nav
.navbar-menu.is-active
// Do not show box shadow, it looks funny on desktop
box-shadow: none
// Do not show red active border on mobile nav
.navbar-item.is-active
.wrap
border-color: transparent
Expand All @@ -97,7 +108,7 @@ header.main
// navbar burger and ensure it's nicely aligned
.mobile-search
margin-left: auto
@extend .is-hidden-desktop
@extend .is-hidden-widescreen
height: $navbar-height
width: $navbar-height
.icon
Expand Down Expand Up @@ -186,3 +197,15 @@ article.blog-post
height: 350px
&.small
height: 180px

.autocomplete-suggestions
.autocomplete-suggestion
padding: 10px 8px
&.selected
background: $primary
color: white
b
color: white
b
color: black
font-weight: bold
44 changes: 44 additions & 0 deletions app/controllers/comparisons_controller.rb
@@ -0,0 +1,44 @@
# frozen_string_literal: true

class ComparisonsController < ApplicationController
def show
@projects = Project.where(permalink: requested_project_permalinks)
.for_display(forks: true)
.includes_associations
.order(current_order.sql)
.limit(50)
@display_mode = display_mode
enforce_canonical_query
end

private

def enforce_canonical_query
@expected_ids = @projects.map(&:permalink).sort.join(",")
query_string = Rack::Utils.parse_nested_query(request.query_string).slice("display", "order").to_query
destination = if query_string.present?
comparison_path(@expected_ids) + "?#{query_string}"
else
comparison_path(@expected_ids)
end

redirect_to destination if selection_is_unordered?
end

def selection_is_unordered?
params[:add].present? || (requested_project_permalinks.try(:join, ",").presence != @expected_ids.presence)
end

def requested_project_permalinks
(params[:id].presence || "").split(",") + [params[:add]].map { |id| id.try(:strip).presence }.compact
end

def display_mode
DisplayMode.new params[:display], default: "table"
end

def current_order
@current_order ||= Project::Order.new order: params[:order]
end
helper_method :current_order
end
4 changes: 4 additions & 0 deletions app/controllers/searches_controller.rb
Expand Up @@ -10,6 +10,10 @@ def show
redirect_to_search_with_forks_included if should_redirect_to_included_forks?
end

def by_name
render json: Project.suggest(params[:q])
end

private

def perform_search
Expand Down
10 changes: 7 additions & 3 deletions app/helpers/application_helper.rb
Expand Up @@ -79,13 +79,17 @@ def markdown(text)
# object might make sense...
#
def link_with_preserved_display_settings(**args)
"#{request.path}?#{current_display_settings_query_string(**args)}"
end

def current_display_settings_query_string(**args)
addressable = Addressable::URI.new.tap do |uri|
uri.query_values = default_display_settings.merge(args).compact
uri.query_values = current_display_settings.merge(args).compact
end
"#{request.path}?#{addressable.query}"
addressable.query
end

def default_display_settings
def current_display_settings
{
order: try(:current_order)&.ordered_by,
q: @search&.query,
Expand Down
4 changes: 4 additions & 0 deletions app/helpers/component_helpers.rb
Expand Up @@ -59,6 +59,10 @@ def small_health_indicator(project)
render "components/small_health_indicator", project: project
end

def project_details_buttons(project)
render "components/project/details_buttons", project: project
end

def project_order_dropdown(order)
render "components/project_order_dropdown", order: order
end
Expand Down
10 changes: 10 additions & 0 deletions app/models/project.rb
Expand Up @@ -42,6 +42,16 @@ def self.for_display(forks: false)
.with_score
end

def self.suggest(name)
return [] if name.blank?

Project
.where("permalink ILIKE ?", "#{sanitize_sql_like(name)}%")
.order("score DESC NULLS LAST")
.limit(25)
.pluck(:permalink)
end

include PgSearch
pg_search_scope :search_scope,
# This is unfortunately not used when using explicit tsvector columns,
Expand Down
68 changes: 68 additions & 0 deletions app/views/comparisons/show.html.slim
@@ -0,0 +1,68 @@
- content_for :title, "Compare Projects"

.hero
section.section: .container
.columns: .column
h2
a href=comparison_path("")
span Compare projects
- if @projects.size < 2
.description
markdown:
Project comparisons allow you to view any selection of projects side by side just like they're shown on regular categories or in search results. **[You can try out an example](/compare/hanami,merb,rails,sinatra)** or start yourself by adding a library to the comparison via the input below.

.columns: .column
.tags
- @expected_ids.split(",").each do |permalink|
span.tag.is-primary.is-medium
span= permalink
a.delete.is-small href=(comparison_path((@expected_ids.split(",") - [permalink]).join(",")) + "?#{current_display_settings_query_string}")

= form_tag request.path, method: :get, autocomplete: "off", class: "autocomplete-form" do
.field.has-addons.has-addons-centered
// Persist display settings across submissions
- current_display_settings.compact.each do |name, value|
input type="hidden" name=name value=value

.control.is-expanded
input.input.autocomplete-comparison autofocus="autofocus" placeholder="Type a project name to add it to the comparison" type="text" name="add" value=""
.control
button.button type="submit"
span.icon: i.fa.fa-plus
span Add to comparison

/ Our autocomplete library interferes with the enter key on the input field -
/ it should be possible to just type a name and hit enter without interacting
/ with the autocompletion in any way.
javascript:
(function() {
var submitComparisonForm = function() { document.querySelector("form.autocomplete-form").submit() }
document.querySelector("input.autocomplete-comparison").addEventListener("keydown", function(event) {
if (event.key === "Enter") { submitComparisonForm(); }
});
new autoComplete({
minChars: 1,
selector: "input.autocomplete-comparison",
source: function(term, callback) {
fetch("/search/by_name?q=" + term)
.then(function(response) {
response.json().then(function(data) {
callback(data);
});
});
},
onSelect: submitComparisonForm
});
})();
- if @projects.any?
section.section: .container
.level
.level-left
.level-right
.level-item= project_display_picker @display_mode
.level-item= project_order_dropdown current_order

= render "projects/listing", projects: @projects, show_categories: false, display_mode: @display_mode
5 changes: 1 addition & 4 deletions app/views/components/_project.html.slim
Expand Up @@ -38,10 +38,7 @@
- if local_assigns[:compact]
.metrics.compact= metrics_row project, :rubygem_downloads, :github_repo_stargazers_count, :rubygem_current_version, :rubygem_releases_count, :rubygem_first_release_on, :rubygem_latest_release_on

.columns: .column.has-text-right
a.button.is-outlined href="/projects/#{project.permalink}"
span.icon: i.fa.fa-plus
span Show more project details
= project_details_buttons project

- else
= project_metrics project, compact: compact
8 changes: 8 additions & 0 deletions app/views/components/project/_details_buttons.html.slim
@@ -0,0 +1,8 @@
.columns: .column
.buttons.is-right
a.button.is-outlined href="/projects/#{project.permalink}"
span.icon: i.fa.fa-plus
span Show more project details
a.button.is-outlined href=comparison_path(project.permalink)
span.icon: i.fa.fa-arrows-h
span Compare
5 changes: 1 addition & 4 deletions app/views/components/project/_metrics.html.slim
Expand Up @@ -51,7 +51,4 @@
.column: .metrics
= metrics_row project, :github_repo_issue_closure_rate, :github_repo_pull_request_acceptance_rate, :github_repo_average_recent_committed_at, :rubygem_reverse_dependencies_count

.columns: .column.has-text-right
a.button.is-outlined href="/projects/#{project.permalink}"
span.icon: i.fa.fa-plus
span Show more project details
= project_details_buttons project
7 changes: 6 additions & 1 deletion app/views/layouts/application.html.slim
Expand Up @@ -58,7 +58,7 @@ html.has-navbar-fixed-top lang="en"

.navbar-menu#mainMenu
.navbar-end
a.navbar-item href="/" class=active_when(controller: :welcome)
a.navbar-item.is-hidden-widescreen-only href="/" class=active_when(controller: :welcome)
.wrap
span.icon: i.fa.fa-home
span Home
Expand All @@ -68,6 +68,11 @@ html.has-navbar-fixed-top lang="en"
span.icon: i.fa.fa-bars
span Categories

a.navbar-item href=comparison_path class=active_when(controller: :comparisons)
.wrap
span.icon: i.fa.fa-arrows-h
span Compare

a.navbar-item href=page_path("docs/index") class=active_when(controller: :pages)
.wrap
span.icon: i.fa.fa-life-ring
Expand Down
11 changes: 9 additions & 2 deletions config/routes.rb
Expand Up @@ -6,8 +6,15 @@
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
Rails.application.routes.draw do
resources :categories, only: %i[index show]
resources :projects, only: %i[show], constraints: { id: Patterns::ROUTE_PATTERN }
resource :search, only: %i[show]
resources :projects, only: %i[show], constraints: { id: Patterns::ROUTE_PATTERN } do
end
get "compare(/:id)", to: "comparisons#show", constraints: { id: /.*/ }, as: :comparison

resource :search, only: %i[show] do
collection do
get :by_name
end
end
resources :blog, only: %i[index show], constraints: { id: /[^\.]+/ }

namespace :webhooks do
Expand Down
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -4,7 +4,8 @@
"dependencies": {
"bulma": "^0.7.0",
"chart.js": "^2.7.3",
"headroom.js": "^0.9.4"
"headroom.js": "^0.9.4",
"javascript-autocomplete": "^1.0.3"
},
"version": "1.0.0",
"main": "index.js",
Expand Down

0 comments on commit a28c539

Please sign in to comment.