Commit 875728b1184d4524bf96a6de47b329eba7645446

Authored by Dmitriy Zaporozhets
2 parents 77e3fab8 27ad8826

Merge branch 'feature/admin_project_transfer' of /home/git/repositories/gitlab/gitlabhq

CHANGELOG
... ... @@ -17,6 +17,7 @@ v 6.3.0
17 17 - Fix 500 error for repos with newline in file name
18 18 - Extended html titles
19 19 - API: create/update repo files
  20 + - Admin can transfer project to any namespace
20 21  
21 22 v 6.2.0
22 23 - Public project pages are now visible to everyone (files, issues, wik, etc.)
... ...
app/assets/javascripts/api.js.coffee
... ... @@ -2,6 +2,7 @@
2 2 users_path: "/api/:version/users.json"
3 3 user_path: "/api/:version/users/:id.json"
4 4 notes_path: "/api/:version/projects/:id/notes.json"
  5 + namespaces_path: "/api/:version/namespaces.json"
5 6  
6 7 # Get 20 (depends on api) recent notes
7 8 # and sort the ascending from oldest to newest
... ... @@ -49,6 +50,20 @@
49 50 ).done (users) ->
50 51 callback(users)
51 52  
  53 + # Return namespaces list. Filtered by query
  54 + namespaces: (query, callback) ->
  55 + url = Api.buildUrl(Api.namespaces_path)
  56 +
  57 + $.ajax(
  58 + url: url
  59 + data:
  60 + private_token: gon.api_token
  61 + search: query
  62 + per_page: 20
  63 + dataType: "json"
  64 + ).done (namespaces) ->
  65 + callback(namespaces)
  66 +
52 67 buildUrl: (url) ->
53 68 url = gon.relative_url_root + url if gon.relative_url_root?
54 69 return url.replace(':version', gon.api_version)
... ...
app/assets/javascripts/namespace_select.js.coffee 0 → 100644
... ... @@ -0,0 +1,24 @@
  1 +$ ->
  2 + namespaceFormatResult = (namespace) ->
  3 + markup = "<div class='namespace-result'>"
  4 + markup += "<span class='namespace-kind'>" + namespace.kind + "</span>"
  5 + markup += "<span class='namespace-path'>" + namespace.path + "</span>"
  6 + markup += "</div>"
  7 + markup
  8 +
  9 + formatSelection = (namespace) ->
  10 + namespace.kind + ": " + namespace.path
  11 +
  12 + $('.ajax-namespace-select').each (i, select) ->
  13 + $(select).select2
  14 + placeholder: "Search for namespace"
  15 + multiple: $(select).hasClass('multiselect')
  16 + minimumInputLength: 0
  17 + query: (query) ->
  18 + Api.namespaces query.term, (namespaces) ->
  19 + data = { results: namespaces }
  20 + query.callback(data)
  21 +
  22 + dropdownCssClass: "ajax-namespace-dropdown"
  23 + formatResult: namespaceFormatResult
  24 + formatSelection: formatSelection
... ...
app/assets/stylesheets/common.scss
... ... @@ -358,3 +358,33 @@ table {
358 358 background: #555;
359 359 color: #BBB;
360 360 }
  361 +
  362 +.ajax-users-select {
  363 + width: 400px;
  364 +
  365 + &.input-large {
  366 + width: 210px;
  367 + }
  368 +}
  369 +
  370 +.user-result {
  371 + .user-image {
  372 + float: left;
  373 + }
  374 + .user-name {
  375 + }
  376 + .user-username {
  377 + color: #999;
  378 + }
  379 +}
  380 +
  381 +.namespace-result {
  382 + .namespace-kind {
  383 + color: #AAA;
  384 + font-weight: normal;
  385 + }
  386 + .namespace-path {
  387 + margin-left: 10px;
  388 + font-weight: bolder;
  389 + }
  390 +}
... ...
app/assets/stylesheets/selects.scss
1   -.ajax-users-select {
2   - width: 400px;
3   -
4   - &.input-large {
5   - width: 210px;
6   - }
7   -}
8   -
9   -.user-result {
10   - .user-image {
11   - float: left;
12   - }
13   - .user-name {
14   - }
15   - .user-username {
16   - color: #999;
17   - }
18   -}
19   -
20 1 /** Chosen.js selectbox style override **/
21 2 .chosen-container {
22 3 min-width: 100px;
... ...
app/controllers/admin/projects_controller.rb
1 1 class Admin::ProjectsController < Admin::ApplicationController
2   - before_filter :project, only: [:edit, :show, :update, :destroy, :team_update]
  2 + before_filter :project, only: [:show, :transfer]
  3 + before_filter :group, only: [:show, :transfer]
  4 + before_filter :repository, only: [:show, :transfer]
3 5  
4 6 def index
5 7 owner_id = params[:owner_id]
... ... @@ -14,8 +16,16 @@ class Admin::ProjectsController &lt; Admin::ApplicationController
14 16 end
15 17  
16 18 def show
17   - @repository = @project.repository
18   - @group = @project.group
  19 + end
  20 +
  21 + def transfer
  22 + result = ::Projects::TransferContext.new(@project, current_user, project: params).execute(:admin)
  23 +
  24 + if result
  25 + redirect_to [:admin, @project]
  26 + else
  27 + render :show
  28 + end
19 29 end
20 30  
21 31 protected
... ... @@ -26,4 +36,12 @@ class Admin::ProjectsController &lt; Admin::ApplicationController
26 36 @project = Project.find_with_namespace(id)
27 37 @project || render_404
28 38 end
  39 +
  40 + def group
  41 + @group ||= project.group
  42 + end
  43 +
  44 + def repository
  45 + @repository ||= project.repository
  46 + end
29 47 end
... ...
app/helpers/namespaces_helper.rb
... ... @@ -16,4 +16,13 @@ module NamespacesHelper
16 16  
17 17 grouped_options_for_select(options, selected)
18 18 end
  19 +
  20 + def namespace_select_tag(id, opts = {})
  21 + css_class = "ajax-namespace-select "
  22 + css_class << "multiselect " if opts[:multiple]
  23 + css_class << (opts[:class] || '')
  24 + value = opts[:selected] || ''
  25 +
  26 + hidden_field_tag(id, value, class: css_class)
  27 + end
19 28 end
... ...
app/models/namespace.rb
... ... @@ -87,4 +87,8 @@ class Namespace &lt; ActiveRecord::Base
87 87 def send_update_instructions
88 88 projects.each(&:send_move_instructions)
89 89 end
  90 +
  91 + def kind
  92 + type == 'Group' ? 'group' : 'user'
  93 + end
90 94 end
... ...
app/views/admin/projects/show.html.haml
... ... @@ -74,6 +74,23 @@
74 74 %span.cgreen
75 75 %i.icon-lock
76 76 Private
  77 + .ui-box
  78 + .title
  79 + Transfer project
  80 + .ui-box-body
  81 + = form_for @project, url: transfer_admin_project_path(@project), method: :put do |f|
  82 + .control-group
  83 + = f.label :namespace_id, "Namespace"
  84 + .controls
  85 + = namespace_select_tag :namespace_id, selected: params[:namespace_id], class: 'input-large'
  86 +
  87 + .control-group
  88 + .controls
  89 + = f.submit 'Transfer', class: 'btn btn-primary'
  90 +
  91 +
  92 +
  93 +
77 94 .span6
78 95 - if @group
79 96 .ui-box
... ...
config/routes.rb
... ... @@ -89,7 +89,13 @@ Gitlab::Application.routes.draw do
89 89 resources :broadcast_messages, only: [:index, :create, :destroy]
90 90 resource :logs, only: [:show]
91 91 resource :background_jobs, controller: 'background_jobs', only: [:show]
92   - resources :projects, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ }, only: [:index, :show]
  92 +
  93 + resources :projects, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ }, only: [:index, :show] do
  94 + member do
  95 + put :transfer
  96 + end
  97 + end
  98 +
93 99 root to: "dashboard#index"
94 100 end
95 101  
... ...
lib/api/api.rb
... ... @@ -40,5 +40,6 @@ module API
40 40 mount ProjectHooks
41 41 mount Services
42 42 mount Files
  43 + mount Namespaces
43 44 end
44 45 end
... ...
lib/api/entities.rb
... ... @@ -136,5 +136,9 @@ module API
136 136 expose :target_id, :target_type, :author_id
137 137 expose :data, :target_title
138 138 end
  139 +
  140 + class Namespace < Grape::Entity
  141 + expose :id, :path, :kind
  142 + end
139 143 end
140 144 end
... ...
lib/api/namespaces.rb 0 → 100644
... ... @@ -0,0 +1,23 @@
  1 +module API
  2 + # namespaces API
  3 + class Namespaces < Grape::API
  4 + before {
  5 + authenticate!
  6 + authenticated_as_admin!
  7 + }
  8 +
  9 + resource :namespaces do
  10 + # Get a namespaces list
  11 + #
  12 + # Example Request:
  13 + # GET /namespaces
  14 + get do
  15 + @namespaces = Namespace.scoped
  16 + @namespaces = @namespaces.search(params[:search]) if params[:search].present?
  17 + @namespaces = paginate @namespaces
  18 +
  19 + present @namespaces, with: Entities::Namespace
  20 + end
  21 + end
  22 + end
  23 +end
... ...
spec/requests/api/namespaces_spec.rb 0 → 100644
... ... @@ -0,0 +1,31 @@
  1 +require 'spec_helper'
  2 +
  3 +describe API::API do
  4 + include ApiHelpers
  5 + before(:each) { ActiveRecord::Base.observers.enable(:user_observer) }
  6 + after(:each) { ActiveRecord::Base.observers.disable(:user_observer) }
  7 +
  8 + let(:admin) { create(:admin) }
  9 + let!(:group1) { create(:group) }
  10 + let!(:group2) { create(:group) }
  11 +
  12 + describe "GET /namespaces" do
  13 + context "when unauthenticated" do
  14 + it "should return authentication error" do
  15 + get api("/namespaces")
  16 + response.status.should == 401
  17 + end
  18 + end
  19 +
  20 + context "when authenticated as admin" do
  21 + it "admin: should return an array of all namespaces" do
  22 + get api("/namespaces", admin)
  23 + response.status.should == 200
  24 + json_response.should be_an Array
  25 +
  26 + # Admin namespace + 2 group namespaces
  27 + json_response.length.should == 3
  28 + end
  29 + end
  30 + end
  31 +end
... ...