From 251240c026cf6153be0b8fc5b78dcc0b33deefe2 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 28 Mar 2016 17:01:44 -0500 Subject: [PATCH 01/68] Refresh page according remaining todos --- app/assets/javascripts/todos.js.coffee | 43 ++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee index b6b4bd90e6a..e9652ee411f 100644 --- a/app/assets/javascripts/todos.js.coffee +++ b/app/assets/javascripts/todos.js.coffee @@ -1,4 +1,6 @@ class @Todos + PER_PAGE = 20 + constructor: (@name) -> @clearListeners() @initBtnListeners() @@ -24,6 +26,7 @@ class @Todos dataType: 'json' data: '_method': 'delete' success: (data) => + @redirectIfNeeded data.count @clearDone $this.closest('li') @updateBadges data @@ -54,3 +57,43 @@ class @Todos updateBadges: (data) -> $('.todos-pending .badge, .todos-pending-count').text data.count $('.todos-done .badge').text data.done_count + + getRenderedPages: -> + $('.gl-pagination .page').length + + getCurrentPage: -> + parseInt($.trim($('.gl-pagination .page.active').text())) + + redirectIfNeeded: (total) -> + currPages = @getRenderedPages() + currPage = @getCurrentPage() + + newPages = Math.ceil(total / PER_PAGE) + url = location.href # Includes query strings + + # Refresh if no remaining Todos + if !total + location.reload() + return + + # Do nothing if no pagination + return if !currPages + + # If new total of pages if different than we have now + if newPages isnt currPages + # Redirect to previous page if there´s one available + if currPages > 1 and currPage is currPages + url = @updateQueryStringParameter(url, 'page', currPages - 1) + + location.replace url + + updateQueryStringParameter: (uri, key, value) -> + separator = if uri.indexOf('?') isnt -1 then "&" else "?" + + # Matches key and value + regex = new RegExp("([?&])" + key + "=.*?(&|#|$)", "i") + + if uri.match(regex) + return uri.replace(regex, '$1' + key + "=" + value + '$2') + + uri + separator + key + "=" + value From 5c307cf190201a7e29bf88537b4b4d9821c89294 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 28 Mar 2016 17:05:04 -0500 Subject: [PATCH 02/68] typo --- app/assets/javascripts/todos.js.coffee | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee index e9652ee411f..ee75e4a0b86 100644 --- a/app/assets/javascripts/todos.js.coffee +++ b/app/assets/javascripts/todos.js.coffee @@ -79,21 +79,21 @@ class @Todos # Do nothing if no pagination return if !currPages - # If new total of pages if different than we have now + # If new total of pages is different than we have now if newPages isnt currPages - # Redirect to previous page if there´s one available + # Redirect to previous page if there's one available if currPages > 1 and currPage is currPages url = @updateQueryStringParameter(url, 'page', currPages - 1) location.replace url updateQueryStringParameter: (uri, key, value) -> - separator = if uri.indexOf('?') isnt -1 then "&" else "?" + separator = if uri.indexOf('?') isnt -1 then '&' else '?' # Matches key and value - regex = new RegExp("([?&])" + key + "=.*?(&|#|$)", "i") + regex = new RegExp('([?&])' + key + '=.*?(&|#|$)', 'i') if uri.match(regex) - return uri.replace(regex, '$1' + key + "=" + value + '$2') + return uri.replace(regex, '$1' + key + '=' + value + '$2') - uri + separator + key + "=" + value + uri + separator + key + '=' + value From f3134c2a7045accd70f6877874c43c3ead134d35 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 30 Mar 2016 17:11:51 -0500 Subject: [PATCH 03/68] Expose todos_per_page variable --- app/assets/javascripts/todos.js.coffee | 5 ++--- app/controllers/dashboard/todos_controller.rb | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee index 29dad4ed6fd..ce44a16e224 100644 --- a/app/assets/javascripts/todos.js.coffee +++ b/app/assets/javascripts/todos.js.coffee @@ -1,7 +1,6 @@ class @Todos - PER_PAGE = 20 - constructor: (@name) -> + @todos_per_page = gon.todos_per_page || 20 @clearListeners() @initBtnListeners() @@ -70,7 +69,7 @@ class @Todos currPages = @getRenderedPages() currPage = @getCurrentPage() - newPages = Math.ceil(total / PER_PAGE) + newPages = Math.ceil(total / @todos_per_page) url = location.href # Includes query strings # Refresh if no remaining Todos diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb index 5abf97342c3..d91311aa51a 100644 --- a/app/controllers/dashboard/todos_controller.rb +++ b/app/controllers/dashboard/todos_controller.rb @@ -2,6 +2,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController before_action :find_todos, only: [:index, :destroy, :destroy_all] def index + gon.todos_per_page = Todo.default_per_page @todos = @todos.page(params[:page]) end From 8c0aba9458a25266bb52bbc2101a83ed05967722 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 1 Apr 2016 12:55:45 -0500 Subject: [PATCH 04/68] Get pagination options form the view --- app/assets/javascripts/todos.js.coffee | 27 ++++++++++++------- app/controllers/dashboard/todos_controller.rb | 1 - app/views/dashboard/todos/index.html.haml | 1 + 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee index ce44a16e224..f39184777ac 100644 --- a/app/assets/javascripts/todos.js.coffee +++ b/app/assets/javascripts/todos.js.coffee @@ -1,6 +1,11 @@ class @Todos - constructor: (@name) -> - @todos_per_page = gon.todos_per_page || 20 + constructor: (opts = {}) -> + { + @el = $('.js-todos-options') + } = opts + + @perPage = @el.data('perPage') + @clearListeners() @initBtnListeners() @@ -59,26 +64,30 @@ class @Todos $('.todos-pending .badge, .todos-pending-count').text data.count $('.todos-done .badge').text data.done_count - getRenderedPages: -> - $('.gl-pagination .page').length + getTotalPages: -> + @el.data('totalPages') getCurrentPage: -> - parseInt($.trim($('.gl-pagination .page.active').text())) + @el.data('currentPage') + + getTodosPerPage: -> + @el.data('perPage') + redirectIfNeeded: (total) -> - currPages = @getRenderedPages() + currPages = @getTotalPages() currPage = @getCurrentPage() - newPages = Math.ceil(total / @todos_per_page) + newPages = Math.ceil(total / @getTodosPerPage()) url = location.href # Includes query strings # Refresh if no remaining Todos - if !total + if not total location.reload() return # Do nothing if no pagination - return if !currPages + return if not currPages # If new total of pages is different than we have now if newPages isnt currPages diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb index d91311aa51a..5abf97342c3 100644 --- a/app/controllers/dashboard/todos_controller.rb +++ b/app/controllers/dashboard/todos_controller.rb @@ -2,7 +2,6 @@ class Dashboard::TodosController < Dashboard::ApplicationController before_action :find_todos, only: [:index, :destroy, :destroy_all] def index - gon.todos_per_page = Todo.default_per_page @todos = @todos.page(params[:page]) end diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index f9ec3a89158..49ab8aad1d5 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -45,6 +45,7 @@ .prepend-top-default - if @todos.any? + .js-todos-options{ data: {per_page: @todos.limit_value, current_page: @todos.current_page, total_pages: @todos.total_pages} } - @todos.group_by(&:project).each do |group| .panel.panel-default.panel-small.js-todos-list - project = group[0] From 45c93b52e497c6c3fc10272623fc7f38f11e9079 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 7 Apr 2016 21:22:58 -0500 Subject: [PATCH 05/68] Todos spec --- spec/features/todos/todos_spec.rb | 79 +++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 spec/features/todos/todos_spec.rb diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb new file mode 100644 index 00000000000..214daf88fc9 --- /dev/null +++ b/spec/features/todos/todos_spec.rb @@ -0,0 +1,79 @@ +require 'spec_helper' + +describe 'Dashboard Todos', feature: true do + let(:user){ create(:user) } + let(:author){ create(:user) } + let(:project){ create(:project) } + let(:issue){ create(:issue) } + let(:todos_per_page){ Todo.default_per_page } + let(:todos_total){ todos_per_page + 1 } + + describe 'GET /dashboard/todos' do + context 'User do not have todos' do + before do + login_as(user) + visit dashboard_todos_path + end + it 'shows "All done" message' do + expect(page).to have_content "You're all done!" + end + end + + context 'User has a todo', js: true do + before do + create(:todo, :mentioned, user: user, project: project, target: issue, author: author) + login_as(user) + visit dashboard_todos_path + end + + it 'todo is present' do + expect(page).to have_selector('.todos-list .todo', count: 1) + end + + describe 'deleting the todo' do + before do + first('.done-todo').click + end + + it 'is removed from the list' do + expect(page).not_to have_selector('.todos-list .todo') + end + + it 'shows "All done" message' do + expect(page).to have_content("You're all done!") + end + end + end + + context 'User has multiple pages of Todos' do + let(:todo_total_pages){ (todos_total.to_f/todos_per_page).ceil } + + before do + todos_total.times do + create(:todo, :mentioned, user: user, project: project, target: issue, author: author) + end + + login_as(user) + visit dashboard_todos_path + end + + it 'is paginated' do + expect(page).to have_selector('.gl-pagination') + end + + it 'is has the right number of pages' do + expect(page).to have_selector('.gl-pagination .page', count: todo_total_pages) + end + + describe 'deleting last todo from last page', js: true do + it 'redirects to the previous page' do + page.within('.gl-pagination') do + click_link todo_total_pages.to_s + end + first('.done-todo').click + expect(page).to have_content(Todo.last.body) + end + end + end + end +end From 153afd0cd4e2f5cad7d13cb489fbaa5854ff6b0c Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 11 Apr 2016 16:10:00 -0500 Subject: [PATCH 06/68] Reutilize existing method to update param value --- app/assets/javascripts/todos.js.coffee | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee index f16b735ece0..37b4d2f66c7 100644 --- a/app/assets/javascripts/todos.js.coffee +++ b/app/assets/javascripts/todos.js.coffee @@ -93,21 +93,12 @@ class @Todos if newPages isnt currPages # Redirect to previous page if there's one available if currPages > 1 and currPage is currPages - url = @updateQueryStringParameter(url, 'page', currPages - 1) + pageParams = + page: currPages - 1 + url = gl.utils.mergeUrlParams(pageParams, url) location.replace url - updateQueryStringParameter: (uri, key, value) -> - separator = if uri.indexOf('?') isnt -1 then '&' else '?' - - # Matches key and value - regex = new RegExp('([?&])' + key + '=.*?(&|#|$)', 'i') - - if uri.match(regex) - return uri.replace(regex, '$1' + key + '=' + value + '$2') - - uri + separator + key + '=' + value - goToTodoUrl: (e)-> todoLink = $(this).data('url') if e.metaKey From 5d69f5b46d475f34fb71dfb4e8b683e90897f1da Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 31 Mar 2016 19:51:28 +0200 Subject: [PATCH 07/68] Use Ci::Commit as Pipeline --- app/controllers/projects/commit_controller.rb | 7 +- .../projects/merge_requests_controller.rb | 3 + .../projects/pipelines_controller.rb | 0 app/helpers/ci_status_helper.rb | 19 +-- app/helpers/gitlab_routing_helper.rb | 12 ++ app/models/ci/build.rb | 11 +- app/models/ci/commit.rb | 124 +++++++++--------- app/models/commit.rb | 7 +- app/models/commit_status.rb | 40 ++---- app/models/concerns/ci_status.rb | 58 ++++++++ app/models/merge_request.rb | 2 +- app/models/project.rb | 8 +- app/services/ci/create_builds_service.rb | 27 ++-- .../ci/create_trigger_request_service.rb | 2 +- app/services/create_commit_builds_service.rb | 9 +- app/views/admin/runners/show.html.haml | 2 +- app/views/projects/_last_commit.html.haml | 9 +- app/views/projects/builds/show.html.haml | 8 +- app/views/projects/ci/builds/_build.html.haml | 13 +- .../projects/ci/commits/_commit.html.haml | 0 .../ci_commits/_header_title.html.haml | 0 app/views/projects/ci_commits/index.html.haml | 0 app/views/projects/ci_commits/new.html.haml | 0 app/views/projects/commit/_builds.html.haml | 69 +--------- .../projects/commit/_ci_commit.html.haml | 69 ++++++++++ .../projects/commit/_commit_box.html.haml | 8 +- app/views/projects/commit/show.html.haml | 2 +- app/views/projects/commits/_commit.html.haml | 7 +- .../issues/_related_branches.html.haml | 2 +- app/views/shared/projects/_project.html.haml | 7 +- .../20160331153918_add_fields_to_ci_commit.rb | 7 + .../20160331204039_add_action_to_ci_commit.rb | 5 + ...0160411122626_add_duration_to_ci_commit.rb | 5 + db/schema.rb | 20 ++- lib/api/commit_statuses.rb | 2 +- 35 files changed, 326 insertions(+), 238 deletions(-) create mode 100644 app/controllers/projects/pipelines_controller.rb create mode 100644 app/models/concerns/ci_status.rb create mode 100644 app/views/projects/ci/commits/_commit.html.haml create mode 100644 app/views/projects/ci_commits/_header_title.html.haml create mode 100644 app/views/projects/ci_commits/index.html.haml create mode 100644 app/views/projects/ci_commits/new.html.haml create mode 100644 app/views/projects/commit/_ci_commit.html.haml create mode 100644 db/migrate/20160331153918_add_fields_to_ci_commit.rb create mode 100644 db/migrate/20160331204039_add_action_to_ci_commit.rb create mode 100644 db/migrate/20160411122626_add_duration_to_ci_commit.rb diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 576fa3cedb2..f9a4aeaa627 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -94,8 +94,8 @@ class Projects::CommitController < Projects::ApplicationController @commit ||= @project.commit(params[:id]) end - def ci_commit - @ci_commit ||= project.ci_commit(commit.sha) + def ci_commits + @ci_commits ||= project.ci_commits.where(sha: commit.sha) end def define_show_vars @@ -108,7 +108,8 @@ class Projects::CommitController < Projects::ApplicationController @diff_refs = [commit.parent || commit, commit] @notes_count = commit.notes.count - @statuses = ci_commit.statuses if ci_commit + @statuses = CommitStatus.where(commit: ci_commits) + @builds = Ci::Build.where(commit: ci_commits) end def assign_revert_commit_vars diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 49064f5d505..ed5a1901056 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -118,6 +118,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController @diffs = @merge_request.compare.diffs(diff_options) if @merge_request.compare @ci_commit = @merge_request.ci_commit + @ci_commits = [@ci_commit].compact @statuses = @ci_commit.statuses if @ci_commit @note_counts = Note.where(commit_id: @commits.map(&:id)). @@ -308,6 +309,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_request_diff = @merge_request.merge_request_diff @ci_commit = @merge_request.ci_commit + @ci_commits = [@ci_commit].compact @statuses = @ci_commit.statuses if @ci_commit if @merge_request.locked_long_ago? @@ -318,6 +320,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController def define_widget_vars @ci_commit = @merge_request.ci_commit + @ci_commits = [@ci_commit].compact closes_issues end diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb new file mode 100644 index 00000000000..e69de29bb2d diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index 8b1575d5e0c..fd2179c7af5 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -1,17 +1,4 @@ module CiStatusHelper - def ci_status_path(ci_commit) - project = ci_commit.project - builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha) - end - - def ci_status_icon(ci_commit) - ci_icon_for_status(ci_commit.status) - end - - def ci_status_label(ci_commit) - ci_label_for_status(ci_commit.status) - end - def ci_status_with_icon(status, target = nil) content = ci_icon_for_status(status) + ' '.html_safe + ci_label_for_status(status) klass = "ci-status ci-#{status}" @@ -47,10 +34,10 @@ module CiStatusHelper end def render_ci_status(ci_commit, tooltip_placement: 'auto left') - link_to ci_status_icon(ci_commit), - ci_status_path(ci_commit), + link_to ci_icon_for_status(ci_commit.status), + project_ci_commit_path(ci_commit.project, ci_commit), class: "ci-status-link ci-status-icon-#{ci_commit.status.dasherize}", - title: "Build #{ci_status_label(ci_commit)}", + title: "Build #{ci_label_for_status(ci_commit.status)}", data: { toggle: 'tooltip', placement: tooltip_placement } end diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index f3fddef01cb..f1af8e163cd 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -25,10 +25,22 @@ module GitlabRoutingHelper namespace_project_commits_path(project.namespace, project, @ref || project.repository.root_ref) end + def project_pipelines_path(project, *args) + namespace_project_pipelines_path(project.namespace, project, *args) + end + def project_builds_path(project, *args) namespace_project_builds_path(project.namespace, project, *args) end + def project_commit_path(project, commit) + builds_namespace_project_commit_path(project.namespace, project, commit.id) + end + + def project_ci_commit_path(project, ci_commit) + builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha) + end + def activity_project_path(project, *args) activity_namespace_project_path(project.namespace, project, *args) end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 7d33838044b..15fc714b538 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -50,7 +50,6 @@ module Ci scope :unstarted, ->() { where(runner_id: nil) } scope :ignore_failures, ->() { where(allow_failure: false) } - scope :similar, ->(build) { where(ref: build.ref, tag: build.tag, trigger_request_id: build.trigger_request_id) } mount_uploader :artifacts_file, ArtifactUploader mount_uploader :artifacts_metadata, ArtifactUploader @@ -62,6 +61,8 @@ module Ci before_destroy { project } + after_create :execute_hooks + class << self def columns_without_lazy (column_names - LAZY_ATTRIBUTES).map do |column_name| @@ -126,12 +127,16 @@ module Ci end def retried? - !self.commit.latest_statuses_for_ref(self.ref).include?(self) + !self.commit.latest.include?(self) + end + + def retry + Ci::Build.retry(self) end def depends_on_builds # Get builds of the same type - latest_builds = self.commit.builds.similar(self).latest + latest_builds = self.commit.builds.latest # Return builds from previous stages latest_builds.where('stage_idx < ?', stage_idx) diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index f4cf7034b14..70fe63877cb 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -19,6 +19,7 @@ module Ci class Commit < ActiveRecord::Base extend Ci::Model + include CiStatus belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id has_many :statuses, class_name: 'CommitStatus' @@ -28,12 +29,18 @@ module Ci validates_presence_of :sha validate :valid_commit_sha + # Make sure that status is saved + before_save :status + before_save :started_at + before_save :finished_at + before_save :duration + def self.truncate_sha(sha) sha[0...8] end - def to_param - sha + def stages + statuses.group(:stage).order(:stage_idx).pluck(:stage) end def project_id @@ -68,15 +75,26 @@ module Ci nil end - def stage - running_or_pending = statuses.latest.running_or_pending.ordered - running_or_pending.first.try(:stage) + def branch? + !tag? end - def create_builds(ref, tag, user, trigger_request = nil) + def retryable? + builds.latest.any? do |build| + build.failed? || build.retryable? + end + end + + def invalidate + status = nil + started_at = nil + finished_at = nil + end + + def create_builds(user, trigger_request = nil) return unless config_processor config_processor.stages.any? do |stage| - CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request, 'success').present? + CreateBuildsService.new(self).execute(stage, user, 'success', trigger_request).present? end end @@ -84,7 +102,7 @@ module Ci return unless config_processor # don't create other builds if this one is retried - latest_builds = builds.similar(build).latest + latest_builds = builds.latest return unless latest_builds.exists?(build.id) # get list of stages after this build @@ -97,26 +115,12 @@ module Ci # create builds for next stages based next_stages.any? do |stage| - CreateBuildsService.new.execute(self, stage, build.ref, build.tag, build.user, build.trigger_request, status).present? + CreateBuildsService.new(self).execute(stage, build.user, status, build.trigger_request).present? end end - def refs - statuses.order(:ref).pluck(:ref).uniq - end - - def latest_statuses - @latest_statuses ||= statuses.latest.to_a - end - - def latest_statuses_for_ref(ref) - latest_statuses.select { |status| status.ref == ref } - end - - def matrix_builds(build = nil) - matrix_builds = builds.latest.ordered - matrix_builds = matrix_builds.similar(build) if build - matrix_builds.to_a + def latest + statuses.latest end def retried @@ -124,56 +128,23 @@ module Ci end def status - if yaml_errors.present? - return 'failed' - end - - @status ||= Ci::Status.get_status(latest_statuses) - end - - def pending? - status == 'pending' - end - - def running? - status == 'running' - end - - def success? - status == 'success' - end - - def failed? - status == 'failed' - end - - def canceled? - status == 'canceled' - end - - def active? - running? || pending? - end - - def complete? - canceled? || success? || failed? + read_attribute(:status) || update_status end def duration - duration_array = statuses.map(&:duration).compact - duration_array.reduce(:+).to_i + read_attribute(:duration) || update_duration end def started_at - @started_at ||= statuses.order('started_at ASC').first.try(:started_at) + read_attribute(:started_at) || update_started_at end def finished_at - @finished_at ||= statuses.order('finished_at DESC').first.try(:finished_at) + read_attribute(:finished_at) || update_finished_at end def coverage - coverage_array = latest_statuses.map(&:coverage).compact + coverage_array = latest.map(&:coverage).compact if coverage_array.size >= 1 '%.2f' % (coverage_array.reduce(:+) / coverage_array.size) end @@ -191,6 +162,7 @@ module Ci end def ci_yaml_file + return nil if defined?(@ci_yaml_file) @ci_yaml_file ||= begin blob = project.repository.blob_at(sha, '.gitlab-ci.yml') blob.load_all_data!(project.repository) @@ -206,6 +178,32 @@ module Ci private + def update_status + status = + if yaml_errors.present? + 'failed' + else + latest.status + end + end + + def update_started_at + started_at = + statuses.order(:id).first.try(:started_at) + end + + def update_finished_at + finished_at = + statuses.order(id: :desc).first.try(:finished_at) + end + + def update_duration + duration = begin + duration_array = latest.map(&:duration).compact + duration_array.reduce(:+).to_i + end + end + def save_yaml_error(error) return if self.yaml_errors? self.yaml_errors = error diff --git a/app/models/commit.rb b/app/models/commit.rb index d09876a07d9..a898f7ba337 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -209,12 +209,13 @@ class Commit @raw.short_id(7) end - def ci_commit - project.ci_commit(sha) + def ci_commits + @ci_commits ||= project.ci_commits.where(sha: sha) end def status - ci_commit.try(:status) || :not_found + return @status if defined?(@status) + @status ||= ci_commits.status end def revert_branch_name diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 3377a85a55a..da7d6ea6b94 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -33,6 +33,8 @@ # class CommitStatus < ActiveRecord::Base + include CiStatus + self.table_name = 'ci_builds' belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id @@ -40,21 +42,13 @@ class CommitStatus < ActiveRecord::Base belongs_to :user validates :commit, presence: true - validates :status, inclusion: { in: %w(pending running failed success canceled) } validates_presence_of :name alias_attribute :author, :user - scope :running, -> { where(status: 'running') } - scope :pending, -> { where(status: 'pending') } - scope :success, -> { where(status: 'success') } - scope :failed, -> { where(status: 'failed') } - scope :running_or_pending, -> { where(status: [:running, :pending]) } - scope :finished, -> { where(status: [:success, :failed, :canceled]) } - scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :ref)) } + scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name)) } scope :ordered, -> { order(:ref, :stage_idx, :name) } - scope :for_ref, ->(ref) { where(ref: ref) } AVAILABLE_STATUSES = ['pending', 'running', 'success', 'failed', 'canceled'] @@ -87,31 +81,13 @@ class CommitStatus < ActiveRecord::Base MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.commit.project, nil).trigger(commit_status) end - state :pending, value: 'pending' - state :running, value: 'running' - state :failed, value: 'failed' - state :success, value: 'success' - state :canceled, value: 'canceled' + after_transition any => any do |commit_status| + commit_status.commit.invalidate + commit_status.save + end end - delegate :sha, :short_sha, to: :commit, prefix: false - - # TODO: this should be removed with all references - def before_sha - Gitlab::Git::BLANK_SHA - end - - def started? - !pending? && !canceled? && started_at - end - - def active? - running? || pending? - end - - def complete? - canceled? || success? || failed? - end + delegate :before_sha, :sha, :short_sha, to: :commit, prefix: false def ignored? allow_failure? && (failed? || canceled?) diff --git a/app/models/concerns/ci_status.rb b/app/models/concerns/ci_status.rb new file mode 100644 index 00000000000..9fe20bc9d73 --- /dev/null +++ b/app/models/concerns/ci_status.rb @@ -0,0 +1,58 @@ +module CiStatus + extend ActiveSupport::Concern + + module ClassMethods + def status + objs = all.to_a + if objs.none? + nil + elsif objs.all? { |status| status.success? || status.try(:ignored?) } + 'success' + elsif objs.all?(&:pending?) + 'pending' + elsif objs.any?(&:running?) || all.any?(&:pending?) + 'running' + elsif objs.all?(&:canceled?) + 'canceled' + else + 'failed' + end + end + + def duration + duration_array = all.map(&:duration).compact + duration_array.reduce(:+).to_i + end + end + + included do + validates :status, inclusion: { in: %w(pending running failed success canceled) } + + state_machine :status, initial: :pending do + state :pending, value: 'pending' + state :running, value: 'running' + state :failed, value: 'failed' + state :success, value: 'success' + state :canceled, value: 'canceled' + end + + scope :running, -> { where(status: 'running') } + scope :pending, -> { where(status: 'pending') } + scope :success, -> { where(status: 'success') } + scope :failed, -> { where(status: 'failed') } + scope :running_or_pending, -> { where(status: [:running, :pending]) } + scope :finished, -> { where(status: [:success, :failed, :canceled]) } + end + + def started? + !pending? && !canceled? && started_at + end + + def active? + running? || pending? + end + + def complete? + canceled? || success? || failed? + end +end \ No newline at end of file diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index bf185cb5dd8..33869e215c9 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -589,7 +589,7 @@ class MergeRequest < ActiveRecord::Base end def ci_commit - @ci_commit ||= source_project.ci_commit(last_commit.id) if last_commit && source_project + @ci_commit ||= source_project.ci_commit(last_commit.id, source_branch) if last_commit && source_project end def diff_refs diff --git a/app/models/project.rb b/app/models/project.rb index 3e1f04b4158..b9d589a4594 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -919,12 +919,12 @@ class Project < ActiveRecord::Base !namespace.share_with_group_lock end - def ci_commit(sha) - ci_commits.find_by(sha: sha) + def ci_commit(sha, ref) + ci_commits.find_by(sha: sha, ref: ref) end - def ensure_ci_commit(sha) - ci_commit(sha) || ci_commits.create(sha: sha) + def ensure_ci_commit(sha, ref) + ci_commit(sha, ref) || ci_commits.create(sha: sha, ref: ref) end def enable_ci diff --git a/app/services/ci/create_builds_service.rb b/app/services/ci/create_builds_service.rb index 2cd51a7610f..3b6e045d698 100644 --- a/app/services/ci/create_builds_service.rb +++ b/app/services/ci/create_builds_service.rb @@ -1,7 +1,11 @@ module Ci class CreateBuildsService - def execute(commit, stage, ref, tag, user, trigger_request, status) - builds_attrs = commit.config_processor.builds_for_stage_and_ref(stage, ref, tag, trigger_request) + def initialize(commit) + @commit = commit + end + + def execute(stage, user, status, trigger_request = nil) + builds_attrs = config_processor.builds_for_stage_and_ref(stage, @commit.ref, @commit.tag, trigger_request) # check when to create next build builds_attrs = builds_attrs.select do |build_attrs| @@ -17,7 +21,8 @@ module Ci builds_attrs.map do |build_attrs| # don't create the same build twice - unless commit.builds.find_by(ref: ref, tag: tag, trigger_request: trigger_request, name: build_attrs[:name]) + unless commit.builds.find_by(ref: @commit.ref, tag: @commit.tag, + trigger_request: trigger_request, name: build_attrs[:name]) build_attrs.slice!(:name, :commands, :tag_list, @@ -26,17 +31,21 @@ module Ci :stage, :stage_idx) - build_attrs.merge!(ref: ref, - tag: tag, + build_attrs.merge!(ref: @commit.ref, + tag: @commit.tag, trigger_request: trigger_request, user: user, - project: commit.project) + project: @commit.project) - build = commit.builds.create!(build_attrs) - build.execute_hooks - build + @commit.builds.create!(build_attrs) end end end + + private + + def config_processor + @config_processor ||= @commit.config_processor + end end end diff --git a/app/services/ci/create_trigger_request_service.rb b/app/services/ci/create_trigger_request_service.rb index b3dfc707221..d3745c770ea 100644 --- a/app/services/ci/create_trigger_request_service.rb +++ b/app/services/ci/create_trigger_request_service.rb @@ -7,7 +7,7 @@ module Ci # check if ref is tag tag = project.repository.find_tag(ref).present? - ci_commit = project.ensure_ci_commit(commit.sha) + ci_commit = project.ci_commits.create(commit.sha, ref) trigger_request = trigger.trigger_requests.create!( variables: variables, diff --git a/app/services/create_commit_builds_service.rb b/app/services/create_commit_builds_service.rb index 69d5c42a877..e7e1134ce4b 100644 --- a/app/services/create_commit_builds_service.rb +++ b/app/services/create_commit_builds_service.rb @@ -2,6 +2,7 @@ class CreateCommitBuildsService def execute(project, user, params) return false unless project.builds_enabled? + before_sha = params[:checkout_sha] || params[:before] sha = params[:checkout_sha] || params[:after] origin_ref = params[:ref] @@ -10,15 +11,16 @@ class CreateCommitBuildsService end ref = Gitlab::Git.ref_name(origin_ref) + tag = Gitlab::Git.tag_ref?(origin_ref) # Skip branch removal if sha == Gitlab::Git::BLANK_SHA return false end - commit = project.ci_commit(sha) + commit = project.ci_commit(sha, ref) unless commit - commit = project.ci_commits.new(sha: sha) + commit = project.ci_commits.new(sha: sha, ref: ref, before_sha: before_sha, tag: tag) # Skip creating ci_commit when no gitlab-ci.yml is found unless commit.ci_yaml_file @@ -32,8 +34,7 @@ class CreateCommitBuildsService # Skip creating builds for commits that have [ci skip] unless commit.skip_ci? # Create builds for commit - tag = Gitlab::Git.tag_ref?(origin_ref) - commit.create_builds(ref, tag, user) + commit.create_builds(user) end commit diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml index 8700b4820cd..50d00051409 100644 --- a/app/views/admin/runners/show.html.haml +++ b/app/views/admin/runners/show.html.haml @@ -117,7 +117,7 @@ %td.build-link - if project - = link_to ci_status_path(build.commit) do + = link_to builds_namespace_project_commit_path(project.namespace, project, build.sha) do %strong #{build.commit.short_sha} %td.timestamp diff --git a/app/views/projects/_last_commit.html.haml b/app/views/projects/_last_commit.html.haml index 386d72e7787..6eccbaa4bfa 100644 --- a/app/views/projects/_last_commit.html.haml +++ b/app/views/projects/_last_commit.html.haml @@ -1,9 +1,8 @@ .project-last-commit - - ci_commit = project.ci_commit(commit.sha) - - if ci_commit - = link_to ci_status_path(ci_commit), class: "ci-status ci-#{ci_commit.status}" do - = ci_status_icon(ci_commit) - = ci_status_label(ci_commit) + - if commit.status + = link_to project_commit_path(commit.project, commit), class: "ci-status ci-#{commit.status}" do + = ci_icon_for_status(commit.status) + = ci_label_for_status(commit.status) = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message" diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index b02aee3db21..41b1ca9f9e8 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -4,7 +4,7 @@ .build-page .gray-content-block.top-block Build ##{@build.id} for commit - %strong.monospace= link_to @build.commit.short_sha, ci_status_path(@build.commit) + %strong.monospace= link_to @build.commit.short_sha, builds_namespace_project_commit_path(@project.namespace, @project, @build.sha) from = link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref) - merge_request = @build.merge_request @@ -13,7 +13,7 @@ = link_to "merge request ##{merge_request.iid}", merge_request_path(merge_request) #up-build-trace - - builds = @build.commit.matrix_builds(@build) + - builds = @build.commit.builds.latest.to_a - if builds.size > 1 %ul.nav-links.no-top.no-bottom - builds.each do |build| @@ -173,7 +173,7 @@ Commit .pull-right %small - = link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace" + = link_to @build.commit.short_sha, builds_namespace_project_commit_path(@project.namespace, @project, @build.sha), class: "monospace" %p %span.attr-name Branch: = link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref) @@ -196,7 +196,7 @@ .build-widget %h4.title #{pluralize(@builds.count(:id), "other build")} for = succeed ":" do - = link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace" + = link_to @build.commit.short_sha, builds_namespace_project_commit_path(@project.namespace, @project, build.sha), class: "monospace" %table.table.builds - @builds.each_with_index do |build, i| %tr.build diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 2cf9115e4dd..218d396b898 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -19,11 +19,12 @@ %td = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace" - %td - - if build.ref - = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref) - - else - .light none + - if !defined?(ref) || ref + %td + - if build.ref + = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref) + - else + .light none - if defined?(runner) && runner %td @@ -48,6 +49,8 @@ %span.label.label-info triggered - if build.try(:allow_failure) %span.label.label-danger allowed to fail + - if defined?(retried) && retried + %span.label.label-warning retried %td.duration - if build.duration diff --git a/app/views/projects/ci/commits/_commit.html.haml b/app/views/projects/ci/commits/_commit.html.haml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/app/views/projects/ci_commits/_header_title.html.haml b/app/views/projects/ci_commits/_header_title.html.haml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/app/views/projects/ci_commits/index.html.haml b/app/views/projects/ci_commits/index.html.haml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/app/views/projects/ci_commits/new.html.haml b/app/views/projects/ci_commits/new.html.haml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/app/views/projects/commit/_builds.html.haml b/app/views/projects/commit/_builds.html.haml index 003b7c18d0e..5c9a319edeb 100644 --- a/app/views/projects/commit/_builds.html.haml +++ b/app/views/projects/commit/_builds.html.haml @@ -1,67 +1,2 @@ -.gray-content-block.middle-block - .pull-right - - if can?(current_user, :update_build, @ci_commit.project) - - if @ci_commit.builds.latest.failed.any?(&:retryable?) - = link_to "Retry failed", retry_builds_namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), class: 'btn btn-grouped btn-primary', method: :post - - - if @ci_commit.builds.running_or_pending.any? - = link_to "Cancel running", cancel_builds_namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post - - .oneline - = pluralize @statuses.count(:id), "build" - - if defined?(link_to_commit) && link_to_commit - for commit - = link_to @ci_commit.short_sha, namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), class: "monospace" - - if @ci_commit.duration > 0 - in - = time_interval_in_words @ci_commit.duration - -- if @ci_commit.yaml_errors.present? - .bs-callout.bs-callout-danger - %h4 Found errors in your .gitlab-ci.yml: - %ul - - @ci_commit.yaml_errors.split(",").each do |error| - %li= error - You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path} - -- if @ci_commit.project.builds_enabled? && !@ci_commit.ci_yaml_file - .bs-callout.bs-callout-warning - \.gitlab-ci.yml not found in this commit - -.table-holder - %table.table.builds - %thead - %tr - %th Status - %th Build ID - %th Ref - %th Stage - %th Name - %th Duration - %th Finished at - - if @ci_commit.project.build_coverage_enabled? - %th Coverage - %th - - @ci_commit.refs.each do |ref| - - builds = @ci_commit.statuses.for_ref(ref).latest.ordered - = render builds, coverage: @ci_commit.project.build_coverage_enabled?, stage: true, allow_retry: true - -- if @ci_commit.retried.any? - .gray-content-block.second-block - Retried builds - - .table-holder - %table.table.builds - %thead - %tr - %th Status - %th Build ID - %th Ref - %th Stage - %th Name - %th Duration - %th Finished at - - if @ci_commit.project.build_coverage_enabled? - %th Coverage - %th - = render @ci_commit.retried, coverage: @ci_commit.project.build_coverage_enabled?, stage: true +- @ci_commits.each do |ci_commit| + = render "ci_commit", ci_commit: ci_commit diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml new file mode 100644 index 00000000000..06520e40bd9 --- /dev/null +++ b/app/views/projects/commit/_ci_commit.html.haml @@ -0,0 +1,69 @@ +.gray-content-block.middle-block + .pull-right + - if can?(current_user, :update_build, @project) + - if ci_commit.builds.latest.failed.any?(&:retryable?) + = link_to "Retry failed", retry_builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: 'btn btn-grouped btn-primary', method: :post + + - if ci_commit.builds.running_or_pending.any? + = link_to "Cancel running", cancel_builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post + + .oneline + = pluralize ci_commit.statuses.count(:id), "build" + - if ci_commit.ref + for + %span.label.label-info + = ci_commit.ref + - if defined?(link_to_commit) && link_to_commit + for commit + = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "monospace" + - if ci_commit.duration > 0 + in + = time_interval_in_words ci_commit.duration + +- if ci_commit.yaml_errors.present? + .bs-callout.bs-callout-danger + %h4 Found errors in your .gitlab-ci.yml: + %ul + - ci_commit.yaml_errors.split(",").each do |error| + %li= error + You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path} + +- if @project.builds_enabled? && !ci_commit.ci_yaml_file + .bs-callout.bs-callout-warning + \.gitlab-ci.yml not found in this commit + +.table-holder + %table.table.builds + %thead + %tr + %th Status + %th Build ID + %th Stage + %th Name + %th Duration + %th Finished at + - if @project.build_coverage_enabled? + %th Coverage + %th + - builds = ci_commit.statuses.latest.ordered + = render builds, coverage: @project.build_coverage_enabled?, stage: true, ref: false, allow_retry: true + +- if ci_commit.retried.any? + .gray-content-block.second-block + Retried builds + + .table-holder + %table.table.builds + %thead + %tr + %th Status + %th Build ID + %th Ref + %th Stage + %th Name + %th Duration + %th Finished at + - if @project.build_coverage_enabled? + %th Coverage + %th + = render ci_commit.retried, coverage: @project.build_coverage_enabled?, stage: true, ref: false diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 71995fcc487..0908e830f83 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -42,12 +42,12 @@ - @commit.parents.each do |parent| = link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent), class: "monospace" -- if @ci_commit +- if @commit.status .pull-right - = link_to ci_status_path(@ci_commit), class: "ci-status ci-#{@ci_commit.status}" do - = ci_status_icon(@ci_commit) + = link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "ci-status ci-#{@commit.status}" do + = ci_icon_for_status(@commit.status) build: - = ci_status_label(@ci_commit) + = ci_label_for_status(@commit.status) .commit-info-row.branches %i.fa.fa-spinner.fa-spin diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml index 21e186120c3..096f7058bd4 100644 --- a/app/views/projects/commit/show.html.haml +++ b/app/views/projects/commit/show.html.haml @@ -5,7 +5,7 @@ .prepend-top-default = render "commit_box" -- if @ci_commit +- if @commit.status = render "ci_menu" - else %div.block-connector diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 7f2903589a9..fa34f7b7d61 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -4,9 +4,8 @@ - notes = commit.notes - note_count = notes.user.count -- ci_commit = project.ci_commit(commit.sha) - cache_key = [project.path_with_namespace, commit.id, current_application_settings, note_count] -- cache_key.push(ci_commit.status) if ci_commit +- cache_key.push(commit.status) if commit.status = cache(cache_key) do %li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" } @@ -17,8 +16,8 @@ %a.text-expander.js-toggle-button ... .pull-right - - if ci_commit - = render_ci_status(ci_commit) + - if commit.status + = render_ci_status(commit)   = clipboard_button(clipboard_text: commit.id) = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml index b10cd03515f..bdfa0c7009e 100644 --- a/app/views/projects/issues/_related_branches.html.haml +++ b/app/views/projects/issues/_related_branches.html.haml @@ -5,7 +5,7 @@ - @related_branches.each do |branch| %li - sha = @project.repository.find_branch(branch).target - - ci_commit = @project.ci_commit(sha) if sha + - ci_commit = @project.ci_commit(sha, branch) if sha - if ci_commit %span.related-branch-ci-status = render_ci_status(ci_commit) diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml index 53ff8959bc8..53261fcace7 100644 --- a/app/views/shared/projects/_project.html.haml +++ b/app/views/shared/projects/_project.html.haml @@ -6,9 +6,8 @@ - css_class = '' unless local_assigns[:css_class] - show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && project.commit - css_class += " no-description" if project.description.blank? && !show_last_commit_as_description -- ci_commit = project.ci_commit(project.commit.sha) if ci && !project.empty_repo? && project.commit - cache_key = [project.namespace, project, controller.controller_name, controller.action_name, current_application_settings, 'v2.3'] -- cache_key.push(ci_commit.status) if ci_commit +- cache_key.push(project.commit.status) if project.commit.status %li.project-row{ class: css_class } = cache(cache_key) do @@ -16,9 +15,9 @@ - if project.main_language %span = project.main_language - - if ci_commit + - if project.commit.status %span - = render_ci_status(ci_commit) + = render_ci_status(project.commit) - if forks %span = icon('code-fork') diff --git a/db/migrate/20160331153918_add_fields_to_ci_commit.rb b/db/migrate/20160331153918_add_fields_to_ci_commit.rb new file mode 100644 index 00000000000..03eb9ba4e53 --- /dev/null +++ b/db/migrate/20160331153918_add_fields_to_ci_commit.rb @@ -0,0 +1,7 @@ +class AddFieldsToCiCommit < ActiveRecord::Migration + def change + add_column :ci_commits, :status, :string + add_column :ci_commits, :started_at, :timestamp + add_column :ci_commits, :finished_at, :timestamp + end +end diff --git a/db/migrate/20160331204039_add_action_to_ci_commit.rb b/db/migrate/20160331204039_add_action_to_ci_commit.rb new file mode 100644 index 00000000000..e9f8eb624d6 --- /dev/null +++ b/db/migrate/20160331204039_add_action_to_ci_commit.rb @@ -0,0 +1,5 @@ +class AddActionToCiCommit < ActiveRecord::Migration + def change + add_column :ci_commits, :action, :string + end +end diff --git a/db/migrate/20160411122626_add_duration_to_ci_commit.rb b/db/migrate/20160411122626_add_duration_to_ci_commit.rb new file mode 100644 index 00000000000..7def7a48cde --- /dev/null +++ b/db/migrate/20160411122626_add_duration_to_ci_commit.rb @@ -0,0 +1,5 @@ +class AddDurationToCiCommit < ActiveRecord::Migration + def change + add_column :ci_commits, :duration, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index ec235c19131..72d63913387 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160331223143) do +ActiveRecord::Schema.define(version: 20160411122626) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -141,6 +141,7 @@ ActiveRecord::Schema.define(version: 20160331223143) do t.text "artifacts_metadata" t.integer "erased_by_id" t.datetime "erased_at" + t.string "plugin" end add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree @@ -168,6 +169,11 @@ ActiveRecord::Schema.define(version: 20160331223143) do t.text "yaml_errors" t.datetime "committed_at" t.integer "gl_project_id" + t.string "status" + t.datetime "started_at" + t.datetime "finished_at" + t.string "action" + t.integer "duration" end add_index "ci_commits", ["gl_project_id"], name: "index_ci_commits_on_gl_project_id", using: :btree @@ -306,11 +312,20 @@ ActiveRecord::Schema.define(version: 20160331223143) do add_index "ci_tags", ["name"], name: "index_ci_tags_on_name", unique: true, using: :btree create_table "ci_trigger_requests", force: :cascade do |t| - t.integer "trigger_id", null: false + t.integer "trigger_id" t.text "variables" t.datetime "created_at" t.datetime "updated_at" t.integer "commit_id" + t.string "sha" + t.string "before_sha" + t.string "ref" + t.string "action" + t.string "status" + t.datetime "started_at" + t.datetime "finished_at" + t.integer "project_id" + t.string "origin_ref" end create_table "ci_triggers", force: :cascade do |t| @@ -731,6 +746,7 @@ ActiveRecord::Schema.define(version: 20160331223143) do t.boolean "public_builds", default: true, null: false t.string "main_language" t.integer "pushes_since_gc", default: 0 + t.boolean "images_enabled" end add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index 8e74e177ea0..e7d76764ff5 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -21,7 +21,7 @@ module API authorize!(:read_commit_status, user_project) not_found!('Commit') unless user_project.commit(params[:sha]) - ci_commit = user_project.ci_commit(params[:sha]) + ci_commit = user_project.ci_commit(params[:sha], params[:ref]) return [] unless ci_commit statuses = ci_commit.statuses From af7214d0f077f738ed57194feb0cd468c43d4310 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 11 Apr 2016 16:55:40 +0200 Subject: [PATCH 08/68] Fix specs --- app/controllers/projects/commit_controller.rb | 8 +- app/helpers/ci_status_helper.rb | 2 + app/models/ci/commit.rb | 27 +++- app/models/commit_status.rb | 9 +- app/models/concerns/ci_status.rb | 3 +- app/services/ci/create_builds_service.rb | 2 +- .../ci/create_trigger_request_service.rb | 4 +- app/services/ci/image_for_build_service.rb | 11 +- app/services/create_commit_builds_service.rb | 1 + app/views/projects/builds/show.html.haml | 2 +- app/views/shared/projects/_project.html.haml | 4 +- db/fixtures/development/14_builds.rb | 2 +- features/steps/project/merge_requests.rb | 2 +- features/steps/shared/project.rb | 2 +- lib/api/commit_statuses.rb | 14 ++- spec/features/commits_spec.rb | 5 + spec/helpers/ci_status_helper_spec.rb | 4 +- spec/lib/gitlab/badge/build_spec.rb | 6 +- spec/models/ci/commit_spec.rb | 117 +----------------- spec/models/commit_status_spec.rb | 16 +-- spec/models/merge_request_spec.rb | 4 +- spec/models/project_spec.rb | 4 +- spec/requests/api/builds_spec.rb | 2 +- spec/requests/api/commit_status_spec.rb | 19 +-- spec/requests/api/commits_spec.rb | 4 +- spec/requests/ci/api/builds_spec.rb | 20 +-- .../services/ci/create_builds_service_spec.rb | 4 +- .../ci/image_for_build_service_spec.rb | 2 +- 28 files changed, 106 insertions(+), 194 deletions(-) diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index f9a4aeaa627..72078c3cc68 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -38,13 +38,13 @@ class Projects::CommitController < Projects::ApplicationController end def cancel_builds - ci_commit.builds.running_or_pending.each(&:cancel) + ci_builds.running_or_pending.each(&:cancel) redirect_back_or_default default: builds_namespace_project_commit_path(project.namespace, project, commit.sha) end def retry_builds - ci_commit.builds.latest.failed.each do |build| + ci_builds.latest.failed.each do |build| if build.retryable? Ci::Build.retry(build) end @@ -98,6 +98,10 @@ class Projects::CommitController < Projects::ApplicationController @ci_commits ||= project.ci_commits.where(sha: commit.sha) end + def ci_builds + @ci_builds ||= Ci::Build.where(commit: ci_commits) + end + def define_show_vars return git_not_found! unless commit diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index fd2179c7af5..effa7ce77e1 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -34,6 +34,8 @@ module CiStatusHelper end def render_ci_status(ci_commit, tooltip_placement: 'auto left') + return unless ci_commit.is_a?(Commit) || ci_commit.is_a?(Ci::Commit) + link_to ci_icon_for_status(ci_commit.status), project_ci_commit_path(ci_commit.project, ci_commit), class: "ci-status-link ci-status-icon-#{ci_commit.status.dasherize}", diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index 70fe63877cb..d09866a4bdf 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -35,6 +35,11 @@ module Ci before_save :finished_at before_save :duration + # Invalidate object and save if when touched + after_touch :reload + after_touch :invalidate + after_touch :save + def self.truncate_sha(sha) sha[0...8] end @@ -86,9 +91,10 @@ module Ci end def invalidate - status = nil - started_at = nil - finished_at = nil + write_attribute(:status, nil) + write_attribute(:started_at, nil) + write_attribute(:finished_at, nil) + write_attribute(:duration, nil) end def create_builds(user, trigger_request = nil) @@ -183,18 +189,18 @@ module Ci if yaml_errors.present? 'failed' else - latest.status + latest.status || 'skipped' end end def update_started_at started_at = - statuses.order(:id).first.try(:started_at) + statuses.minimum(:started_at) end def update_finished_at finished_at = - statuses.order(id: :desc).first.try(:finished_at) + statuses.maximum(:finished_at) end def update_duration @@ -204,9 +210,18 @@ module Ci end end + def update_statuses + update_status + update_started_at + update_finished_at + update_duration + save + end + def save_yaml_error(error) return if self.yaml_errors? self.yaml_errors = error + update_status save end end diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index da7d6ea6b94..e8a331e720c 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -38,7 +38,7 @@ class CommitStatus < ActiveRecord::Base self.table_name = 'ci_builds' belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id - belongs_to :commit, class_name: 'Ci::Commit' + belongs_to :commit, class_name: 'Ci::Commit', touch: true belongs_to :user validates :commit, presence: true @@ -47,7 +47,7 @@ class CommitStatus < ActiveRecord::Base alias_attribute :author, :user - scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name)) } + scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :commit_id)) } scope :ordered, -> { order(:ref, :stage_idx, :name) } AVAILABLE_STATUSES = ['pending', 'running', 'success', 'failed', 'canceled'] @@ -80,11 +80,6 @@ class CommitStatus < ActiveRecord::Base after_transition [:pending, :running] => :success do |commit_status| MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.commit.project, nil).trigger(commit_status) end - - after_transition any => any do |commit_status| - commit_status.commit.invalidate - commit_status.save - end end delegate :before_sha, :sha, :short_sha, to: :commit, prefix: false diff --git a/app/models/concerns/ci_status.rb b/app/models/concerns/ci_status.rb index 9fe20bc9d73..25bee601f43 100644 --- a/app/models/concerns/ci_status.rb +++ b/app/models/concerns/ci_status.rb @@ -26,7 +26,7 @@ module CiStatus end included do - validates :status, inclusion: { in: %w(pending running failed success canceled) } + validates :status, inclusion: { in: %w(pending running failed success canceled skipped) } state_machine :status, initial: :pending do state :pending, value: 'pending' @@ -34,6 +34,7 @@ module CiStatus state :failed, value: 'failed' state :success, value: 'success' state :canceled, value: 'canceled' + state :skipped, value: 'skipped' end scope :running, -> { where(status: 'running') } diff --git a/app/services/ci/create_builds_service.rb b/app/services/ci/create_builds_service.rb index 3b6e045d698..bbc8251a2da 100644 --- a/app/services/ci/create_builds_service.rb +++ b/app/services/ci/create_builds_service.rb @@ -21,7 +21,7 @@ module Ci builds_attrs.map do |build_attrs| # don't create the same build twice - unless commit.builds.find_by(ref: @commit.ref, tag: @commit.tag, + unless @commit.builds.find_by(ref: @commit.ref, tag: @commit.tag, trigger_request: trigger_request, name: build_attrs[:name]) build_attrs.slice!(:name, :commands, diff --git a/app/services/ci/create_trigger_request_service.rb b/app/services/ci/create_trigger_request_service.rb index d3745c770ea..993acf11db9 100644 --- a/app/services/ci/create_trigger_request_service.rb +++ b/app/services/ci/create_trigger_request_service.rb @@ -7,14 +7,14 @@ module Ci # check if ref is tag tag = project.repository.find_tag(ref).present? - ci_commit = project.ci_commits.create(commit.sha, ref) + ci_commit = project.ci_commits.create(sha: commit.sha, ref: ref, tag: tag) trigger_request = trigger.trigger_requests.create!( variables: variables, commit: ci_commit, ) - if ci_commit.create_builds(ref, tag, nil, trigger_request) + if ci_commit.create_builds(nil, trigger_request) trigger_request end end diff --git a/app/services/ci/image_for_build_service.rb b/app/services/ci/image_for_build_service.rb index 50c95ced8a7..3018f27ec05 100644 --- a/app/services/ci/image_for_build_service.rb +++ b/app/services/ci/image_for_build_service.rb @@ -3,8 +3,9 @@ module Ci def execute(project, opts) sha = opts[:sha] || ref_sha(project, opts[:ref]) - commit = project.ci_commits.find_by(sha: sha) - image_name = image_for_commit(commit) + ci_commits = project.ci_commits.where(sha: sha) + ci_commits = ci_commits.where(ref: opts[:ref]) if opts[:ref] + image_name = image_for_status(ci_commits.status) image_path = Rails.root.join('public/ci', image_name) OpenStruct.new(path: image_path, name: image_name) @@ -16,9 +17,9 @@ module Ci project.commit(ref).try(:sha) if ref end - def image_for_commit(commit) - return 'build-unknown.svg' unless commit - 'build-' + commit.status + ".svg" + def image_for_status(status) + status ||= 'unknown' + 'build-' + status + ".svg" end end end diff --git a/app/services/create_commit_builds_service.rb b/app/services/create_commit_builds_service.rb index e7e1134ce4b..0d2aa1ff03d 100644 --- a/app/services/create_commit_builds_service.rb +++ b/app/services/create_commit_builds_service.rb @@ -37,6 +37,7 @@ class CreateCommitBuildsService commit.create_builds(user) end + commit.touch commit end end diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 41b1ca9f9e8..20160a718bc 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -196,7 +196,7 @@ .build-widget %h4.title #{pluralize(@builds.count(:id), "other build")} for = succeed ":" do - = link_to @build.commit.short_sha, builds_namespace_project_commit_path(@project.namespace, @project, build.sha), class: "monospace" + = link_to @build.commit.short_sha, builds_namespace_project_commit_path(@project.namespace, @project, @build.sha), class: "monospace" %table.table.builds - @builds.each_with_index do |build, i| %tr.build diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml index 53261fcace7..ab8b022411d 100644 --- a/app/views/shared/projects/_project.html.haml +++ b/app/views/shared/projects/_project.html.haml @@ -7,7 +7,7 @@ - show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && project.commit - css_class += " no-description" if project.description.blank? && !show_last_commit_as_description - cache_key = [project.namespace, project, controller.controller_name, controller.action_name, current_application_settings, 'v2.3'] -- cache_key.push(project.commit.status) if project.commit.status +- cache_key.push(project.commit.status) if project.commit.try(:status) %li.project-row{ class: css_class } = cache(cache_key) do @@ -15,7 +15,7 @@ - if project.main_language %span = project.main_language - - if project.commit.status + - if project.commit.try(:status) %span = render_ci_status(project.commit) - if forks diff --git a/db/fixtures/development/14_builds.rb b/db/fixtures/development/14_builds.rb index e3ca2b4eea3..b99d24a03c9 100644 --- a/db/fixtures/development/14_builds.rb +++ b/db/fixtures/development/14_builds.rb @@ -19,7 +19,7 @@ class Gitlab::Seeder::Builds commits = @project.repository.commits('master', nil, 5) commits_sha = commits.map { |commit| commit.raw.id } commits_sha.map do |sha| - @project.ensure_ci_commit(sha) + @project.ensure_ci_commit(sha, 'master') end rescue [] diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index a4f02b590ea..9887bf80a89 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -515,7 +515,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps step '"Bug NS-05" has CI status' do project = merge_request.source_project project.enable_ci - ci_commit = create :ci_commit, project: project, sha: merge_request.last_commit.id + ci_commit = create :ci_commit, project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch create :ci_build, commit: ci_commit end diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index b13e82f276b..3eb0cf00e67 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -230,7 +230,7 @@ module SharedProject step 'project "Shop" has CI build' do project = Project.find_by(name: "Shop") - create :ci_commit, project: project, sha: project.commit.sha + create :ci_commit, project: project, sha: project.commit.sha, ref: 'master' end step 'I should see last commit with CI status' do diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index e7d76764ff5..0b52dd8a743 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -21,10 +21,9 @@ module API authorize!(:read_commit_status, user_project) not_found!('Commit') unless user_project.commit(params[:sha]) - ci_commit = user_project.ci_commit(params[:sha], params[:ref]) - return [] unless ci_commit - statuses = ci_commit.statuses + ci_commits = user_project.ci_commits.where(sha: params[:sha]) + statuses = ::CommitStatus.where(commit: ci_commits) statuses = statuses.latest unless parse_boolean(params[:all]) statuses = statuses.where(ref: params[:ref]) if params[:ref].present? statuses = statuses.where(stage: params[:stage]) if params[:stage].present? @@ -51,7 +50,14 @@ module API commit = @project.commit(params[:sha]) not_found! 'Commit' unless commit - ci_commit = @project.ensure_ci_commit(commit.sha) + ref = params[:ref] || + begin + branches = @project.repository.branch_names_contains(commit.sha) + not_found! 'Reference for commit' if branches.none? + branches.first + end + + ci_commit = @project.ensure_ci_commit(commit.sha, ref) name = params[:name] || params[:context] status = GenericCommitStatus.running_or_pending.find_by(commit: ci_commit, name: name, ref: params[:ref]) diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb index dacaa96d760..80e80ea665d 100644 --- a/spec/features/commits_spec.rb +++ b/spec/features/commits_spec.rb @@ -162,4 +162,9 @@ describe 'Commits' do end end end + + def ci_status_path(ci_commit) + project = ci_commit.project + builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha) + end end diff --git a/spec/helpers/ci_status_helper_spec.rb b/spec/helpers/ci_status_helper_spec.rb index 4f8d9c67262..906db0ea829 100644 --- a/spec/helpers/ci_status_helper_spec.rb +++ b/spec/helpers/ci_status_helper_spec.rb @@ -7,7 +7,7 @@ describe CiStatusHelper do let(:failed_commit) { double("Ci::Commit", status: 'failed') } describe 'ci_status_icon' do - it { expect(helper.ci_status_icon(success_commit)).to include('fa-check') } - it { expect(helper.ci_status_icon(failed_commit)).to include('fa-close') } + it { expect(helper.ci_icon_for_status(success_commit.status)).to include('fa-check') } + it { expect(helper.ci_icon_for_status(failed_commit.status)).to include('fa-close') } end end diff --git a/spec/lib/gitlab/badge/build_spec.rb b/spec/lib/gitlab/badge/build_spec.rb index 329792bb685..b6f7a2e7ec4 100644 --- a/spec/lib/gitlab/badge/build_spec.rb +++ b/spec/lib/gitlab/badge/build_spec.rb @@ -42,7 +42,7 @@ describe Gitlab::Badge::Build do end context 'build exists' do - let(:ci_commit) { create(:ci_commit, project: project, sha: sha) } + let(:ci_commit) { create(:ci_commit, project: project, sha: sha, ref: branch) } let!(:build) { create(:ci_build, commit: ci_commit) } @@ -57,7 +57,7 @@ describe Gitlab::Badge::Build do describe '#data' do let(:data) { badge.data } - it 'contains infromation about success' do + it 'contains information about success' do expect(status_node(data, 'success')).to be_truthy end end @@ -74,7 +74,7 @@ describe Gitlab::Badge::Build do describe '#data' do let(:data) { badge.data } - it 'contains infromation about failure' do + it 'contains information about failure' do expect(status_node(data, 'failed')).to be_truthy end end diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb index 412842337ba..fb3b61ad7c7 100644 --- a/spec/models/ci/commit_spec.rb +++ b/spec/models/ci/commit_spec.rb @@ -52,57 +52,9 @@ describe Ci::Commit, models: true do it { expect(commit.sha).to start_with(subject) } end - describe :stage do - subject { commit.stage } - - before do - @second = FactoryGirl.create :commit_status, commit: commit, name: 'deploy', stage: 'deploy', stage_idx: 1, status: 'pending' - @first = FactoryGirl.create :commit_status, commit: commit, name: 'test', stage: 'test', stage_idx: 0, status: 'pending' - end - - it 'returns first running stage' do - is_expected.to eq('test') - end - - context 'first build succeeded' do - before do - @first.success - end - - it 'returns last running stage' do - is_expected.to eq('deploy') - end - end - - context 'all builds succeeded' do - before do - @first.success - @second.success - end - - it 'returns nil' do - is_expected.to be_nil - end - end - end - describe :create_next_builds do end - describe :refs do - subject { commit.refs } - - before do - FactoryGirl.create :commit_status, commit: commit, name: 'deploy' - FactoryGirl.create :commit_status, commit: commit, name: 'deploy', ref: 'develop' - FactoryGirl.create :commit_status, commit: commit, name: 'deploy', ref: 'master' - end - - it 'returns all refs' do - is_expected.to contain_exactly('master', 'develop', nil) - end - end - describe :retried do subject { commit.retried } @@ -117,10 +69,10 @@ describe Ci::Commit, models: true do end describe :create_builds do - let!(:commit) { FactoryGirl.create :ci_commit, project: project } + let!(:commit) { FactoryGirl.create :ci_commit, project: project, ref: 'master', tag: false } def create_builds(trigger_request = nil) - commit.create_builds('master', false, nil, trigger_request) + commit.create_builds(nil, trigger_request) end def create_next_builds @@ -143,67 +95,6 @@ describe Ci::Commit, models: true do expect(create_next_builds).to be_falsey end - context 'for different ref' do - def create_develop_builds - commit.create_builds('develop', false, nil, nil) - end - - it 'creates builds' do - expect(create_builds).to be_truthy - commit.builds.update_all(status: "success") - expect(commit.builds.count(:all)).to eq(2) - - expect(create_develop_builds).to be_truthy - commit.builds.update_all(status: "success") - expect(commit.builds.count(:all)).to eq(4) - expect(commit.refs.size).to eq(2) - expect(commit.builds.pluck(:name).uniq.size).to eq(2) - end - end - - context 'for build triggers' do - let(:trigger) { FactoryGirl.create :ci_trigger, project: project } - let(:trigger_request) { FactoryGirl.create :ci_trigger_request, commit: commit, trigger: trigger } - - it 'creates builds' do - expect(create_builds(trigger_request)).to be_truthy - expect(commit.builds.count(:all)).to eq(2) - end - - it 'rebuilds commit' do - expect(create_builds).to be_truthy - expect(commit.builds.count(:all)).to eq(2) - - expect(create_builds(trigger_request)).to be_truthy - expect(commit.builds.count(:all)).to eq(4) - end - - it 'creates next builds' do - expect(create_builds(trigger_request)).to be_truthy - expect(commit.builds.count(:all)).to eq(2) - commit.builds.update_all(status: "success") - - expect(create_next_builds).to be_truthy - expect(commit.builds.count(:all)).to eq(4) - end - - context 'for [ci skip]' do - before do - allow(commit).to receive(:git_commit_message) { 'message [ci skip]' } - end - - it 'rebuilds commit' do - expect(commit.status).to eq('skipped') - expect(create_builds).to be_truthy - - # since everything in Ci::Commit is cached we need to fetch a new object - new_commit = Ci::Commit.find_by_id(commit.id) - expect(new_commit.status).to eq('pending') - end - end - end - - context 'custom stage with first job allowed to fail' do let(:yaml) do { @@ -284,6 +175,7 @@ describe Ci::Commit, models: true do commit.builds.running_or_pending.each(&:success) expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'success', 'success') + commit.reload expect(commit.status).to eq('success') end @@ -306,6 +198,7 @@ describe Ci::Commit, models: true do commit.builds.running_or_pending.each(&:success) expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'success', 'success') + commit.reload expect(commit.status).to eq('failed') end @@ -329,6 +222,7 @@ describe Ci::Commit, models: true do expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup') expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'failed', 'success') + commit.reload expect(commit.status).to eq('failed') end @@ -351,6 +245,7 @@ describe Ci::Commit, models: true do commit.builds.running_or_pending.each(&:success) expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'failed', 'success') + commit.reload expect(commit.status).to eq('failed') end end diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index 82c68ff6cb1..1c64947f1f5 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -163,21 +163,7 @@ describe CommitStatus, models: true do end it 'return unique statuses' do - is_expected.to eq([@commit2, @commit3, @commit4, @commit5]) - end - end - - describe :for_ref do - subject { CommitStatus.for_ref('bb').order(:id) } - - before do - @commit1 = FactoryGirl.create :commit_status, commit: commit, name: 'aa', ref: 'bb', status: 'running' - @commit2 = FactoryGirl.create :commit_status, commit: commit, name: 'cc', ref: 'cc', status: 'pending' - @commit3 = FactoryGirl.create :commit_status, commit: commit, name: 'aa', ref: nil, status: 'success' - end - - it 'return statuses with equal and nil ref set' do - is_expected.to eq([@commit1]) + is_expected.to eq([@commit4, @commit5]) end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 6f5d912fe5d..d7884cea336 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -404,12 +404,12 @@ describe MergeRequest, models: true do describe 'when the source project exists' do it 'returns the latest commit' do commit = double(:commit, id: '123abc') - ci_commit = double(:ci_commit) + ci_commit = double(:ci_commit, ref: 'master') allow(subject).to receive(:last_commit).and_return(commit) expect(subject.source_project).to receive(:ci_commit). - with('123abc'). + with('123abc', 'master'). and_return(ci_commit) expect(subject.ci_commit).to eq(ci_commit) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index f29c389e094..1688e91ca62 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -441,9 +441,9 @@ describe Project, models: true do describe :ci_commit do let(:project) { create :project } - let(:commit) { create :ci_commit, project: project } + let(:commit) { create :ci_commit, project: project, ref: 'master' } - it { expect(project.ci_commit(commit.sha)).to eq(commit) } + it { expect(project.ci_commit(commit.sha, 'master')).to eq(commit) } end describe :builds_enabled do diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb index 967c34800d0..5ead735be48 100644 --- a/spec/requests/api/builds_spec.rb +++ b/spec/requests/api/builds_spec.rb @@ -59,7 +59,7 @@ describe API::API, api: true do describe 'GET /projects/:id/repository/commits/:sha/builds' do before do - project.ensure_ci_commit(commit.sha) + project.ensure_ci_commit(commit.sha, 'master') get api("/projects/#{project.id}/repository/commits/#{commit.sha}/builds", api_user) end diff --git a/spec/requests/api/commit_status_spec.rb b/spec/requests/api/commit_status_spec.rb index 429a24109fd..f3785b19362 100644 --- a/spec/requests/api/commit_status_spec.rb +++ b/spec/requests/api/commit_status_spec.rb @@ -16,7 +16,8 @@ describe API::CommitStatus, api: true do let(:get_url) { "/projects/#{project.id}/repository/commits/#{sha}/statuses" } context 'ci commit exists' do - let!(:ci_commit) { project.ensure_ci_commit(commit.id) } + let!(:master) { project.ci_commits.create(sha: commit.id, ref: 'master') } + let!(:develop) { project.ci_commits.create(sha: commit.id, ref: 'develop') } it_behaves_like 'a paginated resources' do let(:request) { get api(get_url, reporter) } @@ -25,16 +26,16 @@ describe API::CommitStatus, api: true do context "reporter user" do let(:statuses_id) { json_response.map { |status| status['id'] } } - def create_status(opts = {}) - create(:commit_status, { commit: ci_commit }.merge(opts)) + def create_status(commit, opts = {}) + create(:commit_status, { commit: commit, ref: commit.ref }.merge(opts)) end - let!(:status1) { create_status(status: 'running') } - let!(:status2) { create_status(name: 'coverage', status: 'pending') } - let!(:status3) { create_status(ref: 'develop', status: 'running', allow_failure: true) } - let!(:status4) { create_status(name: 'coverage', status: 'success') } - let!(:status5) { create_status(name: 'coverage', ref: 'develop', status: 'success') } - let!(:status6) { create_status(status: 'success') } + let!(:status1) { create_status(master, status: 'running') } + let!(:status2) { create_status(master, name: 'coverage', status: 'pending') } + let!(:status3) { create_status(develop, status: 'running', allow_failure: true) } + let!(:status4) { create_status(master, name: 'coverage', status: 'success') } + let!(:status5) { create_status(develop, name: 'coverage', status: 'success') } + let!(:status6) { create_status(master, status: 'success') } context 'latest commit statuses' do before { get api(get_url, reporter) } diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 7ff21175c1b..25377a40442 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -51,11 +51,11 @@ describe API::API, api: true do it "should return not_found for CI status" do get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) expect(response.status).to eq(200) - expect(json_response['status']).to eq('not_found') + expect(json_response['status']).to be_nil end it "should return status for CI" do - ci_commit = project.ensure_ci_commit(project.repository.commit.sha) + ci_commit = project.ensure_ci_commit(project.repository.commit.sha, 'master') get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) expect(response.status).to eq(200) expect(json_response['status']).to eq(ci_commit.status) diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 57d7eb927fd..b652b488b5a 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -20,8 +20,8 @@ describe Ci::API::API do describe "POST /builds/register" do it "should start a build" do - commit = FactoryGirl.create(:ci_commit, project: project) - commit.create_builds('master', false, nil) + commit = FactoryGirl.create(:ci_commit, project: project, ref: 'master') + commit.create_builds(nil) build = commit.builds.first post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin } @@ -56,8 +56,8 @@ describe Ci::API::API do end it "returns options" do - commit = FactoryGirl.create(:ci_commit, project: project) - commit.create_builds('master', false, nil) + commit = FactoryGirl.create(:ci_commit, project: project, ref: 'master') + commit.create_builds(nil) post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin } @@ -66,8 +66,8 @@ describe Ci::API::API do end it "returns variables" do - commit = FactoryGirl.create(:ci_commit, project: project) - commit.create_builds('master', false, nil) + commit = FactoryGirl.create(:ci_commit, project: project, ref: 'master') + commit.create_builds(nil) project.variables << Ci::Variable.new(key: "SECRET_KEY", value: "secret_value") post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin } @@ -83,10 +83,10 @@ describe Ci::API::API do it "returns variables for triggers" do trigger = FactoryGirl.create(:ci_trigger, project: project) - commit = FactoryGirl.create(:ci_commit, project: project) + commit = FactoryGirl.create(:ci_commit, project: project, ref: 'master') trigger_request = FactoryGirl.create(:ci_trigger_request_with_variables, commit: commit, trigger: trigger) - commit.create_builds('master', false, nil, trigger_request) + commit.create_builds(nil, trigger_request) project.variables << Ci::Variable.new(key: "SECRET_KEY", value: "secret_value") post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin } @@ -103,8 +103,8 @@ describe Ci::API::API do end it "returns dependent builds" do - commit = FactoryGirl.create(:ci_commit, project: project) - commit.create_builds('master', false, nil, nil) + commit = FactoryGirl.create(:ci_commit, project: project, ref: 'master') + commit.create_builds(nil, nil) commit.builds.where(stage: 'test').each(&:success) post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin } diff --git a/spec/services/ci/create_builds_service_spec.rb b/spec/services/ci/create_builds_service_spec.rb index 1fca3628686..ecc3a88a262 100644 --- a/spec/services/ci/create_builds_service_spec.rb +++ b/spec/services/ci/create_builds_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Ci::CreateBuildsService, services: true do - let(:commit) { create(:ci_commit) } + let(:commit) { create(:ci_commit, ref: 'master') } let(:user) { create(:user) } describe '#execute' do @@ -9,7 +9,7 @@ describe Ci::CreateBuildsService, services: true do # subject do - described_class.new.execute(commit, 'test', 'master', nil, user, nil, status) + described_class.new(commit).execute(commit, nil, user, status) end context 'next builds available' do diff --git a/spec/services/ci/image_for_build_service_spec.rb b/spec/services/ci/image_for_build_service_spec.rb index 870861ad20a..4cc4b3870d1 100644 --- a/spec/services/ci/image_for_build_service_spec.rb +++ b/spec/services/ci/image_for_build_service_spec.rb @@ -5,7 +5,7 @@ module Ci let(:service) { ImageForBuildService.new } let(:project) { FactoryGirl.create(:empty_project) } let(:commit_sha) { '01234567890123456789' } - let(:commit) { project.ensure_ci_commit(commit_sha) } + let(:commit) { project.ensure_ci_commit(commit_sha, 'master') } let(:build) { FactoryGirl.create(:ci_build, commit: commit) } describe :execute do From ac50f9dddfb4555a2c356454c1cbfc1cc0b37d6d Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 12 Apr 2016 10:23:31 +0200 Subject: [PATCH 09/68] Fix rest of rspec and spinach tests --- app/models/ci/commit.rb | 10 +++++----- app/models/concerns/ci_status.rb | 6 ++++-- app/services/ci/create_builds_service.rb | 2 +- features/steps/shared/builds.rb | 6 +++--- features/steps/shared/project.rb | 2 +- spec/lib/ci/status_spec.rb | 15 ++++++++++++--- 6 files changed, 26 insertions(+), 15 deletions(-) diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index d09866a4bdf..2ab1fefe8e1 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -117,7 +117,7 @@ module Ci # get status for all prior builds prior_builds = latest_builds.reject { |other_build| next_stages.include?(other_build.stage) } - status = Ci::Status.get_status(prior_builds) + status = prior_builds.status # create builds for next stages based next_stages.any? do |stage| @@ -185,7 +185,7 @@ module Ci private def update_status - status = + self.status = if yaml_errors.present? 'failed' else @@ -194,17 +194,17 @@ module Ci end def update_started_at - started_at = + self.started_at = statuses.minimum(:started_at) end def update_finished_at - finished_at = + self.finished_at = statuses.maximum(:finished_at) end def update_duration - duration = begin + self.duration = begin duration_array = latest.map(&:duration).compact duration_array.reduce(:+).to_i end diff --git a/app/models/concerns/ci_status.rb b/app/models/concerns/ci_status.rb index 25bee601f43..6450b6dd202 100644 --- a/app/models/concerns/ci_status.rb +++ b/app/models/concerns/ci_status.rb @@ -5,7 +5,7 @@ module CiStatus def status objs = all.to_a if objs.none? - nil + nil elsif objs.all? { |status| status.success? || status.try(:ignored?) } 'success' elsif objs.all?(&:pending?) @@ -14,6 +14,8 @@ module CiStatus 'running' elsif objs.all?(&:canceled?) 'canceled' + elsif objs.all?(&:skipped?) + 'skipped' else 'failed' end @@ -56,4 +58,4 @@ module CiStatus def complete? canceled? || success? || failed? end -end \ No newline at end of file +end diff --git a/app/services/ci/create_builds_service.rb b/app/services/ci/create_builds_service.rb index bbc8251a2da..18274ce24e2 100644 --- a/app/services/ci/create_builds_service.rb +++ b/app/services/ci/create_builds_service.rb @@ -22,7 +22,7 @@ module Ci builds_attrs.map do |build_attrs| # don't create the same build twice unless @commit.builds.find_by(ref: @commit.ref, tag: @commit.tag, - trigger_request: trigger_request, name: build_attrs[:name]) + trigger_request: trigger_request, name: build_attrs[:name]) build_attrs.slice!(:name, :commands, :tag_list, diff --git a/features/steps/shared/builds.rb b/features/steps/shared/builds.rb index c4c7672a432..cf30e23b6bd 100644 --- a/features/steps/shared/builds.rb +++ b/features/steps/shared/builds.rb @@ -10,16 +10,16 @@ module SharedBuilds end step 'project has a recent build' do - @ci_commit = create(:ci_commit, project: @project, sha: @project.commit.sha) + @ci_commit = create(:ci_commit, project: @project, sha: @project.commit.sha, ref: 'master') @build = create(:ci_build_with_coverage, commit: @ci_commit) end step 'recent build is successful' do - @build.update_column(:status, 'success') + @build.update(status: 'success') end step 'recent build failed' do - @build.update_column(:status, 'failed') + @build.update(status: 'failed') end step 'project has another build that is running' do diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index 3eb0cf00e67..084338f30d8 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -230,7 +230,7 @@ module SharedProject step 'project "Shop" has CI build' do project = Project.find_by(name: "Shop") - create :ci_commit, project: project, sha: project.commit.sha, ref: 'master' + commit = create :ci_commit, project: project, sha: project.commit.sha, ref: 'master', status: 'skipped' end step 'I should see last commit with CI status' do diff --git a/spec/lib/ci/status_spec.rb b/spec/lib/ci/status_spec.rb index 47f3df6e3ce..1c37921cd7e 100644 --- a/spec/lib/ci/status_spec.rb +++ b/spec/lib/ci/status_spec.rb @@ -1,8 +1,17 @@ require 'spec_helper' -describe Ci::Status do - describe '.get_status' do - subject { described_class.get_status(statuses) } +describe CiStatus do + before do + @object = Object.new + @object.extend(CiStatus::ClassMethods) + end + + describe '.status' do + before do + allow(@object).to receive(:all).and_return(statuses) + end + + subject { @object.status } shared_examples 'build status summary' do context 'all successful' do From 1ae797c22900a6e32d63883f6a2d76c498e20d9b Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 12 Apr 2016 11:15:19 +0200 Subject: [PATCH 10/68] Fix create_next_builds method --- app/models/ci/commit.rb | 6 +++--- lib/ci/status.rb | 19 ------------------- 2 files changed, 3 insertions(+), 22 deletions(-) delete mode 100644 lib/ci/status.rb diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index 2ab1fefe8e1..94951ced1fd 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -116,12 +116,12 @@ module Ci next_stages.delete(build.stage) # get status for all prior builds - prior_builds = latest_builds.reject { |other_build| next_stages.include?(other_build.stage) } - status = prior_builds.status + prior_builds = latest_builds.where.not(stage: next_stages) + prior_status = prior_builds.status # create builds for next stages based next_stages.any? do |stage| - CreateBuildsService.new(self).execute(stage, build.user, status, build.trigger_request).present? + CreateBuildsService.new(self).execute(stage, build.user, prior_status, build.trigger_request).present? end end diff --git a/lib/ci/status.rb b/lib/ci/status.rb deleted file mode 100644 index 3fb1fe29494..00000000000 --- a/lib/ci/status.rb +++ /dev/null @@ -1,19 +0,0 @@ -module Ci - class Status - def self.get_status(statuses) - if statuses.none? - 'skipped' - elsif statuses.all? { |status| status.success? || status.ignored? } - 'success' - elsif statuses.all?(&:pending?) - 'pending' - elsif statuses.any?(&:running?) || statuses.any?(&:pending?) - 'running' - elsif statuses.all?(&:canceled?) - 'canceled' - else - 'failed' - end - end - end -end From 89f0dc713ca07fe935fa9ce2c31d0ca6febb5d6c Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 12 Apr 2016 12:33:04 +0200 Subject: [PATCH 11/68] Fix rubocop --- features/steps/shared/project.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index 084338f30d8..ea5f9580308 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -230,7 +230,7 @@ module SharedProject step 'project "Shop" has CI build' do project = Project.find_by(name: "Shop") - commit = create :ci_commit, project: project, sha: project.commit.sha, ref: 'master', status: 'skipped' + create :ci_commit, project: project, sha: project.commit.sha, ref: 'master', status: 'skipped' end step 'I should see last commit with CI status' do From 8e84acbf2e7e306ba937aaae87cfed35d2632e10 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 12 Apr 2016 19:57:54 +0200 Subject: [PATCH 12/68] Optimise CI status accessor --- app/models/ci/commit.rb | 6 +++- app/models/commit_status.rb | 35 +++++++++++++++++++ app/models/concerns/ci_status.rb | 1 + .../20160412174954_add_ci_commit_indexes.rb | 7 ++++ db/migrate/20160412175417_update_ci_commit.rb | 31 ++++++++++++++++ 5 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20160412174954_add_ci_commit_indexes.rb create mode 100644 db/migrate/20160412175417_update_ci_commit.rb diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index 94951ced1fd..8865bd76bd2 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -44,8 +44,12 @@ module Ci sha[0...8] end + def self.stages + CommitStatus.where(commit: all).stages + end + def stages - statuses.group(:stage).order(:stage_idx).pluck(:stage) + statuses.stages end def project_id diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index e8a331e720c..8a7b1f00cde 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -49,6 +49,7 @@ class CommitStatus < ActiveRecord::Base scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :commit_id)) } scope :ordered, -> { order(:ref, :stage_idx, :name) } + scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) } AVAILABLE_STATUSES = ['pending', 'running', 'success', 'failed', 'canceled'] @@ -84,6 +85,40 @@ class CommitStatus < ActiveRecord::Base delegate :before_sha, :sha, :short_sha, to: :commit, prefix: false + def self.stages + order_by = 'max(stage_idx)' + group('stage').order(order_by).pluck(:stage, order_by).map(&:first).compact + end + + def self.status_sql + builds = all.select('count(id)').to_sql + success = all.success.select('count(id)').to_sql + ignored = all.failed.where(allow_failure: true).select('count(id)').to_sql if all.try(:ignored) + ignored ||= '0' + pending = all.pending.select('count(id)').to_sql + running = all.running.select('count(id)').to_sql + canceled = all.canceled.select('count(id)').to_sql + + deduce_status = "(CASE + WHEN (#{builds})=0 THEN 'skipped' + WHEN (#{builds})=(#{success})+(#{ignored}) THEN 'success' + WHEN (#{builds})=(#{pending}) THEN 'pending' + WHEN (#{builds})=(#{canceled}) THEN 'canceled' + WHEN (#{running})+(#{pending})>0 THEN 'running' + ELSE 'failed' + END)" + + deduce_status + end + + def self.status + pluck(self.status_sql).first + end + + def self.stages_status + Hash[group(:stage).pluck(:stage, self.status_sql)] + end + def ignored? allow_failure? && (failed? || canceled?) end diff --git a/app/models/concerns/ci_status.rb b/app/models/concerns/ci_status.rb index 6450b6dd202..88b890376cf 100644 --- a/app/models/concerns/ci_status.rb +++ b/app/models/concerns/ci_status.rb @@ -43,6 +43,7 @@ module CiStatus scope :pending, -> { where(status: 'pending') } scope :success, -> { where(status: 'success') } scope :failed, -> { where(status: 'failed') } + scope :canceled, -> { where(status: 'canceled') } scope :running_or_pending, -> { where(status: [:running, :pending]) } scope :finished, -> { where(status: [:success, :failed, :canceled]) } end diff --git a/db/migrate/20160412174954_add_ci_commit_indexes.rb b/db/migrate/20160412174954_add_ci_commit_indexes.rb new file mode 100644 index 00000000000..49fbb3e9bdc --- /dev/null +++ b/db/migrate/20160412174954_add_ci_commit_indexes.rb @@ -0,0 +1,7 @@ +class AddCiCommitIndexes < ActiveRecord::Migration + def change + add_index :ci_commits, [:gl_project_id, :sha] + add_index :ci_commits, [:gl_project_id, :status] + add_index :ci_commits, [:status] + end +end diff --git a/db/migrate/20160412175417_update_ci_commit.rb b/db/migrate/20160412175417_update_ci_commit.rb new file mode 100644 index 00000000000..ebe1d143b1f --- /dev/null +++ b/db/migrate/20160412175417_update_ci_commit.rb @@ -0,0 +1,31 @@ +class UpdateCiCommit < ActiveRecord::Migration + def change + execute("UPDATE ci_commits SET status=#{status}, ref=#{ref}, tag=#{tag} WHERE status IS NULL") + end + + def status + builds = '(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id)' + success = "(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND status='success')" + ignored = "(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND (status='failed' OR status='canceled') AND allow_failure)" + pending = "(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND status='pending')" + running = "(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND status='running')" + canceled = "(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND status='canceled')" + + "(CASE + WHEN #{builds}=0 THEN 'skipped' + WHEN #{builds}=#{success}+#{ignored} THEN 'success' + WHEN #{builds}=#{pending} THEN 'pending' + WHEN #{builds}=#{canceled} THEN 'canceled' + WHEN #{running}+#{pending}>0 THEN 'running' + ELSE 'failed' + END)" + end + + def ref + '(SELECT ref FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id ORDER BY id DESC LIMIT 1)' + end + + def tag + '(SELECT tag FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id ORDER BY id DESC LIMIT 1)' + end +end From 234be12e4ee24cda6f6b7963d54af00ceb5e26c8 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 12 Apr 2016 19:59:44 +0200 Subject: [PATCH 13/68] Optimise CI status accessor --- app/models/commit_status.rb | 25 --------------------- app/models/concerns/ci_status.rb | 38 ++++++++++++++++++-------------- 2 files changed, 22 insertions(+), 41 deletions(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 8a7b1f00cde..882595ca21e 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -90,31 +90,6 @@ class CommitStatus < ActiveRecord::Base group('stage').order(order_by).pluck(:stage, order_by).map(&:first).compact end - def self.status_sql - builds = all.select('count(id)').to_sql - success = all.success.select('count(id)').to_sql - ignored = all.failed.where(allow_failure: true).select('count(id)').to_sql if all.try(:ignored) - ignored ||= '0' - pending = all.pending.select('count(id)').to_sql - running = all.running.select('count(id)').to_sql - canceled = all.canceled.select('count(id)').to_sql - - deduce_status = "(CASE - WHEN (#{builds})=0 THEN 'skipped' - WHEN (#{builds})=(#{success})+(#{ignored}) THEN 'success' - WHEN (#{builds})=(#{pending}) THEN 'pending' - WHEN (#{builds})=(#{canceled}) THEN 'canceled' - WHEN (#{running})+(#{pending})>0 THEN 'running' - ELSE 'failed' - END)" - - deduce_status - end - - def self.status - pluck(self.status_sql).first - end - def self.stages_status Hash[group(:stage).pluck(:stage, self.status_sql)] end diff --git a/app/models/concerns/ci_status.rb b/app/models/concerns/ci_status.rb index 88b890376cf..4c7089bc36c 100644 --- a/app/models/concerns/ci_status.rb +++ b/app/models/concerns/ci_status.rb @@ -2,23 +2,29 @@ module CiStatus extend ActiveSupport::Concern module ClassMethods + def status_sql + builds = all.select('count(id)').to_sql + success = all.success.select('count(id)').to_sql + ignored = all.failed.where(allow_failure: true).select('count(id)').to_sql if all.try(:ignored) + ignored ||= '0' + pending = all.pending.select('count(id)').to_sql + running = all.running.select('count(id)').to_sql + canceled = all.canceled.select('count(id)').to_sql + + deduce_status = "(CASE + WHEN (#{builds})=0 THEN 'skipped' + WHEN (#{builds})=(#{success})+(#{ignored}) THEN 'success' + WHEN (#{builds})=(#{pending}) THEN 'pending' + WHEN (#{builds})=(#{canceled}) THEN 'canceled' + WHEN (#{running})+(#{pending})>0 THEN 'running' + ELSE 'failed' + END)" + + deduce_status + end + def status - objs = all.to_a - if objs.none? - nil - elsif objs.all? { |status| status.success? || status.try(:ignored?) } - 'success' - elsif objs.all?(&:pending?) - 'pending' - elsif objs.any?(&:running?) || all.any?(&:pending?) - 'running' - elsif objs.all?(&:canceled?) - 'canceled' - elsif objs.all?(&:skipped?) - 'skipped' - else - 'failed' - end + pluck(self.status_sql).first end def duration From 4d72ca39803615850267d034e9dc59540fe657b7 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 11:01:10 +0200 Subject: [PATCH 14/68] Remove the use of default scope for Builds --- app/models/ci/build.rb | 11 - db/schema.rb | 598 ++++++++++++++++++++--------------------- 2 files changed, 296 insertions(+), 313 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 15fc714b538..085ecc6951c 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -37,8 +37,6 @@ module Ci class Build < CommitStatus - LAZY_ATTRIBUTES = ['trace'] - belongs_to :runner, class_name: 'Ci::Runner' belongs_to :trigger_request, class_name: 'Ci::TriggerRequest' belongs_to :erased_by, class_name: 'User' @@ -56,20 +54,11 @@ module Ci acts_as_taggable - # To prevent db load megabytes of data from trace - default_scope -> { select(Ci::Build.columns_without_lazy) } - before_destroy { project } after_create :execute_hooks class << self - def columns_without_lazy - (column_names - LAZY_ATTRIBUTES).map do |column_name| - "#{table_name}.#{column_name}" - end - end - def last_month where('created_at > ?', Date.today - 1.month) end diff --git a/db/schema.rb b/db/schema.rb index 72d63913387..e000e35fca8 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160411122626) do +ActiveRecord::Schema.define(version: 20160412175417) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -42,48 +42,48 @@ ActiveRecord::Schema.define(version: 20160411122626) do t.text "sign_in_text" t.datetime "created_at" t.datetime "updated_at" - t.string "home_page_url" - t.integer "default_branch_protection", default: 2 + t.string "home_page_url", limit: 255 + t.integer "default_branch_protection", default: 2 t.text "restricted_visibility_levels" - t.boolean "version_check_enabled", default: true - t.integer "max_attachment_size", default: 10, null: false + t.integer "max_attachment_size", default: 10, null: false t.integer "default_project_visibility" t.integer "default_snippet_visibility" t.text "restricted_signup_domains" - t.boolean "user_oauth_applications", default: true - t.string "after_sign_out_path" - t.integer "session_expire_delay", default: 10080, null: false + t.boolean "version_check_enabled", default: true + t.boolean "user_oauth_applications", default: true + t.string "after_sign_out_path", limit: 255 + t.integer "session_expire_delay", default: 10080, null: false t.text "import_sources" t.text "help_page_text" - t.string "admin_notification_email" - t.boolean "shared_runners_enabled", default: true, null: false - t.integer "max_artifacts_size", default: 100, null: false + t.string "admin_notification_email", limit: 255 + t.boolean "shared_runners_enabled", default: true, null: false + t.integer "max_artifacts_size", default: 100, null: false t.string "runners_registration_token" - t.boolean "require_two_factor_authentication", default: false - t.integer "two_factor_grace_period", default: 48 - t.boolean "metrics_enabled", default: false - t.string "metrics_host", default: "localhost" - t.integer "metrics_pool_size", default: 16 - t.integer "metrics_timeout", default: 10 - t.integer "metrics_method_call_threshold", default: 10 - t.boolean "recaptcha_enabled", default: false + t.boolean "require_two_factor_authentication", default: false + t.integer "two_factor_grace_period", default: 48 + t.boolean "metrics_enabled", default: false + t.string "metrics_host", default: "localhost" + t.integer "metrics_pool_size", default: 16 + t.integer "metrics_timeout", default: 10 + t.integer "metrics_method_call_threshold", default: 10 + t.boolean "recaptcha_enabled", default: false t.string "recaptcha_site_key" t.string "recaptcha_private_key" - t.integer "metrics_port", default: 8089 - t.integer "metrics_sample_interval", default: 15 - t.boolean "sentry_enabled", default: false + t.integer "metrics_port", default: 8089 + t.integer "metrics_sample_interval", default: 15 + t.boolean "sentry_enabled", default: false t.string "sentry_dsn" - t.boolean "akismet_enabled", default: false + t.boolean "akismet_enabled", default: false t.string "akismet_api_key" - t.boolean "email_author_in_body", default: false + t.boolean "email_author_in_body", default: false t.integer "default_group_visibility" end create_table "audit_events", force: :cascade do |t| - t.integer "author_id", null: false - t.string "type", null: false - t.integer "entity_id", null: false - t.string "entity_type", null: false + t.integer "author_id", null: false + t.string "type", limit: 255, null: false + t.integer "entity_id", null: false + t.string "entity_type", limit: 255, null: false t.text "details" t.datetime "created_at" t.datetime "updated_at" @@ -94,13 +94,13 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "audit_events", ["type"], name: "index_audit_events_on_type", using: :btree create_table "broadcast_messages", force: :cascade do |t| - t.text "message", null: false + t.text "message", null: false t.datetime "starts_at" t.datetime "ends_at" - t.datetime "created_at" - t.datetime "updated_at" - t.string "color" - t.string "font" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "color", limit: 510 + t.string "font", limit: 510 end create_table "ci_application_settings", force: :cascade do |t| @@ -112,7 +112,7 @@ ActiveRecord::Schema.define(version: 20160411122626) do create_table "ci_builds", force: :cascade do |t| t.integer "project_id" - t.string "status" + t.string "status", limit: 255 t.datetime "finished_at" t.text "trace" t.datetime "created_at" @@ -123,29 +123,29 @@ ActiveRecord::Schema.define(version: 20160411122626) do t.integer "commit_id" t.text "commands" t.integer "job_id" - t.string "name" - t.boolean "deploy", default: false + t.string "name", limit: 255 + t.boolean "deploy", default: false t.text "options" - t.boolean "allow_failure", default: false, null: false - t.string "stage" + t.boolean "allow_failure", default: false, null: false + t.string "stage", limit: 255 t.integer "trigger_request_id" t.integer "stage_idx" t.boolean "tag" - t.string "ref" + t.string "ref", limit: 255 t.integer "user_id" - t.string "type" - t.string "target_url" - t.string "description" + t.string "type", limit: 255 + t.string "target_url", limit: 255 + t.string "description", limit: 255 t.text "artifacts_file" t.integer "gl_project_id" t.text "artifacts_metadata" t.integer "erased_by_id" t.datetime "erased_at" - t.string "plugin" end add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree add_index "ci_builds", ["commit_id", "status", "type"], name: "index_ci_builds_on_commit_id_and_status_and_type", using: :btree + add_index "ci_builds", ["commit_id", "status"], name: "index_ci_builds_on_commit_id_and_status", using: :btree add_index "ci_builds", ["commit_id", "type", "name", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_name_and_ref", using: :btree add_index "ci_builds", ["commit_id", "type", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_ref", using: :btree add_index "ci_builds", ["commit_id"], name: "index_ci_builds_on_commit_id", using: :btree @@ -155,17 +155,18 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "ci_builds", ["project_id"], name: "index_ci_builds_on_project_id", using: :btree add_index "ci_builds", ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree add_index "ci_builds", ["status"], name: "index_ci_builds_on_status", using: :btree + add_index "ci_builds", ["type", "status", "runner_id"], name: "index_ci_builds_on_test2", using: :btree add_index "ci_builds", ["type"], name: "index_ci_builds_on_type", using: :btree create_table "ci_commits", force: :cascade do |t| t.integer "project_id" - t.string "ref" - t.string "sha" - t.string "before_sha" + t.string "ref", limit: 255 + t.string "sha", limit: 255 + t.string "before_sha", limit: 255 t.text "push_data" t.datetime "created_at" t.datetime "updated_at" - t.boolean "tag", default: false + t.boolean "tag", default: false t.text "yaml_errors" t.datetime "committed_at" t.integer "gl_project_id" @@ -176,12 +177,15 @@ ActiveRecord::Schema.define(version: 20160411122626) do t.integer "duration" end + add_index "ci_commits", ["gl_project_id", "sha"], name: "index_ci_commits_on_gl_project_id_and_sha", using: :btree + add_index "ci_commits", ["gl_project_id", "status"], name: "index_ci_commits_on_gl_project_id_and_status", using: :btree add_index "ci_commits", ["gl_project_id"], name: "index_ci_commits_on_gl_project_id", using: :btree add_index "ci_commits", ["project_id", "committed_at", "id"], name: "index_ci_commits_on_project_id_and_committed_at_and_id", using: :btree add_index "ci_commits", ["project_id", "committed_at"], name: "index_ci_commits_on_project_id_and_committed_at", using: :btree add_index "ci_commits", ["project_id", "sha"], name: "index_ci_commits_on_project_id_and_sha", using: :btree add_index "ci_commits", ["project_id"], name: "index_ci_commits_on_project_id", using: :btree add_index "ci_commits", ["sha"], name: "index_ci_commits_on_sha", using: :btree + add_index "ci_commits", ["status"], name: "index_ci_commits_on_status", using: :btree create_table "ci_events", force: :cascade do |t| t.integer "project_id" @@ -197,16 +201,16 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "ci_events", ["project_id"], name: "index_ci_events_on_project_id", using: :btree create_table "ci_jobs", force: :cascade do |t| - t.integer "project_id", null: false + t.integer "project_id", null: false t.text "commands" - t.boolean "active", default: true, null: false + t.boolean "active", default: true, null: false t.datetime "created_at" t.datetime "updated_at" - t.string "name" - t.boolean "build_branches", default: true, null: false - t.boolean "build_tags", default: false, null: false - t.string "job_type", default: "parallel" - t.string "refs" + t.string "name", limit: 255 + t.boolean "build_branches", default: true, null: false + t.boolean "build_tags", default: false, null: false + t.string "job_type", limit: 255, default: "parallel" + t.string "refs", limit: 255 t.datetime "deleted_at" end @@ -214,25 +218,25 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "ci_jobs", ["project_id"], name: "index_ci_jobs_on_project_id", using: :btree create_table "ci_projects", force: :cascade do |t| - t.string "name" - t.integer "timeout", default: 3600, null: false + t.string "name", limit: 255 + t.integer "timeout", default: 3600, null: false t.datetime "created_at" t.datetime "updated_at" - t.string "token" - t.string "default_ref" - t.string "path" - t.boolean "always_build", default: false, null: false + t.string "token", limit: 255 + t.string "default_ref", limit: 255 + t.string "path", limit: 255 + t.boolean "always_build", default: false, null: false t.integer "polling_interval" - t.boolean "public", default: false, null: false - t.string "ssh_url_to_repo" + t.boolean "public", default: false, null: false + t.string "ssh_url_to_repo", limit: 255 t.integer "gitlab_id" - t.boolean "allow_git_fetch", default: true, null: false - t.string "email_recipients", default: "", null: false - t.boolean "email_add_pusher", default: true, null: false - t.boolean "email_only_broken_builds", default: true, null: false - t.string "skip_refs" - t.string "coverage_regex" - t.boolean "shared_runners_enabled", default: false + t.boolean "allow_git_fetch", default: true, null: false + t.string "email_recipients", limit: 255, default: "", null: false + t.boolean "email_add_pusher", default: true, null: false + t.boolean "email_only_broken_builds", default: true, null: false + t.string "skip_refs", limit: 255 + t.string "coverage_regex", limit: 255 + t.boolean "shared_runners_enabled", default: false t.text "generated_yaml_config" end @@ -251,18 +255,18 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "ci_runner_projects", ["runner_id"], name: "index_ci_runner_projects_on_runner_id", using: :btree create_table "ci_runners", force: :cascade do |t| - t.string "token" + t.string "token", limit: 255 t.datetime "created_at" t.datetime "updated_at" - t.string "description" + t.string "description", limit: 255 t.datetime "contacted_at" - t.boolean "active", default: true, null: false - t.boolean "is_shared", default: false - t.string "name" - t.string "version" - t.string "revision" - t.string "platform" - t.string "architecture" + t.boolean "active", default: true, null: false + t.boolean "is_shared", default: false + t.string "name", limit: 255 + t.string "version", limit: 255 + t.string "revision", limit: 255 + t.string "platform", limit: 255 + t.string "architecture", limit: 255 end add_index "ci_runners", ["description"], name: "index_ci_runners_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} @@ -270,19 +274,19 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "ci_runners", ["token"], name: "index_ci_runners_on_token_trigram", using: :gin, opclasses: {"token"=>"gin_trgm_ops"} create_table "ci_services", force: :cascade do |t| - t.string "type" - t.string "title" - t.integer "project_id", null: false + t.string "type", limit: 255 + t.string "title", limit: 255 + t.integer "project_id", null: false t.datetime "created_at" t.datetime "updated_at" - t.boolean "active", default: false, null: false + t.boolean "active", default: false, null: false t.text "properties" end add_index "ci_services", ["project_id"], name: "index_ci_services_on_project_id", using: :btree create_table "ci_sessions", force: :cascade do |t| - t.string "session_id", null: false + t.string "session_id", limit: 255, null: false t.text "data" t.datetime "created_at" t.datetime "updated_at" @@ -294,9 +298,9 @@ ActiveRecord::Schema.define(version: 20160411122626) do create_table "ci_taggings", force: :cascade do |t| t.integer "tag_id" t.integer "taggable_id" - t.string "taggable_type" + t.string "taggable_type", limit: 255 t.integer "tagger_id" - t.string "tagger_type" + t.string "tagger_type", limit: 255 t.string "context", limit: 128 t.datetime "created_at" end @@ -305,31 +309,22 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "ci_taggings", ["taggable_id", "taggable_type", "context"], name: "index_ci_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree create_table "ci_tags", force: :cascade do |t| - t.string "name" - t.integer "taggings_count", default: 0 + t.string "name", limit: 255 + t.integer "taggings_count", default: 0 end add_index "ci_tags", ["name"], name: "index_ci_tags_on_name", unique: true, using: :btree create_table "ci_trigger_requests", force: :cascade do |t| - t.integer "trigger_id" + t.integer "trigger_id", null: false t.text "variables" t.datetime "created_at" t.datetime "updated_at" t.integer "commit_id" - t.string "sha" - t.string "before_sha" - t.string "ref" - t.string "action" - t.string "status" - t.datetime "started_at" - t.datetime "finished_at" - t.integer "project_id" - t.string "origin_ref" end create_table "ci_triggers", force: :cascade do |t| - t.string "token" + t.string "token", limit: 255 t.integer "project_id" t.datetime "deleted_at" t.datetime "created_at" @@ -342,19 +337,19 @@ ActiveRecord::Schema.define(version: 20160411122626) do create_table "ci_variables", force: :cascade do |t| t.integer "project_id" - t.string "key" + t.string "key", limit: 255 t.text "value" t.text "encrypted_value" - t.string "encrypted_value_salt" - t.string "encrypted_value_iv" + t.string "encrypted_value_salt", limit: 255 + t.string "encrypted_value_iv", limit: 255 t.integer "gl_project_id" end add_index "ci_variables", ["gl_project_id"], name: "index_ci_variables_on_gl_project_id", using: :btree create_table "ci_web_hooks", force: :cascade do |t| - t.string "url", null: false - t.integer "project_id", null: false + t.string "url", limit: 255, null: false + t.integer "project_id", null: false t.datetime "created_at" t.datetime "updated_at" end @@ -362,30 +357,30 @@ ActiveRecord::Schema.define(version: 20160411122626) do create_table "deploy_keys_projects", force: :cascade do |t| t.integer "deploy_key_id", null: false t.integer "project_id", null: false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "deploy_keys_projects", ["project_id"], name: "index_deploy_keys_projects_on_project_id", using: :btree create_table "emails", force: :cascade do |t| - t.integer "user_id", null: false - t.string "email", null: false + t.integer "user_id", null: false + t.string "email", limit: 510, null: false t.datetime "created_at" t.datetime "updated_at" end - add_index "emails", ["email"], name: "index_emails_on_email", unique: true, using: :btree + add_index "emails", ["email"], name: "emails_email_key", unique: true, using: :btree add_index "emails", ["user_id"], name: "index_emails_on_user_id", using: :btree create_table "events", force: :cascade do |t| - t.string "target_type" + t.string "target_type", limit: 510 t.integer "target_id" - t.string "title" + t.string "title", limit: 510 t.text "data" t.integer "project_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.integer "action" t.integer "author_id" end @@ -400,15 +395,15 @@ ActiveRecord::Schema.define(version: 20160411122626) do create_table "forked_project_links", force: :cascade do |t| t.integer "forked_to_project_id", null: false t.integer "forked_from_project_id", null: false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - add_index "forked_project_links", ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree + add_index "forked_project_links", ["forked_to_project_id"], name: "forked_project_links_forked_to_project_id_key", unique: true, using: :btree create_table "identities", force: :cascade do |t| - t.string "extern_uid" - t.string "provider" + t.string "extern_uid", limit: 255 + t.string "provider", limit: 255 t.integer "user_id" t.datetime "created_at" t.datetime "updated_at" @@ -418,21 +413,21 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree create_table "issues", force: :cascade do |t| - t.string "title" + t.string "title", limit: 510 t.integer "assignee_id" t.integer "author_id" t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.integer "position", default: 0 - t.string "branch_name" + t.integer "position", default: 0 + t.string "branch_name", limit: 510 t.text "description" t.integer "milestone_id" - t.string "state" + t.string "state", limit: 510 t.integer "iid" t.integer "updated_by_id" + t.boolean "confidential", default: false t.integer "moved_to_id" - t.boolean "confidential", default: false t.datetime "deleted_at" end @@ -455,10 +450,10 @@ ActiveRecord::Schema.define(version: 20160411122626) do t.datetime "created_at" t.datetime "updated_at" t.text "key" - t.string "title" - t.string "type" - t.string "fingerprint" - t.boolean "public", default: false, null: false + t.string "title", limit: 510 + t.string "type", limit: 510 + t.string "fingerprint", limit: 510 + t.boolean "public", default: false, null: false end add_index "keys", ["created_at", "id"], name: "index_keys_on_created_at_and_id", using: :btree @@ -467,7 +462,7 @@ ActiveRecord::Schema.define(version: 20160411122626) do create_table "label_links", force: :cascade do |t| t.integer "label_id" t.integer "target_id" - t.string "target_type" + t.string "target_type", limit: 255 t.datetime "created_at" t.datetime "updated_at" end @@ -476,23 +471,23 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "label_links", ["target_id", "target_type"], name: "index_label_links_on_target_id_and_target_type", using: :btree create_table "labels", force: :cascade do |t| - t.string "title" - t.string "color" + t.string "title", limit: 255 + t.string "color", limit: 255 t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.boolean "template", default: false + t.boolean "template", default: false t.string "description" end add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree create_table "lfs_objects", force: :cascade do |t| - t.string "oid", null: false - t.integer "size", limit: 8, null: false + t.string "oid", limit: 255, null: false + t.integer "size", limit: 8, null: false t.datetime "created_at" t.datetime "updated_at" - t.string "file" + t.string "file", limit: 255 end add_index "lfs_objects", ["oid"], name: "index_lfs_objects_on_oid", unique: true, using: :btree @@ -507,17 +502,17 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "lfs_objects_projects", ["project_id"], name: "index_lfs_objects_projects_on_project_id", using: :btree create_table "members", force: :cascade do |t| - t.integer "access_level", null: false - t.integer "source_id", null: false - t.string "source_type", null: false + t.integer "access_level", null: false + t.integer "source_id", null: false + t.string "source_type", limit: 255, null: false t.integer "user_id" - t.integer "notification_level", null: false - t.string "type" + t.integer "notification_level", null: false + t.string "type", limit: 255 t.datetime "created_at" t.datetime "updated_at" t.integer "created_by_id" - t.string "invite_email" - t.string "invite_token" + t.string "invite_email", limit: 255 + t.string "invite_token", limit: 255 t.datetime "invite_accepted_at" end @@ -529,10 +524,10 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "members", ["user_id"], name: "index_members_on_user_id", using: :btree create_table "merge_request_diffs", force: :cascade do |t| - t.string "state" + t.string "state", limit: 255 t.text "st_commits" t.text "st_diffs" - t.integer "merge_request_id", null: false + t.integer "merge_request_id", null: false t.datetime "created_at" t.datetime "updated_at" t.string "base_commit_sha" @@ -542,26 +537,26 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "merge_request_diffs", ["merge_request_id"], name: "index_merge_request_diffs_on_merge_request_id", unique: true, using: :btree create_table "merge_requests", force: :cascade do |t| - t.string "target_branch", null: false - t.string "source_branch", null: false - t.integer "source_project_id", null: false + t.string "target_branch", limit: 510, null: false + t.string "source_branch", limit: 510, null: false + t.integer "source_project_id", null: false t.integer "author_id" t.integer "assignee_id" - t.string "title" + t.string "title", limit: 510 t.datetime "created_at" t.datetime "updated_at" t.integer "milestone_id" - t.string "state" - t.string "merge_status" - t.integer "target_project_id", null: false + t.string "state", limit: 510 + t.string "merge_status", limit: 510 + t.integer "target_project_id", null: false t.integer "iid" t.text "description" - t.integer "position", default: 0 + t.integer "position", default: 0 t.datetime "locked_at" t.integer "updated_by_id" - t.string "merge_error" + t.string "merge_error", limit: 255 t.text "merge_params" - t.boolean "merge_when_build_succeeds", default: false, null: false + t.boolean "merge_when_build_succeeds", default: false, null: false t.integer "merge_user_id" t.string "merge_commit_sha" t.datetime "deleted_at" @@ -582,13 +577,13 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "merge_requests", ["title"], name: "index_merge_requests_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} create_table "milestones", force: :cascade do |t| - t.string "title", null: false - t.integer "project_id", null: false + t.string "title", limit: 510, null: false + t.integer "project_id", null: false t.text "description" t.date "due_date" - t.datetime "created_at" - t.datetime "updated_at" - t.string "state" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "state", limit: 510 t.integer "iid" end @@ -601,16 +596,16 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "milestones", ["title"], name: "index_milestones_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} create_table "namespaces", force: :cascade do |t| - t.string "name", null: false - t.string "path", null: false + t.string "name", limit: 510, null: false + t.string "path", limit: 510, null: false t.integer "owner_id" - t.datetime "created_at" - t.datetime "updated_at" - t.string "type" - t.string "description", default: "", null: false - t.string "avatar" - t.boolean "share_with_group_lock", default: false - t.integer "visibility_level", default: 20, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "type", limit: 510 + t.string "description", limit: 510, default: "", null: false + t.string "avatar", limit: 510 + t.boolean "share_with_group_lock", default: false + t.integer "visibility_level", default: 20, null: false end add_index "namespaces", ["created_at", "id"], name: "index_namespaces_on_created_at_and_id", using: :btree @@ -624,19 +619,19 @@ ActiveRecord::Schema.define(version: 20160411122626) do create_table "notes", force: :cascade do |t| t.text "note" - t.string "noteable_type" + t.string "noteable_type", limit: 510 t.integer "author_id" t.datetime "created_at" t.datetime "updated_at" t.integer "project_id" - t.string "attachment" - t.string "line_code" - t.string "commit_id" + t.string "attachment", limit: 510 + t.string "line_code", limit: 510 + t.string "commit_id", limit: 510 t.integer "noteable_id" - t.boolean "system", default: false, null: false t.text "st_diff" + t.boolean "system", null: false t.integer "updated_by_id" - t.boolean "is_award", default: false, null: false + t.boolean "is_award", default: false, null: false end add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree @@ -653,14 +648,14 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "notes", ["updated_at"], name: "index_notes_on_updated_at", using: :btree create_table "oauth_access_grants", force: :cascade do |t| - t.integer "resource_owner_id", null: false - t.integer "application_id", null: false - t.string "token", null: false - t.integer "expires_in", null: false - t.text "redirect_uri", null: false - t.datetime "created_at", null: false + t.integer "resource_owner_id", null: false + t.integer "application_id", null: false + t.string "token", limit: 255, null: false + t.integer "expires_in", null: false + t.text "redirect_uri", null: false + t.datetime "created_at", null: false t.datetime "revoked_at" - t.string "scopes" + t.string "scopes", limit: 255 end add_index "oauth_access_grants", ["token"], name: "index_oauth_access_grants_on_token", unique: true, using: :btree @@ -668,12 +663,12 @@ ActiveRecord::Schema.define(version: 20160411122626) do create_table "oauth_access_tokens", force: :cascade do |t| t.integer "resource_owner_id" t.integer "application_id" - t.string "token", null: false - t.string "refresh_token" + t.string "token", limit: 255, null: false + t.string "refresh_token", limit: 255 t.integer "expires_in" t.datetime "revoked_at" - t.datetime "created_at", null: false - t.string "scopes" + t.datetime "created_at", null: false + t.string "scopes", limit: 255 end add_index "oauth_access_tokens", ["refresh_token"], name: "index_oauth_access_tokens_on_refresh_token", unique: true, using: :btree @@ -681,15 +676,15 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "oauth_access_tokens", ["token"], name: "index_oauth_access_tokens_on_token", unique: true, using: :btree create_table "oauth_applications", force: :cascade do |t| - t.string "name", null: false - t.string "uid", null: false - t.string "secret", null: false - t.text "redirect_uri", null: false - t.string "scopes", default: "", null: false + t.string "name", limit: 255, null: false + t.string "uid", limit: 255, null: false + t.string "secret", limit: 255, null: false + t.text "redirect_uri", null: false + t.string "scopes", limit: 255, default: "", null: false t.datetime "created_at" t.datetime "updated_at" t.integer "owner_id" - t.string "owner_type" + t.string "owner_type", limit: 255 end add_index "oauth_applications", ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type", using: :btree @@ -709,44 +704,43 @@ ActiveRecord::Schema.define(version: 20160411122626) do end create_table "projects", force: :cascade do |t| - t.string "name" - t.string "path" + t.string "name", limit: 510 + t.string "path", limit: 510 t.text "description" t.datetime "created_at" t.datetime "updated_at" t.integer "creator_id" - t.boolean "issues_enabled", default: true, null: false - t.boolean "wall_enabled", default: true, null: false - t.boolean "merge_requests_enabled", default: true, null: false - t.boolean "wiki_enabled", default: true, null: false + t.boolean "issues_enabled", null: false + t.boolean "wall_enabled", null: false + t.boolean "merge_requests_enabled", null: false + t.boolean "wiki_enabled", null: false t.integer "namespace_id" - t.string "issues_tracker", default: "gitlab", null: false - t.string "issues_tracker_id" - t.boolean "snippets_enabled", default: true, null: false + t.string "issues_tracker", limit: 510, default: "gitlab", null: false + t.string "issues_tracker_id", limit: 510 + t.boolean "snippets_enabled", null: false t.datetime "last_activity_at" - t.string "import_url" - t.integer "visibility_level", default: 0, null: false - t.boolean "archived", default: false, null: false - t.string "avatar" - t.string "import_status" - t.float "repository_size", default: 0.0 - t.integer "star_count", default: 0, null: false - t.string "import_type" - t.string "import_source" - t.integer "commit_count", default: 0 + t.string "import_url", limit: 510 + t.integer "visibility_level", default: 0, null: false + t.boolean "archived", null: false + t.string "import_status", limit: 255 + t.float "repository_size", default: 0.0 + t.integer "star_count", default: 0, null: false + t.string "import_type", limit: 255 + t.string "import_source", limit: 255 + t.string "avatar", limit: 255 + t.integer "commit_count", default: 0 t.text "import_error" t.integer "ci_id" - t.boolean "builds_enabled", default: true, null: false - t.boolean "shared_runners_enabled", default: true, null: false + t.boolean "builds_enabled", default: true, null: false + t.boolean "shared_runners_enabled", default: true, null: false t.string "runners_token" t.string "build_coverage_regex" - t.boolean "build_allow_git_fetch", default: true, null: false - t.integer "build_timeout", default: 3600, null: false - t.boolean "pending_delete", default: false - t.boolean "public_builds", default: true, null: false + t.boolean "build_allow_git_fetch", default: true, null: false + t.integer "build_timeout", default: 3600, null: false + t.boolean "pending_delete", default: false + t.boolean "public_builds", default: true, null: false t.string "main_language" - t.integer "pushes_since_gc", default: 0 - t.boolean "images_enabled" + t.integer "pushes_since_gc", default: 0 end add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree @@ -766,17 +760,17 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree create_table "protected_branches", force: :cascade do |t| - t.integer "project_id", null: false - t.string "name", null: false - t.datetime "created_at" - t.datetime "updated_at" - t.boolean "developers_can_push", default: false, null: false + t.integer "project_id", null: false + t.string "name", limit: 510, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.boolean "developers_can_push", default: false, null: false end add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree create_table "releases", force: :cascade do |t| - t.string "tag" + t.string "tag", limit: 255 t.text "description" t.integer "project_id" t.datetime "created_at" @@ -789,32 +783,32 @@ ActiveRecord::Schema.define(version: 20160411122626) do create_table "sent_notifications", force: :cascade do |t| t.integer "project_id" t.integer "noteable_id" - t.string "noteable_type" + t.string "noteable_type", limit: 255 t.integer "recipient_id" - t.string "commit_id" - t.string "reply_key", null: false - t.string "line_code" + t.string "commit_id", limit: 255 + t.string "reply_key", limit: 255, null: false + t.string "line_code", limit: 255 end add_index "sent_notifications", ["reply_key"], name: "index_sent_notifications_on_reply_key", unique: true, using: :btree create_table "services", force: :cascade do |t| - t.string "type" - t.string "title" + t.string "type", limit: 510 + t.string "title", limit: 510 t.integer "project_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.boolean "active", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.boolean "active", null: false t.text "properties" - t.boolean "template", default: false - t.boolean "push_events", default: true - t.boolean "issues_events", default: true - t.boolean "merge_requests_events", default: true - t.boolean "tag_push_events", default: true - t.boolean "note_events", default: true, null: false - t.boolean "build_events", default: false, null: false - t.string "category", default: "common", null: false - t.boolean "default", default: false + t.boolean "template", default: false + t.boolean "push_events", default: true + t.boolean "issues_events", default: true + t.boolean "merge_requests_events", default: true + t.boolean "tag_push_events", default: true + t.boolean "note_events", default: true, null: false + t.boolean "build_events", default: false, null: false + t.string "category", default: "common", null: false + t.boolean "default", default: false end add_index "services", ["category"], name: "index_services_on_category", using: :btree @@ -824,15 +818,15 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "services", ["template"], name: "index_services_on_template", using: :btree create_table "snippets", force: :cascade do |t| - t.string "title" + t.string "title", limit: 510 t.text "content" - t.integer "author_id", null: false + t.integer "author_id", null: false t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.string "file_name" - t.string "type" - t.integer "visibility_level", default: 0, null: false + t.string "file_name", limit: 510 + t.string "type", limit: 510 + t.integer "visibility_level", default: 0, null: false end add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree @@ -860,7 +854,7 @@ ActiveRecord::Schema.define(version: 20160411122626) do create_table "subscriptions", force: :cascade do |t| t.integer "user_id" t.integer "subscribable_id" - t.string "subscribable_type" + t.string "subscribable_type", limit: 255 t.boolean "subscribed" t.datetime "created_at" t.datetime "updated_at" @@ -871,10 +865,10 @@ ActiveRecord::Schema.define(version: 20160411122626) do create_table "taggings", force: :cascade do |t| t.integer "tag_id" t.integer "taggable_id" - t.string "taggable_type" + t.string "taggable_type", limit: 510 t.integer "tagger_id" - t.string "tagger_type" - t.string "context" + t.string "tagger_type", limit: 510 + t.string "context", limit: 510 t.datetime "created_at" end @@ -882,8 +876,8 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree create_table "tags", force: :cascade do |t| - t.string "name" - t.integer "taggings_count", default: 0 + t.string "name", limit: 510 + t.integer "taggings_count", default: 0 end add_index "tags", ["name"], name: "index_tags_on_name", unique: true, using: :btree @@ -911,76 +905,76 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "todos", ["user_id"], name: "index_todos_on_user_id", using: :btree create_table "users", force: :cascade do |t| - t.string "email", default: "", null: false - t.string "encrypted_password", default: "", null: false - t.string "reset_password_token" + t.string "email", limit: 510, default: "", null: false + t.string "encrypted_password", limit: 256, default: "", null: false + t.string "reset_password_token", limit: 510 t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0 + t.integer "sign_in_count", default: 0 t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" - t.string "current_sign_in_ip" - t.string "last_sign_in_ip" + t.string "current_sign_in_ip", limit: 510 + t.string "last_sign_in_ip", limit: 510 t.datetime "created_at" t.datetime "updated_at" - t.string "name" - t.boolean "admin", default: false, null: false - t.integer "projects_limit", default: 10 - t.string "skype", default: "", null: false - t.string "linkedin", default: "", null: false - t.string "twitter", default: "", null: false - t.string "authentication_token" - t.integer "theme_id", default: 1, null: false - t.string "bio" - t.integer "failed_attempts", default: 0 + t.string "name", limit: 510 + t.boolean "admin", null: false + t.integer "projects_limit", default: 10 + t.string "skype", limit: 510, default: "", null: false + t.string "linkedin", limit: 510, default: "", null: false + t.string "twitter", limit: 510, default: "", null: false + t.string "authentication_token", limit: 510 + t.integer "theme_id", default: 1, null: false + t.string "bio", limit: 510 + t.integer "failed_attempts", default: 0 t.datetime "locked_at" - t.string "username" - t.boolean "can_create_group", default: true, null: false - t.boolean "can_create_team", default: true, null: false - t.string "state" - t.integer "color_scheme_id", default: 1, null: false - t.integer "notification_level", default: 1, null: false + t.string "username", limit: 510 + t.boolean "can_create_group", null: false + t.boolean "can_create_team", null: false + t.string "state", limit: 510 + t.integer "color_scheme_id", default: 1, null: false + t.integer "notification_level", default: 1, null: false t.datetime "password_expires_at" t.integer "created_by_id" - t.datetime "last_credential_check_at" - t.string "avatar" - t.string "confirmation_token" + t.string "avatar", limit: 510 + t.string "confirmation_token", limit: 510 t.datetime "confirmed_at" t.datetime "confirmation_sent_at" - t.string "unconfirmed_email" - t.boolean "hide_no_ssh_key", default: false - t.string "website_url", default: "", null: false - t.string "notification_email" - t.boolean "hide_no_password", default: false - t.boolean "password_automatically_set", default: false - t.string "location" - t.string "encrypted_otp_secret" - t.string "encrypted_otp_secret_iv" - t.string "encrypted_otp_secret_salt" - t.boolean "otp_required_for_login", default: false, null: false + t.string "unconfirmed_email", limit: 510 + t.boolean "hide_no_ssh_key" + t.string "website_url", limit: 510, default: "", null: false + t.datetime "last_credential_check_at" + t.string "notification_email", limit: 255 + t.boolean "hide_no_password", default: false + t.boolean "password_automatically_set", default: false + t.string "location", limit: 255 + t.string "public_email", limit: 255, default: "", null: false + t.string "encrypted_otp_secret", limit: 255 + t.string "encrypted_otp_secret_iv", limit: 255 + t.string "encrypted_otp_secret_salt", limit: 255 + t.boolean "otp_required_for_login", default: false, null: false t.text "otp_backup_codes" - t.string "public_email", default: "", null: false - t.integer "dashboard", default: 0 - t.integer "project_view", default: 0 + t.integer "dashboard", default: 0 + t.integer "project_view", default: 0 t.integer "consumed_timestep" - t.integer "layout", default: 0 - t.boolean "hide_project_limit", default: false + t.integer "layout", default: 0 + t.boolean "hide_project_limit", default: false t.string "unlock_token" t.datetime "otp_grace_period_started_at" - t.boolean "ldap_email", default: false, null: false - t.boolean "external", default: false + t.boolean "ldap_email", default: false, null: false + t.boolean "external", default: false end add_index "users", ["admin"], name: "index_users_on_admin", using: :btree - add_index "users", ["authentication_token"], name: "index_users_on_authentication_token", unique: true, using: :btree - add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree + add_index "users", ["authentication_token"], name: "users_authentication_token_key", unique: true, using: :btree + add_index "users", ["confirmation_token"], name: "users_confirmation_token_key", unique: true, using: :btree add_index "users", ["created_at", "id"], name: "index_users_on_created_at_and_id", using: :btree add_index "users", ["current_sign_in_at"], name: "index_users_on_current_sign_in_at", using: :btree - add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree add_index "users", ["email"], name: "index_users_on_email_trigram", using: :gin, opclasses: {"email"=>"gin_trgm_ops"} + add_index "users", ["email"], name: "users_email_key", unique: true, using: :btree add_index "users", ["name"], name: "index_users_on_name", using: :btree add_index "users", ["name"], name: "index_users_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"} - add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree + add_index "users", ["reset_password_token"], name: "users_reset_password_token_key", unique: true, using: :btree add_index "users", ["username"], name: "index_users_on_username", using: :btree add_index "users", ["username"], name: "index_users_on_username_trigram", using: :gin, opclasses: {"username"=>"gin_trgm_ops"} @@ -1000,11 +994,11 @@ ActiveRecord::Schema.define(version: 20160411122626) do t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.string "type", default: "ProjectHook" + t.string "type", limit: 510, default: "ProjectHook" t.integer "service_id" - t.boolean "push_events", default: true, null: false - t.boolean "issues_events", default: false, null: false - t.boolean "merge_requests_events", default: false, null: false + t.boolean "push_events", null: false + t.boolean "issues_events", null: false + t.boolean "merge_requests_events", null: false t.boolean "tag_push_events", default: false t.boolean "note_events", default: false, null: false t.boolean "enable_ssl_verification", default: true From 102537072bd56bac0e66533b8fa7166938687592 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 15:40:12 +0200 Subject: [PATCH 15/68] Fix CiStatus implementation and tests --- app/models/commit_status.rb | 2 -- app/models/concerns/ci_status.rb | 20 +++++++++++--------- spec/lib/ci/status_spec.rb | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 882595ca21e..66eb5dcecf9 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -51,8 +51,6 @@ class CommitStatus < ActiveRecord::Base scope :ordered, -> { order(:ref, :stage_idx, :name) } scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) } - AVAILABLE_STATUSES = ['pending', 'running', 'success', 'failed', 'canceled'] - state_machine :status, initial: :pending do event :run do transition pending: :running diff --git a/app/models/concerns/ci_status.rb b/app/models/concerns/ci_status.rb index 4c7089bc36c..67e15b2d55b 100644 --- a/app/models/concerns/ci_status.rb +++ b/app/models/concerns/ci_status.rb @@ -1,15 +1,17 @@ module CiStatus extend ActiveSupport::Concern - module ClassMethods + AVAILABLE_STATUSES = %w(pending running success failed canceled skipped) + + class_methods do def status_sql - builds = all.select('count(id)').to_sql - success = all.success.select('count(id)').to_sql - ignored = all.failed.where(allow_failure: true).select('count(id)').to_sql if all.try(:ignored) + builds = all.select('count(*)').to_sql + success = all.success.select('count(*)').to_sql + ignored = all.ignored.select('count(*)').to_sql if all.try(:ignored) ignored ||= '0' - pending = all.pending.select('count(id)').to_sql - running = all.running.select('count(id)').to_sql - canceled = all.canceled.select('count(id)').to_sql + pending = all.pending.select('count(*)').to_sql + running = all.running.select('count(*)').to_sql + canceled = all.canceled.select('count(*)').to_sql deduce_status = "(CASE WHEN (#{builds})=0 THEN 'skipped' @@ -24,7 +26,7 @@ module CiStatus end def status - pluck(self.status_sql).first + all.pluck(self.status_sql).first end def duration @@ -34,7 +36,7 @@ module CiStatus end included do - validates :status, inclusion: { in: %w(pending running failed success canceled skipped) } + validates :status, inclusion: { in: AVAILABLE_STATUSES } state_machine :status, initial: :pending do state :pending, value: 'pending' diff --git a/spec/lib/ci/status_spec.rb b/spec/lib/ci/status_spec.rb index 1c37921cd7e..886b82a7afa 100644 --- a/spec/lib/ci/status_spec.rb +++ b/spec/lib/ci/status_spec.rb @@ -8,7 +8,7 @@ describe CiStatus do describe '.status' do before do - allow(@object).to receive(:all).and_return(statuses) + allow(@object).to receive(:all).and_return(CommitStatus.where(id: statuses)) end subject { @object.status } From daa29729cc64c4c5ca150993bb2375f4c838b9f4 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 15:40:26 +0200 Subject: [PATCH 16/68] Add indexes concurrently on PostgreSQL --- db/migrate/20160412174954_add_ci_commit_indexes.rb | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/db/migrate/20160412174954_add_ci_commit_indexes.rb b/db/migrate/20160412174954_add_ci_commit_indexes.rb index 49fbb3e9bdc..4cb61333178 100644 --- a/db/migrate/20160412174954_add_ci_commit_indexes.rb +++ b/db/migrate/20160412174954_add_ci_commit_indexes.rb @@ -1,7 +1,13 @@ class AddCiCommitIndexes < ActiveRecord::Migration + disable_ddl_transaction! + def change - add_index :ci_commits, [:gl_project_id, :sha] - add_index :ci_commits, [:gl_project_id, :status] - add_index :ci_commits, [:status] + add_index :ci_commits, [:gl_project_id, :sha], index_options + add_index :ci_commits, [:gl_project_id, :status], index_options + add_index :ci_commits, [:status], index_options + end + + def index_options + { algorithm: :concurrently } if Gitlab::Database.postgresql? end end From 872bbb9fe2a5fbb5195aa448b615191baae960d7 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 15:46:35 +0200 Subject: [PATCH 17/68] Cleanup required migrations --- db/migrate/20160331204039_add_action_to_ci_commit.rb | 5 ----- db/migrate/20160411122626_add_duration_to_ci_commit.rb | 5 ----- ...i_commit.rb => 20160412173416_add_fields_to_ci_commit.rb} | 1 + ...pdate_ci_commit.rb => 20160412173417_update_ci_commit.rb} | 4 ++++ ...it_indexes.rb => 20160412173418_add_ci_commit_indexes.rb} | 2 ++ 5 files changed, 7 insertions(+), 10 deletions(-) delete mode 100644 db/migrate/20160331204039_add_action_to_ci_commit.rb delete mode 100644 db/migrate/20160411122626_add_duration_to_ci_commit.rb rename db/migrate/{20160331153918_add_fields_to_ci_commit.rb => 20160412173416_add_fields_to_ci_commit.rb} (82%) rename db/migrate/{20160412175417_update_ci_commit.rb => 20160412173417_update_ci_commit.rb} (88%) rename db/migrate/{20160412174954_add_ci_commit_indexes.rb => 20160412173418_add_ci_commit_indexes.rb} (97%) diff --git a/db/migrate/20160331204039_add_action_to_ci_commit.rb b/db/migrate/20160331204039_add_action_to_ci_commit.rb deleted file mode 100644 index e9f8eb624d6..00000000000 --- a/db/migrate/20160331204039_add_action_to_ci_commit.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddActionToCiCommit < ActiveRecord::Migration - def change - add_column :ci_commits, :action, :string - end -end diff --git a/db/migrate/20160411122626_add_duration_to_ci_commit.rb b/db/migrate/20160411122626_add_duration_to_ci_commit.rb deleted file mode 100644 index 7def7a48cde..00000000000 --- a/db/migrate/20160411122626_add_duration_to_ci_commit.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddDurationToCiCommit < ActiveRecord::Migration - def change - add_column :ci_commits, :duration, :integer - end -end diff --git a/db/migrate/20160331153918_add_fields_to_ci_commit.rb b/db/migrate/20160412173416_add_fields_to_ci_commit.rb similarity index 82% rename from db/migrate/20160331153918_add_fields_to_ci_commit.rb rename to db/migrate/20160412173416_add_fields_to_ci_commit.rb index 03eb9ba4e53..125956a3ddd 100644 --- a/db/migrate/20160331153918_add_fields_to_ci_commit.rb +++ b/db/migrate/20160412173416_add_fields_to_ci_commit.rb @@ -3,5 +3,6 @@ class AddFieldsToCiCommit < ActiveRecord::Migration add_column :ci_commits, :status, :string add_column :ci_commits, :started_at, :timestamp add_column :ci_commits, :finished_at, :timestamp + add_column :ci_commits, :duration, :integer end end diff --git a/db/migrate/20160412175417_update_ci_commit.rb b/db/migrate/20160412173417_update_ci_commit.rb similarity index 88% rename from db/migrate/20160412175417_update_ci_commit.rb rename to db/migrate/20160412173417_update_ci_commit.rb index ebe1d143b1f..fd92444dbac 100644 --- a/db/migrate/20160412175417_update_ci_commit.rb +++ b/db/migrate/20160412173417_update_ci_commit.rb @@ -1,8 +1,12 @@ class UpdateCiCommit < ActiveRecord::Migration + # This migration can be run online, but needs to be executed for the second time after restarting Unicorn workers + # Otherwise Offline migration should be used. def change execute("UPDATE ci_commits SET status=#{status}, ref=#{ref}, tag=#{tag} WHERE status IS NULL") end + private + def status builds = '(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id)' success = "(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND status='success')" diff --git a/db/migrate/20160412174954_add_ci_commit_indexes.rb b/db/migrate/20160412173418_add_ci_commit_indexes.rb similarity index 97% rename from db/migrate/20160412174954_add_ci_commit_indexes.rb rename to db/migrate/20160412173418_add_ci_commit_indexes.rb index 4cb61333178..c1e238bc021 100644 --- a/db/migrate/20160412174954_add_ci_commit_indexes.rb +++ b/db/migrate/20160412173418_add_ci_commit_indexes.rb @@ -7,6 +7,8 @@ class AddCiCommitIndexes < ActiveRecord::Migration add_index :ci_commits, [:status], index_options end + private + def index_options { algorithm: :concurrently } if Gitlab::Database.postgresql? end From 251a78022d12c62ff738540d4104bbf0730ef234 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 15:58:22 +0200 Subject: [PATCH 18/68] Cleanup changes --- .../projects/pipelines_controller.rb | 0 app/helpers/ci_status_helper.rb | 12 +- app/helpers/gitlab_routing_helper.rb | 8 - app/views/admin/runners/show.html.haml | 2 +- app/views/projects/_last_commit.html.haml | 2 +- app/views/projects/builds/show.html.haml | 6 +- .../projects/ci/commits/_commit.html.haml | 0 .../ci_commits/_header_title.html.haml | 0 app/views/projects/ci_commits/index.html.haml | 0 app/views/projects/ci_commits/new.html.haml | 0 db/schema.rb | 590 +++++++++--------- spec/features/commits_spec.rb | 5 - spec/helpers/ci_status_helper_spec.rb | 2 +- 13 files changed, 305 insertions(+), 322 deletions(-) delete mode 100644 app/controllers/projects/pipelines_controller.rb delete mode 100644 app/views/projects/ci/commits/_commit.html.haml delete mode 100644 app/views/projects/ci_commits/_header_title.html.haml delete mode 100644 app/views/projects/ci_commits/index.html.haml delete mode 100644 app/views/projects/ci_commits/new.html.haml diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index effa7ce77e1..417050b4132 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -1,4 +1,9 @@ module CiStatusHelper + def ci_status_path(ci_commit) + project = ci_commit.project + builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha) + end + def ci_status_with_icon(status, target = nil) content = ci_icon_for_status(status) + ' '.html_safe + ci_label_for_status(status) klass = "ci-status ci-#{status}" @@ -34,10 +39,11 @@ module CiStatusHelper end def render_ci_status(ci_commit, tooltip_placement: 'auto left') - return unless ci_commit.is_a?(Commit) || ci_commit.is_a?(Ci::Commit) - + # TODO: split this method into + # - render_commit_status + # - render_pipeline_status link_to ci_icon_for_status(ci_commit.status), - project_ci_commit_path(ci_commit.project, ci_commit), + ci_status_path(ci_commit), class: "ci-status-link ci-status-icon-#{ci_commit.status.dasherize}", title: "Build #{ci_label_for_status(ci_commit.status)}", data: { toggle: 'tooltip', placement: tooltip_placement } diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index f1af8e163cd..f07eff3fb57 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -33,14 +33,6 @@ module GitlabRoutingHelper namespace_project_builds_path(project.namespace, project, *args) end - def project_commit_path(project, commit) - builds_namespace_project_commit_path(project.namespace, project, commit.id) - end - - def project_ci_commit_path(project, ci_commit) - builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha) - end - def activity_project_path(project, *args) activity_namespace_project_path(project.namespace, project, *args) end diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml index 50d00051409..8700b4820cd 100644 --- a/app/views/admin/runners/show.html.haml +++ b/app/views/admin/runners/show.html.haml @@ -117,7 +117,7 @@ %td.build-link - if project - = link_to builds_namespace_project_commit_path(project.namespace, project, build.sha) do + = link_to ci_status_path(build.commit) do %strong #{build.commit.short_sha} %td.timestamp diff --git a/app/views/projects/_last_commit.html.haml b/app/views/projects/_last_commit.html.haml index 6eccbaa4bfa..66c30283c7a 100644 --- a/app/views/projects/_last_commit.html.haml +++ b/app/views/projects/_last_commit.html.haml @@ -1,6 +1,6 @@ .project-last-commit - if commit.status - = link_to project_commit_path(commit.project, commit), class: "ci-status ci-#{commit.status}" do + = link_to builds_namespace_project_commit_path(commit.project.namespace, commit.project, commit), class: "ci-status ci-#{commit.status}" do = ci_icon_for_status(commit.status) = ci_label_for_status(commit.status) diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 20160a718bc..b50b2cf3764 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -4,7 +4,7 @@ .build-page .gray-content-block.top-block Build ##{@build.id} for commit - %strong.monospace= link_to @build.commit.short_sha, builds_namespace_project_commit_path(@project.namespace, @project, @build.sha) + %strong.monospace= link_to @build.commit.short_sha, ci_status_path(@build.commit) from = link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref) - merge_request = @build.merge_request @@ -173,7 +173,7 @@ Commit .pull-right %small - = link_to @build.commit.short_sha, builds_namespace_project_commit_path(@project.namespace, @project, @build.sha), class: "monospace" + = link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace" %p %span.attr-name Branch: = link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref) @@ -196,7 +196,7 @@ .build-widget %h4.title #{pluralize(@builds.count(:id), "other build")} for = succeed ":" do - = link_to @build.commit.short_sha, builds_namespace_project_commit_path(@project.namespace, @project, @build.sha), class: "monospace" + = link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace" %table.table.builds - @builds.each_with_index do |build, i| %tr.build diff --git a/app/views/projects/ci/commits/_commit.html.haml b/app/views/projects/ci/commits/_commit.html.haml deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/app/views/projects/ci_commits/_header_title.html.haml b/app/views/projects/ci_commits/_header_title.html.haml deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/app/views/projects/ci_commits/index.html.haml b/app/views/projects/ci_commits/index.html.haml deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/app/views/projects/ci_commits/new.html.haml b/app/views/projects/ci_commits/new.html.haml deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/db/schema.rb b/db/schema.rb index 90e238fcfe3..44482de467e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160412175417) do +ActiveRecord::Schema.define(version: 20160331223143) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -42,48 +42,48 @@ ActiveRecord::Schema.define(version: 20160412175417) do t.text "sign_in_text" t.datetime "created_at" t.datetime "updated_at" - t.string "home_page_url", limit: 255 - t.integer "default_branch_protection", default: 2 + t.string "home_page_url" + t.integer "default_branch_protection", default: 2 t.text "restricted_visibility_levels" - t.integer "max_attachment_size", default: 10, null: false + t.boolean "version_check_enabled", default: true + t.integer "max_attachment_size", default: 10, null: false t.integer "default_project_visibility" t.integer "default_snippet_visibility" t.text "restricted_signup_domains" - t.boolean "version_check_enabled", default: true - t.boolean "user_oauth_applications", default: true - t.string "after_sign_out_path", limit: 255 - t.integer "session_expire_delay", default: 10080, null: false + t.boolean "user_oauth_applications", default: true + t.string "after_sign_out_path" + t.integer "session_expire_delay", default: 10080, null: false t.text "import_sources" t.text "help_page_text" - t.string "admin_notification_email", limit: 255 - t.boolean "shared_runners_enabled", default: true, null: false - t.integer "max_artifacts_size", default: 100, null: false + t.string "admin_notification_email" + t.boolean "shared_runners_enabled", default: true, null: false + t.integer "max_artifacts_size", default: 100, null: false t.string "runners_registration_token" - t.boolean "require_two_factor_authentication", default: false - t.integer "two_factor_grace_period", default: 48 - t.boolean "metrics_enabled", default: false - t.string "metrics_host", default: "localhost" - t.integer "metrics_pool_size", default: 16 - t.integer "metrics_timeout", default: 10 - t.integer "metrics_method_call_threshold", default: 10 - t.boolean "recaptcha_enabled", default: false + t.boolean "require_two_factor_authentication", default: false + t.integer "two_factor_grace_period", default: 48 + t.boolean "metrics_enabled", default: false + t.string "metrics_host", default: "localhost" + t.integer "metrics_pool_size", default: 16 + t.integer "metrics_timeout", default: 10 + t.integer "metrics_method_call_threshold", default: 10 + t.boolean "recaptcha_enabled", default: false t.string "recaptcha_site_key" t.string "recaptcha_private_key" - t.integer "metrics_port", default: 8089 - t.integer "metrics_sample_interval", default: 15 - t.boolean "sentry_enabled", default: false + t.integer "metrics_port", default: 8089 + t.integer "metrics_sample_interval", default: 15 + t.boolean "sentry_enabled", default: false t.string "sentry_dsn" - t.boolean "akismet_enabled", default: false + t.boolean "akismet_enabled", default: false t.string "akismet_api_key" - t.boolean "email_author_in_body", default: false + t.boolean "email_author_in_body", default: false t.integer "default_group_visibility" end create_table "audit_events", force: :cascade do |t| - t.integer "author_id", null: false - t.string "type", limit: 255, null: false - t.integer "entity_id", null: false - t.string "entity_type", limit: 255, null: false + t.integer "author_id", null: false + t.string "type", null: false + t.integer "entity_id", null: false + t.string "entity_type", null: false t.text "details" t.datetime "created_at" t.datetime "updated_at" @@ -94,13 +94,13 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "audit_events", ["type"], name: "index_audit_events_on_type", using: :btree create_table "broadcast_messages", force: :cascade do |t| - t.text "message", null: false + t.text "message", null: false t.datetime "starts_at" t.datetime "ends_at" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.string "color", limit: 510 - t.string "font", limit: 510 + t.datetime "created_at" + t.datetime "updated_at" + t.string "color" + t.string "font" end create_table "ci_application_settings", force: :cascade do |t| @@ -112,7 +112,7 @@ ActiveRecord::Schema.define(version: 20160412175417) do create_table "ci_builds", force: :cascade do |t| t.integer "project_id" - t.string "status", limit: 255 + t.string "status" t.datetime "finished_at" t.text "trace" t.datetime "created_at" @@ -123,19 +123,19 @@ ActiveRecord::Schema.define(version: 20160412175417) do t.integer "commit_id" t.text "commands" t.integer "job_id" - t.string "name", limit: 255 - t.boolean "deploy", default: false + t.string "name" + t.boolean "deploy", default: false t.text "options" - t.boolean "allow_failure", default: false, null: false - t.string "stage", limit: 255 + t.boolean "allow_failure", default: false, null: false + t.string "stage" t.integer "trigger_request_id" t.integer "stage_idx" t.boolean "tag" - t.string "ref", limit: 255 + t.string "ref" t.integer "user_id" - t.string "type", limit: 255 - t.string "target_url", limit: 255 - t.string "description", limit: 255 + t.string "type" + t.string "target_url" + t.string "description" t.text "artifacts_file" t.integer "gl_project_id" t.text "artifacts_metadata" @@ -145,7 +145,6 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree add_index "ci_builds", ["commit_id", "status", "type"], name: "index_ci_builds_on_commit_id_and_status_and_type", using: :btree - add_index "ci_builds", ["commit_id", "status"], name: "index_ci_builds_on_commit_id_and_status", using: :btree add_index "ci_builds", ["commit_id", "type", "name", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_name_and_ref", using: :btree add_index "ci_builds", ["commit_id", "type", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_ref", using: :btree add_index "ci_builds", ["commit_id"], name: "index_ci_builds_on_commit_id", using: :btree @@ -155,37 +154,28 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "ci_builds", ["project_id"], name: "index_ci_builds_on_project_id", using: :btree add_index "ci_builds", ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree add_index "ci_builds", ["status"], name: "index_ci_builds_on_status", using: :btree - add_index "ci_builds", ["type", "status", "runner_id"], name: "index_ci_builds_on_test2", using: :btree add_index "ci_builds", ["type"], name: "index_ci_builds_on_type", using: :btree create_table "ci_commits", force: :cascade do |t| t.integer "project_id" - t.string "ref", limit: 255 - t.string "sha", limit: 255 - t.string "before_sha", limit: 255 + t.string "ref" + t.string "sha" + t.string "before_sha" t.text "push_data" t.datetime "created_at" t.datetime "updated_at" - t.boolean "tag", default: false + t.boolean "tag", default: false t.text "yaml_errors" t.datetime "committed_at" t.integer "gl_project_id" - t.string "status" - t.datetime "started_at" - t.datetime "finished_at" - t.string "action" - t.integer "duration" end - add_index "ci_commits", ["gl_project_id", "sha"], name: "index_ci_commits_on_gl_project_id_and_sha", using: :btree - add_index "ci_commits", ["gl_project_id", "status"], name: "index_ci_commits_on_gl_project_id_and_status", using: :btree add_index "ci_commits", ["gl_project_id"], name: "index_ci_commits_on_gl_project_id", using: :btree add_index "ci_commits", ["project_id", "committed_at", "id"], name: "index_ci_commits_on_project_id_and_committed_at_and_id", using: :btree add_index "ci_commits", ["project_id", "committed_at"], name: "index_ci_commits_on_project_id_and_committed_at", using: :btree add_index "ci_commits", ["project_id", "sha"], name: "index_ci_commits_on_project_id_and_sha", using: :btree add_index "ci_commits", ["project_id"], name: "index_ci_commits_on_project_id", using: :btree add_index "ci_commits", ["sha"], name: "index_ci_commits_on_sha", using: :btree - add_index "ci_commits", ["status"], name: "index_ci_commits_on_status", using: :btree create_table "ci_events", force: :cascade do |t| t.integer "project_id" @@ -201,16 +191,16 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "ci_events", ["project_id"], name: "index_ci_events_on_project_id", using: :btree create_table "ci_jobs", force: :cascade do |t| - t.integer "project_id", null: false + t.integer "project_id", null: false t.text "commands" - t.boolean "active", default: true, null: false + t.boolean "active", default: true, null: false t.datetime "created_at" t.datetime "updated_at" - t.string "name", limit: 255 - t.boolean "build_branches", default: true, null: false - t.boolean "build_tags", default: false, null: false - t.string "job_type", limit: 255, default: "parallel" - t.string "refs", limit: 255 + t.string "name" + t.boolean "build_branches", default: true, null: false + t.boolean "build_tags", default: false, null: false + t.string "job_type", default: "parallel" + t.string "refs" t.datetime "deleted_at" end @@ -218,25 +208,25 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "ci_jobs", ["project_id"], name: "index_ci_jobs_on_project_id", using: :btree create_table "ci_projects", force: :cascade do |t| - t.string "name", limit: 255 - t.integer "timeout", default: 3600, null: false + t.string "name" + t.integer "timeout", default: 3600, null: false t.datetime "created_at" t.datetime "updated_at" - t.string "token", limit: 255 - t.string "default_ref", limit: 255 - t.string "path", limit: 255 - t.boolean "always_build", default: false, null: false + t.string "token" + t.string "default_ref" + t.string "path" + t.boolean "always_build", default: false, null: false t.integer "polling_interval" - t.boolean "public", default: false, null: false - t.string "ssh_url_to_repo", limit: 255 + t.boolean "public", default: false, null: false + t.string "ssh_url_to_repo" t.integer "gitlab_id" - t.boolean "allow_git_fetch", default: true, null: false - t.string "email_recipients", limit: 255, default: "", null: false - t.boolean "email_add_pusher", default: true, null: false - t.boolean "email_only_broken_builds", default: true, null: false - t.string "skip_refs", limit: 255 - t.string "coverage_regex", limit: 255 - t.boolean "shared_runners_enabled", default: false + t.boolean "allow_git_fetch", default: true, null: false + t.string "email_recipients", default: "", null: false + t.boolean "email_add_pusher", default: true, null: false + t.boolean "email_only_broken_builds", default: true, null: false + t.string "skip_refs" + t.string "coverage_regex" + t.boolean "shared_runners_enabled", default: false t.text "generated_yaml_config" end @@ -255,18 +245,18 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "ci_runner_projects", ["runner_id"], name: "index_ci_runner_projects_on_runner_id", using: :btree create_table "ci_runners", force: :cascade do |t| - t.string "token", limit: 255 + t.string "token" t.datetime "created_at" t.datetime "updated_at" - t.string "description", limit: 255 + t.string "description" t.datetime "contacted_at" - t.boolean "active", default: true, null: false - t.boolean "is_shared", default: false - t.string "name", limit: 255 - t.string "version", limit: 255 - t.string "revision", limit: 255 - t.string "platform", limit: 255 - t.string "architecture", limit: 255 + t.boolean "active", default: true, null: false + t.boolean "is_shared", default: false + t.string "name" + t.string "version" + t.string "revision" + t.string "platform" + t.string "architecture" end add_index "ci_runners", ["description"], name: "index_ci_runners_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} @@ -274,19 +264,19 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "ci_runners", ["token"], name: "index_ci_runners_on_token_trigram", using: :gin, opclasses: {"token"=>"gin_trgm_ops"} create_table "ci_services", force: :cascade do |t| - t.string "type", limit: 255 - t.string "title", limit: 255 - t.integer "project_id", null: false + t.string "type" + t.string "title" + t.integer "project_id", null: false t.datetime "created_at" t.datetime "updated_at" - t.boolean "active", default: false, null: false + t.boolean "active", default: false, null: false t.text "properties" end add_index "ci_services", ["project_id"], name: "index_ci_services_on_project_id", using: :btree create_table "ci_sessions", force: :cascade do |t| - t.string "session_id", limit: 255, null: false + t.string "session_id", null: false t.text "data" t.datetime "created_at" t.datetime "updated_at" @@ -298,9 +288,9 @@ ActiveRecord::Schema.define(version: 20160412175417) do create_table "ci_taggings", force: :cascade do |t| t.integer "tag_id" t.integer "taggable_id" - t.string "taggable_type", limit: 255 + t.string "taggable_type" t.integer "tagger_id" - t.string "tagger_type", limit: 255 + t.string "tagger_type" t.string "context", limit: 128 t.datetime "created_at" end @@ -309,8 +299,8 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "ci_taggings", ["taggable_id", "taggable_type", "context"], name: "index_ci_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree create_table "ci_tags", force: :cascade do |t| - t.string "name", limit: 255 - t.integer "taggings_count", default: 0 + t.string "name" + t.integer "taggings_count", default: 0 end add_index "ci_tags", ["name"], name: "index_ci_tags_on_name", unique: true, using: :btree @@ -324,7 +314,7 @@ ActiveRecord::Schema.define(version: 20160412175417) do end create_table "ci_triggers", force: :cascade do |t| - t.string "token", limit: 255 + t.string "token" t.integer "project_id" t.datetime "deleted_at" t.datetime "created_at" @@ -337,19 +327,19 @@ ActiveRecord::Schema.define(version: 20160412175417) do create_table "ci_variables", force: :cascade do |t| t.integer "project_id" - t.string "key", limit: 255 + t.string "key" t.text "value" t.text "encrypted_value" - t.string "encrypted_value_salt", limit: 255 - t.string "encrypted_value_iv", limit: 255 + t.string "encrypted_value_salt" + t.string "encrypted_value_iv" t.integer "gl_project_id" end add_index "ci_variables", ["gl_project_id"], name: "index_ci_variables_on_gl_project_id", using: :btree create_table "ci_web_hooks", force: :cascade do |t| - t.string "url", limit: 255, null: false - t.integer "project_id", null: false + t.string "url", null: false + t.integer "project_id", null: false t.datetime "created_at" t.datetime "updated_at" end @@ -357,30 +347,30 @@ ActiveRecord::Schema.define(version: 20160412175417) do create_table "deploy_keys_projects", force: :cascade do |t| t.integer "deploy_key_id", null: false t.integer "project_id", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at" + t.datetime "updated_at" end add_index "deploy_keys_projects", ["project_id"], name: "index_deploy_keys_projects_on_project_id", using: :btree create_table "emails", force: :cascade do |t| - t.integer "user_id", null: false - t.string "email", limit: 510, null: false + t.integer "user_id", null: false + t.string "email", null: false t.datetime "created_at" t.datetime "updated_at" end - add_index "emails", ["email"], name: "emails_email_key", unique: true, using: :btree + add_index "emails", ["email"], name: "index_emails_on_email", unique: true, using: :btree add_index "emails", ["user_id"], name: "index_emails_on_user_id", using: :btree create_table "events", force: :cascade do |t| - t.string "target_type", limit: 510 + t.string "target_type" t.integer "target_id" - t.string "title", limit: 510 + t.string "title" t.text "data" t.integer "project_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at" + t.datetime "updated_at" t.integer "action" t.integer "author_id" end @@ -395,15 +385,15 @@ ActiveRecord::Schema.define(version: 20160412175417) do create_table "forked_project_links", force: :cascade do |t| t.integer "forked_to_project_id", null: false t.integer "forked_from_project_id", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at" + t.datetime "updated_at" end - add_index "forked_project_links", ["forked_to_project_id"], name: "forked_project_links_forked_to_project_id_key", unique: true, using: :btree + add_index "forked_project_links", ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree create_table "identities", force: :cascade do |t| - t.string "extern_uid", limit: 255 - t.string "provider", limit: 255 + t.string "extern_uid" + t.string "provider" t.integer "user_id" t.datetime "created_at" t.datetime "updated_at" @@ -413,21 +403,21 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree create_table "issues", force: :cascade do |t| - t.string "title", limit: 510 + t.string "title" t.integer "assignee_id" t.integer "author_id" t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.integer "position", default: 0 - t.string "branch_name", limit: 510 + t.integer "position", default: 0 + t.string "branch_name" t.text "description" t.integer "milestone_id" - t.string "state", limit: 510 + t.string "state" t.integer "iid" t.integer "updated_by_id" - t.boolean "confidential", default: false t.integer "moved_to_id" + t.boolean "confidential", default: false t.datetime "deleted_at" end @@ -450,10 +440,10 @@ ActiveRecord::Schema.define(version: 20160412175417) do t.datetime "created_at" t.datetime "updated_at" t.text "key" - t.string "title", limit: 510 - t.string "type", limit: 510 - t.string "fingerprint", limit: 510 - t.boolean "public", default: false, null: false + t.string "title" + t.string "type" + t.string "fingerprint" + t.boolean "public", default: false, null: false end add_index "keys", ["created_at", "id"], name: "index_keys_on_created_at_and_id", using: :btree @@ -462,7 +452,7 @@ ActiveRecord::Schema.define(version: 20160412175417) do create_table "label_links", force: :cascade do |t| t.integer "label_id" t.integer "target_id" - t.string "target_type", limit: 255 + t.string "target_type" t.datetime "created_at" t.datetime "updated_at" end @@ -471,23 +461,23 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "label_links", ["target_id", "target_type"], name: "index_label_links_on_target_id_and_target_type", using: :btree create_table "labels", force: :cascade do |t| - t.string "title", limit: 255 - t.string "color", limit: 255 + t.string "title" + t.string "color" t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.boolean "template", default: false + t.boolean "template", default: false t.string "description" end add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree create_table "lfs_objects", force: :cascade do |t| - t.string "oid", limit: 255, null: false - t.integer "size", limit: 8, null: false + t.string "oid", null: false + t.integer "size", limit: 8, null: false t.datetime "created_at" t.datetime "updated_at" - t.string "file", limit: 255 + t.string "file" end add_index "lfs_objects", ["oid"], name: "index_lfs_objects_on_oid", unique: true, using: :btree @@ -502,17 +492,17 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "lfs_objects_projects", ["project_id"], name: "index_lfs_objects_projects_on_project_id", using: :btree create_table "members", force: :cascade do |t| - t.integer "access_level", null: false - t.integer "source_id", null: false - t.string "source_type", limit: 255, null: false + t.integer "access_level", null: false + t.integer "source_id", null: false + t.string "source_type", null: false t.integer "user_id" - t.integer "notification_level", null: false - t.string "type", limit: 255 + t.integer "notification_level", null: false + t.string "type" t.datetime "created_at" t.datetime "updated_at" t.integer "created_by_id" - t.string "invite_email", limit: 255 - t.string "invite_token", limit: 255 + t.string "invite_email" + t.string "invite_token" t.datetime "invite_accepted_at" end @@ -524,10 +514,10 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "members", ["user_id"], name: "index_members_on_user_id", using: :btree create_table "merge_request_diffs", force: :cascade do |t| - t.string "state", limit: 255 + t.string "state" t.text "st_commits" t.text "st_diffs" - t.integer "merge_request_id", null: false + t.integer "merge_request_id", null: false t.datetime "created_at" t.datetime "updated_at" t.string "base_commit_sha" @@ -537,26 +527,26 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "merge_request_diffs", ["merge_request_id"], name: "index_merge_request_diffs_on_merge_request_id", unique: true, using: :btree create_table "merge_requests", force: :cascade do |t| - t.string "target_branch", limit: 510, null: false - t.string "source_branch", limit: 510, null: false - t.integer "source_project_id", null: false + t.string "target_branch", null: false + t.string "source_branch", null: false + t.integer "source_project_id", null: false t.integer "author_id" t.integer "assignee_id" - t.string "title", limit: 510 + t.string "title" t.datetime "created_at" t.datetime "updated_at" t.integer "milestone_id" - t.string "state", limit: 510 - t.string "merge_status", limit: 510 - t.integer "target_project_id", null: false + t.string "state" + t.string "merge_status" + t.integer "target_project_id", null: false t.integer "iid" t.text "description" - t.integer "position", default: 0 + t.integer "position", default: 0 t.datetime "locked_at" t.integer "updated_by_id" - t.string "merge_error", limit: 255 + t.string "merge_error" t.text "merge_params" - t.boolean "merge_when_build_succeeds", default: false, null: false + t.boolean "merge_when_build_succeeds", default: false, null: false t.integer "merge_user_id" t.string "merge_commit_sha" t.datetime "deleted_at" @@ -577,13 +567,13 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "merge_requests", ["title"], name: "index_merge_requests_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} create_table "milestones", force: :cascade do |t| - t.string "title", limit: 510, null: false - t.integer "project_id", null: false + t.string "title", null: false + t.integer "project_id", null: false t.text "description" t.date "due_date" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.string "state", limit: 510 + t.datetime "created_at" + t.datetime "updated_at" + t.string "state" t.integer "iid" end @@ -596,16 +586,16 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "milestones", ["title"], name: "index_milestones_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} create_table "namespaces", force: :cascade do |t| - t.string "name", limit: 510, null: false - t.string "path", limit: 510, null: false + t.string "name", null: false + t.string "path", null: false t.integer "owner_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.string "type", limit: 510 - t.string "description", limit: 510, default: "", null: false - t.string "avatar", limit: 510 - t.boolean "share_with_group_lock", default: false - t.integer "visibility_level", default: 20, null: false + t.datetime "created_at" + t.datetime "updated_at" + t.string "type" + t.string "description", default: "", null: false + t.string "avatar" + t.boolean "share_with_group_lock", default: false + t.integer "visibility_level", default: 20, null: false end add_index "namespaces", ["created_at", "id"], name: "index_namespaces_on_created_at_and_id", using: :btree @@ -619,19 +609,19 @@ ActiveRecord::Schema.define(version: 20160412175417) do create_table "notes", force: :cascade do |t| t.text "note" - t.string "noteable_type", limit: 510 + t.string "noteable_type" t.integer "author_id" t.datetime "created_at" t.datetime "updated_at" t.integer "project_id" - t.string "attachment", limit: 510 - t.string "line_code", limit: 510 - t.string "commit_id", limit: 510 + t.string "attachment" + t.string "line_code" + t.string "commit_id" t.integer "noteable_id" + t.boolean "system", default: false, null: false t.text "st_diff" - t.boolean "system", null: false t.integer "updated_by_id" - t.boolean "is_award", default: false, null: false + t.boolean "is_award", default: false, null: false end add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree @@ -660,14 +650,14 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "notification_settings", ["user_id"], name: "index_notification_settings_on_user_id", using: :btree create_table "oauth_access_grants", force: :cascade do |t| - t.integer "resource_owner_id", null: false - t.integer "application_id", null: false - t.string "token", limit: 255, null: false - t.integer "expires_in", null: false - t.text "redirect_uri", null: false - t.datetime "created_at", null: false + t.integer "resource_owner_id", null: false + t.integer "application_id", null: false + t.string "token", null: false + t.integer "expires_in", null: false + t.text "redirect_uri", null: false + t.datetime "created_at", null: false t.datetime "revoked_at" - t.string "scopes", limit: 255 + t.string "scopes" end add_index "oauth_access_grants", ["token"], name: "index_oauth_access_grants_on_token", unique: true, using: :btree @@ -675,12 +665,12 @@ ActiveRecord::Schema.define(version: 20160412175417) do create_table "oauth_access_tokens", force: :cascade do |t| t.integer "resource_owner_id" t.integer "application_id" - t.string "token", limit: 255, null: false - t.string "refresh_token", limit: 255 + t.string "token", null: false + t.string "refresh_token" t.integer "expires_in" t.datetime "revoked_at" - t.datetime "created_at", null: false - t.string "scopes", limit: 255 + t.datetime "created_at", null: false + t.string "scopes" end add_index "oauth_access_tokens", ["refresh_token"], name: "index_oauth_access_tokens_on_refresh_token", unique: true, using: :btree @@ -688,15 +678,15 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "oauth_access_tokens", ["token"], name: "index_oauth_access_tokens_on_token", unique: true, using: :btree create_table "oauth_applications", force: :cascade do |t| - t.string "name", limit: 255, null: false - t.string "uid", limit: 255, null: false - t.string "secret", limit: 255, null: false - t.text "redirect_uri", null: false - t.string "scopes", limit: 255, default: "", null: false + t.string "name", null: false + t.string "uid", null: false + t.string "secret", null: false + t.text "redirect_uri", null: false + t.string "scopes", default: "", null: false t.datetime "created_at" t.datetime "updated_at" t.integer "owner_id" - t.string "owner_type", limit: 255 + t.string "owner_type" end add_index "oauth_applications", ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type", using: :btree @@ -716,43 +706,43 @@ ActiveRecord::Schema.define(version: 20160412175417) do end create_table "projects", force: :cascade do |t| - t.string "name", limit: 510 - t.string "path", limit: 510 + t.string "name" + t.string "path" t.text "description" t.datetime "created_at" t.datetime "updated_at" t.integer "creator_id" - t.boolean "issues_enabled", null: false - t.boolean "wall_enabled", null: false - t.boolean "merge_requests_enabled", null: false - t.boolean "wiki_enabled", null: false + t.boolean "issues_enabled", default: true, null: false + t.boolean "wall_enabled", default: true, null: false + t.boolean "merge_requests_enabled", default: true, null: false + t.boolean "wiki_enabled", default: true, null: false t.integer "namespace_id" - t.string "issues_tracker", limit: 510, default: "gitlab", null: false - t.string "issues_tracker_id", limit: 510 - t.boolean "snippets_enabled", null: false + t.string "issues_tracker", default: "gitlab", null: false + t.string "issues_tracker_id" + t.boolean "snippets_enabled", default: true, null: false t.datetime "last_activity_at" - t.string "import_url", limit: 510 - t.integer "visibility_level", default: 0, null: false - t.boolean "archived", null: false - t.string "import_status", limit: 255 - t.float "repository_size", default: 0.0 - t.integer "star_count", default: 0, null: false - t.string "import_type", limit: 255 - t.string "import_source", limit: 255 - t.string "avatar", limit: 255 - t.integer "commit_count", default: 0 + t.string "import_url" + t.integer "visibility_level", default: 0, null: false + t.boolean "archived", default: false, null: false + t.string "avatar" + t.string "import_status" + t.float "repository_size", default: 0.0 + t.integer "star_count", default: 0, null: false + t.string "import_type" + t.string "import_source" + t.integer "commit_count", default: 0 t.text "import_error" t.integer "ci_id" - t.boolean "builds_enabled", default: true, null: false - t.boolean "shared_runners_enabled", default: true, null: false + t.boolean "builds_enabled", default: true, null: false + t.boolean "shared_runners_enabled", default: true, null: false t.string "runners_token" t.string "build_coverage_regex" - t.boolean "build_allow_git_fetch", default: true, null: false - t.integer "build_timeout", default: 3600, null: false - t.boolean "pending_delete", default: false - t.boolean "public_builds", default: true, null: false + t.boolean "build_allow_git_fetch", default: true, null: false + t.integer "build_timeout", default: 3600, null: false + t.boolean "pending_delete", default: false + t.boolean "public_builds", default: true, null: false t.string "main_language" - t.integer "pushes_since_gc", default: 0 + t.integer "pushes_since_gc", default: 0 end add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree @@ -772,17 +762,17 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree create_table "protected_branches", force: :cascade do |t| - t.integer "project_id", null: false - t.string "name", limit: 510, null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.boolean "developers_can_push", default: false, null: false + t.integer "project_id", null: false + t.string "name", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.boolean "developers_can_push", default: false, null: false end add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree create_table "releases", force: :cascade do |t| - t.string "tag", limit: 255 + t.string "tag" t.text "description" t.integer "project_id" t.datetime "created_at" @@ -795,32 +785,32 @@ ActiveRecord::Schema.define(version: 20160412175417) do create_table "sent_notifications", force: :cascade do |t| t.integer "project_id" t.integer "noteable_id" - t.string "noteable_type", limit: 255 + t.string "noteable_type" t.integer "recipient_id" - t.string "commit_id", limit: 255 - t.string "reply_key", limit: 255, null: false - t.string "line_code", limit: 255 + t.string "commit_id" + t.string "reply_key", null: false + t.string "line_code" end add_index "sent_notifications", ["reply_key"], name: "index_sent_notifications_on_reply_key", unique: true, using: :btree create_table "services", force: :cascade do |t| - t.string "type", limit: 510 - t.string "title", limit: 510 + t.string "type" + t.string "title" t.integer "project_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.boolean "active", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.boolean "active", null: false t.text "properties" - t.boolean "template", default: false - t.boolean "push_events", default: true - t.boolean "issues_events", default: true - t.boolean "merge_requests_events", default: true - t.boolean "tag_push_events", default: true - t.boolean "note_events", default: true, null: false - t.boolean "build_events", default: false, null: false - t.string "category", default: "common", null: false - t.boolean "default", default: false + t.boolean "template", default: false + t.boolean "push_events", default: true + t.boolean "issues_events", default: true + t.boolean "merge_requests_events", default: true + t.boolean "tag_push_events", default: true + t.boolean "note_events", default: true, null: false + t.boolean "build_events", default: false, null: false + t.string "category", default: "common", null: false + t.boolean "default", default: false end add_index "services", ["category"], name: "index_services_on_category", using: :btree @@ -830,15 +820,15 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "services", ["template"], name: "index_services_on_template", using: :btree create_table "snippets", force: :cascade do |t| - t.string "title", limit: 510 + t.string "title" t.text "content" - t.integer "author_id", null: false + t.integer "author_id", null: false t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.string "file_name", limit: 510 - t.string "type", limit: 510 - t.integer "visibility_level", default: 0, null: false + t.string "file_name" + t.string "type" + t.integer "visibility_level", default: 0, null: false end add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree @@ -866,7 +856,7 @@ ActiveRecord::Schema.define(version: 20160412175417) do create_table "subscriptions", force: :cascade do |t| t.integer "user_id" t.integer "subscribable_id" - t.string "subscribable_type", limit: 255 + t.string "subscribable_type" t.boolean "subscribed" t.datetime "created_at" t.datetime "updated_at" @@ -877,10 +867,10 @@ ActiveRecord::Schema.define(version: 20160412175417) do create_table "taggings", force: :cascade do |t| t.integer "tag_id" t.integer "taggable_id" - t.string "taggable_type", limit: 510 + t.string "taggable_type" t.integer "tagger_id" - t.string "tagger_type", limit: 510 - t.string "context", limit: 510 + t.string "tagger_type" + t.string "context" t.datetime "created_at" end @@ -888,8 +878,8 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree create_table "tags", force: :cascade do |t| - t.string "name", limit: 510 - t.integer "taggings_count", default: 0 + t.string "name" + t.integer "taggings_count", default: 0 end add_index "tags", ["name"], name: "index_tags_on_name", unique: true, using: :btree @@ -917,76 +907,76 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "todos", ["user_id"], name: "index_todos_on_user_id", using: :btree create_table "users", force: :cascade do |t| - t.string "email", limit: 510, default: "", null: false - t.string "encrypted_password", limit: 256, default: "", null: false - t.string "reset_password_token", limit: 510 + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false + t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0 + t.integer "sign_in_count", default: 0 t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" - t.string "current_sign_in_ip", limit: 510 - t.string "last_sign_in_ip", limit: 510 + t.string "current_sign_in_ip" + t.string "last_sign_in_ip" t.datetime "created_at" t.datetime "updated_at" - t.string "name", limit: 510 - t.boolean "admin", null: false - t.integer "projects_limit", default: 10 - t.string "skype", limit: 510, default: "", null: false - t.string "linkedin", limit: 510, default: "", null: false - t.string "twitter", limit: 510, default: "", null: false - t.string "authentication_token", limit: 510 - t.integer "theme_id", default: 1, null: false - t.string "bio", limit: 510 - t.integer "failed_attempts", default: 0 + t.string "name" + t.boolean "admin", default: false, null: false + t.integer "projects_limit", default: 10 + t.string "skype", default: "", null: false + t.string "linkedin", default: "", null: false + t.string "twitter", default: "", null: false + t.string "authentication_token" + t.integer "theme_id", default: 1, null: false + t.string "bio" + t.integer "failed_attempts", default: 0 t.datetime "locked_at" - t.string "username", limit: 510 - t.boolean "can_create_group", null: false - t.boolean "can_create_team", null: false - t.string "state", limit: 510 - t.integer "color_scheme_id", default: 1, null: false - t.integer "notification_level", default: 1, null: false + t.string "username" + t.boolean "can_create_group", default: true, null: false + t.boolean "can_create_team", default: true, null: false + t.string "state" + t.integer "color_scheme_id", default: 1, null: false + t.integer "notification_level", default: 1, null: false t.datetime "password_expires_at" t.integer "created_by_id" - t.string "avatar", limit: 510 - t.string "confirmation_token", limit: 510 + t.datetime "last_credential_check_at" + t.string "avatar" + t.string "confirmation_token" t.datetime "confirmed_at" t.datetime "confirmation_sent_at" - t.string "unconfirmed_email", limit: 510 - t.boolean "hide_no_ssh_key" - t.string "website_url", limit: 510, default: "", null: false - t.datetime "last_credential_check_at" - t.string "notification_email", limit: 255 - t.boolean "hide_no_password", default: false - t.boolean "password_automatically_set", default: false - t.string "location", limit: 255 - t.string "public_email", limit: 255, default: "", null: false - t.string "encrypted_otp_secret", limit: 255 - t.string "encrypted_otp_secret_iv", limit: 255 - t.string "encrypted_otp_secret_salt", limit: 255 - t.boolean "otp_required_for_login", default: false, null: false + t.string "unconfirmed_email" + t.boolean "hide_no_ssh_key", default: false + t.string "website_url", default: "", null: false + t.string "notification_email" + t.boolean "hide_no_password", default: false + t.boolean "password_automatically_set", default: false + t.string "location" + t.string "encrypted_otp_secret" + t.string "encrypted_otp_secret_iv" + t.string "encrypted_otp_secret_salt" + t.boolean "otp_required_for_login", default: false, null: false t.text "otp_backup_codes" - t.integer "dashboard", default: 0 - t.integer "project_view", default: 0 + t.string "public_email", default: "", null: false + t.integer "dashboard", default: 0 + t.integer "project_view", default: 0 t.integer "consumed_timestep" - t.integer "layout", default: 0 - t.boolean "hide_project_limit", default: false + t.integer "layout", default: 0 + t.boolean "hide_project_limit", default: false t.string "unlock_token" t.datetime "otp_grace_period_started_at" - t.boolean "ldap_email", default: false, null: false - t.boolean "external", default: false + t.boolean "ldap_email", default: false, null: false + t.boolean "external", default: false end add_index "users", ["admin"], name: "index_users_on_admin", using: :btree - add_index "users", ["authentication_token"], name: "users_authentication_token_key", unique: true, using: :btree - add_index "users", ["confirmation_token"], name: "users_confirmation_token_key", unique: true, using: :btree + add_index "users", ["authentication_token"], name: "index_users_on_authentication_token", unique: true, using: :btree + add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree add_index "users", ["created_at", "id"], name: "index_users_on_created_at_and_id", using: :btree add_index "users", ["current_sign_in_at"], name: "index_users_on_current_sign_in_at", using: :btree + add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree add_index "users", ["email"], name: "index_users_on_email_trigram", using: :gin, opclasses: {"email"=>"gin_trgm_ops"} - add_index "users", ["email"], name: "users_email_key", unique: true, using: :btree add_index "users", ["name"], name: "index_users_on_name", using: :btree add_index "users", ["name"], name: "index_users_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"} - add_index "users", ["reset_password_token"], name: "users_reset_password_token_key", unique: true, using: :btree + add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree add_index "users", ["username"], name: "index_users_on_username", using: :btree add_index "users", ["username"], name: "index_users_on_username_trigram", using: :gin, opclasses: {"username"=>"gin_trgm_ops"} @@ -1006,11 +996,11 @@ ActiveRecord::Schema.define(version: 20160412175417) do t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.string "type", limit: 510, default: "ProjectHook" + t.string "type", default: "ProjectHook" t.integer "service_id" - t.boolean "push_events", null: false - t.boolean "issues_events", null: false - t.boolean "merge_requests_events", null: false + t.boolean "push_events", default: true, null: false + t.boolean "issues_events", default: false, null: false + t.boolean "merge_requests_events", default: false, null: false t.boolean "tag_push_events", default: false t.boolean "note_events", default: false, null: false t.boolean "enable_ssl_verification", default: true diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb index 80e80ea665d..dacaa96d760 100644 --- a/spec/features/commits_spec.rb +++ b/spec/features/commits_spec.rb @@ -162,9 +162,4 @@ describe 'Commits' do end end end - - def ci_status_path(ci_commit) - project = ci_commit.project - builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha) - end end diff --git a/spec/helpers/ci_status_helper_spec.rb b/spec/helpers/ci_status_helper_spec.rb index 906db0ea829..f942695b6f0 100644 --- a/spec/helpers/ci_status_helper_spec.rb +++ b/spec/helpers/ci_status_helper_spec.rb @@ -6,7 +6,7 @@ describe CiStatusHelper do let(:success_commit) { double("Ci::Commit", status: 'success') } let(:failed_commit) { double("Ci::Commit", status: 'failed') } - describe 'ci_status_icon' do + describe 'ci_icon_for_status' do it { expect(helper.ci_icon_for_status(success_commit.status)).to include('fa-check') } it { expect(helper.ci_icon_for_status(failed_commit.status)).to include('fa-close') } end From f3a4f854494963edeeae87a5f9b2a483f0c6dbfd Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 16:07:29 +0200 Subject: [PATCH 19/68] Update db/schema.rb --- db/schema.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/db/schema.rb b/db/schema.rb index 44482de467e..f53478a8594 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160331223143) do +ActiveRecord::Schema.define(version: 20160412175417) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -168,14 +168,21 @@ ActiveRecord::Schema.define(version: 20160331223143) do t.text "yaml_errors" t.datetime "committed_at" t.integer "gl_project_id" + t.string "status" + t.datetime "started_at" + t.datetime "finished_at" + t.integer "duration" end + add_index "ci_commits", ["gl_project_id", "sha"], name: "index_ci_commits_on_gl_project_id_and_sha", using: :btree + add_index "ci_commits", ["gl_project_id", "status"], name: "index_ci_commits_on_gl_project_id_and_status", using: :btree add_index "ci_commits", ["gl_project_id"], name: "index_ci_commits_on_gl_project_id", using: :btree add_index "ci_commits", ["project_id", "committed_at", "id"], name: "index_ci_commits_on_project_id_and_committed_at_and_id", using: :btree add_index "ci_commits", ["project_id", "committed_at"], name: "index_ci_commits_on_project_id_and_committed_at", using: :btree add_index "ci_commits", ["project_id", "sha"], name: "index_ci_commits_on_project_id_and_sha", using: :btree add_index "ci_commits", ["project_id"], name: "index_ci_commits_on_project_id", using: :btree add_index "ci_commits", ["sha"], name: "index_ci_commits_on_sha", using: :btree + add_index "ci_commits", ["status"], name: "index_ci_commits_on_status", using: :btree create_table "ci_events", force: :cascade do |t| t.integer "project_id" From 8fb976a3ff5fa05ae35cb03de6ca34e8c2139841 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 16:14:27 +0200 Subject: [PATCH 20/68] Fix migrations on MySQL --- db/migrate/20160412173418_add_ci_commit_indexes.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/db/migrate/20160412173418_add_ci_commit_indexes.rb b/db/migrate/20160412173418_add_ci_commit_indexes.rb index c1e238bc021..603d4a41610 100644 --- a/db/migrate/20160412173418_add_ci_commit_indexes.rb +++ b/db/migrate/20160412173418_add_ci_commit_indexes.rb @@ -10,6 +10,10 @@ class AddCiCommitIndexes < ActiveRecord::Migration private def index_options - { algorithm: :concurrently } if Gitlab::Database.postgresql? + if Gitlab::Database.postgresql? + { algorithm: :concurrently } + else + { } + end end end From 9e68109f2d454dd05cf42a03a41a2e858e1e11bc Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 16:32:42 +0200 Subject: [PATCH 21/68] Optimise Merge Request builds rendering --- app/controllers/projects/merge_requests_controller.rb | 2 -- app/views/projects/commit/_ci_commit.html.haml | 6 +++--- app/views/projects/merge_requests/show/_builds.html.haml | 3 ++- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 2fcef6c430a..4b8fc049047 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -118,7 +118,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController @diffs = @merge_request.compare.diffs(diff_options) if @merge_request.compare @ci_commit = @merge_request.ci_commit - @ci_commits = [@ci_commit].compact @statuses = @ci_commit.statuses if @ci_commit @note_counts = Note.where(commit_id: @commits.map(&:id)). @@ -311,7 +310,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_request_diff = @merge_request.merge_request_diff @ci_commit = @merge_request.ci_commit - @ci_commits = [@ci_commit].compact @statuses = @ci_commit.statuses if @ci_commit if @merge_request.locked_long_ago? diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml index 06520e40bd9..25714e6cb47 100644 --- a/app/views/projects/commit/_ci_commit.html.haml +++ b/app/views/projects/commit/_ci_commit.html.haml @@ -2,10 +2,10 @@ .pull-right - if can?(current_user, :update_build, @project) - if ci_commit.builds.latest.failed.any?(&:retryable?) - = link_to "Retry failed", retry_builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: 'btn btn-grouped btn-primary', method: :post + = link_to "Retry failed", retry_builds_namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), class: 'btn btn-grouped btn-primary', method: :post - if ci_commit.builds.running_or_pending.any? - = link_to "Cancel running", cancel_builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post + = link_to "Cancel running", cancel_builds_namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post .oneline = pluralize ci_commit.statuses.count(:id), "build" @@ -15,7 +15,7 @@ = ci_commit.ref - if defined?(link_to_commit) && link_to_commit for commit - = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "monospace" + = link_to ci_commit.short_sha, namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), class: "monospace" - if ci_commit.duration > 0 in = time_interval_in_words ci_commit.duration diff --git a/app/views/projects/merge_requests/show/_builds.html.haml b/app/views/projects/merge_requests/show/_builds.html.haml index 307a75d02ca..a116ffe2e15 100644 --- a/app/views/projects/merge_requests/show/_builds.html.haml +++ b/app/views/projects/merge_requests/show/_builds.html.haml @@ -1 +1,2 @@ -= render "projects/commit/builds", link_to_commit: true += render "projects/commit/ci_commit", ci_commit: @ci_commit, link_to_commit: true + From 3d38e461918e75e719938801ae2d63aae680a85c Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 17:26:22 +0200 Subject: [PATCH 22/68] Support skipped status --- app/models/concerns/ci_status.rb | 3 +++ features/steps/dashboard/dashboard.rb | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/models/concerns/ci_status.rb b/app/models/concerns/ci_status.rb index 67e15b2d55b..fd86d2f7553 100644 --- a/app/models/concerns/ci_status.rb +++ b/app/models/concerns/ci_status.rb @@ -12,12 +12,14 @@ module CiStatus pending = all.pending.select('count(*)').to_sql running = all.running.select('count(*)').to_sql canceled = all.canceled.select('count(*)').to_sql + skipped = all.skipped.select('count(*)').to_sql deduce_status = "(CASE WHEN (#{builds})=0 THEN 'skipped' WHEN (#{builds})=(#{success})+(#{ignored}) THEN 'success' WHEN (#{builds})=(#{pending}) THEN 'pending' WHEN (#{builds})=(#{canceled}) THEN 'canceled' + WHEN (#{builds})=(#{skipped}) THEN 'skipped' WHEN (#{running})+(#{pending})>0 THEN 'running' ELSE 'failed' END)" @@ -52,6 +54,7 @@ module CiStatus scope :success, -> { where(status: 'success') } scope :failed, -> { where(status: 'failed') } scope :canceled, -> { where(status: 'canceled') } + scope :skipped, -> { where(status: 'skipped') } scope :running_or_pending, -> { where(status: [:running, :pending]) } scope :finished, -> { where(status: [:success, :failed, :canceled]) } end diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb index b5980b35102..ba0e829dc0c 100644 --- a/features/steps/dashboard/dashboard.rb +++ b/features/steps/dashboard/dashboard.rb @@ -13,7 +13,7 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps end step 'I should see "Shop" project CI status' do - expect(page).to have_link "Build skipped" + expect(page).to have_link "Build: skipped" end step 'I should see last push widget' do From ae24b257189be758da6b5189f3a836654fe72f7e Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 17:30:31 +0200 Subject: [PATCH 23/68] Simplify state update of Ci::Commit object --- app/models/ci/commit.rb | 72 +++++++---------------------------------- 1 file changed, 11 insertions(+), 61 deletions(-) diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index 8865bd76bd2..07cd489223f 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -29,16 +29,8 @@ module Ci validates_presence_of :sha validate :valid_commit_sha - # Make sure that status is saved - before_save :status - before_save :started_at - before_save :finished_at - before_save :duration - # Invalidate object and save if when touched - after_touch :reload - after_touch :invalidate - after_touch :save + after_touch :update_state def self.truncate_sha(sha) sha[0...8] @@ -94,13 +86,6 @@ module Ci end end - def invalidate - write_attribute(:status, nil) - write_attribute(:started_at, nil) - write_attribute(:finished_at, nil) - write_attribute(:duration, nil) - end - def create_builds(user, trigger_request = nil) return unless config_processor config_processor.stages.any? do |stage| @@ -137,22 +122,6 @@ module Ci @retried ||= (statuses.order(id: :desc) - statuses.latest) end - def status - read_attribute(:status) || update_status - end - - def duration - read_attribute(:duration) || update_duration - end - - def started_at - read_attribute(:started_at) || update_started_at - end - - def finished_at - read_attribute(:finished_at) || update_finished_at - end - def coverage coverage_array = latest.map(&:coverage).compact if coverage_array.size >= 1 @@ -188,45 +157,26 @@ module Ci private - def update_status - self.status = - if yaml_errors.present? - 'failed' - else - latest.status || 'skipped' - end - end - - def update_started_at - self.started_at = - statuses.minimum(:started_at) - end - - def update_finished_at - self.finished_at = - statuses.maximum(:finished_at) - end - - def update_duration + def update_state + reload + self.status = if yaml_errors.present? + 'failed' + else + latest.status + end + self.started_at = statuses.minimum(:started_at) + self.finished_at = statuses.maximum(:finished_at) self.duration = begin duration_array = latest.map(&:duration).compact duration_array.reduce(:+).to_i end - end - - def update_statuses - update_status - update_started_at - update_finished_at - update_duration save end def save_yaml_error(error) return if self.yaml_errors? self.yaml_errors = error - update_status - save + update_state end end end From 2783877e869c7bfec30423f0a1e73bc33c269453 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 13 Apr 2016 10:54:51 -0500 Subject: [PATCH 24/68] Move declarations --- app/assets/javascripts/todos.js.coffee | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee index 37b4d2f66c7..08b29bd8214 100644 --- a/app/assets/javascripts/todos.js.coffee +++ b/app/assets/javascripts/todos.js.coffee @@ -73,14 +73,10 @@ class @Todos getTodosPerPage: -> @el.data('perPage') - redirectIfNeeded: (total) -> currPages = @getTotalPages() currPage = @getCurrentPage() - newPages = Math.ceil(total / @getTodosPerPage()) - url = location.href # Includes query strings - # Refresh if no remaining Todos if not total location.reload() @@ -89,6 +85,9 @@ class @Todos # Do nothing if no pagination return if not currPages + newPages = Math.ceil(total / @getTodosPerPage()) + url = location.href # Includes query strings + # If new total of pages is different than we have now if newPages isnt currPages # Redirect to previous page if there's one available From 2a7f7f75285e3818ac9c90b3e6c7893f4999e33e Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 20:51:03 +0200 Subject: [PATCH 25/68] Update handling of skipped status --- app/models/ci/build.rb | 2 +- app/models/ci/commit.rb | 23 ++++++++--------------- app/models/concerns/ci_status.rb | 10 +++++++++- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 085ecc6951c..c0b334d3600 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -116,7 +116,7 @@ module Ci end def retried? - !self.commit.latest.include?(self) + !self.commit.statuses.latest.include?(self) end def retry diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index 07cd489223f..334d3c7bc45 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -114,16 +114,12 @@ module Ci end end - def latest - statuses.latest - end - def retried @retried ||= (statuses.order(id: :desc) - statuses.latest) end def coverage - coverage_array = latest.map(&:coverage).compact + coverage_array = statuses.latest.map(&:coverage).compact if coverage_array.size >= 1 '%.2f' % (coverage_array.reduce(:+) / coverage_array.size) end @@ -158,18 +154,15 @@ module Ci private def update_state - reload - self.status = if yaml_errors.present? - 'failed' + statuses.reload + self.status = if yaml_errors.blank? + statuses.latest.status || 'skipped' else - latest.status + 'failed' end - self.started_at = statuses.minimum(:started_at) - self.finished_at = statuses.maximum(:finished_at) - self.duration = begin - duration_array = latest.map(&:duration).compact - duration_array.reduce(:+).to_i - end + self.started_at = statuses.started_at + self.finished_at = statuses.finished_at + self.duration = statuses.latest.duration save end diff --git a/app/models/concerns/ci_status.rb b/app/models/concerns/ci_status.rb index fd86d2f7553..8190b2a20c6 100644 --- a/app/models/concerns/ci_status.rb +++ b/app/models/concerns/ci_status.rb @@ -15,7 +15,7 @@ module CiStatus skipped = all.skipped.select('count(*)').to_sql deduce_status = "(CASE - WHEN (#{builds})=0 THEN 'skipped' + WHEN (#{builds})=0 THEN NULL WHEN (#{builds})=(#{success})+(#{ignored}) THEN 'success' WHEN (#{builds})=(#{pending}) THEN 'pending' WHEN (#{builds})=(#{canceled}) THEN 'canceled' @@ -35,6 +35,14 @@ module CiStatus duration_array = all.map(&:duration).compact duration_array.reduce(:+).to_i end + + def started_at + all.minimum(:started_at) + end + + def finished_at + all.minimum(:finished_at) + end end included do From 5117412e33821f8eaf50befd2e00e431bfc74738 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 20:54:21 +0200 Subject: [PATCH 26/68] Fix implementation of config_processor and ci_yaml_file --- app/models/ci/commit.rb | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index 334d3c7bc45..ae30407bcae 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -127,24 +127,29 @@ module Ci def config_processor return nil unless ci_yaml_file - @config_processor ||= Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.path_with_namespace) - rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e - save_yaml_error(e.message) - nil - rescue - save_yaml_error("Undefined error") - nil + return @config_processor if defined?(@config_processor) + + @config_processor ||= begin + Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.path_with_namespace) + rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e + save_yaml_error(e.message) + nil + rescue + save_yaml_error("Undefined error") + nil + end end def ci_yaml_file - return nil if defined?(@ci_yaml_file) + return @ci_yaml_file if defined?(@ci_yaml_file) + @ci_yaml_file ||= begin blob = project.repository.blob_at(sha, '.gitlab-ci.yml') blob.load_all_data!(project.repository) blob.data + rescue + nil end - rescue - nil end def skip_ci? From 0b1655e7b2e2aa57cb7ea8401743d709bf246074 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sat, 16 Apr 2016 21:46:26 +0200 Subject: [PATCH 27/68] Rename CiStatus to Statusable --- app/models/ci/commit.rb | 2 +- app/models/commit_status.rb | 8 ++++++-- app/models/concerns/{ci_status.rb => statuseable.rb} | 4 ++-- app/models/project.rb | 2 +- app/views/projects/commit/_ci_commit.html.haml | 2 +- spec/models/commit_spec.rb | 8 ++++++++ .../concerns/statuseable_spec.rb} | 4 ++-- 7 files changed, 21 insertions(+), 9 deletions(-) rename app/models/concerns/{ci_status.rb => statuseable.rb} (97%) rename spec/{lib/ci/status_spec.rb => models/concerns/statuseable_spec.rb} (97%) diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index ae30407bcae..412ab44aaf6 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -19,7 +19,7 @@ module Ci class Commit < ActiveRecord::Base extend Ci::Model - include CiStatus + include Statuseable belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id has_many :statuses, class_name: 'CommitStatus' diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 66eb5dcecf9..06d296eef08 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -33,7 +33,7 @@ # class CommitStatus < ActiveRecord::Base - include CiStatus + include Statuseable self.table_name = 'ci_builds' @@ -81,7 +81,11 @@ class CommitStatus < ActiveRecord::Base end end - delegate :before_sha, :sha, :short_sha, to: :commit, prefix: false + delegate :sha, :short_sha, to: :commit + + def before_sha + commit.before_sha || Gitlab::Git::BLANK_SHA + end def self.stages order_by = 'max(stage_idx)' diff --git a/app/models/concerns/ci_status.rb b/app/models/concerns/statuseable.rb similarity index 97% rename from app/models/concerns/ci_status.rb rename to app/models/concerns/statuseable.rb index 8190b2a20c6..f34dca29120 100644 --- a/app/models/concerns/ci_status.rb +++ b/app/models/concerns/statuseable.rb @@ -1,4 +1,4 @@ -module CiStatus +module Statuseable extend ActiveSupport::Concern AVAILABLE_STATUSES = %w(pending running success failed canceled skipped) @@ -41,7 +41,7 @@ module CiStatus end def finished_at - all.minimum(:finished_at) + all.maximum(:finished_at) end end diff --git a/app/models/project.rb b/app/models/project.rb index 95eb7c51b80..3a55e6c5dd6 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -958,7 +958,7 @@ class Project < ActiveRecord::Base end def ci_commit(sha, ref) - ci_commits.find_by(sha: sha, ref: ref) + ci_commits.order(id: :desc).find_by(sha: sha, ref: ref) end def ensure_ci_commit(sha, ref) diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml index 25714e6cb47..0c01f2ed95f 100644 --- a/app/views/projects/commit/_ci_commit.html.haml +++ b/app/views/projects/commit/_ci_commit.html.haml @@ -1,6 +1,6 @@ .gray-content-block.middle-block .pull-right - - if can?(current_user, :update_build, @project) + - if can?(current_user, :update_build, @ci_commit.project) - if ci_commit.builds.latest.failed.any?(&:retryable?) = link_to "Retry failed", retry_builds_namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), class: 'btn btn-grouped btn-primary', method: :post diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index 0e9111c8029..ad47e338a33 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -163,4 +163,12 @@ eos it { expect(commit.reverts_commit?(another_commit)).to be_truthy } end end + + describe '#ci_commits' do + # TODO: kamil + end + + describe '#status' do + # TODO: kamil + end end diff --git a/spec/lib/ci/status_spec.rb b/spec/models/concerns/statuseable_spec.rb similarity index 97% rename from spec/lib/ci/status_spec.rb rename to spec/models/concerns/statuseable_spec.rb index 886b82a7afa..dacbd3034c0 100644 --- a/spec/lib/ci/status_spec.rb +++ b/spec/models/concerns/statuseable_spec.rb @@ -1,9 +1,9 @@ require 'spec_helper' -describe CiStatus do +describe Statuseable do before do @object = Object.new - @object.extend(CiStatus::ClassMethods) + @object.extend(Statuseable::ClassMethods) end describe '.status' do From dc0d7f1a9b4018541596680c643cc5489fd8e625 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sat, 16 Apr 2016 21:47:10 +0200 Subject: [PATCH 28/68] Revert unneeded changes --- app/views/projects/commit/_ci_commit.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml index 0c01f2ed95f..25714e6cb47 100644 --- a/app/views/projects/commit/_ci_commit.html.haml +++ b/app/views/projects/commit/_ci_commit.html.haml @@ -1,6 +1,6 @@ .gray-content-block.middle-block .pull-right - - if can?(current_user, :update_build, @ci_commit.project) + - if can?(current_user, :update_build, @project) - if ci_commit.builds.latest.failed.any?(&:retryable?) = link_to "Retry failed", retry_builds_namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), class: 'btn btn-grouped btn-primary', method: :post From 1c5b172abb1279a25731d35ee913daa91738606d Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sat, 16 Apr 2016 22:43:40 +0200 Subject: [PATCH 29/68] Write specs for this feature --- app/models/ci/commit.rb | 9 ++-- app/models/commit_status.rb | 17 ++++--- app/models/concerns/statuseable.rb | 2 +- lib/api/commit_statuses.rb | 19 +++++--- spec/models/ci/commit_spec.rb | 71 ++++++++++++++++++++++++++++ spec/models/commit_status_spec.rb | 74 ++++++++++++++++++++++++++++++ spec/models/project_spec.rb | 15 +++++- 7 files changed, 188 insertions(+), 19 deletions(-) diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index 412ab44aaf6..f2667e5476b 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -26,7 +26,10 @@ module Ci has_many :builds, class_name: 'Ci::Build' has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest' + delegate :stages, to: :statuses + validates_presence_of :sha + validates_presence_of :status validate :valid_commit_sha # Invalidate object and save if when touched @@ -40,10 +43,6 @@ module Ci CommitStatus.where(commit: all).stages end - def stages - statuses.stages - end - def project_id project.id end @@ -82,7 +81,7 @@ module Ci def retryable? builds.latest.any? do |build| - build.failed? || build.retryable? + build.failed? && build.retryable? end end diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 06d296eef08..24a26b4be8c 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -93,7 +93,10 @@ class CommitStatus < ActiveRecord::Base end def self.stages_status - Hash[group(:stage).pluck(:stage, self.status_sql)] + all.stages.inject({}) do |h, stage| + h[stage] = all.where(stage: stage).status + h + end end def ignored? @@ -101,11 +104,13 @@ class CommitStatus < ActiveRecord::Base end def duration - if started_at && finished_at - finished_at - started_at - elsif started_at - Time.now - started_at - end + duration = + if started_at && finished_at + finished_at - started_at + elsif started_at + Time.now - started_at + end + duration.to_i end def stuck? diff --git a/app/models/concerns/statuseable.rb b/app/models/concerns/statuseable.rb index f34dca29120..7b0c9789fb9 100644 --- a/app/models/concerns/statuseable.rb +++ b/app/models/concerns/statuseable.rb @@ -33,7 +33,7 @@ module Statuseable def duration duration_array = all.map(&:duration).compact - duration_array.reduce(:+).to_i + duration_array.reduce(:+) end def started_at diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index 0b52dd8a743..7388ed2f4ea 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -50,12 +50,19 @@ module API commit = @project.commit(params[:sha]) not_found! 'Commit' unless commit - ref = params[:ref] || - begin - branches = @project.repository.branch_names_contains(commit.sha) - not_found! 'Reference for commit' if branches.none? - branches.first - end + # Since the CommitStatus is attached to Ci::Commit (in the future Pipeline) + # We need to always have the pipeline object + # To have a valid pipeline object that can be attached to specific MR + # Other CI service needs to send `ref` + # If we don't receive it, we will attach the CommitStatus to + # the first found branch on that commit + + ref = params[:ref] + unless ref + branches = @project.repository.branch_names_contains(commit.sha) + not_found! 'References for commit' if branches.none? + ref = branches.first + end ci_commit = @project.ensure_ci_commit(commit.sha, ref) diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb index fb3b61ad7c7..aef4f007202 100644 --- a/spec/models/ci/commit_spec.rb +++ b/spec/models/ci/commit_spec.rb @@ -27,6 +27,8 @@ describe Ci::Commit, models: true do it { is_expected.to have_many(:trigger_requests) } it { is_expected.to have_many(:builds) } it { is_expected.to validate_presence_of :sha } + it { is_expected.to validate_presence_of :status } + it { is_expected.to delegate_method(:stages).to(:statuses) } it { is_expected.to respond_to :git_author_name } it { is_expected.to respond_to :git_author_email } @@ -297,4 +299,73 @@ describe Ci::Commit, models: true do expect(commit.coverage).to be_nil end end + + describe '#retryable?' do + subject { commit.retryable? } + + context 'no failed builds' do + before do + FactoryGirl.create :ci_build, name: "rspec", commit: commit, status: 'success' + end + + it 'be not retryable' do + is_expected.to be_falsey + end + end + + context 'with failed builds' do + before do + FactoryGirl.create :ci_build, name: "rspec", commit: commit, status: 'running' + FactoryGirl.create :ci_build, name: "rubocop", commit: commit, status: 'failed' + end + + it 'be retryable' do + is_expected.to be_truthy + end + end + end + + describe '#stages' do + let(:commit2) { FactoryGirl.create :ci_commit, project: project } + subject { CommitStatus.where(commit: [commit, commit2]).stages } + + before do + FactoryGirl.create :ci_build, commit: commit2, stage: 'test', stage_idx: 1 + FactoryGirl.create :ci_build, commit: commit, stage: 'build', stage_idx: 0 + end + + it 'return all stages' do + is_expected.to eq(%w(build test)) + end + end + + describe '#update_state' do + it 'execute update_state after touching object' do + expect(commit).to receive(:update_state).and_return(true) + commit.touch + end + + context 'dependent objects' do + let(:commit_status) { build :commit_status, commit: commit } + + it 'execute update_state after saving dependent object' do + expect(commit).to receive(:update_state).and_return(true) + commit_status.save + end + end + + context 'update state' do + let(:build) { FactoryGirl.create :ci_build, :success, commit: commit, started_at: Time.now - 120, finished_at: Time.now - 60 } + + before do + build + end + + [:status, :started_at, :finished_at, :duration].each do |param| + it "update #{param}" do + expect(commit.send(param)).to eq(build.send(param)) + end + end + end + end end diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index 1c64947f1f5..31d546820c2 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -182,4 +182,78 @@ describe CommitStatus, models: true do is_expected.to eq([@commit1, @commit2]) end end + + describe '#before_sha' do + subject { commit_status.before_sha } + + context 'when no before_sha is set for ci::commit' do + before { commit.before_sha = nil } + + it 'return blank sha' do + is_expected.to eq(Gitlab::Git::BLANK_SHA) + end + end + + context 'for before_sha set for ci::commit' do + let(:value) { '1234' } + before { commit.before_sha = value } + + it 'return the set value' do + is_expected.to eq(value) + end + end + end + + describe '#stages' do + before do + FactoryGirl.create :commit_status, commit: commit, stage: 'build', stage_idx: 0, status: 'success' + FactoryGirl.create :commit_status, commit: commit, stage: 'build', stage_idx: 0, status: 'failed' + FactoryGirl.create :commit_status, commit: commit, stage: 'deploy', stage_idx: 2, status: 'running' + FactoryGirl.create :commit_status, commit: commit, stage: 'test', stage_idx: 1, status: 'success' + end + + context 'stages list' do + subject { CommitStatus.where(commit: commit).stages } + + it 'return ordered list of stages' do + is_expected.to eq(%w(build test deploy)) + end + end + + context 'stages with statuses' do + subject { CommitStatus.where(commit: commit).stages_status } + + it 'return list of stages with statuses' do + is_expected.to eq({ + 'build' => 'failed', + 'test' => 'success', + 'deploy' => 'running' + }) + end + end + end + + describe '#branch?' do + subject { commit_status.branch? } + + context 'is not a tag' do + before do + commit_status.tag = false + end + + it 'return true when tag is set to false' do + is_expected.to be_truthy + end + end + + context 'is not a tag' do + before do + commit_status.tag = true + end + + it 'return false when tag is set to true' do + is_expected.to be_falsey + end + end + end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 1688e91ca62..becc743de31 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -443,7 +443,20 @@ describe Project, models: true do let(:project) { create :project } let(:commit) { create :ci_commit, project: project, ref: 'master' } - it { expect(project.ci_commit(commit.sha, 'master')).to eq(commit) } + subject { project.ci_commit(commit.sha, 'master') } + + it { is_expected.to eq(commit) } + + context 'return latest' do + let(:commit2) { create :ci_commit, project: project, ref: 'master' } + + before do + commit + commit2 + end + + it { is_expected.to eq(commit2) } + end end describe :builds_enabled do From 433ca7390d800a2fda3e3c5eb29272d0ba2d9df6 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sun, 17 Apr 2016 09:32:49 -0400 Subject: [PATCH 30/68] Make some logic less twistable --- CHANGELOG | 1 + app/models/concerns/statuseable.rb | 2 +- app/views/projects/builds/index.html.haml | 2 +- app/views/projects/ci/builds/_build.html.haml | 2 +- .../_generic_commit_status.html.haml | 13 +++++++------ spec/requests/api/commits_spec.rb | 2 +- 6 files changed, 12 insertions(+), 10 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 5c375fcdb39..4b5564223f4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -52,6 +52,7 @@ v 8.7.0 (unreleased) - Hide `Create a group` help block when creating a new project in a group - Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.) - Allow issues and merge requests to be assigned to the author !2765 + - Make Ci::Commit to group only similar builds and make it stateful (ref, tag) (Kamil Trzciński) - Gracefully handle notes on deleted commits in merge requests (Stan Hu) - Decouple membership and notifications - Fix creation of merge requests for orphaned branches (Stan Hu) diff --git a/app/models/concerns/statuseable.rb b/app/models/concerns/statuseable.rb index 7b0c9789fb9..8a293b7b76e 100644 --- a/app/models/concerns/statuseable.rb +++ b/app/models/concerns/statuseable.rb @@ -7,7 +7,7 @@ module Statuseable def status_sql builds = all.select('count(*)').to_sql success = all.success.select('count(*)').to_sql - ignored = all.ignored.select('count(*)').to_sql if all.try(:ignored) + ignored = all.ignored.select('count(*)').to_sql if all.respond_to?(:ignored) ignored ||= '0' pending = all.pending.select('count(*)').to_sql running = all.running.select('count(*)').to_sql diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml index aa85f495e39..0406fc21d77 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -58,6 +58,6 @@ %th Coverage %th - = render @builds, commit_sha: true, stage: true, allow_retry: true, coverage: @project.build_coverage_enabled? + = render @builds, commit_sha: true, ref: true, stage: true, allow_retry: true, coverage: @project.build_coverage_enabled? = paginate @builds, theme: 'gitlab' diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 218d396b898..e123eb1cc7a 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -19,7 +19,7 @@ %td = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace" - - if !defined?(ref) || ref + - if defined?(ref) && ref %td - if build.ref = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref) diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml index c15386b4883..f21c864e35c 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml @@ -15,12 +15,13 @@ - if defined?(commit_sha) && commit_sha %td = link_to generic_commit_status.short_sha, namespace_project_commit_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.sha), class: "monospace" - - %td - - if generic_commit_status.ref - = link_to generic_commit_status.ref, namespace_project_commits_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.ref) - - else - .light none + + - if defined?(ref) && ref + %td + - if generic_commit_status.ref + = link_to generic_commit_status.ref, namespace_project_commits_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.ref) + - else + .light none - if defined?(runner) && runner %td diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 25377a40442..e28998d51b5 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -48,7 +48,7 @@ describe API::API, api: true do expect(response.status).to eq(404) end - it "should return not_found for CI status" do + it "should return nil for commit without CI" do get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) expect(response.status).to eq(200) expect(json_response['status']).to be_nil From 4b025ba02bb19c72a6872d0c6821fa0b10c58800 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 13 Apr 2016 12:30:42 +0200 Subject: [PATCH 31/68] Document the feature freeze of stable branches Fixes gitlab-org/gitlab-ce#15183 --- PROCESS.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/PROCESS.md b/PROCESS.md index cad45d23df9..e34f59c6bce 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -105,6 +105,25 @@ sensitive as to how you word things. Use Emoji to express your feelings (heart, star, smile, etc.). Some good tips about giving feedback to merge requests is in the [Thoughtbot code review guide]. +## Feature Freeze + +5 working days before the 22nd the stable branches for the upcoming release will +be frozen for major changes. Merge requests may still be merged into master +during this period. By freezing the stable branches prior to a release there's +no need to worry about last minute merge requests potentially breaking a lot of +things. + +What is considered to be a major change is determined on a case by case basis as +this definition depends very much on the context of changes. For example, a 5 +line change might have a big impact on the entire application. Ultimately the +decision will be made by those reviewing a merge request and the release +manager. + +During the feature freeze all merge requests that are meant to go into the next +release should have the correct milestone assigned _and_ have the label +~"Pick into Stable" set. Merge requests without a milestone and this label will +not be merged into any stable branches. + ## Copy & paste responses ### Improperly formatted issue From 28ce41c00ebba5b60c69f75d9bce97c11210779a Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 18 Apr 2016 07:35:43 -0400 Subject: [PATCH 32/68] Fix tests --- app/models/commit_status.rb | 2 +- features/steps/dashboard/dashboard.rb | 2 +- spec/models/ci/commit_spec.rb | 27 ++++++++++++++++++++++++++- spec/models/commit_status_spec.rb | 24 ------------------------ 4 files changed, 28 insertions(+), 27 deletions(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 24a26b4be8c..aa56314aa16 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -110,7 +110,7 @@ class CommitStatus < ActiveRecord::Base elsif started_at Time.now - started_at end - duration.to_i + duration end def stuck? diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb index ba0e829dc0c..b5980b35102 100644 --- a/features/steps/dashboard/dashboard.rb +++ b/features/steps/dashboard/dashboard.rb @@ -13,7 +13,7 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps end step 'I should see "Shop" project CI status' do - expect(page).to have_link "Build: skipped" + expect(page).to have_link "Build skipped" end step 'I should see last push widget' do diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb index aef4f007202..c12327c2a77 100644 --- a/spec/models/ci/commit_spec.rb +++ b/spec/models/ci/commit_spec.rb @@ -355,7 +355,8 @@ describe Ci::Commit, models: true do end context 'update state' do - let(:build) { FactoryGirl.create :ci_build, :success, commit: commit, started_at: Time.now - 120, finished_at: Time.now - 60 } + let(:current) { Time.now.change(:usec => 0) } + let(:build) { FactoryGirl.create :ci_build, :success, commit: commit, started_at: current - 120, finished_at: current - 60 } before do build @@ -368,4 +369,28 @@ describe Ci::Commit, models: true do end end end + + describe '#branch?' do + subject { commit.branch? } + + context 'is not a tag' do + before do + commit.tag = false + end + + it 'return true when tag is set to false' do + is_expected.to be_truthy + end + end + + context 'is not a tag' do + before do + commit.tag = true + end + + it 'return false when tag is set to true' do + is_expected.to be_falsey + end + end + end end diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index 31d546820c2..971e6750375 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -232,28 +232,4 @@ describe CommitStatus, models: true do end end end - - describe '#branch?' do - subject { commit_status.branch? } - - context 'is not a tag' do - before do - commit_status.tag = false - end - - it 'return true when tag is set to false' do - is_expected.to be_truthy - end - end - - context 'is not a tag' do - before do - commit_status.tag = true - end - - it 'return false when tag is set to true' do - is_expected.to be_falsey - end - end - end end From e5ff37c1fcd384f417518c8cca6a2a2dcc3c2767 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 18 Apr 2016 07:45:23 -0400 Subject: [PATCH 33/68] Fix rubocop --- spec/models/ci/commit_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb index c12327c2a77..82c18aaa01a 100644 --- a/spec/models/ci/commit_spec.rb +++ b/spec/models/ci/commit_spec.rb @@ -355,7 +355,7 @@ describe Ci::Commit, models: true do end context 'update state' do - let(:current) { Time.now.change(:usec => 0) } + let(:current) { Time.now.change(usec: 0) } let(:build) { FactoryGirl.create :ci_build, :success, commit: commit, started_at: current - 120, finished_at: current - 60 } before do From ee2dc0624093a0fe8665778fd0cc734754e7cdc9 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 18 Apr 2016 15:08:24 -0400 Subject: [PATCH 34/68] Fix CHANGELOG --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 4b5564223f4..29df53f689f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -52,7 +52,7 @@ v 8.7.0 (unreleased) - Hide `Create a group` help block when creating a new project in a group - Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.) - Allow issues and merge requests to be assigned to the author !2765 - - Make Ci::Commit to group only similar builds and make it stateful (ref, tag) (Kamil Trzciński) + - Make Ci::Commit to group only similar builds and make it stateful (ref, tag) - Gracefully handle notes on deleted commits in merge requests (Stan Hu) - Decouple membership and notifications - Fix creation of merge requests for orphaned branches (Stan Hu) From 62f6601c598d59781137109c0eee5c5ea1792e13 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Fri, 15 Apr 2016 12:04:07 -0300 Subject: [PATCH 35/68] Show project members only for members --- .../projects/project_members_controller.rb | 7 ++++- app/helpers/projects_helper.rb | 4 +++ app/models/ability.rb | 12 ++++++++- app/views/layouts/nav/_project.html.haml | 2 +- .../project_members_controller_spec.rb | 27 +++++++++++++++++++ 5 files changed, 49 insertions(+), 3 deletions(-) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index e457db2f0b7..f8c9ff657df 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -1,6 +1,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController # Authorize - before_action :authorize_admin_project_member!, except: :leave + before_action :authorize_admin_project_member!, except: [:leave, :index] + before_action :authorize_read_members_list!, only: [:index] def index @project_members = @project.project_members @@ -112,4 +113,8 @@ class Projects::ProjectMembersController < Projects::ApplicationController def member_params params.require(:project_member).permit(:user_id, :access_level) end + + def authorize_read_members_list! + render_403 unless can?(current_user, :read_members_list , @project) + end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 7e00aacceaa..fc3662bc097 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -144,6 +144,10 @@ module ProjectsHelper nav_tabs << :settings end + if can?(current_user, :read_members_list, project) + nav_tabs << :team + end + if can?(current_user, :read_issue, project) nav_tabs << :issues end diff --git a/app/models/ability.rb b/app/models/ability.rb index 6103a2947e2..a037aee6d51 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -154,9 +154,17 @@ class Ability end end + def project_member_rules(team, user) + all_members_rules = [] + + #Rules only for members which does not include public behavior + all_members_rules << :read_members_list if team.members.include?(user) + all_members_rules + end + def project_team_rules(team, user) # Rules based on role in project - if team.master?(user) + filtered_rules = if team.master?(user) project_master_rules elsif team.developer?(user) project_dev_rules @@ -165,6 +173,8 @@ class Ability elsif team.guest?(user) project_guest_rules end + + Array(filtered_rules) + project_member_rules(team, user) end def public_project_rules diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 86b46e8c75e..a15b7758c4b 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -77,7 +77,7 @@ Merge Requests %span.count.merge_counter= number_with_delimiter(@project.merge_requests.opened.count) - - if project_nav_tab? :settings + - if project_nav_tab? :team = nav_link(controller: [:project_members, :teams]) do = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do = icon('users fw') diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index d47e4ab9a4f..c52c586cc9b 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -46,4 +46,31 @@ describe Projects::ProjectMembersController do end end end + + describe 'index' do + let(:project) { create(:project, :internal) } + + context 'when user is member' do + let(:member) { create(:user) } + + before do + project.team << [member, :guest] + sign_in(member) + get :index, namespace_id: project.namespace.to_param, project_id: project.to_param + end + + it { expect(response.status).to eq(200) } + end + + context 'when user is not member' do + let(:not_member) { create(:user) } + + before do + sign_in(not_member) + get :index, namespace_id: project.namespace.to_param, project_id: project.to_param + end + + it { expect(response.status).to eq(403) } + end + end end From 0b91ff287d12d59bb4193fff4c8e605f8a1a6e69 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Mon, 18 Apr 2016 17:52:10 -0300 Subject: [PATCH 36/68] Projects members tab should follow visibility levels --- app/models/ability.rb | 15 +++------------ .../projects/project_members_controller_spec.rb | 15 ++------------- .../security/project/internal_access_spec.rb | 10 +++++----- .../security/project/private_access_spec.rb | 6 +++--- .../security/project/public_access_spec.rb | 12 ++++++------ 5 files changed, 19 insertions(+), 39 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index a037aee6d51..386c3d82d2c 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -154,17 +154,9 @@ class Ability end end - def project_member_rules(team, user) - all_members_rules = [] - - #Rules only for members which does not include public behavior - all_members_rules << :read_members_list if team.members.include?(user) - all_members_rules - end - def project_team_rules(team, user) # Rules based on role in project - filtered_rules = if team.master?(user) + if team.master?(user) project_master_rules elsif team.developer?(user) project_dev_rules @@ -173,8 +165,6 @@ class Ability elsif team.guest?(user) project_guest_rules end - - Array(filtered_rules) + project_member_rules(team, user) end def public_project_rules @@ -199,7 +189,8 @@ class Ability :create_project, :create_issue, :create_note, - :upload_file + :upload_file, + :read_members_list ] end diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index c52c586cc9b..1bc5ad4706b 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -48,7 +48,7 @@ describe Projects::ProjectMembersController do end describe 'index' do - let(:project) { create(:project, :internal) } + let(:project) { create(:project, :private) } context 'when user is member' do let(:member) { create(:user) } @@ -59,18 +59,7 @@ describe Projects::ProjectMembersController do get :index, namespace_id: project.namespace.to_param, project_id: project.to_param end - it { expect(response.status).to eq(200) } - end - - context 'when user is not member' do - let(:not_member) { create(:user) } - - before do - sign_in(not_member) - get :index, namespace_id: project.namespace.to_param, project_id: project.to_param - end - - it { expect(response.status).to eq(403) } + it { expect(response.status).to eq(200) } end end end diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb index 79d5bf4cf06..8625ea6bc10 100644 --- a/spec/features/security/project/internal_access_spec.rb +++ b/spec/features/security/project/internal_access_spec.rb @@ -101,12 +101,12 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for owner } it { is_expected.to be_allowed_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_denied_for :external } end describe "GET /:project_path/blob" do diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb index 0a89193eb67..544270b4037 100644 --- a/spec/features/security/project/private_access_spec.rb +++ b/spec/features/security/project/private_access_spec.rb @@ -101,9 +101,9 @@ describe "Private Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for owner } it { is_expected.to be_allowed_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for guest } it { is_expected.to be_denied_for :user } it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb index 40daac89d40..4def4f99bc0 100644 --- a/spec/features/security/project/public_access_spec.rb +++ b/spec/features/security/project/public_access_spec.rb @@ -101,12 +101,12 @@ describe "Public Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for owner } it { is_expected.to be_allowed_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for :external } end describe "GET /:project_path/builds" do From 50c9c6a875edcc652d414f8af2f19fa9e3fce4b7 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 18 Apr 2016 16:28:15 -0500 Subject: [PATCH 37/68] Grammar fix --- spec/features/todos/todos_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb index 214daf88fc9..113d4c40cfc 100644 --- a/spec/features/todos/todos_spec.rb +++ b/spec/features/todos/todos_spec.rb @@ -9,7 +9,7 @@ describe 'Dashboard Todos', feature: true do let(:todos_total){ todos_per_page + 1 } describe 'GET /dashboard/todos' do - context 'User do not have todos' do + context 'User does not have todos' do before do login_as(user) visit dashboard_todos_path From 3602aa7d687995b7c4f6d8bf7cc4eea45a6cd754 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 18 Apr 2016 16:28:30 -0500 Subject: [PATCH 38/68] Use Turbolinks to redirect --- app/assets/javascripts/todos.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee index 08b29bd8214..01251219928 100644 --- a/app/assets/javascripts/todos.js.coffee +++ b/app/assets/javascripts/todos.js.coffee @@ -96,7 +96,7 @@ class @Todos page: currPages - 1 url = gl.utils.mergeUrlParams(pageParams, url) - location.replace url + Turbolinks.visit(url) goToTodoUrl: (e)-> todoLink = $(this).data('url') From 6fdde5fabd9defed06a3308da7788c9a7f4632cf Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 19 Apr 2016 12:56:58 +0200 Subject: [PATCH 39/68] filter labels by including all filter titles as part of the query --- app/finders/issuable_finder.rb | 2 +- app/models/concerns/issuable.rb | 2 +- spec/features/issues/filter_by_labels_spec.rb | 170 ++++++++++++++++++ 3 files changed, 172 insertions(+), 2 deletions(-) create mode 100644 spec/features/issues/filter_by_labels_spec.rb diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index f1df6832bf6..f8e2062d110 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -270,7 +270,7 @@ class IssuableFinder if filter_by_no_label? items = items.without_label else - items = items.with_label(label_names) + items = items.with_label(label_names.flatten) if projects items = items.where(labels: { project_id: projects }) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index afa2ca039ae..e2a2899941f 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -37,7 +37,7 @@ module Issuable scope :closed, -> { with_state(:closed) } scope :order_milestone_due_desc, -> { joins(:milestone).reorder('milestones.due_date DESC, milestones.id DESC') } scope :order_milestone_due_asc, -> { joins(:milestone).reorder('milestones.due_date ASC, milestones.id ASC') } - scope :with_label, ->(title) { joins(:labels).where(labels: { title: title }) } + scope :with_label, ->(title) { joins(:labels).where(labels: { title: title }).group('issues.id').having("count(distinct labels.title) = #{title.count}") } scope :without_label, -> { joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{name}' AND label_links.target_id = #{table_name}.id").where(label_links: { id: nil }) } scope :join_project, -> { joins(:project) } diff --git a/spec/features/issues/filter_by_labels_spec.rb b/spec/features/issues/filter_by_labels_spec.rb new file mode 100644 index 00000000000..27d9ea7c62f --- /dev/null +++ b/spec/features/issues/filter_by_labels_spec.rb @@ -0,0 +1,170 @@ +# Uncomment once this is merged with multi-filter-labels +# Changes are related to using AND in label filters instead of OR + +# require 'rails_helper' +# +# feature 'Issue filtering by Labels', feature: true do +# let(:project) { create(:project, :public) } +# let!(:user) { create(:user)} +# let!(:label) { create(:label, project: project) } +# +# before do +# ['bug', 'feature', 'enhancement'].each do |title| +# create(:label, +# project: project, +# title: title) +# end +# +# issue1 = create(:issue, title: "Bugfix1", project: project) +# issue1.labels << project.labels.find_by(title: 'bug') +# +# issue2 = create(:issue, title: "Bugfix2", project: project) +# issue2.labels << project.labels.find_by(title: 'bug') +# issue2.labels << project.labels.find_by(title: 'enhancement') +# +# issue3 = create(:issue, title: "Feature1", project: project) +# issue3.labels << project.labels.find_by(title: 'feature') +# +# project.team << [user, :master] +# login_as(user) +# +# visit namespace_project_issues_path(project.namespace, project) +# end +# +# context 'filter by label bug', js: true do +# before do +# page.find('.js-label-select').click +# sleep 0.5 +# execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()") +# page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click +# sleep 2 +# end +# +# it 'should show issue "Bugfix1" and "Bugfix2" in issues list' do +# expect(page).to have_content "Bugfix1" +# expect(page).to have_content "Bugfix2" +# end +# +# it 'should not show "Feature1" in issues list' do +# expect(page).not_to have_content "Feature1" +# end +# +# it 'should show label "bug" in filtered-labels' do +# expect(find('.filtered-labels')).to have_content "bug" +# end +# +# it 'should not show label "feature" and "enhancement" in filtered-labels' do +# expect(find('.filtered-labels')).not_to have_content "feature" +# expect(find('.filtered-labels')).not_to have_content "enhancement" +# end +# end +# +# context 'filter by label feature', js: true do +# before do +# page.find('.js-label-select').click +# sleep 0.5 +# execute_script("$('.dropdown-menu-labels li:contains(\"feature\") a').click()") +# page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click +# sleep 2 +# end +# +# it 'should show issue "Feature1" in issues list' do +# expect(page).to have_content "Feature1" +# end +# +# it 'should not show "Bugfix1" and "Bugfix2" in issues list' do +# expect(page).not_to have_content "Bugfix2" +# expect(page).not_to have_content "Bugfix1" +# end +# +# it 'should show label "feature" in filtered-labels' do +# expect(find('.filtered-labels')).to have_content "feature" +# end +# +# it 'should not show label "bug" and "enhancement" in filtered-labels' do +# expect(find('.filtered-labels')).not_to have_content "bug" +# expect(find('.filtered-labels')).not_to have_content "enhancement" +# end +# end +# +# context 'filter by label enhancement', js: true do +# before do +# page.find('.js-label-select').click +# sleep 0.5 +# execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()") +# page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click +# sleep 2 +# end +# +# it 'should show issue "Bugfix2" in issues list' do +# expect(page).to have_content "Bugfix2" +# end +# +# it 'should not show "Feature1" and "Bugfix1" in issues list' do +# expect(page).not_to have_content "Feature1" +# expect(page).not_to have_content "Bugfix1" +# end +# +# it 'should show label "enhancement" in filtered-labels' do +# expect(find('.filtered-labels')).to have_content "enhancement" +# end +# +# it 'should not show label "feature" and "bug" in filtered-labels' do +# expect(find('.filtered-labels')).not_to have_content "bug" +# expect(find('.filtered-labels')).not_to have_content "feature" +# end +# end +# +# context 'filter by label enhancement or feature', js: true do +# before do +# page.find('.js-label-select').click +# sleep 0.5 +# execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()") +# execute_script("$('.dropdown-menu-labels li:contains(\"feature\") a').click()") +# page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click +# sleep 2 +# end +# +# it 'should not show "Bugfix1" or "Feature1" in issues list' do +# expect(page).not_to have_content "Bugfix1" +# expect(page).not_to have_content "Feature1" +# end +# +# it 'should show label "enhancement" and "feature" in filtered-labels' do +# expect(find('.filtered-labels')).to have_content "enhancement" +# expect(find('.filtered-labels')).to have_content "feature" +# end +# +# it 'should not show label "bug" in filtered-labels' do +# expect(find('.filtered-labels')).not_to have_content "bug" +# end +# end +# +# context 'filter by label enhancement and bug in issues list', js: true do +# before do +# page.find('.js-label-select').click +# sleep 0.5 +# execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()") +# execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()") +# page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click +# sleep 2 +# end +# +# it 'should show issue "Bugfix2" in issues list' do +# expect(page).to have_content "Bugfix2" +# end +# +# it 'should not show "Feature1"' do +# expect(page).not_to have_content "Feature1" +# end +# +# it 'should show label "bug" and "enhancement" in filtered-labels' do +# expect(find('.filtered-labels')).to have_content "bug" +# expect(find('.filtered-labels')).to have_content "enhancement" +# end +# +# it 'should not show label "feature" in filtered-labels' do +# expect(find('.filtered-labels')).not_to have_content "feature" +# end +# end +# end From 8619208b644f2acdc30ccf36209ac56527bb4188 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 19 Apr 2016 15:36:28 +0200 Subject: [PATCH 40/68] fix other spec failures --- app/helpers/milestones_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb index 87fc2db6901..e2d01632df6 100644 --- a/app/helpers/milestones_helper.rb +++ b/app/helpers/milestones_helper.rb @@ -32,7 +32,7 @@ module MilestonesHelper end def milestone_issues_by_label_count(milestone, label, state:) - milestone.issues.with_label(label.title).send(state).size + milestone.issues.with_label([label.title]).send(state).size end def milestone_progress_bar(milestone) From d6ab11a1ee043e0cacc1994219d3cc4d037dce5d Mon Sep 17 00:00:00 2001 From: Evan Lucas Date: Thu, 7 Apr 2016 10:03:22 -0500 Subject: [PATCH 41/68] add tap coverage example to project settings Previously, there were examples for Simplecov, pytest-cov, and phpunit. This adds an example for using the tap cli tool (https://github.com/tapjs/node-tap) --- app/views/projects/_builds_settings.html.haml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/views/projects/_builds_settings.html.haml b/app/views/projects/_builds_settings.html.haml index b6074373e2b..0de019983ca 100644 --- a/app/views/projects/_builds_settings.html.haml +++ b/app/views/projects/_builds_settings.html.haml @@ -55,6 +55,9 @@ %li gcovr (C/C++) - %code ^TOTAL.*\s+(\d+\%)$ + %li + tap --coverage-report=text-summary (Node.js) - + %code ^Statements\s*:\s*([^%]+) .form-group .col-sm-offset-2.col-sm-10 From 324e57693b21339273a9b9cafd9d27348fcd0488 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 19 Apr 2016 20:05:30 +0200 Subject: [PATCH 42/68] Implement top navigation concept for profile area Main idea is to keep left sidebar static so user is not confused by changing context. Instead we put changing navigation with changing content in one main block Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/nav.scss | 8 ++++++++ app/helpers/page_layout_helper.rb | 8 ++++++++ app/helpers/tab_helper.rb | 8 ++++++++ app/views/doorkeeper/applications/index.html.haml | 1 - app/views/layouts/_page.html.haml | 3 +++ app/views/layouts/application.html.haml | 2 +- app/views/layouts/nav/_dashboard.html.haml | 3 +-- app/views/layouts/nav/_profile.html.haml | 12 ++---------- app/views/layouts/profile.html.haml | 3 ++- app/views/profiles/accounts/show.html.haml | 1 - app/views/profiles/audit_log.html.haml | 1 - app/views/profiles/emails/index.html.haml | 1 - app/views/profiles/keys/index.html.haml | 1 - app/views/profiles/notifications/show.html.haml | 1 - app/views/profiles/passwords/edit.html.haml | 1 - app/views/profiles/preferences/show.html.haml | 1 - 16 files changed, 33 insertions(+), 22 deletions(-) diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 192d53b048a..e3f9825fe15 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -185,3 +185,11 @@ } } } + +.layout-nav { + margin-bottom: 5px; + + .nav-links { + padding: 0 $gl-padding; + } +} diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb index 82f805fa444..e4e8b934bc8 100644 --- a/app/helpers/page_layout_helper.rb +++ b/app/helpers/page_layout_helper.rb @@ -84,6 +84,14 @@ module PageLayoutHelper end end + def nav(name = nil) + if name + @nav = name + else + @nav + end + end + def fluid_layout(enabled = false) if @fluid_layout.nil? @fluid_layout = (current_user && current_user.layout == "fluid") || enabled diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb index 04e53fe7c61..96a83671009 100644 --- a/app/helpers/tab_helper.rb +++ b/app/helpers/tab_helper.rb @@ -110,4 +110,12 @@ module TabHelper 'active' end end + + def profile_tab_class + if controller.controller_path =~ /\Aprofiles/ + return 'active' + end + + 'active' if current_controller?('oauth/applications') + end end diff --git a/app/views/doorkeeper/applications/index.html.haml b/app/views/doorkeeper/applications/index.html.haml index 0aff79749ef..79df17ba612 100644 --- a/app/views/doorkeeper/applications/index.html.haml +++ b/app/views/doorkeeper/applications/index.html.haml @@ -1,5 +1,4 @@ - page_title "Applications" -- header_title page_title, applications_profile_path .row.prepend-top-default .col-lg-3.profile-settings-sidebar diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index c799e9c588d..d4e5d5e4bfd 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -25,6 +25,9 @@ .content-wrapper = render "layouts/flash" = yield :flash_message + - if defined?(nav) && nav + .layout-nav + = render "layouts/nav/#{nav}" %div{ class: (container_class unless @no_container) } .content .clearfix diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index babfb032236..e4d1c773d03 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -6,6 +6,6 @@ = yield :scripts_body_top = render "layouts/header/default", title: header_title - = render 'layouts/page', sidebar: sidebar + = render 'layouts/page', sidebar: sidebar, nav: nav = yield :scripts_body diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index 5cef652da14..ca49c313ff7 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -48,8 +48,7 @@ %span Help - %li.separate-item - = nav_link(controller: :profile) do + = nav_link(html_options: {class: profile_tab_class}) do = link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do = icon('user fw') %span diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml index 3b9d31a6fc5..d90dd3e2485 100644 --- a/app/views/layouts/nav/_profile.html.haml +++ b/app/views/layouts/nav/_profile.html.haml @@ -1,17 +1,9 @@ -%ul.nav.nav-sidebar - = nav_link do - = link_to root_path, title: 'Go to dashboard', class: 'back-link' do - = icon('caret-square-o-left fw') - %span - Go to dashboard - - %li.separate-item - +%ul.nav-links = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do = link_to profile_path, title: 'Profile Settings' do = icon('user fw') %span - Profile Settings + Profile = nav_link(controller: [:accounts, :two_factor_auths]) do = link_to profile_account_path, title: 'Account' do = icon('gear fw') diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml index dfa6cc5702e..b77d3402a2e 100644 --- a/app/views/layouts/profile.html.haml +++ b/app/views/layouts/profile.html.haml @@ -1,5 +1,6 @@ - page_title "Profile Settings" - header_title "Profile Settings", profile_path unless header_title -- sidebar "profile" +- sidebar "dashboard" +- nav "profile" = render template: "layouts/application" diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index 6efd119f260..afd3d79321f 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -1,5 +1,4 @@ - page_title "Account" -- header_title page_title, profile_account_path - if current_user.ldap_user? .alert.alert-info diff --git a/app/views/profiles/audit_log.html.haml b/app/views/profiles/audit_log.html.haml index f630c03e5f6..9c404b6935f 100644 --- a/app/views/profiles/audit_log.html.haml +++ b/app/views/profiles/audit_log.html.haml @@ -1,5 +1,4 @@ - page_title "Audit Log" -- header_title page_title, audit_log_profile_path .row.prepend-top-default .col-lg-3.profile-settings-sidebar diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml index 3f328f96cea..57527361eb6 100644 --- a/app/views/profiles/emails/index.html.haml +++ b/app/views/profiles/emails/index.html.haml @@ -1,5 +1,4 @@ - page_title "Emails" -- header_title page_title, profile_emails_path .row.prepend-top-default .col-lg-3.profile-settings-sidebar diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml index e0f8c9a5733..6a067a03535 100644 --- a/app/views/profiles/keys/index.html.haml +++ b/app/views/profiles/keys/index.html.haml @@ -1,5 +1,4 @@ - page_title "SSH Keys" -- header_title page_title, profile_keys_path .row.prepend-top-default .col-lg-3.profile-settings-sidebar diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index a2a505c082b..7696f112bb3 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -1,5 +1,4 @@ - page_title "Notifications" -- header_title page_title, profile_notifications_path %div - if @user.errors.any? diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml index 5ac8a8b9d09..243428b690e 100644 --- a/app/views/profiles/passwords/edit.html.haml +++ b/app/views/profiles/passwords/edit.html.haml @@ -1,5 +1,4 @@ - page_title "Password" -- header_title page_title, edit_profile_password_path .row.prepend-top-default .col-lg-3.profile-settings-sidebar diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index f80211669fb..bfe53be6854 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -1,5 +1,4 @@ - page_title 'Preferences' -- header_title page_title, profile_preferences_path = form_for @user, url: profile_preferences_path, remote: true, method: :put, html: {class: 'row prepend-top-default js-preferences-form'} do |f| .col-lg-3.profile-settings-sidebar From 52268d1d9a7c64886dcc99a1a82c02af1ccbe7a1 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 19 Apr 2016 21:11:33 +0200 Subject: [PATCH 43/68] Fix tests and remove counters Signed-off-by: Dmitriy Zaporozhets --- app/views/layouts/nav/_profile.html.haml | 2 -- features/steps/profile/active_tab.rb | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml index d90dd3e2485..d730840d63a 100644 --- a/app/views/layouts/nav/_profile.html.haml +++ b/app/views/layouts/nav/_profile.html.haml @@ -19,7 +19,6 @@ = icon('envelope-o fw') %span Emails - %span.count= number_with_delimiter(current_user.emails.count + 1) - unless current_user.ldap_user? = nav_link(controller: :passwords) do = link_to edit_profile_password_path, title: 'Password' do @@ -37,7 +36,6 @@ = icon('key fw') %span SSH Keys - %span.count= number_with_delimiter(current_user.keys.count) = nav_link(controller: :preferences) do = link_to profile_preferences_path, title: 'Preferences' do -# TODO (rspeicher): Better icon? diff --git a/features/steps/profile/active_tab.rb b/features/steps/profile/active_tab.rb index 4724a326277..3b59089a093 100644 --- a/features/steps/profile/active_tab.rb +++ b/features/steps/profile/active_tab.rb @@ -22,4 +22,8 @@ class Spinach::Features::ProfileActiveTab < Spinach::FeatureSteps step 'the active main tab should be Audit Log' do ensure_active_main_tab('Audit Log') end + + def ensure_active_main_tab(content) + expect(find('.layout-nav li.active')).to have_content(content) + end end From af18cddddf4ecb402ac613ddcc1f313f6adb7aad Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 20 Apr 2016 10:56:28 +0200 Subject: [PATCH 44/68] udpated a few things based on MR feedback. Also added model spec --- app/finders/issuable_finder.rb | 5 +- app/helpers/milestones_helper.rb | 2 +- app/models/concerns/issuable.rb | 9 +- spec/features/issues/filter_by_labels_spec.rb | 335 +++++++++--------- spec/models/concerns/issuable_spec.rb | 26 +- 5 files changed, 202 insertions(+), 175 deletions(-) diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index f8e2062d110..ca71acb65d3 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -270,8 +270,7 @@ class IssuableFinder if filter_by_no_label? items = items.without_label else - items = items.with_label(label_names.flatten) - + items = items.with_label(label_names) if projects items = items.where(labels: { project_id: projects }) end @@ -282,7 +281,7 @@ class IssuableFinder end def label_names - params[:label_name].split(',') + params[:label_name].is_a?(String) ? params[:label_name].split(',') : params[:label_name] end def current_user_related? diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb index e2d01632df6..87fc2db6901 100644 --- a/app/helpers/milestones_helper.rb +++ b/app/helpers/milestones_helper.rb @@ -32,7 +32,7 @@ module MilestonesHelper end def milestone_issues_by_label_count(milestone, label, state:) - milestone.issues.with_label([label.title]).send(state).size + milestone.issues.with_label(label.title).send(state).size end def milestone_progress_bar(milestone) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index e2a2899941f..d5166e81474 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -37,7 +37,6 @@ module Issuable scope :closed, -> { with_state(:closed) } scope :order_milestone_due_desc, -> { joins(:milestone).reorder('milestones.due_date DESC, milestones.id DESC') } scope :order_milestone_due_asc, -> { joins(:milestone).reorder('milestones.due_date ASC, milestones.id ASC') } - scope :with_label, ->(title) { joins(:labels).where(labels: { title: title }).group('issues.id').having("count(distinct labels.title) = #{title.count}") } scope :without_label, -> { joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{name}' AND label_links.target_id = #{table_name}.id").where(label_links: { id: nil }) } scope :join_project, -> { joins(:project) } @@ -122,6 +121,14 @@ module Issuable joins(join_clause).group(issuable_table[:id]).reorder("COUNT(notes.id) DESC") end + + def with_label(title) + if title.is_a?(Array) && title.count > 1 + joins(:labels).where(labels: { title: title }).group('issues.id').having("count(distinct labels.title) = #{title.count}") + else + joins(:labels).where(labels: { title: title }) + end + end end def today? diff --git a/spec/features/issues/filter_by_labels_spec.rb b/spec/features/issues/filter_by_labels_spec.rb index 27d9ea7c62f..b189a97fbc0 100644 --- a/spec/features/issues/filter_by_labels_spec.rb +++ b/spec/features/issues/filter_by_labels_spec.rb @@ -1,170 +1,167 @@ -# Uncomment once this is merged with multi-filter-labels -# Changes are related to using AND in label filters instead of OR +require 'rails_helper' -# require 'rails_helper' -# -# feature 'Issue filtering by Labels', feature: true do -# let(:project) { create(:project, :public) } -# let!(:user) { create(:user)} -# let!(:label) { create(:label, project: project) } -# -# before do -# ['bug', 'feature', 'enhancement'].each do |title| -# create(:label, -# project: project, -# title: title) -# end -# -# issue1 = create(:issue, title: "Bugfix1", project: project) -# issue1.labels << project.labels.find_by(title: 'bug') -# -# issue2 = create(:issue, title: "Bugfix2", project: project) -# issue2.labels << project.labels.find_by(title: 'bug') -# issue2.labels << project.labels.find_by(title: 'enhancement') -# -# issue3 = create(:issue, title: "Feature1", project: project) -# issue3.labels << project.labels.find_by(title: 'feature') -# -# project.team << [user, :master] -# login_as(user) -# -# visit namespace_project_issues_path(project.namespace, project) -# end -# -# context 'filter by label bug', js: true do -# before do -# page.find('.js-label-select').click -# sleep 0.5 -# execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()") -# page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click -# sleep 2 -# end -# -# it 'should show issue "Bugfix1" and "Bugfix2" in issues list' do -# expect(page).to have_content "Bugfix1" -# expect(page).to have_content "Bugfix2" -# end -# -# it 'should not show "Feature1" in issues list' do -# expect(page).not_to have_content "Feature1" -# end -# -# it 'should show label "bug" in filtered-labels' do -# expect(find('.filtered-labels')).to have_content "bug" -# end -# -# it 'should not show label "feature" and "enhancement" in filtered-labels' do -# expect(find('.filtered-labels')).not_to have_content "feature" -# expect(find('.filtered-labels')).not_to have_content "enhancement" -# end -# end -# -# context 'filter by label feature', js: true do -# before do -# page.find('.js-label-select').click -# sleep 0.5 -# execute_script("$('.dropdown-menu-labels li:contains(\"feature\") a').click()") -# page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click -# sleep 2 -# end -# -# it 'should show issue "Feature1" in issues list' do -# expect(page).to have_content "Feature1" -# end -# -# it 'should not show "Bugfix1" and "Bugfix2" in issues list' do -# expect(page).not_to have_content "Bugfix2" -# expect(page).not_to have_content "Bugfix1" -# end -# -# it 'should show label "feature" in filtered-labels' do -# expect(find('.filtered-labels')).to have_content "feature" -# end -# -# it 'should not show label "bug" and "enhancement" in filtered-labels' do -# expect(find('.filtered-labels')).not_to have_content "bug" -# expect(find('.filtered-labels')).not_to have_content "enhancement" -# end -# end -# -# context 'filter by label enhancement', js: true do -# before do -# page.find('.js-label-select').click -# sleep 0.5 -# execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()") -# page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click -# sleep 2 -# end -# -# it 'should show issue "Bugfix2" in issues list' do -# expect(page).to have_content "Bugfix2" -# end -# -# it 'should not show "Feature1" and "Bugfix1" in issues list' do -# expect(page).not_to have_content "Feature1" -# expect(page).not_to have_content "Bugfix1" -# end -# -# it 'should show label "enhancement" in filtered-labels' do -# expect(find('.filtered-labels')).to have_content "enhancement" -# end -# -# it 'should not show label "feature" and "bug" in filtered-labels' do -# expect(find('.filtered-labels')).not_to have_content "bug" -# expect(find('.filtered-labels')).not_to have_content "feature" -# end -# end -# -# context 'filter by label enhancement or feature', js: true do -# before do -# page.find('.js-label-select').click -# sleep 0.5 -# execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()") -# execute_script("$('.dropdown-menu-labels li:contains(\"feature\") a').click()") -# page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click -# sleep 2 -# end -# -# it 'should not show "Bugfix1" or "Feature1" in issues list' do -# expect(page).not_to have_content "Bugfix1" -# expect(page).not_to have_content "Feature1" -# end -# -# it 'should show label "enhancement" and "feature" in filtered-labels' do -# expect(find('.filtered-labels')).to have_content "enhancement" -# expect(find('.filtered-labels')).to have_content "feature" -# end -# -# it 'should not show label "bug" in filtered-labels' do -# expect(find('.filtered-labels')).not_to have_content "bug" -# end -# end -# -# context 'filter by label enhancement and bug in issues list', js: true do -# before do -# page.find('.js-label-select').click -# sleep 0.5 -# execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()") -# execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()") -# page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click -# sleep 2 -# end -# -# it 'should show issue "Bugfix2" in issues list' do -# expect(page).to have_content "Bugfix2" -# end -# -# it 'should not show "Feature1"' do -# expect(page).not_to have_content "Feature1" -# end -# -# it 'should show label "bug" and "enhancement" in filtered-labels' do -# expect(find('.filtered-labels')).to have_content "bug" -# expect(find('.filtered-labels')).to have_content "enhancement" -# end -# -# it 'should not show label "feature" in filtered-labels' do -# expect(find('.filtered-labels')).not_to have_content "feature" -# end -# end -# end +feature 'Issue filtering by Labels', feature: true do + let(:project) { create(:project, :public) } + let!(:user) { create(:user)} + let!(:label) { create(:label, project: project) } + + before do + ['bug', 'feature', 'enhancement'].each do |title| + create(:label, + project: project, + title: title) + end + + issue1 = create(:issue, title: "Bugfix1", project: project) + issue1.labels << project.labels.find_by(title: 'bug') + + issue2 = create(:issue, title: "Bugfix2", project: project) + issue2.labels << project.labels.find_by(title: 'bug') + issue2.labels << project.labels.find_by(title: 'enhancement') + + issue3 = create(:issue, title: "Feature1", project: project) + issue3.labels << project.labels.find_by(title: 'feature') + + project.team << [user, :master] + login_as(user) + + visit namespace_project_issues_path(project.namespace, project) + end + + context 'filter by label bug', js: true do + before do + page.find('.js-label-select').click + sleep 0.5 + execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()") + page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click + sleep 2 + end + + it 'should show issue "Bugfix1" and "Bugfix2" in issues list' do + expect(page).to have_content "Bugfix1" + expect(page).to have_content "Bugfix2" + end + + it 'should not show "Feature1" in issues list' do + expect(page).not_to have_content "Feature1" + end + + it 'should show label "bug" in filtered-labels' do + expect(find('.filtered-labels')).to have_content "bug" + end + + it 'should not show label "feature" and "enhancement" in filtered-labels' do + expect(find('.filtered-labels')).not_to have_content "feature" + expect(find('.filtered-labels')).not_to have_content "enhancement" + end + end + + context 'filter by label feature', js: true do + before do + page.find('.js-label-select').click + sleep 0.5 + execute_script("$('.dropdown-menu-labels li:contains(\"feature\") a').click()") + page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click + sleep 2 + end + + it 'should show issue "Feature1" in issues list' do + expect(page).to have_content "Feature1" + end + + it 'should not show "Bugfix1" and "Bugfix2" in issues list' do + expect(page).not_to have_content "Bugfix2" + expect(page).not_to have_content "Bugfix1" + end + + it 'should show label "feature" in filtered-labels' do + expect(find('.filtered-labels')).to have_content "feature" + end + + it 'should not show label "bug" and "enhancement" in filtered-labels' do + expect(find('.filtered-labels')).not_to have_content "bug" + expect(find('.filtered-labels')).not_to have_content "enhancement" + end + end + + context 'filter by label enhancement', js: true do + before do + page.find('.js-label-select').click + sleep 0.5 + execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()") + page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click + sleep 2 + end + + it 'should show issue "Bugfix2" in issues list' do + expect(page).to have_content "Bugfix2" + end + + it 'should not show "Feature1" and "Bugfix1" in issues list' do + expect(page).not_to have_content "Feature1" + expect(page).not_to have_content "Bugfix1" + end + + it 'should show label "enhancement" in filtered-labels' do + expect(find('.filtered-labels')).to have_content "enhancement" + end + + it 'should not show label "feature" and "bug" in filtered-labels' do + expect(find('.filtered-labels')).not_to have_content "bug" + expect(find('.filtered-labels')).not_to have_content "feature" + end + end + + context 'filter by label enhancement or feature', js: true do + before do + page.find('.js-label-select').click + sleep 0.5 + execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()") + execute_script("$('.dropdown-menu-labels li:contains(\"feature\") a').click()") + page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click + sleep 2 + end + + it 'should not show "Bugfix1" or "Feature1" in issues list' do + expect(page).not_to have_content "Bugfix1" + expect(page).not_to have_content "Feature1" + end + + it 'should show label "enhancement" and "feature" in filtered-labels' do + expect(find('.filtered-labels')).to have_content "enhancement" + expect(find('.filtered-labels')).to have_content "feature" + end + + it 'should not show label "bug" in filtered-labels' do + expect(find('.filtered-labels')).not_to have_content "bug" + end + end + + context 'filter by label enhancement and bug in issues list', js: true do + before do + page.find('.js-label-select').click + sleep 0.5 + execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()") + execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()") + page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click + sleep 2 + end + + it 'should show issue "Bugfix2" in issues list' do + expect(page).to have_content "Bugfix2" + end + + it 'should not show "Feature1"' do + expect(page).not_to have_content "Feature1" + end + + it 'should show label "bug" and "enhancement" in filtered-labels' do + expect(find('.filtered-labels')).to have_content "bug" + expect(find('.filtered-labels')).to have_content "enhancement" + end + + it 'should not show label "feature" in filtered-labels' do + expect(find('.filtered-labels')).not_to have_content "feature" + end + end +end diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index b16ccc6e305..dc7a9a10893 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -212,4 +212,28 @@ describe Issue, "Issuable" do expect(issue.downvotes).to eq(1) end end -end + + describe ".with_label" do + let(:example_label) { 'test1' } + let(:example_labels) { ['test1', 'test2'] } + + it 'finds issue with 1 label' do + setup_labels([example_label]) + + expect(Issue.with_label(example_label).count).to eq(1) + end + + it 'finds issue with 2 labels' do + setup_labels(example_labels) + + expect(Issue.with_label(example_labels).to_a.count).to eq(1) + end + + def setup_labels(label_names) + labels = label_names.map do |label| + create(:label, project: issue.project, title: label) + end + issue.labels << labels + end + end +end \ No newline at end of file From c0948396d1fd8bf4cbd187f33583b2f6db5d2178 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 20 Apr 2016 11:25:33 +0200 Subject: [PATCH 45/68] fix rubocop warning --- spec/models/concerns/issuable_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index dc7a9a10893..6b61bbb2fab 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -220,13 +220,13 @@ describe Issue, "Issuable" do it 'finds issue with 1 label' do setup_labels([example_label]) - expect(Issue.with_label(example_label).count).to eq(1) + expect(Issue.with_label(example_label).size).to eq(1) end it 'finds issue with 2 labels' do setup_labels(example_labels) - expect(Issue.with_label(example_labels).to_a.count).to eq(1) + expect(Issue.with_label(example_labels).to_a.size).to eq(1) end def setup_labels(label_names) From 976593fa7fd657437c68ade72f2061260318e7a9 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 20 Apr 2016 11:50:07 +0200 Subject: [PATCH 46/68] final line missing --- spec/models/concerns/issuable_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 6b61bbb2fab..63a0a47e923 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -236,4 +236,4 @@ describe Issue, "Issuable" do issue.labels << labels end end -end \ No newline at end of file +end From eb99e5f5c1cb2dfd2e8d058c3866f08aba5f2a4c Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Wed, 20 Apr 2016 10:57:31 -0300 Subject: [PATCH 47/68] Remove unused authorization from controller --- app/controllers/projects/project_members_controller.rb | 5 ----- app/helpers/projects_helper.rb | 2 +- app/models/ability.rb | 3 +-- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index f8c9ff657df..33b2625c0ac 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -1,7 +1,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController # Authorize before_action :authorize_admin_project_member!, except: [:leave, :index] - before_action :authorize_read_members_list!, only: [:index] def index @project_members = @project.project_members @@ -113,8 +112,4 @@ class Projects::ProjectMembersController < Projects::ApplicationController def member_params params.require(:project_member).permit(:user_id, :access_level) end - - def authorize_read_members_list! - render_403 unless can?(current_user, :read_members_list , @project) - end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index fc3662bc097..82f362b3b39 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -144,7 +144,7 @@ module ProjectsHelper nav_tabs << :settings end - if can?(current_user, :read_members_list, project) + if can?(current_user, :read_project_member, project) nav_tabs << :team end diff --git a/app/models/ability.rb b/app/models/ability.rb index 386c3d82d2c..6103a2947e2 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -189,8 +189,7 @@ class Ability :create_project, :create_issue, :create_note, - :upload_file, - :read_members_list + :upload_file ] end From 9581aba4d46f4f31e0ae1f4b76726d5ad01041e7 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Wed, 20 Apr 2016 12:41:35 -0300 Subject: [PATCH 48/68] Add changelog entry and fix convention in a spec --- CHANGELOG | 1 + spec/controllers/projects/project_members_controller_spec.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 290c9568149..ee2ffc7035b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -41,6 +41,7 @@ v 8.7.0 (unreleased) - Add endpoints to archive or unarchive a project !3372 - Fix a bug whith trailing slash in bamboo_url - Add links to CI setup documentation from project settings and builds pages + - Display project members page to all members - Handle nil descriptions in Slack issue messages (Stan Hu) - Add automated repository integrity checks - API: Expose open_issues_count, closed_issues_count, open_merge_requests_count for labels (Robert Schilling) diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index 1bc5ad4706b..ed64e7cf9af 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -47,7 +47,7 @@ describe Projects::ProjectMembersController do end end - describe 'index' do + describe '#index' do let(:project) { create(:project, :private) } context 'when user is member' do From 7bcacafc8a601896f8750f29fcde71f8f8ffb910 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 14 Apr 2016 16:52:01 +0100 Subject: [PATCH 49/68] Fixed issue with incorrect line being highlighted if line is in discussion view --- app/assets/javascripts/merge_request_tabs.js.coffee | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee index e8613cab72b..55e09c6acb9 100644 --- a/app/assets/javascripts/merge_request_tabs.js.coffee +++ b/app/assets/javascripts/merge_request_tabs.js.coffee @@ -176,12 +176,12 @@ class @MergeRequestTabs if locationHash isnt '' hashClassString = ".#{locationHash.replace('#', '')}" - $diffLine = $(locationHash) + $diffLine = $("#{locationHash}:not(.match)", $('#diffs')) - if $diffLine.is ':not(tr)' - $diffLine = $("td#{locationHash}, td#{hashClassString}") + if not $diffLine.is 'tr' + $diffLine = $('#diffs').find("td#{locationHash}, td#{hashClassString}") else - $diffLine = $('td', $diffLine) + $diffLine = $diffLine.find('td') if $diffLine.length $diffLine.addClass 'hll' From 6495c13d70cfd26cb46fb8bd0b7bc138cb939ff0 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 14 Apr 2016 17:00:05 +0100 Subject: [PATCH 50/68] Fixed issue with lines not being selectable when expanding diff --- app/views/projects/blob/diff.html.haml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml index 38e62c81fed..88d51b00e3e 100644 --- a/app/views/projects/blob/diff.html.haml +++ b/app/views/projects/blob/diff.html.haml @@ -1,17 +1,17 @@ - if @lines.present? - if @form.unfold? && @form.since != 1 && !@form.bottom? - %tr.line_holder{ id: @form.since } + %tr.line_holder = render "projects/diffs/match_line", { line: @match_line, line_old: @form.since, line_new: @form.since, bottom: false, new_file: false } - @lines.each_with_index do |line, index| - line_new = index + @form.since - line_old = line_new - @form.offset - %tr.line_holder + %tr.line_holder{ id: line_old } %td.old_line.diff-line-num{ data: { linenumber: line_old } } - = link_to raw(line_old), "#" - %td.new_line.diff-line-num{ data: { linenumber: line_old } } - = link_to raw(line_new) , "#" + = link_to raw(line_old), "##{line_old}" + %td.new_line.diff-line-num + = link_to raw(line_new) , "##{line_old}" %td.line_content.noteable_line==#{' ' * @form.indent}#{line} - if @form.unfold? && @form.bottom? && @form.to < @blob.loc From 28faa9719d70867f6ca0b0fb6b9fbf125812eb47 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 14 Apr 2016 17:07:44 +0100 Subject: [PATCH 51/68] Fixed issue when scrolling to element and there is an expander --- app/assets/javascripts/merge_request_tabs.js.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee index 55e09c6acb9..372732d0aac 100644 --- a/app/assets/javascripts/merge_request_tabs.js.coffee +++ b/app/assets/javascripts/merge_request_tabs.js.coffee @@ -87,8 +87,8 @@ class @MergeRequestTabs if window.location.hash navBarHeight = $('.navbar-gitlab').outerHeight() - $el = $("#{container} #{window.location.hash}") - $.scrollTo("#{container} #{window.location.hash}", offset: -navBarHeight) if $el.length + $el = $("#{container} #{window.location.hash}:not(.match)") + $.scrollTo("#{container} #{window.location.hash}:not(.match)", offset: -navBarHeight) if $el.length # Activate a tab based on the current action activateTab: (action) -> From 2dbfa1de27f5123b4be75b46cdebfae1f3fdbdeb Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 20 Apr 2016 17:30:21 +0100 Subject: [PATCH 52/68] Put back removed line number --- app/views/projects/blob/diff.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml index 88d51b00e3e..5926d181ba3 100644 --- a/app/views/projects/blob/diff.html.haml +++ b/app/views/projects/blob/diff.html.haml @@ -10,7 +10,7 @@ %tr.line_holder{ id: line_old } %td.old_line.diff-line-num{ data: { linenumber: line_old } } = link_to raw(line_old), "##{line_old}" - %td.new_line.diff-line-num + %td.new_line.diff-line-num{ data: { linenumber: line_old } } = link_to raw(line_new) , "##{line_old}" %td.line_content.noteable_line==#{' ' * @form.indent}#{line} From f93b0f3ea25af48f9f165cf6c0e3c2fb359bca35 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 20 Apr 2016 18:30:53 +0200 Subject: [PATCH 53/68] Layout navigation should respect layout width settings Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/nav.scss | 8 -------- app/views/layouts/_page.html.haml | 3 ++- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index e3f9825fe15..192d53b048a 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -185,11 +185,3 @@ } } } - -.layout-nav { - margin-bottom: 5px; - - .nav-links { - padding: 0 $gl-padding; - } -} diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index d4e5d5e4bfd..ca9c2a0bf2e 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -27,7 +27,8 @@ = yield :flash_message - if defined?(nav) && nav .layout-nav - = render "layouts/nav/#{nav}" + %div{ class: container_class } + = render "layouts/nav/#{nav}" %div{ class: (container_class unless @no_container) } .content .clearfix From 826857f47b2a69726d73671a532ae64095451f07 Mon Sep 17 00:00:00 2001 From: Arinde Eniola Date: Thu, 14 Apr 2016 19:32:33 +0100 Subject: [PATCH 54/68] disable spellcheck and autocorrect for username field in admin page --- app/views/admin/users/_form.html.haml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml index b05fdbd5552..fe0b9d3a491 100644 --- a/app/views/admin/users/_form.html.haml +++ b/app/views/admin/users/_form.html.haml @@ -7,17 +7,17 @@ .form-group = f.label :name, class: 'control-label' .col-sm-10 - = f.text_field :name, required: true, autocomplete: "off", class: 'form-control' + = f.text_field :name, required: true, autocomplete: 'off', class: 'form-control' %span.help-inline * required .form-group = f.label :username, class: 'control-label' .col-sm-10 - = f.text_field :username, required: true, autocomplete: "off", class: 'form-control' + = f.text_field :username, required: true, autocomplete: 'off', autocorrect: 'off', autocapitalize: 'off', spellcheck: false, class: 'form-control' %span.help-inline * required .form-group = f.label :email, class: 'control-label' .col-sm-10 - = f.text_field :email, required: true, autocomplete: "off", class: 'form-control' + = f.text_field :email, required: true, autocomplete: 'off', class: 'form-control' %span.help-inline * required - if @user.new_record? From 26166b774eb2c297b64168fa873c0ac532eca8da Mon Sep 17 00:00:00 2001 From: Arinde Eniola Date: Tue, 19 Apr 2016 09:21:48 +0100 Subject: [PATCH 55/68] add item to chengelog --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 433799eee2f..d4b8a509261 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -91,6 +91,7 @@ v 8.7.0 (unreleased) - Diff design updates (colors, button styles, etc) - Copying and pasting a diff no longer pastes the line numbers or +/- - Add null check to formData when updating profile content to fix Firefox bug + - Disable spellcheck and autocorrect for username field in admin page - Delete tags using Rugged for performance reasons (Robert Schilling) - Add Slack notifications when Wiki is edited (Sebastian Klier) - Diffs load at the correct point when linking from from number From dcd54f63451ab689ec03653b525b5f218a9f420a Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 18 Apr 2016 16:00:30 -0700 Subject: [PATCH 56/68] Attempt to create pg_trgm extension in migration for test/dev environments Closes #15210 --- ...0160226114608_add_trigram_indexes_for_searching.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb b/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb index 003169c13c6..d7b00e3d6ed 100644 --- a/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb +++ b/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb @@ -4,6 +4,8 @@ class AddTrigramIndexesForSearching < ActiveRecord::Migration def up return unless Gitlab::Database.postgresql? + create_trigrams_extension + unless trigrams_enabled? raise 'You must enable the pg_trgm extension. You can do so by running ' \ '"CREATE EXTENSION pg_trgm;" as a PostgreSQL super user, this must be ' \ @@ -37,6 +39,15 @@ class AddTrigramIndexesForSearching < ActiveRecord::Migration row && row['enabled'] == 't' ? true : false end + def create_trigrams_extension + # This may not work if the user doesn't have permission. We attempt in + # case we do have permission, particularly for test/dev environments. + begin + enable_extension 'pg_trgm' + rescue + end + end + def to_index { ci_runners: [:token, :description], From 6865bc16f8974e80b1ae657d7330be30c12c21ad Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 21 Apr 2016 09:12:03 +0200 Subject: [PATCH 57/68] refactored specs, adding more scenarios --- spec/features/issues/filter_by_labels_spec.rb | 33 +++++++++---------- spec/models/concerns/issuable_spec.rb | 27 +++++++++++---- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/spec/features/issues/filter_by_labels_spec.rb b/spec/features/issues/filter_by_labels_spec.rb index 7944403f874..9696af4b006 100644 --- a/spec/features/issues/filter_by_labels_spec.rb +++ b/spec/features/issues/filter_by_labels_spec.rb @@ -4,23 +4,25 @@ feature 'Issue filtering by Labels', feature: true do let(:project) { create(:project, :public) } let!(:user) { create(:user)} let!(:label) { create(:label, project: project) } + let(:bug) { create(:label, project: project, title: 'bug') } + let(:feature) { create(:label, project: project, title: 'feature') } + let(:enhancement) { create(:label, project: project, title: 'enhancement') } before do - ['bug', 'feature', 'enhancement'].each do |title| - create(:label, - project: project, - title: title) - end + + bug = create(:label, project: project, title: 'bug') + feature = create(:label, project: project, title: 'feature') + enhancement = create(:label, project: project, title: 'enhancement') issue1 = create(:issue, title: "Bugfix1", project: project) - issue1.labels << project.labels.find_by(title: 'bug') + issue1.labels << bug issue2 = create(:issue, title: "Bugfix2", project: project) - issue2.labels << project.labels.find_by(title: 'bug') - issue2.labels << project.labels.find_by(title: 'enhancement') + issue2.labels << bug + issue2.labels << enhancement issue3 = create(:issue, title: "Feature1", project: project) - issue3.labels << project.labels.find_by(title: 'feature') + issue3.labels << feature project.team << [user, :master] login_as(user) @@ -122,13 +124,9 @@ feature 'Issue filtering by Labels', feature: true do sleep 2 end - it 'should show issue "Bugfix2" or "Feature1" in issues list' do - expect(page).to have_content "Bugfix2" - expect(page).to have_content "Feature1" - end - - it 'should not show "Bugfix1" in issues list' do + it 'should not show "Bugfix1" or "Feature1" in issues list' do expect(page).not_to have_content "Bugfix1" + expect(page).not_to have_content "Feature1" end it 'should show label "enhancement" and "feature" in filtered-labels' do @@ -141,7 +139,7 @@ feature 'Issue filtering by Labels', feature: true do end end - context 'filter by label enhancement or bug in issues list', js: true do + context 'filter by label enhancement and bug in issues list', js: true do before do page.find('.js-label-select').click sleep 0.5 @@ -151,9 +149,8 @@ feature 'Issue filtering by Labels', feature: true do sleep 2 end - it 'should show issue "Bugfix2" or "Bugfix1" in issues list' do + it 'should show issue "Bugfix2" in issues list' do expect(page).to have_content "Bugfix2" - expect(page).to have_content "Bugfix1" end it 'should not show "Feature1"' do diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 63a0a47e923..cf9c1cc1fba 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -217,16 +217,26 @@ describe Issue, "Issuable" do let(:example_label) { 'test1' } let(:example_labels) { ['test1', 'test2'] } - it 'finds issue with 1 label' do - setup_labels([example_label]) - - expect(Issue.with_label(example_label).size).to eq(1) + before(:each) do + setup_other_issue end - it 'finds issue with 2 labels' do + it 'finds the correct issue with 1 label' do + setup_labels([example_label]) + + expect(Issue.with_label(example_label)).to eq([issue]) + end + + it 'finds the correct issue with 2 labels' do setup_labels(example_labels) - expect(Issue.with_label(example_labels).to_a.size).to eq(1) + expect(Issue.with_label(example_labels)).to eq([issue]) + end + + it 'finds the correct issue with 1 of 2 labels' do + setup_labels(example_labels) + + expect(Issue.with_label(example_label)).to eq([issue]) end def setup_labels(label_names) @@ -235,5 +245,10 @@ describe Issue, "Issuable" do end issue.labels << labels end + + def setup_other_issue + issue2 = create(:issue) + issue2.labels << create(:label, project: issue2.project, title: 'other_label') + end end end From f7348cd348ad8f4a18d74dd668283a4e236f5790 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 19 Apr 2016 10:41:51 +0200 Subject: [PATCH 58/68] Fix cross-project label ref with invalid project Closes #15168 --- lib/banzai/filter/label_reference_filter.rb | 2 +- .../filter/label_reference_filter_spec.rb | 42 ++++++++++++------- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index a2987850d03..db4d60c019c 100644 --- a/lib/banzai/filter/label_reference_filter.rb +++ b/lib/banzai/filter/label_reference_filter.rb @@ -20,7 +20,7 @@ module Banzai text.gsub(pattern) do |match| project = project_from_ref($~[:project]) params = label_params($~[:label_id].to_i, $~[:label_name]) - label = project.labels.find_by(params) + label = project.labels.find_by(params) if project if label yield match, label.id, $~[:project], $~ diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb index 94468abcbb3..b0a38e7c251 100644 --- a/spec/lib/banzai/filter/label_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb @@ -178,27 +178,37 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do end describe 'cross project label references' do - let(:another_project) { create(:empty_project, :public) } - let(:project_name) { another_project.name_with_namespace } - let(:label) { create(:label, project: another_project, color: '#00ff00') } - let(:reference) { label.to_reference(project) } + context 'valid project referenced' do + let(:another_project) { create(:empty_project, :public) } + let(:project_name) { another_project.name_with_namespace } + let(:label) { create(:label, project: another_project, color: '#00ff00') } + let(:reference) { label.to_reference(project) } - let!(:result) { reference_filter("See #{reference}") } + let!(:result) { reference_filter("See #{reference}") } - it 'points to referenced project issues page' do - expect(result.css('a').first.attr('href')) - .to eq urls.namespace_project_issues_url(another_project.namespace, - another_project, - label_name: label.name) + it 'points to referenced project issues page' do + expect(result.css('a').first.attr('href')) + .to eq urls.namespace_project_issues_url(another_project.namespace, + another_project, + label_name: label.name) + end + + it 'has valid color' do + expect(result.css('a span').first.attr('style')) + .to match /background-color: #00ff00/ + end + + it 'contains cross project content' do + expect(result.css('a').first.text).to eq "#{label.name} in #{project_name}" + end end - it 'has valid color' do - expect(result.css('a span').first.attr('style')) - .to match /background-color: #00ff00/ - end + context 'project that does not exist referenced' do + let(:result) { reference_filter('aaa/bbb~ccc') } - it 'contains cross project content' do - expect(result.css('a').first.text).to eq "#{label.name} in #{project_name}" + it 'does not link reference' do + expect(result.to_html).to eq 'aaa/bbb~ccc' + end end end end From dcc45eda8887e9af20e43ec449bff76fd52f8ebf Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 19 Apr 2016 10:44:30 +0200 Subject: [PATCH 59/68] Add Changelog entry for cross-project label fix --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index d4b8a509261..3e63c2c453d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ v 8.8.0 (unreleased) v 8.7.0 (unreleased) - The number of InfluxDB points stored per UDP packet can now be configured + - Fix error when cross-project label reference used with non-existent project - Transactions for /internal/allowed now have an "action" tag set - Method instrumentation now uses Module#prepend instead of aliasing methods - Repository.clean_old_archives is now instrumented From 35d8bc4485f3151dcf9ea5964b9cc18bdfd7d3d4 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 19 Apr 2016 12:07:13 +0200 Subject: [PATCH 60/68] Refactor banzai code that finds cross-project labels --- lib/banzai/filter/label_reference_filter.rb | 42 ++++++++++++--------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index db4d60c019c..8488a493b55 100644 --- a/lib/banzai/filter/label_reference_filter.rb +++ b/lib/banzai/filter/label_reference_filter.rb @@ -18,9 +18,7 @@ module Banzai def references_in(text, pattern = Label.reference_pattern) text.gsub(pattern) do |match| - project = project_from_ref($~[:project]) - params = label_params($~[:label_id].to_i, $~[:label_name]) - label = project.labels.find_by(params) if project + label = find_label($~[:project], $~[:label_id], $~[:label_name]) if label yield match, label.id, $~[:project], $~ @@ -30,6 +28,29 @@ module Banzai end end + def find_label(project_ref, label_id, label_name) + project = project_from_ref(project_ref) + return unless project + + label_params = label_params(label_id, label_name) + project.labels.find_by(label_params) + end + + # Parameters to pass to `Label.find_by` based on the given arguments + # + # id - Integer ID to pass. If present, returns {id: id} + # name - String name to pass. If `id` is absent, finds by name without + # surrounding quotes. + # + # Returns a Hash. + def label_params(id, name) + if name + { name: name.tr('"', '') } + else + { id: id.to_i } + end + end + def url_for_object(label, project) h = Gitlab::Routing.url_helpers h.namespace_project_issues_url(project.namespace, project, label_name: label.name, @@ -43,21 +64,6 @@ module Banzai LabelsHelper.render_colored_cross_project_label(object) end end - - # Parameters to pass to `Label.find_by` based on the given arguments - # - # id - Integer ID to pass. If present, returns {id: id} - # name - String name to pass. If `id` is absent, finds by name without - # surrounding quotes. - # - # Returns a Hash. - def label_params(id, name) - if name - { name: name.tr('"', '') } - else - { id: id } - end - end end end end From 2e0182b27821edc3c61b3aac5e8b214f7d08d8c2 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 21 Apr 2016 08:54:24 +0100 Subject: [PATCH 61/68] Fixed issue with author link color on dark diffs --- app/assets/stylesheets/pages/notes.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index aacb12532c3..e308f01f91b 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -196,6 +196,9 @@ ul.notes { } } + .author_link { + color: $gl-gray; + } } .note-headline-light, From 2d2b73ae4954a96d060fdf21ce8e95ccbdebe68f Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 21 Apr 2016 10:30:48 +0200 Subject: [PATCH 62/68] use wait_for_ajax instead of sleeping for 2 days! --- spec/features/issues/filter_by_labels_spec.rb | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/spec/features/issues/filter_by_labels_spec.rb b/spec/features/issues/filter_by_labels_spec.rb index 9696af4b006..29c3f9eaad4 100644 --- a/spec/features/issues/filter_by_labels_spec.rb +++ b/spec/features/issues/filter_by_labels_spec.rb @@ -1,6 +1,8 @@ require 'rails_helper' feature 'Issue filtering by Labels', feature: true do + include WaitForAjax + let(:project) { create(:project, :public) } let!(:user) { create(:user)} let!(:label) { create(:label, project: project) } @@ -33,10 +35,10 @@ feature 'Issue filtering by Labels', feature: true do context 'filter by label bug', js: true do before do page.find('.js-label-select').click - sleep 0.5 + wait_for_ajax execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()") page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click - sleep 2 + wait_for_ajax end it 'should show issue "Bugfix1" and "Bugfix2" in issues list' do @@ -61,10 +63,10 @@ feature 'Issue filtering by Labels', feature: true do context 'filter by label feature', js: true do before do page.find('.js-label-select').click - sleep 0.5 + wait_for_ajax execute_script("$('.dropdown-menu-labels li:contains(\"feature\") a').click()") page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click - sleep 2 + wait_for_ajax end it 'should show issue "Feature1" in issues list' do @@ -89,10 +91,10 @@ feature 'Issue filtering by Labels', feature: true do context 'filter by label enhancement', js: true do before do page.find('.js-label-select').click - sleep 0.5 + wait_for_ajax execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()") page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click - sleep 2 + wait_for_ajax end it 'should show issue "Bugfix2" in issues list' do @@ -117,11 +119,11 @@ feature 'Issue filtering by Labels', feature: true do context 'filter by label enhancement or feature', js: true do before do page.find('.js-label-select').click - sleep 0.5 + wait_for_ajax execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()") execute_script("$('.dropdown-menu-labels li:contains(\"feature\") a').click()") page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click - sleep 2 + wait_for_ajax end it 'should not show "Bugfix1" or "Feature1" in issues list' do @@ -142,11 +144,11 @@ feature 'Issue filtering by Labels', feature: true do context 'filter by label enhancement and bug in issues list', js: true do before do page.find('.js-label-select').click - sleep 0.5 + wait_for_ajax execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()") execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()") page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click - sleep 2 + wait_for_ajax end it 'should show issue "Bugfix2" in issues list' do From b09b175def7c66487d4571d90f23f613d868f25c Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 21 Apr 2016 13:20:00 +0200 Subject: [PATCH 63/68] refactored specs based on feedback --- spec/features/issues/filter_by_labels_spec.rb | 4 -- spec/models/concerns/issuable_spec.rb | 45 ++++++++----------- 2 files changed, 18 insertions(+), 31 deletions(-) diff --git a/spec/features/issues/filter_by_labels_spec.rb b/spec/features/issues/filter_by_labels_spec.rb index 29c3f9eaad4..7f654684143 100644 --- a/spec/features/issues/filter_by_labels_spec.rb +++ b/spec/features/issues/filter_by_labels_spec.rb @@ -6,12 +6,8 @@ feature 'Issue filtering by Labels', feature: true do let(:project) { create(:project, :public) } let!(:user) { create(:user)} let!(:label) { create(:label, project: project) } - let(:bug) { create(:label, project: project, title: 'bug') } - let(:feature) { create(:label, project: project, title: 'feature') } - let(:enhancement) { create(:label, project: project, title: 'enhancement') } before do - bug = create(:label, project: project, title: 'bug') feature = create(:label, project: project, title: 'feature') enhancement = create(:label, project: project, title: 'enhancement') diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index cf9c1cc1fba..4a4cd093435 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -214,41 +214,32 @@ describe Issue, "Issuable" do end describe ".with_label" do - let(:example_label) { 'test1' } - let(:example_labels) { ['test1', 'test2'] } + let(:project) { create(:project, :public) } + let(:bug) { create(:label, project: project, title: 'bug') } + let(:feature) { create(:label, project: project, title: 'feature') } + let(:enhancement) { create(:label, project: project, title: 'enhancement') } + let(:issue1) { create(:issue, title: "Bugfix1", project: project) } + let(:issue2) { create(:issue, title: "Bugfix2", project: project) } + let(:issue3) { create(:issue, title: "Feature1", project: project) } before(:each) do - setup_other_issue + issue1.labels << bug + issue1.labels << feature + issue2.labels << bug + issue2.labels << enhancement + issue3.labels << feature end - it 'finds the correct issue with 1 label' do - setup_labels([example_label]) - - expect(Issue.with_label(example_label)).to eq([issue]) + it 'finds the correct issue containing just enhancement label' do + expect(Issue.with_label(enhancement.title)).to match_array([issue2]) end - it 'finds the correct issue with 2 labels' do - setup_labels(example_labels) - - expect(Issue.with_label(example_labels)).to eq([issue]) + it 'finds the correct issues containing the same label' do + expect(Issue.with_label(bug.title)).to match_array([issue1, issue2]) end - it 'finds the correct issue with 1 of 2 labels' do - setup_labels(example_labels) - - expect(Issue.with_label(example_label)).to eq([issue]) - end - - def setup_labels(label_names) - labels = label_names.map do |label| - create(:label, project: issue.project, title: label) - end - issue.labels << labels - end - - def setup_other_issue - issue2 = create(:issue) - issue2.labels << create(:label, project: issue2.project, title: 'other_label') + it 'finds the correct issues containing only both labels' do + expect(Issue.with_label([bug.title, enhancement.title])).to match_array([issue2]) end end end From b3f4e8b218c380cc68213d1e59a6d992cd541099 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Thu, 21 Apr 2016 07:27:27 -0400 Subject: [PATCH 64/68] Revert "Merge branch 'sentry-js' into 'master' " This reverts commit 0f309794e78243b1ee16ba6f1451dbb0752956c5, reversing changes made to 1e596fef1c42a1dd925636c48fea01be444dc3ab. --- app/assets/javascripts/application.js.coffee | 1 - app/assets/javascripts/raven_config.js.coffee | 44 - lib/gitlab/gon_helper.rb | 1 - vendor/assets/javascripts/raven.js | 2435 ----------------- 4 files changed, 2481 deletions(-) delete mode 100644 app/assets/javascripts/raven_config.js.coffee delete mode 100644 vendor/assets/javascripts/raven.js diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 642e7429acf..5bac8eef1cb 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -55,7 +55,6 @@ #= require_tree . #= require fuzzaldrin-plus #= require cropper -#= require raven window.slugify = (text) -> text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase() diff --git a/app/assets/javascripts/raven_config.js.coffee b/app/assets/javascripts/raven_config.js.coffee deleted file mode 100644 index d031a655abf..00000000000 --- a/app/assets/javascripts/raven_config.js.coffee +++ /dev/null @@ -1,44 +0,0 @@ -@raven = - init: -> - if gon.sentry_dsn? - Raven.config(gon.sentry_dsn, { - includePaths: [/gon.relative_url_root/] - ignoreErrors: [ - # Random plugins/extensions - 'top.GLOBALS', - # See: http://blog.errorception.com/2012/03/tale-of-unfindable-js-error. html - 'originalCreateNotification', - 'canvas.contentDocument', - 'MyApp_RemoveAllHighlights', - 'http://tt.epicplay.com', - 'Can\'t find variable: ZiteReader', - 'jigsaw is not defined', - 'ComboSearch is not defined', - 'http://loading.retry.widdit.com/', - 'atomicFindClose', - # ISP "optimizing" proxy - `Cache-Control: no-transform` seems to - # reduce this. (thanks @acdha) - # See http://stackoverflow.com/questions/4113268 - 'bmi_SafeAddOnload', - 'EBCallBackMessageReceived', - # See http://toolbar.conduit.com/Developer/HtmlAndGadget/Methods/JSInjection.aspx - 'conduitPage' - ], - ignoreUrls: [ - # Chrome extensions - /extensions\//i, - /^chrome:\/\//i, - # Other plugins - /127\.0\.0\.1:4001\/isrunning/i, # Cacaoweb - /webappstoolbarba\.texthelp\.com\//i, - /metrics\.itunes\.apple\.com\.edgesuite\.net\//i - ] - }).install() - - if gon.current_user_id - Raven.setUserContext({ - id: gon.current_user_id - }) - -$ -> - raven.init() diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb index eb27d82f110..5ebaad6ca6e 100644 --- a/lib/gitlab/gon_helper.rb +++ b/lib/gitlab/gon_helper.rb @@ -7,7 +7,6 @@ module Gitlab gon.max_file_size = current_application_settings.max_attachment_size gon.relative_url_root = Gitlab.config.gitlab.relative_url_root gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class - gon.sentry_dsn = ApplicationSetting.current.sentry_dsn if Rails.env.production? if current_user gon.current_user_id = current_user.id diff --git a/vendor/assets/javascripts/raven.js b/vendor/assets/javascripts/raven.js deleted file mode 100644 index d99c6f1c2c8..00000000000 --- a/vendor/assets/javascripts/raven.js +++ /dev/null @@ -1,2435 +0,0 @@ -/*! Raven.js 2.3.0 (b09d766) | github.com/getsentry/raven-js */ - -/* - * Includes TraceKit - * https://github.com/getsentry/TraceKit - * - * Copyright 2016 Matt Robenolt and other contributors - * Released under the BSD license - * https://github.com/getsentry/raven-js/blob/master/LICENSE - * - */ - -(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Raven = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 300) { - isMinified = true; - break; - } - } - - if (isMinified) { - // The source is minified and we don't know which column. Fuck it. - if (isUndefined(frame.column)) return; - - // If the source is minified and has a frame column - // we take a chunk of the offending line to hopefully shed some light - return [ - [], // no pre_context - context[pivot].substr(frame.column, 50), // grab 50 characters, starting at the offending column - [] // no post_context - ]; - } - - return [ - context.slice(0, pivot), // pre_context - context[pivot], // context_line - context.slice(pivot + 1) // post_context - ]; - }, - - _processException: function(type, message, fileurl, lineno, frames, options) { - var stacktrace, fullMessage; - - if (!!this._globalOptions.ignoreErrors.test && this._globalOptions.ignoreErrors.test(message)) return; - - message += ''; - message = truncate(message, this._globalOptions.maxMessageLength); - - fullMessage = (type ? type + ': ' : '') + message; - fullMessage = truncate(fullMessage, this._globalOptions.maxMessageLength); - - if (frames && frames.length) { - fileurl = frames[0].filename || fileurl; - // Sentry expects frames oldest to newest - // and JS sends them as newest to oldest - frames.reverse(); - stacktrace = {frames: frames}; - } else if (fileurl) { - stacktrace = { - frames: [{ - filename: fileurl, - lineno: lineno, - in_app: true - }] - }; - } - - if (!!this._globalOptions.ignoreUrls.test && this._globalOptions.ignoreUrls.test(fileurl)) return; - if (!!this._globalOptions.whitelistUrls.test && !this._globalOptions.whitelistUrls.test(fileurl)) return; - - var data = objectMerge({ - // sentry.interfaces.Exception - exception: { - values: [{ - type: type, - value: message, - stacktrace: stacktrace - }] - }, - culprit: fileurl, - message: fullMessage - }, options); - - // Fire away! - this._send(data); - }, - - _trimPacket: function(data) { - // For now, we only want to truncate the two different messages - // but this could/should be expanded to just trim everything - var max = this._globalOptions.maxMessageLength; - data.message = truncate(data.message, max); - if (data.exception) { - var exception = data.exception.values[0]; - exception.value = truncate(exception.value, max); - } - - return data; - }, - - _getHttpData: function() { - if (!this._hasDocument || !document.location || !document.location.href) { - return; - } - - var httpData = { - headers: { - 'User-Agent': navigator.userAgent - } - }; - - httpData.url = document.location.href; - - if (document.referrer) { - httpData.headers.Referer = document.referrer; - } - - return httpData; - }, - - - _send: function(data) { - var self = this; - - var globalOptions = this._globalOptions; - - var baseData = { - project: this._globalProject, - logger: globalOptions.logger, - platform: 'javascript' - }, httpData = this._getHttpData(); - - if (httpData) { - baseData.request = httpData; - } - - data = objectMerge(baseData, data); - - // Merge in the tags and extra separately since objectMerge doesn't handle a deep merge - data.tags = objectMerge(objectMerge({}, this._globalContext.tags), data.tags); - data.extra = objectMerge(objectMerge({}, this._globalContext.extra), data.extra); - - // Send along our own collected metadata with extra - data.extra['session:duration'] = now() - this._startTime; - - // If there are no tags/extra, strip the key from the payload alltogther. - if (isEmptyObject(data.tags)) delete data.tags; - - if (this._globalContext.user) { - // sentry.interfaces.User - data.user = this._globalContext.user; - } - - // Include the release if it's defined in globalOptions - if (globalOptions.release) data.release = globalOptions.release; - - // Include server_name if it's defined in globalOptions - if (globalOptions.serverName) data.server_name = globalOptions.serverName; - - if (isFunction(globalOptions.dataCallback)) { - data = globalOptions.dataCallback(data) || data; - } - - // Why?????????? - if (!data || isEmptyObject(data)) { - return; - } - - // Check if the request should be filtered or not - if (isFunction(globalOptions.shouldSendCallback) && !globalOptions.shouldSendCallback(data)) { - return; - } - - // Send along an event_id if not explicitly passed. - // This event_id can be used to reference the error within Sentry itself. - // Set lastEventId after we know the error should actually be sent - this._lastEventId = data.event_id || (data.event_id = uuid4()); - - // Try and clean up the packet before sending by truncating long values - data = this._trimPacket(data); - - this._logDebug('debug', 'Raven about to send:', data); - - if (!this.isSetup()) return; - - var auth = { - sentry_version: '7', - sentry_client: 'raven-js/' + this.VERSION, - sentry_key: this._globalKey - }; - if (this._globalSecret) { - auth.sentry_secret = this._globalSecret; - } - - var url = this._globalEndpoint; - (globalOptions.transport || this._makeRequest).call(this, { - url: url, - auth: auth, - data: data, - options: globalOptions, - onSuccess: function success() { - self._triggerEvent('success', { - data: data, - src: url - }); - }, - onError: function failure() { - self._triggerEvent('failure', { - data: data, - src: url - }); - } - }); - }, - - _makeImageRequest: function(opts) { - // Tack on sentry_data to auth options, which get urlencoded - opts.auth.sentry_data = JSON.stringify(opts.data); - - var img = this._newImage(), - src = opts.url + '?' + urlencode(opts.auth), - crossOrigin = opts.options.crossOrigin; - - if (crossOrigin || crossOrigin === '') { - img.crossOrigin = crossOrigin; - } - img.onload = opts.onSuccess; - img.onerror = img.onabort = opts.onError; - img.src = src; - }, - - _makeXhrRequest: function(opts) { - var request; - - var url = opts.url; - function handler() { - if (request.status === 200) { - if (opts.onSuccess) { - opts.onSuccess(); - } - } else if (opts.onError) { - opts.onError(); - } - } - - request = new XMLHttpRequest(); - if ('withCredentials' in request) { - request.onreadystatechange = function () { - if (request.readyState !== 4) { - return; - } - handler(); - }; - } else { - request = new XDomainRequest(); - // xdomainrequest cannot go http -> https (or vice versa), - // so always use protocol relative - url = url.replace(/^https?:/, ''); - - // onreadystatechange not supported by XDomainRequest - request.onload = handler; - } - - // NOTE: auth is intentionally sent as part of query string (NOT as custom - // HTTP header) so as to avoid preflight CORS requests - request.open('POST', url + '?' + urlencode(opts.auth)); - request.send(JSON.stringify(opts.data)); - }, - - _makeRequest: function(opts) { - var hasCORS = - 'withCredentials' in new XMLHttpRequest() || - typeof XDomainRequest !== 'undefined'; - - return (hasCORS ? this._makeXhrRequest : this._makeImageRequest)(opts); - }, - - // Note: this is shitty, but I can't figure out how to get - // sinon to stub document.createElement without breaking everything - // so this wrapper is just so I can stub it for tests. - _newImage: function() { - return document.createElement('img'); - }, - - _logDebug: function(level) { - if (this._originalConsoleMethods[level] && this.debug) { - // In IE<10 console methods do not have their own 'apply' method - Function.prototype.apply.call( - this._originalConsoleMethods[level], - this._originalConsole, - [].slice.call(arguments, 1) - ); - } - }, - - _mergeContext: function(key, context) { - if (isUndefined(context)) { - delete this._globalContext[key]; - } else { - this._globalContext[key] = objectMerge(this._globalContext[key] || {}, context); - } - } -}; - -// Deprecations -Raven.prototype.setUser = Raven.prototype.setUserContext; -Raven.prototype.setReleaseContext = Raven.prototype.setRelease; - -module.exports = Raven; - -},{"1":1,"4":4,"5":5}],3:[function(_dereq_,module,exports){ -/** - * Enforces a single instance of the Raven client, and the - * main entry point for Raven. If you are a consumer of the - * Raven library, you SHOULD load this file (vs raven.js). - **/ - -'use strict'; - -var RavenConstructor = _dereq_(2); - -var _Raven = window.Raven; - -var Raven = new RavenConstructor(); - -/* - * Allow multiple versions of Raven to be installed. - * Strip Raven from the global context and returns the instance. - * - * @return {Raven} - */ -Raven.noConflict = function () { - window.Raven = _Raven; - return Raven; -}; - -Raven.afterLoad(); - -module.exports = Raven; - -},{"2":2}],4:[function(_dereq_,module,exports){ -'use strict'; - -var objectPrototype = Object.prototype; - -function isUndefined(what) { - return what === void 0; -} - -function isFunction(what) { - return typeof what === 'function'; -} - -function isString(what) { - return objectPrototype.toString.call(what) === '[object String]'; -} - -function isObject(what) { - return typeof what === 'object' && what !== null; -} - -function isEmptyObject(what) { - for (var _ in what) return false; // eslint-disable-line guard-for-in, no-unused-vars - return true; -} - -// Sorta yanked from https://github.com/joyent/node/blob/aa3b4b4/lib/util.js#L560 -// with some tiny modifications -function isError(what) { - var toString = objectPrototype.toString.call(what); - return isObject(what) && - toString === '[object Error]' || - toString === '[object Exception]' || // Firefox NS_ERROR_FAILURE Exceptions - what instanceof Error; -} - -function each(obj, callback) { - var i, j; - - if (isUndefined(obj.length)) { - for (i in obj) { - if (hasKey(obj, i)) { - callback.call(null, i, obj[i]); - } - } - } else { - j = obj.length; - if (j) { - for (i = 0; i < j; i++) { - callback.call(null, i, obj[i]); - } - } - } -} - -function objectMerge(obj1, obj2) { - if (!obj2) { - return obj1; - } - each(obj2, function(key, value){ - obj1[key] = value; - }); - return obj1; -} - -function truncate(str, max) { - return !max || str.length <= max ? str : str.substr(0, max) + '\u2026'; -} - -/** - * hasKey, a better form of hasOwnProperty - * Example: hasKey(MainHostObject, property) === true/false - * - * @param {Object} host object to check property - * @param {string} key to check - */ -function hasKey(object, key) { - return objectPrototype.hasOwnProperty.call(object, key); -} - -function joinRegExp(patterns) { - // Combine an array of regular expressions and strings into one large regexp - // Be mad. - var sources = [], - i = 0, len = patterns.length, - pattern; - - for (; i < len; i++) { - pattern = patterns[i]; - if (isString(pattern)) { - // If it's a string, we need to escape it - // Taken from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions - sources.push(pattern.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1')); - } else if (pattern && pattern.source) { - // If it's a regexp already, we want to extract the source - sources.push(pattern.source); - } - // Intentionally skip other cases - } - return new RegExp(sources.join('|'), 'i'); -} - -function urlencode(o) { - var pairs = []; - each(o, function(key, value) { - pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(value)); - }); - return pairs.join('&'); -} - -function uuid4() { - var crypto = window.crypto || window.msCrypto; - - if (!isUndefined(crypto) && crypto.getRandomValues) { - // Use window.crypto API if available - var arr = new Uint16Array(8); - crypto.getRandomValues(arr); - - // set 4 in byte 7 - arr[3] = arr[3] & 0xFFF | 0x4000; - // set 2 most significant bits of byte 9 to '10' - arr[4] = arr[4] & 0x3FFF | 0x8000; - - var pad = function(num) { - var v = num.toString(16); - while (v.length < 4) { - v = '0' + v; - } - return v; - }; - - return pad(arr[0]) + pad(arr[1]) + pad(arr[2]) + pad(arr[3]) + pad(arr[4]) + - pad(arr[5]) + pad(arr[6]) + pad(arr[7]); - } else { - // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523 - return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) { - var r = Math.random()*16|0, - v = c === 'x' ? r : r&0x3|0x8; - return v.toString(16); - }); - } -} - -module.exports = { - isUndefined: isUndefined, - isFunction: isFunction, - isString: isString, - isObject: isObject, - isEmptyObject: isEmptyObject, - isError: isError, - each: each, - objectMerge: objectMerge, - truncate: truncate, - hasKey: hasKey, - joinRegExp: joinRegExp, - urlencode: urlencode, - uuid4: uuid4 -}; - -},{}],5:[function(_dereq_,module,exports){ -'use strict'; - -var utils = _dereq_(4); - -var hasKey = utils.hasKey; -var isString = utils.isString; -var isUndefined = utils.isUndefined; - -/* - TraceKit - Cross brower stack traces - github.com/occ/TraceKit - MIT license -*/ - -var TraceKit = { - remoteFetching: false, - collectWindowErrors: true, - // 3 lines before, the offending line, 3 lines after - linesOfContext: 7, - debug: false -}; - -// global reference to slice -var _slice = [].slice; -var UNKNOWN_FUNCTION = '?'; - -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Error_types -var ERROR_TYPES_RE = /^(?:Uncaught )?((?:Eval|Internal|Range|Reference|Syntax|Type|URI)Error)\: ?(.*)$/; - -function getLocationHref() { - if (typeof document === 'undefined') - return ''; - - return document.location.href; -} - -/** - * TraceKit.report: cross-browser processing of unhandled exceptions - * - * Syntax: - * TraceKit.report.subscribe(function(stackInfo) { ... }) - * TraceKit.report.unsubscribe(function(stackInfo) { ... }) - * TraceKit.report(exception) - * try { ...code... } catch(ex) { TraceKit.report(ex); } - * - * Supports: - * - Firefox: full stack trace with line numbers, plus column number - * on top frame; column number is not guaranteed - * - Opera: full stack trace with line and column numbers - * - Chrome: full stack trace with line and column numbers - * - Safari: line and column number for the top frame only; some frames - * may be missing, and column number is not guaranteed - * - IE: line and column number for the top frame only; some frames - * may be missing, and column number is not guaranteed - * - * In theory, TraceKit should work on all of the following versions: - * - IE5.5+ (only 8.0 tested) - * - Firefox 0.9+ (only 3.5+ tested) - * - Opera 7+ (only 10.50 tested; versions 9 and earlier may require - * Exceptions Have Stacktrace to be enabled in opera:config) - * - Safari 3+ (only 4+ tested) - * - Chrome 1+ (only 5+ tested) - * - Konqueror 3.5+ (untested) - * - * Requires TraceKit.computeStackTrace. - * - * Tries to catch all unhandled exceptions and report them to the - * subscribed handlers. Please note that TraceKit.report will rethrow the - * exception. This is REQUIRED in order to get a useful stack trace in IE. - * If the exception does not reach the top of the browser, you will only - * get a stack trace from the point where TraceKit.report was called. - * - * Handlers receive a stackInfo object as described in the - * TraceKit.computeStackTrace docs. - */ -TraceKit.report = (function reportModuleWrapper() { - var handlers = [], - lastArgs = null, - lastException = null, - lastExceptionStack = null; - - /** - * Add a crash handler. - * @param {Function} handler - */ - function subscribe(handler) { - installGlobalHandler(); - handlers.push(handler); - } - - /** - * Remove a crash handler. - * @param {Function} handler - */ - function unsubscribe(handler) { - for (var i = handlers.length - 1; i >= 0; --i) { - if (handlers[i] === handler) { - handlers.splice(i, 1); - } - } - } - - /** - * Remove all crash handlers. - */ - function unsubscribeAll() { - uninstallGlobalHandler(); - handlers = []; - } - - /** - * Dispatch stack information to all handlers. - * @param {Object.} stack - */ - function notifyHandlers(stack, isWindowError) { - var exception = null; - if (isWindowError && !TraceKit.collectWindowErrors) { - return; - } - for (var i in handlers) { - if (hasKey(handlers, i)) { - try { - handlers[i].apply(null, [stack].concat(_slice.call(arguments, 2))); - } catch (inner) { - exception = inner; - } - } - } - - if (exception) { - throw exception; - } - } - - var _oldOnerrorHandler, _onErrorHandlerInstalled; - - /** - * Ensures all global unhandled exceptions are recorded. - * Supported by Gecko and IE. - * @param {string} message Error message. - * @param {string} url URL of script that generated the exception. - * @param {(number|string)} lineNo The line number at which the error - * occurred. - * @param {?(number|string)} colNo The column number at which the error - * occurred. - * @param {?Error} ex The actual Error object. - */ - function traceKitWindowOnError(message, url, lineNo, colNo, ex) { - var stack = null; - - if (lastExceptionStack) { - TraceKit.computeStackTrace.augmentStackTraceWithInitialElement(lastExceptionStack, url, lineNo, message); - processLastException(); - } else if (ex) { - // New chrome and blink send along a real error object - // Let's just report that like a normal error. - // See: https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror - stack = TraceKit.computeStackTrace(ex); - notifyHandlers(stack, true); - } else { - var location = { - 'url': url, - 'line': lineNo, - 'column': colNo - }; - location.func = TraceKit.computeStackTrace.guessFunctionName(location.url, location.line); - location.context = TraceKit.computeStackTrace.gatherContext(location.url, location.line); - - var name = undefined; - var msg = message; // must be new var or will modify original `arguments` - var groups; - if (isString(message)) { - var groups = message.match(ERROR_TYPES_RE); - if (groups) { - name = groups[1]; - msg = groups[2]; - } - } - - stack = { - 'name': name, - 'message': msg, - 'url': getLocationHref(), - 'stack': [location] - }; - notifyHandlers(stack, true); - } - - if (_oldOnerrorHandler) { - return _oldOnerrorHandler.apply(this, arguments); - } - - return false; - } - - function installGlobalHandler () - { - if (_onErrorHandlerInstalled) { - return; - } - _oldOnerrorHandler = window.onerror; - window.onerror = traceKitWindowOnError; - _onErrorHandlerInstalled = true; - } - - function uninstallGlobalHandler () - { - if (!_onErrorHandlerInstalled) { - return; - } - window.onerror = _oldOnerrorHandler; - _onErrorHandlerInstalled = false; - _oldOnerrorHandler = undefined; - } - - function processLastException() { - var _lastExceptionStack = lastExceptionStack, - _lastArgs = lastArgs; - lastArgs = null; - lastExceptionStack = null; - lastException = null; - notifyHandlers.apply(null, [_lastExceptionStack, false].concat(_lastArgs)); - } - - /** - * Reports an unhandled Error to TraceKit. - * @param {Error} ex - * @param {?boolean} rethrow If false, do not re-throw the exception. - * Only used for window.onerror to not cause an infinite loop of - * rethrowing. - */ - function report(ex, rethrow) { - var args = _slice.call(arguments, 1); - if (lastExceptionStack) { - if (lastException === ex) { - return; // already caught by an inner catch block, ignore - } else { - processLastException(); - } - } - - var stack = TraceKit.computeStackTrace(ex); - lastExceptionStack = stack; - lastException = ex; - lastArgs = args; - - // If the stack trace is incomplete, wait for 2 seconds for - // slow slow IE to see if onerror occurs or not before reporting - // this exception; otherwise, we will end up with an incomplete - // stack trace - window.setTimeout(function () { - if (lastException === ex) { - processLastException(); - } - }, (stack.incomplete ? 2000 : 0)); - - if (rethrow !== false) { - throw ex; // re-throw to propagate to the top level (and cause window.onerror) - } - } - - report.subscribe = subscribe; - report.unsubscribe = unsubscribe; - report.uninstall = unsubscribeAll; - return report; -}()); - -/** - * TraceKit.computeStackTrace: cross-browser stack traces in JavaScript - * - * Syntax: - * s = TraceKit.computeStackTrace(exception) // consider using TraceKit.report instead (see below) - * Returns: - * s.name - exception name - * s.message - exception message - * s.stack[i].url - JavaScript or HTML file URL - * s.stack[i].func - function name, or empty for anonymous functions (if guessing did not work) - * s.stack[i].args - arguments passed to the function, if known - * s.stack[i].line - line number, if known - * s.stack[i].column - column number, if known - * s.stack[i].context - an array of source code lines; the middle element corresponds to the correct line# - * - * Supports: - * - Firefox: full stack trace with line numbers and unreliable column - * number on top frame - * - Opera 10: full stack trace with line and column numbers - * - Opera 9-: full stack trace with line numbers - * - Chrome: full stack trace with line and column numbers - * - Safari: line and column number for the topmost stacktrace element - * only - * - IE: no line numbers whatsoever - * - * Tries to guess names of anonymous functions by looking for assignments - * in the source code. In IE and Safari, we have to guess source file names - * by searching for function bodies inside all page scripts. This will not - * work for scripts that are loaded cross-domain. - * Here be dragons: some function names may be guessed incorrectly, and - * duplicate functions may be mismatched. - * - * TraceKit.computeStackTrace should only be used for tracing purposes. - * Logging of unhandled exceptions should be done with TraceKit.report, - * which builds on top of TraceKit.computeStackTrace and provides better - * IE support by utilizing the window.onerror event to retrieve information - * about the top of the stack. - * - * Note: In IE and Safari, no stack trace is recorded on the Error object, - * so computeStackTrace instead walks its *own* chain of callers. - * This means that: - * * in Safari, some methods may be missing from the stack trace; - * * in IE, the topmost function in the stack trace will always be the - * caller of computeStackTrace. - * - * This is okay for tracing (because you are likely to be calling - * computeStackTrace from the function you want to be the topmost element - * of the stack trace anyway), but not okay for logging unhandled - * exceptions (because your catch block will likely be far away from the - * inner function that actually caused the exception). - * - */ -TraceKit.computeStackTrace = (function computeStackTraceWrapper() { - var sourceCache = {}; - - /** - * Attempts to retrieve source code via XMLHttpRequest, which is used - * to look up anonymous function names. - * @param {string} url URL of source code. - * @return {string} Source contents. - */ - function loadSource(url) { - if (!TraceKit.remoteFetching) { //Only attempt request if remoteFetching is on. - return ''; - } - try { - var getXHR = function() { - try { - return new window.XMLHttpRequest(); - } catch (e) { - // explicitly bubble up the exception if not found - return new window.ActiveXObject('Microsoft.XMLHTTP'); - } - }; - - var request = getXHR(); - request.open('GET', url, false); - request.send(''); - return request.responseText; - } catch (e) { - return ''; - } - } - - /** - * Retrieves source code from the source code cache. - * @param {string} url URL of source code. - * @return {Array.} Source contents. - */ - function getSource(url) { - if (!isString(url)) return []; - if (!hasKey(sourceCache, url)) { - // URL needs to be able to fetched within the acceptable domain. Otherwise, - // cross-domain errors will be triggered. - var source = ''; - var domain = ''; - try { domain = document.domain; } catch (e) {} - if (url.indexOf(domain) !== -1) { - source = loadSource(url); - } - sourceCache[url] = source ? source.split('\n') : []; - } - - return sourceCache[url]; - } - - /** - * Tries to use an externally loaded copy of source code to determine - * the name of a function by looking at the name of the variable it was - * assigned to, if any. - * @param {string} url URL of source code. - * @param {(string|number)} lineNo Line number in source code. - * @return {string} The function name, if discoverable. - */ - function guessFunctionName(url, lineNo) { - var reFunctionArgNames = /function ([^(]*)\(([^)]*)\)/, - reGuessFunction = /['"]?([0-9A-Za-z$_]+)['"]?\s*[:=]\s*(function|eval|new Function)/, - line = '', - maxLines = 10, - source = getSource(url), - m; - - if (!source.length) { - return UNKNOWN_FUNCTION; - } - - // Walk backwards from the first line in the function until we find the line which - // matches the pattern above, which is the function definition - for (var i = 0; i < maxLines; ++i) { - line = source[lineNo - i] + line; - - if (!isUndefined(line)) { - if ((m = reGuessFunction.exec(line))) { - return m[1]; - } else if ((m = reFunctionArgNames.exec(line))) { - return m[1]; - } - } - } - - return UNKNOWN_FUNCTION; - } - - /** - * Retrieves the surrounding lines from where an exception occurred. - * @param {string} url URL of source code. - * @param {(string|number)} line Line number in source code to centre - * around for context. - * @return {?Array.} Lines of source code. - */ - function gatherContext(url, line) { - var source = getSource(url); - - if (!source.length) { - return null; - } - - var context = [], - // linesBefore & linesAfter are inclusive with the offending line. - // if linesOfContext is even, there will be one extra line - // *before* the offending line. - linesBefore = Math.floor(TraceKit.linesOfContext / 2), - // Add one extra line if linesOfContext is odd - linesAfter = linesBefore + (TraceKit.linesOfContext % 2), - start = Math.max(0, line - linesBefore - 1), - end = Math.min(source.length, line + linesAfter - 1); - - line -= 1; // convert to 0-based index - - for (var i = start; i < end; ++i) { - if (!isUndefined(source[i])) { - context.push(source[i]); - } - } - - return context.length > 0 ? context : null; - } - - /** - * Escapes special characters, except for whitespace, in a string to be - * used inside a regular expression as a string literal. - * @param {string} text The string. - * @return {string} The escaped string literal. - */ - function escapeRegExp(text) { - return text.replace(/[\-\[\]{}()*+?.,\\\^$|#]/g, '\\$&'); - } - - /** - * Escapes special characters in a string to be used inside a regular - * expression as a string literal. Also ensures that HTML entities will - * be matched the same as their literal friends. - * @param {string} body The string. - * @return {string} The escaped string. - */ - function escapeCodeAsRegExpForMatchingInsideHTML(body) { - return escapeRegExp(body).replace('<', '(?:<|<)').replace('>', '(?:>|>)').replace('&', '(?:&|&)').replace('"', '(?:"|")').replace(/\s+/g, '\\s+'); - } - - /** - * Determines where a code fragment occurs in the source code. - * @param {RegExp} re The function definition. - * @param {Array.} urls A list of URLs to search. - * @return {?Object.} An object containing - * the url, line, and column number of the defined function. - */ - function findSourceInUrls(re, urls) { - var source, m; - for (var i = 0, j = urls.length; i < j; ++i) { - // console.log('searching', urls[i]); - if ((source = getSource(urls[i])).length) { - source = source.join('\n'); - if ((m = re.exec(source))) { - // console.log('Found function in ' + urls[i]); - - return { - 'url': urls[i], - 'line': source.substring(0, m.index).split('\n').length, - 'column': m.index - source.lastIndexOf('\n', m.index) - 1 - }; - } - } - } - - // console.log('no match'); - - return null; - } - - /** - * Determines at which column a code fragment occurs on a line of the - * source code. - * @param {string} fragment The code fragment. - * @param {string} url The URL to search. - * @param {(string|number)} line The line number to examine. - * @return {?number} The column number. - */ - function findSourceInLine(fragment, url, line) { - var source = getSource(url), - re = new RegExp('\\b' + escapeRegExp(fragment) + '\\b'), - m; - - line -= 1; - - if (source && source.length > line && (m = re.exec(source[line]))) { - return m.index; - } - - return null; - } - - /** - * Determines where a function was defined within the source code. - * @param {(Function|string)} func A function reference or serialized - * function definition. - * @return {?Object.} An object containing - * the url, line, and column number of the defined function. - */ - function findSourceByFunctionBody(func) { - if (typeof document === 'undefined') - return; - - var urls = [window.location.href], - scripts = document.getElementsByTagName('script'), - body, - code = '' + func, - codeRE = /^function(?:\s+([\w$]+))?\s*\(([\w\s,]*)\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/, - eventRE = /^function on([\w$]+)\s*\(event\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/, - re, - parts, - result; - - for (var i = 0; i < scripts.length; ++i) { - var script = scripts[i]; - if (script.src) { - urls.push(script.src); - } - } - - if (!(parts = codeRE.exec(code))) { - re = new RegExp(escapeRegExp(code).replace(/\s+/g, '\\s+')); - } - - // not sure if this is really necessary, but I don’t have a test - // corpus large enough to confirm that and it was in the original. - else { - var name = parts[1] ? '\\s+' + parts[1] : '', - args = parts[2].split(',').join('\\s*,\\s*'); - - body = escapeRegExp(parts[3]).replace(/;$/, ';?'); // semicolon is inserted if the function ends with a comment.replace(/\s+/g, '\\s+'); - re = new RegExp('function' + name + '\\s*\\(\\s*' + args + '\\s*\\)\\s*{\\s*' + body + '\\s*}'); - } - - // look for a normal function definition - if ((result = findSourceInUrls(re, urls))) { - return result; - } - - // look for an old-school event handler function - if ((parts = eventRE.exec(code))) { - var event = parts[1]; - body = escapeCodeAsRegExpForMatchingInsideHTML(parts[2]); - - // look for a function defined in HTML as an onXXX handler - re = new RegExp('on' + event + '=[\\\'"]\\s*' + body + '\\s*[\\\'"]', 'i'); - - if ((result = findSourceInUrls(re, urls[0]))) { - return result; - } - - // look for ??? - re = new RegExp(body); - - if ((result = findSourceInUrls(re, urls))) { - return result; - } - } - - return null; - } - - // Contents of Exception in various browsers. - // - // SAFARI: - // ex.message = Can't find variable: qq - // ex.line = 59 - // ex.sourceId = 580238192 - // ex.sourceURL = http://... - // ex.expressionBeginOffset = 96 - // ex.expressionCaretOffset = 98 - // ex.expressionEndOffset = 98 - // ex.name = ReferenceError - // - // FIREFOX: - // ex.message = qq is not defined - // ex.fileName = http://... - // ex.lineNumber = 59 - // ex.columnNumber = 69 - // ex.stack = ...stack trace... (see the example below) - // ex.name = ReferenceError - // - // CHROME: - // ex.message = qq is not defined - // ex.name = ReferenceError - // ex.type = not_defined - // ex.arguments = ['aa'] - // ex.stack = ...stack trace... - // - // INTERNET EXPLORER: - // ex.message = ... - // ex.name = ReferenceError - // - // OPERA: - // ex.message = ...message... (see the example below) - // ex.name = ReferenceError - // ex.opera#sourceloc = 11 (pretty much useless, duplicates the info in ex.message) - // ex.stacktrace = n/a; see 'opera:config#UserPrefs|Exceptions Have Stacktrace' - - /** - * Computes stack trace information from the stack property. - * Chrome and Gecko use this property. - * @param {Error} ex - * @return {?Object.} Stack trace information. - */ - function computeStackTraceFromStackProp(ex) { - if (isUndefined(ex.stack) || !ex.stack) return; - - var chrome = /^\s*at (.*?) ?\(((?:file|https?|blob|chrome-extension|native|eval|).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i, - gecko = /^\s*(.*?)(?:\((.*?)\))?(?:^|@)((?:file|https?|blob|chrome|\[native).*?)(?::(\d+))?(?::(\d+))?\s*$/i, - winjs = /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:ms-appx|https?|blob):.*?):(\d+)(?::(\d+))?\)?\s*$/i, - lines = ex.stack.split('\n'), - stack = [], - parts, - element, - reference = /^(.*) is undefined$/.exec(ex.message); - - for (var i = 0, j = lines.length; i < j; ++i) { - if ((parts = chrome.exec(lines[i]))) { - var isNative = parts[2] && parts[2].indexOf('native') !== -1; - element = { - 'url': !isNative ? parts[2] : null, - 'func': parts[1] || UNKNOWN_FUNCTION, - 'args': isNative ? [parts[2]] : [], - 'line': parts[3] ? +parts[3] : null, - 'column': parts[4] ? +parts[4] : null - }; - } else if ( parts = winjs.exec(lines[i]) ) { - element = { - 'url': parts[2], - 'func': parts[1] || UNKNOWN_FUNCTION, - 'args': [], - 'line': +parts[3], - 'column': parts[4] ? +parts[4] : null - }; - } else if ((parts = gecko.exec(lines[i]))) { - element = { - 'url': parts[3], - 'func': parts[1] || UNKNOWN_FUNCTION, - 'args': parts[2] ? parts[2].split(',') : [], - 'line': parts[4] ? +parts[4] : null, - 'column': parts[5] ? +parts[5] : null - }; - } else { - continue; - } - - if (!element.func && element.line) { - element.func = guessFunctionName(element.url, element.line); - } - - if (element.line) { - element.context = gatherContext(element.url, element.line); - } - - stack.push(element); - } - - if (!stack.length) { - return null; - } - - if (stack[0].line && !stack[0].column && reference) { - stack[0].column = findSourceInLine(reference[1], stack[0].url, stack[0].line); - } else if (!stack[0].column && !isUndefined(ex.columnNumber)) { - // FireFox uses this awesome columnNumber property for its top frame - // Also note, Firefox's column number is 0-based and everything else expects 1-based, - // so adding 1 - stack[0].column = ex.columnNumber + 1; - } - - return { - 'name': ex.name, - 'message': ex.message, - 'url': getLocationHref(), - 'stack': stack - }; - } - - /** - * Computes stack trace information from the stacktrace property. - * Opera 10 uses this property. - * @param {Error} ex - * @return {?Object.} Stack trace information. - */ - function computeStackTraceFromStacktraceProp(ex) { - // Access and store the stacktrace property before doing ANYTHING - // else to it because Opera is not very good at providing it - // reliably in other circumstances. - var stacktrace = ex.stacktrace; - if (isUndefined(ex.stacktrace) || !ex.stacktrace) return; - - var opera10Regex = / line (\d+).*script (?:in )?(\S+)(?:: in function (\S+))?$/i, - opera11Regex = / line (\d+), column (\d+)\s*(?:in (?:]+)>|([^\)]+))\((.*)\))? in (.*):\s*$/i, - lines = stacktrace.split('\n'), - stack = [], - parts; - - for (var line = 0; line < lines.length; line += 2) { - var element = null; - if ((parts = opera10Regex.exec(lines[line]))) { - element = { - 'url': parts[2], - 'line': +parts[1], - 'column': null, - 'func': parts[3], - 'args':[] - }; - } else if ((parts = opera11Regex.exec(lines[line]))) { - element = { - 'url': parts[6], - 'line': +parts[1], - 'column': +parts[2], - 'func': parts[3] || parts[4], - 'args': parts[5] ? parts[5].split(',') : [] - }; - } - - if (element) { - if (!element.func && element.line) { - element.func = guessFunctionName(element.url, element.line); - } - if (element.line) { - try { - element.context = gatherContext(element.url, element.line); - } catch (exc) {} - } - - if (!element.context) { - element.context = [lines[line + 1]]; - } - - stack.push(element); - } - } - - if (!stack.length) { - return null; - } - - return { - 'name': ex.name, - 'message': ex.message, - 'url': getLocationHref(), - 'stack': stack - }; - } - - /** - * NOT TESTED. - * Computes stack trace information from an error message that includes - * the stack trace. - * Opera 9 and earlier use this method if the option to show stack - * traces is turned on in opera:config. - * @param {Error} ex - * @return {?Object.} Stack information. - */ - function computeStackTraceFromOperaMultiLineMessage(ex) { - // Opera includes a stack trace into the exception message. An example is: - // - // Statement on line 3: Undefined variable: undefinedFunc - // Backtrace: - // Line 3 of linked script file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.js: In function zzz - // undefinedFunc(a); - // Line 7 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function yyy - // zzz(x, y, z); - // Line 3 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function xxx - // yyy(a, a, a); - // Line 1 of function script - // try { xxx('hi'); return false; } catch(ex) { TraceKit.report(ex); } - // ... - - var lines = ex.message.split('\n'); - if (lines.length < 4) { - return null; - } - - var lineRE1 = /^\s*Line (\d+) of linked script ((?:file|https?|blob)\S+)(?:: in function (\S+))?\s*$/i, - lineRE2 = /^\s*Line (\d+) of inline#(\d+) script in ((?:file|https?|blob)\S+)(?:: in function (\S+))?\s*$/i, - lineRE3 = /^\s*Line (\d+) of function script\s*$/i, - stack = [], - scripts = document.getElementsByTagName('script'), - inlineScriptBlocks = [], - parts; - - for (var s in scripts) { - if (hasKey(scripts, s) && !scripts[s].src) { - inlineScriptBlocks.push(scripts[s]); - } - } - - for (var line = 2; line < lines.length; line += 2) { - var item = null; - if ((parts = lineRE1.exec(lines[line]))) { - item = { - 'url': parts[2], - 'func': parts[3], - 'args': [], - 'line': +parts[1], - 'column': null - }; - } else if ((parts = lineRE2.exec(lines[line]))) { - item = { - 'url': parts[3], - 'func': parts[4], - 'args': [], - 'line': +parts[1], - 'column': null // TODO: Check to see if inline#1 (+parts[2]) points to the script number or column number. - }; - var relativeLine = (+parts[1]); // relative to the start of the