Skip to content
This repository has been archived by the owner on Apr 17, 2023. It is now read-only.

Commit

Permalink
Using typeahead when adding users to a team
Browse files Browse the repository at this point in the history
Fixes: #39

Signed-off-by: Jürgen Löhel <[email protected]>
  • Loading branch information
Jürgen Löhel committed Nov 12, 2015
1 parent 99fa347 commit 3d0050e
Show file tree
Hide file tree
Showing 15 changed files with 99 additions and 13 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ gem "crono"
gem "net-ldap"
gem "redcarpet"
gem "font-awesome-rails"
gem "bootstrap-typeahead-rails"

# This is already a Rails dependency, but we use it to run portusctl
gem "thor"
Expand Down
6 changes: 3 additions & 3 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ GEM
bootstrap-sass (3.3.5)
autoprefixer-rails (>= 5.0.0.1)
sass (>= 3.2.19)
bootstrap-typeahead-rails (0.10.5.1)
railties (>= 3.0)
builder (3.2.2)
byebug (5.0.0)
columnize (= 0.9.0)
Expand Down Expand Up @@ -319,6 +321,7 @@ DEPENDENCIES
awesome_print
base32
bootstrap-sass (~> 3.3.4)
bootstrap-typeahead-rails
byebug
capybara
codeclimate-test-reporter
Expand Down Expand Up @@ -365,6 +368,3 @@ DEPENDENCIES
webmock
wirb
wirble

BUNDLED WITH
1.10.5
1 change: 1 addition & 0 deletions app/assets/javascripts/application.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
#= require bootstrap-sprockets
#= require lifeitup_layout
#= require_tree .
#= require bootstrap-typeahead-rails
16 changes: 16 additions & 0 deletions app/assets/javascripts/teams.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,22 @@ $(document).on "page:change", ->
$('#namespace_description').focus()
)

searchSelektor = $('.remote .typeahead')
teamID = $('.remote').attr('id')
bloodhound = new Bloodhound(
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('username'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote:
cache: false,
url: teamID + '/typeahead/%QUERY',
wildcard: '%QUERY'
)
bloodhound.initialize()

$('.remote .typeahead').typeahead null,
displayKey: 'username',
source: bloodhound.ttAdapter()

$('#add_namespace_btn').unbind('click').on 'click', (event) ->
$('#namespace_namespace').val('')

Expand Down
5 changes: 5 additions & 0 deletions app/assets/stylesheets/application.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
// Typeahead
/*
*= require bootstrap-typeahead-rails
*/

// Vendored fonts and styles.
@import "font-awesome";
@import "pacifico";
Expand Down
4 changes: 4 additions & 0 deletions app/assets/stylesheets/forms.scss
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,7 @@ section.sign-up {
min-height: initial;
}
}

.twitter-typeahead, .tt-dropdown-menu {
width: 100%;
}
7 changes: 5 additions & 2 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class ApplicationController < ActionController::Base
include Pundit
rescue_from Pundit::NotAuthorizedError, with: :deny_access

respond_to :html
respond_to :html, :json

# Two things can happen when signing in.
# 1. The current user has no email: this happens on LDAP registration. In
Expand Down Expand Up @@ -83,6 +83,9 @@ def protected_controllers?(*controllers)
# Render the 401 page.
def deny_access
@status = 401
render template: "errors/401", status: 401, layout: "errors"
respond_to do |format|
format.html { render template: "errors/401", status: @status, layout: "errors" }
format.all { render nothing: true, status: @status }
end
end
end
13 changes: 12 additions & 1 deletion app/controllers/teams_controller.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
class TeamsController < ApplicationController
include ChangeDescription

before_action :set_team, only: [:show, :update]
before_action :set_team, only: [:show, :update, :typeahead]
after_action :verify_policy_scoped, only: :index
respond_to :js, :html

Expand Down Expand Up @@ -39,6 +39,17 @@ def update
change_description(@team, :team)
end

# GET /teams/1/typeahead/%QUERY
def typeahead
authorize @team
@query = params[:query]
matches = User.search_from_query(@team.member_ids, "#{@query}%").pluck(:username)
matches = matches.map { |user| { username: user } }
respond_to do |format|
format.json { render json: matches.to_json }
end
end

private

def set_team
Expand Down
5 changes: 5 additions & 0 deletions app/models/team.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,9 @@ def self.all_non_special
# Right now, all the special namespaces are simply marked as hidden.
Team.where(hidden: false)
end

# Returns all the member-IDs
def member_ids
team_users.pluck(:user_id)
end
end
5 changes: 5 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ def inactive_message
"Sorry, this account has been disabled."
end

# Returns all users who match the query.
def self.search_from_query(members, query)
enabled.where.not(id: members).where(arel_table[:username].matches(query))
end

protected

# Returns whether the given user can be disabled or not. The following rules
Expand Down
5 changes: 3 additions & 2 deletions app/policies/team_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ def owner?
user.admin? || @team.owners.exists?(user.id)
end

alias_method :update?, :owner?
alias_method :show?, :member?
alias_method :update?, :owner?
alias_method :show?, :member?
alias_method :typeahead?, :owner?

class Scope
attr_reader :user, :scope
Expand Down
6 changes: 3 additions & 3 deletions app/policies/team_user_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def owner?
user.admin? || @team_user.team.owners.exists?(user.id)
end

alias_method :destroy?, :owner?
alias_method :update?, :owner?
alias_method :create?, :owner?
alias_method :destroy?, :owner?
alias_method :update?, :owner?
alias_method :create?, :owner?
end
3 changes: 2 additions & 1 deletion app/views/teams/show.html.slim
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@
.form-group
= f.label :user, {class: 'control-label col-md-2'}
.col-md-7
= f.text_field(:user, class: 'form-control', placeholder: 'Name', required: true)
.remote id="#{@team.id}"
= f.text_field(:user, class: 'form-control typeahead', placeholder: 'Name', required: true)
.form-group
.col-md-offset-2.col-md-7
= f.submit('Add', class: 'btn btn-primary')
Expand Down
7 changes: 6 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
Rails.application.routes.draw do
resources :errors, only: [:show]
resources :teams, only: [:index, :show, :create, :update]
resources :teams, only: [:index, :show, :create, :update] do
member do
get "typeahead/:query" => "teams#typeahead", :defaults => { format: "json" }
end
end
resources :team_users, only: [:create, :destroy, :update]
# get "teams/typeahead/:id/:query" => "teams#typeahead", :defaults => { format: "json" }
resources :namespaces, only: [:create, :index, :show, :update] do
put "toggle_public", on: :member
end
Expand Down
28 changes: 28 additions & 0 deletions spec/controllers/teams_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
require "rails_helper"

RSpec.describe TeamsController, type: :controller do
render_views

let(:valid_attributes) do
{ name: "qa team", description: "short test description" }
end
Expand Down Expand Up @@ -123,6 +125,32 @@
end
end

describe "typeahead" do
it "does allow to search for valid users by owners" do
sign_in owner
get :typeahead, id: team.id, query: "user", format: "json"
expect(response.status).to eq(200)
user1 = create(:user)
create(:user, username: "user2")
TeamUser.create(team: team, user: user1, role: TeamUser.roles["viewer"])
get :typeahead, id: team.id, query: "user", format: "json"
usernames = JSON.parse(response.body)
expect(usernames.length).to eq(1)
expect(usernames[0]["username"]).to eq("user2")
end

it "does not allow to search by contributers or viewers" do
disallowed_roles = ["viewer", "contributer"]
disallowed_roles.each do |role|
user = create(:user)
TeamUser.create(team: team, user: user, role: TeamUser.roles[role])
sign_in user
get :typeahead, id: team.id, query: "user", format: "js"
expect(response.status).to eq(401)
end
end
end

describe "activity tracking" do
before :each do
sign_in owner
Expand Down

0 comments on commit 3d0050e

Please sign in to comment.