FogBugz project import
This commit is contained in:
parent
86556a079e
commit
e156f42079
29 changed files with 889 additions and 16 deletions
|
@ -47,6 +47,7 @@ v 7.14.1
|
|||
- Only include base URL in OmniAuth full_host parameter (Stan Hu)
|
||||
- Fix Error 500 in API when accessing a group that has an avatar (Stan Hu)
|
||||
- Ability to enable SSL verification for Webhooks
|
||||
- Add FogBugz project import (Jared Szechy)
|
||||
|
||||
v 7.14.0
|
||||
- Fix bug where non-project members of the target project could set labels on new merge requests.
|
||||
|
|
4
Gemfile
4
Gemfile
|
@ -157,6 +157,9 @@ gem "slack-notifier", "~> 1.0.0"
|
|||
# Asana integration
|
||||
gem 'asana', '~> 0.0.6'
|
||||
|
||||
# FogBugz integration
|
||||
gem 'ruby-fogbugz'
|
||||
|
||||
# d3
|
||||
gem 'd3_rails', '~> 3.5.5'
|
||||
|
||||
|
@ -259,6 +262,7 @@ group :test do
|
|||
gem 'email_spec', '~> 1.6.0'
|
||||
gem 'webmock', '~> 1.21.0'
|
||||
gem 'test_after_commit'
|
||||
gem 'sham_rack'
|
||||
end
|
||||
|
||||
group :production do
|
||||
|
|
|
@ -575,6 +575,8 @@ GEM
|
|||
powerpack (~> 0.0.6)
|
||||
rainbow (>= 1.99.1, < 3.0)
|
||||
ruby-progressbar (~> 1.4)
|
||||
ruby-fogbugz (0.1.1)
|
||||
crack
|
||||
ruby-progressbar (1.7.1)
|
||||
ruby-saml (1.0.0)
|
||||
nokogiri (>= 1.5.10)
|
||||
|
@ -609,6 +611,8 @@ GEM
|
|||
thor (~> 0.14)
|
||||
settingslogic (2.0.9)
|
||||
sexp_processor (4.4.5)
|
||||
sham_rack (1.3.6)
|
||||
rack
|
||||
shoulda-matchers (2.8.0)
|
||||
activesupport (>= 3.0.0)
|
||||
sidekiq (3.3.0)
|
||||
|
@ -845,12 +849,14 @@ DEPENDENCIES
|
|||
rqrcode-rails3
|
||||
rspec-rails (~> 3.3.0)
|
||||
rubocop (= 0.28.0)
|
||||
ruby-fogbugz
|
||||
sanitize (~> 2.0)
|
||||
sass-rails (~> 4.0.5)
|
||||
sdoc
|
||||
seed-fu
|
||||
select2-rails (~> 3.5.9)
|
||||
settingslogic
|
||||
sham_rack
|
||||
shoulda-matchers (~> 2.8.0)
|
||||
sidekiq (~> 3.3)
|
||||
sidetiq (= 0.6.3)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
require 'gon'
|
||||
require 'fogbugz'
|
||||
|
||||
class ApplicationController < ActionController::Base
|
||||
include Gitlab::CurrentSettings
|
||||
|
@ -20,7 +21,7 @@ class ApplicationController < ActionController::Base
|
|||
protect_from_forgery with: :exception
|
||||
|
||||
helper_method :abilities, :can?, :current_application_settings
|
||||
helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :git_import_enabled?
|
||||
helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled?
|
||||
|
||||
rescue_from Encoding::CompatibilityError do |exception|
|
||||
log_exception(exception)
|
||||
|
@ -337,6 +338,10 @@ class ApplicationController < ActionController::Base
|
|||
current_application_settings.import_sources.include?('google_code')
|
||||
end
|
||||
|
||||
def fogbugz_import_enabled?
|
||||
current_application_settings.import_sources.include?('fogbugz')
|
||||
end
|
||||
|
||||
def git_import_enabled?
|
||||
current_application_settings.import_sources.include?('git')
|
||||
end
|
||||
|
|
106
app/controllers/import/fogbugz_controller.rb
Normal file
106
app/controllers/import/fogbugz_controller.rb
Normal file
|
@ -0,0 +1,106 @@
|
|||
class Import::FogbugzController < Import::BaseController
|
||||
before_action :verify_fogbugz_import_enabled
|
||||
before_action :user_map, only: [:new_user_map, :create_user_map]
|
||||
|
||||
# Doesn't work yet due to bug in ruby-fogbugz, see below
|
||||
rescue_from Fogbugz::AuthenticationException, with: :fogbugz_unauthorized
|
||||
|
||||
def new
|
||||
|
||||
end
|
||||
|
||||
def callback
|
||||
begin
|
||||
res = Gitlab::FogbugzImport::Client.new(import_params.symbolize_keys)
|
||||
rescue
|
||||
# Needed until https://github.com/firmafon/ruby-fogbugz/pull/9 is merged
|
||||
return redirect_to :back, alert: 'Could not authenticate with FogBugz, check your URL, email, and password'
|
||||
end
|
||||
session[:fogbugz_token] = res.get_token
|
||||
session[:fogbugz_uri] = params[:uri]
|
||||
|
||||
redirect_to new_user_map_import_fogbugz_path
|
||||
end
|
||||
|
||||
def new_user_map
|
||||
|
||||
end
|
||||
|
||||
def create_user_map
|
||||
user_map = params[:users]
|
||||
|
||||
unless user_map.is_a?(Hash) && user_map.all? { |k, v| !v[:name].blank? }
|
||||
flash.now[:alert] = 'All users must have a name.'
|
||||
|
||||
render 'new_user_map' and return
|
||||
end
|
||||
|
||||
session[:fogbugz_user_map] = user_map
|
||||
|
||||
flash[:notice] = 'The user map has been saved. Continue by selecting the projects you want to import.'
|
||||
|
||||
redirect_to status_import_fogbugz_path
|
||||
end
|
||||
|
||||
def status
|
||||
unless client.valid?
|
||||
return redirect_to new_import_fogbugz_path
|
||||
end
|
||||
|
||||
@repos = client.repos
|
||||
|
||||
@already_added_projects = current_user.created_projects.where(import_type: 'fogbugz')
|
||||
already_added_projects_names = @already_added_projects.pluck(:import_source)
|
||||
|
||||
@repos.reject! { |repo| already_added_projects_names.include? repo.name }
|
||||
end
|
||||
|
||||
def jobs
|
||||
jobs = current_user.created_projects.where(import_type: 'fogbugz').to_json(only: [:id, :import_status])
|
||||
render json: jobs
|
||||
end
|
||||
|
||||
def create
|
||||
@repo_id = params[:repo_id]
|
||||
repo = client.repo(@repo_id)
|
||||
fb_session = { uri: session[:fogbugz_uri], token: session[:fogbugz_token] }
|
||||
@target_namespace = current_user.namespace
|
||||
@project_name = repo.name
|
||||
|
||||
namespace = @target_namespace
|
||||
|
||||
umap = session[:fogbugz_user_map] || client.user_map
|
||||
|
||||
@project = Gitlab::FogbugzImport::ProjectCreator.new(repo, fb_session, namespace, current_user, umap).execute
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def client
|
||||
@client ||= Gitlab::FogbugzImport::Client.new(token: session[:fogbugz_token], uri: session[:fogbugz_uri])
|
||||
end
|
||||
|
||||
def user_map
|
||||
@user_map ||= begin
|
||||
user_map = client.user_map
|
||||
|
||||
stored_user_map = session[:fogbugz_user_map]
|
||||
user_map.update(stored_user_map) if stored_user_map
|
||||
|
||||
user_map
|
||||
end
|
||||
end
|
||||
|
||||
def fogbugz_unauthorized(exception)
|
||||
flash[:alert] = exception.message
|
||||
redirect_to new_import_fogbugz_path
|
||||
end
|
||||
|
||||
def import_params
|
||||
params.permit(:uri, :email, :password)
|
||||
end
|
||||
|
||||
def verify_fogbugz_import_enabled
|
||||
not_found! unless fogbugz_import_enabled?
|
||||
end
|
||||
end
|
|
@ -83,7 +83,7 @@ class ApplicationSetting < ActiveRecord::Base
|
|||
default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
|
||||
default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
|
||||
restricted_signup_domains: Settings.gitlab['restricted_signup_domains'],
|
||||
import_sources: ['github','bitbucket','gitlab','gitorious','google_code','git']
|
||||
import_sources: ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git']
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -43,6 +43,8 @@ class Project < ActiveRecord::Base
|
|||
extend Gitlab::ConfigHelper
|
||||
extend Enumerize
|
||||
|
||||
UNKNOWN_IMPORT_URL = 'http://unknown.git'
|
||||
|
||||
default_value_for :archived, false
|
||||
default_value_for :visibility_level, gitlab_config_features.visibility_level
|
||||
default_value_for :issues_enabled, gitlab_config_features.issues
|
||||
|
|
43
app/services/projects/download_service.rb
Normal file
43
app/services/projects/download_service.rb
Normal file
|
@ -0,0 +1,43 @@
|
|||
module Projects
|
||||
class DownloadService < BaseService
|
||||
|
||||
WHITELIST = [
|
||||
/^[^.]+\.fogbugz.com$/
|
||||
]
|
||||
|
||||
def initialize(project, url)
|
||||
@project, @url = project, url
|
||||
end
|
||||
|
||||
def execute
|
||||
return nil unless valid_url?(@url)
|
||||
|
||||
uploader = FileUploader.new(@project)
|
||||
uploader.download!(@url)
|
||||
uploader.store!
|
||||
|
||||
filename = uploader.image? ? uploader.file.basename : uploader.file.filename
|
||||
|
||||
{
|
||||
'alt' => filename,
|
||||
'url' => uploader.secure_url,
|
||||
'is_image' => uploader.image?
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def valid_url?(url)
|
||||
url && http?(url) && valid_domain?(url)
|
||||
end
|
||||
|
||||
def http?(url)
|
||||
url =~ /\A#{URI::regexp(['http', 'https'])}\z/
|
||||
end
|
||||
|
||||
def valid_domain?(url)
|
||||
host = URI.parse(url).host
|
||||
WHITELIST.any? { |entry| entry === host }
|
||||
end
|
||||
end
|
||||
end
|
25
app/views/import/fogbugz/new.html.haml
Normal file
25
app/views/import/fogbugz/new.html.haml
Normal file
|
@ -0,0 +1,25 @@
|
|||
- page_title "FogBugz Import"
|
||||
%h3.page-title
|
||||
%i.fa.fa-bug
|
||||
Import projects from FogBugz
|
||||
%hr
|
||||
|
||||
= form_tag callback_import_fogbugz_path, class: 'form-horizontal' do
|
||||
%p
|
||||
To get started you enter your FogBugz URL and login information below.
|
||||
In the next steps, you'll be able to map users and select the projects
|
||||
you want to import.
|
||||
.form-group
|
||||
= label_tag :uri, 'FogBugz URL', class: 'control-label'
|
||||
.col-sm-4
|
||||
= text_field_tag :uri, nil, placeholder: 'https://mycompany.fogbugz.com', class: 'form-control'
|
||||
.form-group
|
||||
= label_tag :email, 'FogBugz Email', class: 'control-label'
|
||||
.col-sm-4
|
||||
= text_field_tag :email, nil, class: 'form-control'
|
||||
.form-group
|
||||
= label_tag :password, 'FogBugz Password', class: 'control-label'
|
||||
.col-sm-4
|
||||
= password_field_tag :password, nil, class: 'form-control'
|
||||
.form-actions
|
||||
= submit_tag 'Continue to the next step', class: 'btn btn-create'
|
49
app/views/import/fogbugz/new_user_map.html.haml
Normal file
49
app/views/import/fogbugz/new_user_map.html.haml
Normal file
|
@ -0,0 +1,49 @@
|
|||
- page_title 'User map', 'FogBugz import'
|
||||
%h3.page-title
|
||||
%i.fa.fa-bug
|
||||
Import projects from FogBugz
|
||||
%hr
|
||||
|
||||
= form_tag create_user_map_import_fogbugz_path, class: 'form-horizontal' do
|
||||
%p
|
||||
Customize how FogBugz email addresses and usernames are imported into GitLab.
|
||||
In the next step, you'll be able to select the projects you want to import.
|
||||
%p
|
||||
The user map is a mapping of the FogBugz users that participated on your projects to the way their email address and usernames wil be imported into GitLab. You can change this by populating the table below.
|
||||
%ul
|
||||
%li
|
||||
%strong Default: Map a FogBugz account ID to a full name
|
||||
%p
|
||||
An empty GitLab User field will add the FogBugz user's full name
|
||||
(e.g. "By John Smith") in the description of all issues and comments.
|
||||
It will also associate and/or assign these issues and comments with
|
||||
the project creator.
|
||||
%li
|
||||
%strong Map a FogBugz account ID to a GitLab user
|
||||
%p
|
||||
Selecting a GitLab user will add a link to the GitLab user in the descriptions
|
||||
of issues and comments (e.g. "By <a href="#">@johnsmith</a>"). It will also
|
||||
associate and/or assign these issues and comments with the selected user.
|
||||
|
||||
%table.table
|
||||
%thead
|
||||
%tr
|
||||
%th ID
|
||||
%th Name
|
||||
%th Email
|
||||
%th GitLab User
|
||||
%tbody
|
||||
- @user_map.each do |id, user|
|
||||
%tr
|
||||
%td= id
|
||||
%td= text_field_tag "users[#{id}][name]", user[:name], class: 'form-control'
|
||||
%td= text_field_tag "users[#{id}][email]", user[:email], class: 'form-control'
|
||||
%td
|
||||
= users_select_tag("users[#{id}][gitlab_user]", class: 'custom-form-control',
|
||||
scope: :all, email_user: true, selected: user[:gitlab_user])
|
||||
|
||||
.form-actions
|
||||
= submit_tag 'Continue to the next step', class: 'btn btn-create'
|
||||
|
||||
:coffeescript
|
||||
new UsersSelect()
|
51
app/views/import/fogbugz/status.html.haml
Normal file
51
app/views/import/fogbugz/status.html.haml
Normal file
|
@ -0,0 +1,51 @@
|
|||
- page_title "FogBugz import"
|
||||
%h3.page-title
|
||||
%i.fa.fa-bug
|
||||
Import projects from FogBugz
|
||||
|
||||
- if @repos.any?
|
||||
%p.light
|
||||
Select projects you want to import.
|
||||
%p.light
|
||||
Optionally, you can
|
||||
= link_to 'customize', new_user_map_import_fogbugz_path
|
||||
how FogBugz email addresses and usernames are imported into GitLab.
|
||||
%hr
|
||||
%p
|
||||
= button_tag 'Import all projects', class: 'btn btn-success js-import-all'
|
||||
|
||||
%table.table.import-jobs
|
||||
%thead
|
||||
%tr
|
||||
%th From FogBugz
|
||||
%th To GitLab
|
||||
%th Status
|
||||
%tbody
|
||||
- @already_added_projects.each do |project|
|
||||
%tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
|
||||
%td
|
||||
= project.import_source
|
||||
%td
|
||||
%strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
|
||||
%td.job-status
|
||||
- if project.import_status == 'finished'
|
||||
%span
|
||||
%i.fa.fa-check
|
||||
done
|
||||
- elsif project.import_status == 'started'
|
||||
%i.fa.fa-spinner.fa-spin
|
||||
started
|
||||
- else
|
||||
= project.human_import_status_name
|
||||
|
||||
- @repos.each do |repo|
|
||||
%tr{id: "repo_#{repo.id}"}
|
||||
%td
|
||||
= repo.name
|
||||
%td.import-target
|
||||
= "#{current_user.username}/#{repo.name}"
|
||||
%td.import-actions.job-status
|
||||
= button_tag "Import", class: "btn js-add-to-import"
|
||||
|
||||
:coffeescript
|
||||
new ImporterStatus("#{jobs_import_fogbugz_path}", "#{import_fogbugz_path}")
|
|
@ -72,6 +72,11 @@
|
|||
%i.fa.fa-google
|
||||
Google Code
|
||||
|
||||
- if fogbugz_import_enabled?
|
||||
= link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do
|
||||
%i.fa.fa-bug
|
||||
Fogbugz
|
||||
|
||||
- if git_import_enabled?
|
||||
= link_to "#", class: 'btn js-toggle-button import_git' do
|
||||
%i.fa.fa-git
|
||||
|
|
|
@ -7,22 +7,31 @@ class RepositoryImportWorker
|
|||
def perform(project_id)
|
||||
project = Project.find(project_id)
|
||||
|
||||
import_result = gitlab_shell.send(:import_repository,
|
||||
unless project.import_url == Project::UNKNOWN_IMPORT_URL
|
||||
import_result = gitlab_shell.send(:import_repository,
|
||||
project.path_with_namespace,
|
||||
project.import_url)
|
||||
return project.import_fail unless import_result
|
||||
return project.import_fail unless import_result
|
||||
else
|
||||
unless project.create_repository
|
||||
return project.import_fail
|
||||
end
|
||||
end
|
||||
|
||||
data_import_result = if project.import_type == 'github'
|
||||
Gitlab::GithubImport::Importer.new(project).execute
|
||||
elsif project.import_type == 'gitlab'
|
||||
Gitlab::GitlabImport::Importer.new(project).execute
|
||||
elsif project.import_type == 'bitbucket'
|
||||
Gitlab::BitbucketImport::Importer.new(project).execute
|
||||
elsif project.import_type == 'google_code'
|
||||
Gitlab::GoogleCodeImport::Importer.new(project).execute
|
||||
else
|
||||
true
|
||||
end
|
||||
data_import_result = case project.import_type
|
||||
when 'github'
|
||||
Gitlab::GithubImport::Importer.new(project).execute
|
||||
when 'gitlab'
|
||||
Gitlab::GitlabImport::Importer.new(project).execute
|
||||
when 'bitbucket'
|
||||
Gitlab::BitbucketImport::Importer.new(project).execute
|
||||
when 'google_code'
|
||||
Gitlab::GoogleCodeImport::Importer.new(project).execute
|
||||
when 'fogbugz'
|
||||
Gitlab::FogbugzImport::Importer.new(project).execute
|
||||
else
|
||||
true
|
||||
end
|
||||
return project.import_fail unless data_import_result
|
||||
|
||||
Gitlab::BitbucketImport::KeyDeleter.new(project).execute if project.import_type == 'bitbucket'
|
||||
|
|
|
@ -158,7 +158,7 @@ Settings.gitlab.default_projects_features['snippets'] = false if Settings.
|
|||
Settings.gitlab.default_projects_features['visibility_level'] = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE)
|
||||
Settings.gitlab['repository_downloads_path'] = File.absolute_path(Settings.gitlab['repository_downloads_path'] || 'tmp/repositories', Rails.root)
|
||||
Settings.gitlab['restricted_signup_domains'] ||= []
|
||||
Settings.gitlab['import_sources'] ||= ['github','bitbucket','gitlab','gitorious','google_code','git']
|
||||
Settings.gitlab['import_sources'] ||= ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git']
|
||||
|
||||
#
|
||||
# Reply by email
|
||||
|
|
|
@ -99,6 +99,15 @@ Gitlab::Application.routes.draw do
|
|||
get :new_user_map, path: :user_map
|
||||
post :create_user_map, path: :user_map
|
||||
end
|
||||
|
||||
resource :fogbugz, only: [:create, :new], controller: :fogbugz do
|
||||
get :status
|
||||
post :callback
|
||||
get :jobs
|
||||
|
||||
get :new_user_map, path: :user_map
|
||||
post :create_user_map, path: :user_map
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
1. [Bitbucket](import_projects_from_bitbucket.md)
|
||||
2. [GitHub](import_projects_from_github.md)
|
||||
3. [GitLab.com](import_projects_from_gitlab_com.md)
|
||||
4. [FogBugz](import_projects_from_fogbugz.md)
|
||||
4. [SVN](migrating_from_svn.md)
|
||||
|
||||
### Note
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 52 KiB |
BIN
doc/workflow/importing/fogbugz_importer/fogbugz_import_login.png
Normal file
BIN
doc/workflow/importing/fogbugz_importer/fogbugz_import_login.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 43 KiB |
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
Binary file not shown.
After Width: | Height: | Size: 61 KiB |
Binary file not shown.
After Width: | Height: | Size: 154 KiB |
29
doc/workflow/importing/import_projects_from_fogbugz.md
Normal file
29
doc/workflow/importing/import_projects_from_fogbugz.md
Normal file
|
@ -0,0 +1,29 @@
|
|||
# Import your project from FogBugz to GitLab
|
||||
|
||||
It only takes a few simple steps to import your project from FogBugz.
|
||||
The importer will import all of your cases and comments with original case
|
||||
numbers and timestamps. You will also have the opportunity to map FogBugz
|
||||
users to GitLab users.
|
||||
|
||||
* From your GitLab dashboard click 'New project'
|
||||
|
||||
* Click on the 'FogBugz' button
|
||||
|
||||
![FogBugz](fogbugz_importer/fogbugz_import_select_fogbogz.png)
|
||||
|
||||
* Enter your FogBugz URL, email address, and password.
|
||||
|
||||
![Login](fogbugz_importer/fogbugz_import_login.png)
|
||||
|
||||
* Create mapping from FogBugz users to GitLab users.
|
||||
|
||||
![User Map](fogbugz_importer/fogbugz_import_user_map.png)
|
||||
|
||||
* Select the projects you wish to import by clicking the Import buttons
|
||||
|
||||
![Import Project](fogbugz_importer/fogbugz_import_select_project.png)
|
||||
|
||||
* Once the import has finished click the link to take you to the project
|
||||
dashboard. Follow the directions to push your existing repository.
|
||||
|
||||
![Finished](fogbugz_importer/fogbugz_import_finished.png)
|
56
lib/gitlab/fogbugz_import/client.rb
Normal file
56
lib/gitlab/fogbugz_import/client.rb
Normal file
|
@ -0,0 +1,56 @@
|
|||
require 'fogbugz'
|
||||
|
||||
module Gitlab
|
||||
module FogbugzImport
|
||||
class Client
|
||||
attr_reader :api
|
||||
|
||||
def initialize(options = {})
|
||||
if options[:uri] && options[:token]
|
||||
@api = ::Fogbugz::Interface.new(options)
|
||||
elsif options[:uri] && options[:email] && options[:password]
|
||||
@api = ::Fogbugz::Interface.new(options)
|
||||
@api.authenticate
|
||||
@api
|
||||
end
|
||||
end
|
||||
|
||||
def get_token
|
||||
@api.token
|
||||
end
|
||||
|
||||
def valid?
|
||||
!get_token.blank?
|
||||
end
|
||||
|
||||
def user_map
|
||||
users = {}
|
||||
res = @api.command(:listPeople)
|
||||
res['people']['person'].each do |user|
|
||||
users[user['ixPerson']] = { name: user['sFullName'], email: user['sEmail'] }
|
||||
end
|
||||
users
|
||||
end
|
||||
|
||||
def repos
|
||||
res = @api.command(:listProjects)
|
||||
@repos ||= res['projects']['project'].map { |proj| FogbugzImport::Repository.new(proj) }
|
||||
end
|
||||
|
||||
def repo(id)
|
||||
repos.find { |r| r.id.to_s == id.to_s }
|
||||
end
|
||||
|
||||
def cases(project_id)
|
||||
project_name = repo(project_id).name
|
||||
res = @api.command(:search, q: "project:'#{project_name}'", cols: 'ixPersonAssignedTo,ixPersonOpenedBy,ixPersonClosedBy,sStatus,sPriority,sCategory,fOpen,sTitle,sLatestTextSummary,dtOpened,dtClosed,dtResolved,dtLastUpdated,events')
|
||||
return [] unless res['cases']['count'].to_i > 0
|
||||
res['cases']['case']
|
||||
end
|
||||
|
||||
def categories
|
||||
@api.command(:listCategories)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
298
lib/gitlab/fogbugz_import/importer.rb
Normal file
298
lib/gitlab/fogbugz_import/importer.rb
Normal file
|
@ -0,0 +1,298 @@
|
|||
module Gitlab
|
||||
module FogbugzImport
|
||||
class Importer
|
||||
attr_reader :project, :repo
|
||||
|
||||
def initialize(project)
|
||||
@project = project
|
||||
|
||||
import_data = project.import_data.try(:data)
|
||||
repo_data = import_data['repo'] if import_data
|
||||
@repo = FogbugzImport::Repository.new(repo_data)
|
||||
|
||||
@known_labels = Set.new
|
||||
end
|
||||
|
||||
def execute
|
||||
return true unless repo.valid?
|
||||
|
||||
data = project.import_data.try(:data)
|
||||
|
||||
client = Gitlab::FogbugzImport::Client.new(token: data['fb_session']['token'], uri: data['fb_session']['uri'])
|
||||
|
||||
@cases = client.cases(@repo.id.to_i)
|
||||
@categories = client.categories
|
||||
|
||||
import_cases
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def user_map
|
||||
@user_map ||= begin
|
||||
user_map = Hash.new
|
||||
import_data = project.import_data.try(:data)
|
||||
stored_user_map = import_data['user_map'] if import_data
|
||||
user_map.update(stored_user_map) if stored_user_map
|
||||
|
||||
user_map
|
||||
end
|
||||
end
|
||||
|
||||
def import_labels
|
||||
@categories['categories']['category'].each do |label|
|
||||
create_label(label['sCategory'])
|
||||
@known_labels << name
|
||||
end
|
||||
end
|
||||
|
||||
def nice_label_color(name)
|
||||
case name
|
||||
when 'Blocker'
|
||||
'#ff0000'
|
||||
when 'Crash'
|
||||
'#ffcfcf'
|
||||
when 'Major'
|
||||
'#deffcf'
|
||||
when 'Minor'
|
||||
'#cfe9ff'
|
||||
when 'Bug'
|
||||
'#d9534f'
|
||||
when 'Feature'
|
||||
'#44ad8e'
|
||||
when 'Technical Task'
|
||||
'#4b6dd0'
|
||||
else
|
||||
'#e2e2e2'
|
||||
end
|
||||
end
|
||||
|
||||
def create_label(name)
|
||||
color = nice_label_color(name)
|
||||
Label.create!(project_id: project.id, title: name, color: color)
|
||||
end
|
||||
|
||||
def user_info(person_id)
|
||||
user_hash = user_map[person_id.to_s]
|
||||
|
||||
user_name = ''
|
||||
gitlab_id = nil
|
||||
|
||||
unless user_hash.nil?
|
||||
user_name = user_hash['name']
|
||||
if user = User.find_by(id: user_hash['gitlab_user'])
|
||||
user_name = "@#{user.username}"
|
||||
gitlab_id = user.id
|
||||
end
|
||||
end
|
||||
|
||||
{ name: user_name, gitlab_id: gitlab_id }
|
||||
end
|
||||
|
||||
def import_cases
|
||||
return unless @cases
|
||||
|
||||
while bug = @cases.shift
|
||||
author = user_info(bug['ixPersonOpenedBy'])[:name]
|
||||
date = DateTime.parse(bug['dtOpened'])
|
||||
|
||||
comments = bug['events']['event']
|
||||
|
||||
content = format_content(opened_content(comments))
|
||||
body = format_issue_body(author, date, content)
|
||||
|
||||
labels = []
|
||||
[bug['sCategory'], bug['sPriority']].each do |label|
|
||||
unless label.blank?
|
||||
labels << label
|
||||
unless @known_labels.include?(label)
|
||||
create_label(label)
|
||||
@known_labels << label
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
assignee_id = user_info(bug['ixPersonAssignedTo'])[:gitlab_id]
|
||||
author_id = user_info(bug['ixPersonOpenedBy'])[:gitlab_id] || project.creator_id
|
||||
|
||||
issue = Issue.create!(
|
||||
project_id: project.id,
|
||||
title: bug['sTitle'],
|
||||
description: body,
|
||||
author_id: author_id,
|
||||
assignee_id: assignee_id,
|
||||
state: bug['fOpen'] == 'true' ? 'opened' : 'closed'
|
||||
)
|
||||
issue.add_labels_by_names(labels)
|
||||
|
||||
if issue.iid != bug['ixBug']
|
||||
issue.update_attribute(:iid, bug['ixBug'])
|
||||
end
|
||||
|
||||
import_issue_comments(issue, comments)
|
||||
|
||||
issue.update_attribute(:created_at, date)
|
||||
|
||||
last_update = DateTime.parse(bug['dtLastUpdated'])
|
||||
issue.update_attribute(:updated_at, last_update)
|
||||
end
|
||||
end
|
||||
|
||||
def opened_content(comments)
|
||||
while comment = comments.shift
|
||||
if comment['sVerb'] == 'Opened'
|
||||
return comment['s']
|
||||
end
|
||||
end
|
||||
''
|
||||
end
|
||||
|
||||
def import_issue_comments(issue, comments)
|
||||
Note.transaction do
|
||||
while comment = comments.shift
|
||||
verb = comment['sVerb']
|
||||
|
||||
next if verb == 'Opened' || verb === 'Closed'
|
||||
|
||||
content = format_content(comment['s'])
|
||||
attachments = format_attachments(comment['rgAttachments'])
|
||||
updates = format_updates(comment)
|
||||
|
||||
next if content.blank? && attachments.empty? && updates.empty?
|
||||
|
||||
author = user_info(comment['ixPerson'])[:name]
|
||||
author_id = user_info(comment['ixPerson'])[:gitlab_id] || project.creator_id
|
||||
date = DateTime.parse(comment['dt'])
|
||||
|
||||
body = format_issue_comment_body(
|
||||
comment['ixBugEvent'],
|
||||
author,
|
||||
date,
|
||||
content,
|
||||
attachments,
|
||||
updates
|
||||
)
|
||||
|
||||
note = Note.create!(
|
||||
project_id: project.id,
|
||||
noteable_type: "Issue",
|
||||
noteable_id: issue.id,
|
||||
author_id: author_id,
|
||||
note: body
|
||||
)
|
||||
|
||||
note.update_attribute(:created_at, date)
|
||||
note.update_attribute(:updated_at, date)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def linkify_issues(s)
|
||||
s = s.gsub(/([Ii]ssue) ([0-9]+)/, '\1 #\2')
|
||||
s = s.gsub(/([Cc]ase) ([0-9]+)/, '\1 #\2')
|
||||
s
|
||||
end
|
||||
|
||||
def escape_for_markdown(s)
|
||||
s = s.gsub(/^#/, "\\#")
|
||||
s = s.gsub(/^-/, "\\-")
|
||||
s = s.gsub("`", "\\~")
|
||||
s = s.gsub("\r", "")
|
||||
s = s.gsub("\n", " \n")
|
||||
s
|
||||
end
|
||||
|
||||
def format_content(raw_content)
|
||||
return raw_content if raw_content.nil?
|
||||
linkify_issues(escape_for_markdown(raw_content))
|
||||
end
|
||||
|
||||
def format_attachments(raw_attachments)
|
||||
return [] unless raw_attachments
|
||||
|
||||
attachments = case raw_attachments['attachment']
|
||||
when Array
|
||||
raw_attachments['attachment']
|
||||
when Hash
|
||||
[raw_attachments['attachment']]
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
attachments.map! { |a| format_attachment(a) }
|
||||
attachments.compact
|
||||
end
|
||||
|
||||
def format_attachment(attachment)
|
||||
link = build_attachment_url(attachment['sURL'])
|
||||
|
||||
res = ::Projects::DownloadService.new(project, link).execute
|
||||
|
||||
return nil if res.nil?
|
||||
|
||||
text = "[#{res['alt']}](#{res['url']})"
|
||||
text = "!#{text}" if res['is_image']
|
||||
text
|
||||
end
|
||||
|
||||
def build_attachment_url(rel_url)
|
||||
data = project.import_data.try(:data)
|
||||
uri = data['fb_session']['uri']
|
||||
token = data['fb_session']['token']
|
||||
"#{uri}/#{rel_url}&token=#{token}"
|
||||
end
|
||||
|
||||
def format_updates(comment)
|
||||
updates = []
|
||||
|
||||
if comment['sChanges']
|
||||
updates << "*Changes: #{linkify_issues(comment['sChanges'].chomp)}*"
|
||||
end
|
||||
|
||||
if comment['evtDescription']
|
||||
updates << "*#{comment['evtDescription']}*"
|
||||
end
|
||||
|
||||
updates
|
||||
end
|
||||
|
||||
def format_issue_body(author, date, content)
|
||||
body = []
|
||||
body << "*By #{author} on #{date} (imported from FogBugz)*"
|
||||
body << '---'
|
||||
|
||||
if content.blank?
|
||||
content = '*(No description has been entered for this issue)*'
|
||||
end
|
||||
body << content
|
||||
|
||||
body.join("\n\n")
|
||||
end
|
||||
|
||||
def format_issue_comment_body(id, author, date, content, attachments, updates)
|
||||
body = []
|
||||
body << "*By #{author} on #{date} (imported from FogBugz)*"
|
||||
body << '---'
|
||||
|
||||
if content.blank?
|
||||
content = "*(No comment has been entered for this change)*"
|
||||
end
|
||||
body << content
|
||||
|
||||
if updates.any?
|
||||
body << '---'
|
||||
body += updates
|
||||
end
|
||||
|
||||
if attachments.any?
|
||||
body << '---'
|
||||
body += attachments
|
||||
end
|
||||
|
||||
body.join("\n\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
38
lib/gitlab/fogbugz_import/project_creator.rb
Normal file
38
lib/gitlab/fogbugz_import/project_creator.rb
Normal file
|
@ -0,0 +1,38 @@
|
|||
module Gitlab
|
||||
module FogbugzImport
|
||||
class ProjectCreator
|
||||
attr_reader :repo, :fb_session, :namespace, :current_user, :user_map
|
||||
|
||||
def initialize(repo, fb_session, namespace, current_user, user_map = nil)
|
||||
@repo = repo
|
||||
@fb_session = fb_session
|
||||
@namespace = namespace
|
||||
@current_user = current_user
|
||||
@user_map = user_map
|
||||
end
|
||||
|
||||
def execute
|
||||
project = ::Projects::CreateService.new(current_user,
|
||||
name: repo.safe_name,
|
||||
path: repo.path,
|
||||
namespace: namespace,
|
||||
creator: current_user,
|
||||
visibility_level: Gitlab::VisibilityLevel::INTERNAL,
|
||||
import_type: 'fogbugz',
|
||||
import_source: repo.name,
|
||||
import_url: Project::UNKNOWN_IMPORT_URL
|
||||
).execute
|
||||
|
||||
import_data = project.create_import_data(
|
||||
data: {
|
||||
'repo' => repo.raw_data,
|
||||
'user_map' => user_map,
|
||||
'fb_session' => fb_session
|
||||
}
|
||||
)
|
||||
|
||||
project
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
31
lib/gitlab/fogbugz_import/repository.rb
Normal file
31
lib/gitlab/fogbugz_import/repository.rb
Normal file
|
@ -0,0 +1,31 @@
|
|||
module Gitlab
|
||||
module FogbugzImport
|
||||
class Repository
|
||||
attr_accessor :raw_data
|
||||
|
||||
def initialize(raw_data)
|
||||
@raw_data = raw_data
|
||||
end
|
||||
|
||||
def valid?
|
||||
raw_data.is_a?(Hash)
|
||||
end
|
||||
|
||||
def id
|
||||
raw_data['ixProject']
|
||||
end
|
||||
|
||||
def name
|
||||
raw_data['sProject']
|
||||
end
|
||||
|
||||
def safe_name
|
||||
name.gsub(/[^\s\w.-]/, '')
|
||||
end
|
||||
|
||||
def path
|
||||
safe_name.gsub(/[\s]/, '_')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -19,6 +19,7 @@ module Gitlab
|
|||
'GitLab.com' => 'gitlab',
|
||||
'Gitorious.org' => 'gitorious',
|
||||
'Google Code' => 'google_code',
|
||||
'FogBugz' => 'fogbugz',
|
||||
'Any repo by URL' => 'git',
|
||||
}
|
||||
end
|
||||
|
|
39
spec/controllers/import/fogbugz_controller_spec.rb
Normal file
39
spec/controllers/import/fogbugz_controller_spec.rb
Normal file
|
@ -0,0 +1,39 @@
|
|||
require 'spec_helper'
|
||||
require_relative 'import_spec_helper'
|
||||
|
||||
describe Import::FogbugzController do
|
||||
include ImportSpecHelper
|
||||
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
describe 'GET status' do
|
||||
before do
|
||||
@repo = OpenStruct.new(name: 'vim')
|
||||
stub_client(valid?: true)
|
||||
end
|
||||
|
||||
it 'assigns variables' do
|
||||
@project = create(:project, import_type: 'fogbugz', creator_id: user.id)
|
||||
stub_client(repos: [@repo])
|
||||
|
||||
get :status
|
||||
|
||||
expect(assigns(:already_added_projects)).to eq([@project])
|
||||
expect(assigns(:repos)).to eq([@repo])
|
||||
end
|
||||
|
||||
it 'does not show already added project' do
|
||||
@project = create(:project, import_type: 'fogbugz', creator_id: user.id, import_source: 'vim')
|
||||
stub_client(repos: [@repo])
|
||||
|
||||
get :status
|
||||
|
||||
expect(assigns(:already_added_projects)).to eq([@project])
|
||||
expect(assigns(:repos)).to eq([])
|
||||
end
|
||||
end
|
||||
end
|
65
spec/services/projects/download_service_spec.rb
Normal file
65
spec/services/projects/download_service_spec.rb
Normal file
|
@ -0,0 +1,65 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Projects::DownloadService do
|
||||
describe 'File service' do
|
||||
before do
|
||||
@user = create :user
|
||||
@project = create :project, creator_id: @user.id, namespace: @user.namespace
|
||||
end
|
||||
|
||||
context 'for a URL that is not on whitelist' do
|
||||
before do
|
||||
url = 'https://code.jquery.com/jquery-2.1.4.min.js'
|
||||
@link_to_file = download_file(@project, url)
|
||||
end
|
||||
|
||||
it { expect(@link_to_file).to eq(nil) }
|
||||
end
|
||||
|
||||
context 'for URLs that are on the whitelist' do
|
||||
before do
|
||||
sham_rack_app = ShamRack.at('mycompany.fogbugz.com').stub
|
||||
sham_rack_app.register_resource('/rails_sample.jpg', File.read(Rails.root + 'spec/fixtures/rails_sample.jpg'), 'image/jpg')
|
||||
sham_rack_app.register_resource('/doc_sample.txt', File.read(Rails.root + 'spec/fixtures/doc_sample.txt'), 'text/plain')
|
||||
end
|
||||
|
||||
after do
|
||||
ShamRack.unmount_all
|
||||
end
|
||||
|
||||
context 'an image file' do
|
||||
before do
|
||||
url = 'http://mycompany.fogbugz.com/rails_sample.jpg'
|
||||
@link_to_file = download_file(@project, url)
|
||||
end
|
||||
|
||||
it { expect(@link_to_file).to have_key('alt') }
|
||||
it { expect(@link_to_file).to have_key('url') }
|
||||
it { expect(@link_to_file).to have_key('is_image') }
|
||||
it { expect(@link_to_file['is_image']).to be true }
|
||||
it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") }
|
||||
it { expect(@link_to_file['url']).to match('rails_sample.jpg') }
|
||||
it { expect(@link_to_file['alt']).to eq('rails_sample') }
|
||||
end
|
||||
|
||||
context 'a txt file' do
|
||||
before do
|
||||
url = 'http://mycompany.fogbugz.com/doc_sample.txt'
|
||||
@link_to_file = download_file(@project, url)
|
||||
end
|
||||
|
||||
it { expect(@link_to_file).to have_key('alt') }
|
||||
it { expect(@link_to_file).to have_key('url') }
|
||||
it { expect(@link_to_file).to have_key('is_image') }
|
||||
it { expect(@link_to_file['is_image']).to be false }
|
||||
it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") }
|
||||
it { expect(@link_to_file['url']).to match('doc_sample.txt') }
|
||||
it { expect(@link_to_file['alt']).to eq('doc_sample.txt') }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def download_file(repository, url)
|
||||
Projects::DownloadService.new(repository, url).execute
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue