Commit 468a2ed6 authored by Filipa Lacerda's avatar Filipa Lacerda

[ci skip] Merge branch 'master' into 3776-ci-view-for-sast

* master: (28 commits)
  Merge branch '4984-broken-master-gitlab-git_access_wiki_spec' into 'master'
  fix broken specs
  remove common_d3 bundle
  remove graphs_show webpack bundle
  Chart.html.haml refactor (EE Version)
  Refactor and simplify the docs on 'include' for CI/CD
  [GH Import] Create an empty wiki if wiki import failed
  Resolve "group request membership mail with too long list of "To:""
  Fix a test that was hardcoding a documentation URL
  Add changelog entry
  Fix single digit value clipping
  Fix the ee-files-location-check job
  Extend Epics API - searching and sorting
  Increase feature flag cache TTL to one hour
  Convert Gitaly commit parent IDs to array as early as possible
  Clarify changelog for squash encoding fix
  Avoid slow File Lock checks when not used
  Fix squash rebase not working when diff contained encoded data
  Workaround for epic spec failure
  Don't attempt to update user tracked fields if database is in read-only
  ...
parents 69fbe24f 9b3f93a2
import Chart from 'vendor/Chart';
// export to global scope
window.Chart = Chart;
import Chart from 'vendor/Chart'; import Chart from 'chart.js';
import _ from 'underscore'; import _ from 'underscore';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
......
import flash from '../flash'; import flash from '~/flash';
import { __ } from '../locale'; import { __ } from '~/locale';
import axios from '../lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import ContributorsStatGraph from './stat_graph_contributors'; import ContributorsStatGraph from './stat_graph_contributors';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, camelcase, one-var-declaration-per-line, quotes, no-param-reassign, quote-props, comma-dangle, prefer-template, max-len, no-return-assign, no-shadow */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, camelcase, one-var-declaration-per-line, quotes, no-param-reassign, quote-props, comma-dangle, prefer-template, max-len, no-return-assign, no-shadow */
import _ from 'underscore'; import _ from 'underscore';
import { n__, s__, createDateTimeFormat, sprintf } from '~/locale';
import { ContributorsGraph, ContributorsAuthorGraph, ContributorsMasterGraph } from './stat_graph_contributors_graph'; import { ContributorsGraph, ContributorsAuthorGraph, ContributorsMasterGraph } from './stat_graph_contributors_graph';
import ContributorsStatGraphUtil from './stat_graph_contributors_util'; import ContributorsStatGraphUtil from './stat_graph_contributors_util';
import { n__, s__, createDateTimeFormat, sprintf } from '../locale';
export default (function() { export default (function() {
function ContributorsStatGraph() { function ContributorsStatGraph() {
......
...@@ -7,7 +7,7 @@ import { axisLeft, axisBottom } from 'd3-axis'; ...@@ -7,7 +7,7 @@ import { axisLeft, axisBottom } from 'd3-axis';
import { area } from 'd3-shape'; import { area } from 'd3-shape';
import { brushX } from 'd3-brush'; import { brushX } from 'd3-brush';
import { timeParse } from 'd3-time-format'; import { timeParse } from 'd3-time-format';
import { dateTickFormat } from '../lib/utils/tick_formats'; import { dateTickFormat } from '~/lib/utils/tick_formats';
const d3 = { extent, max, select, scaleTime, scaleLinear, axisLeft, axisBottom, area, brushX, timeParse }; const d3 = { extent, max, select, scaleTime, scaleLinear, axisLeft, axisBottom, area, brushX, timeParse };
......
import Chart from 'vendor/Chart'; import Chart from 'chart.js';
const options = { const options = {
scaleOverlay: true, scaleOverlay: true,
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
.status-neutral, .status-neutral,
.status-red, { .status-red, {
height: 100%; height: 100%;
min-width: 25px; min-width: 30px;
padding: 0 5px; padding: 0 5px;
font-size: $tooltip-font-size; font-size: $tooltip-font-size;
font-weight: normal; font-weight: normal;
......
...@@ -7,18 +7,11 @@ module Emails ...@@ -7,18 +7,11 @@ module Emails
helper_method :member_source, :member helper_method :member_source, :member
end end
def member_access_requested_email(member_source_type, member_id) def member_access_requested_email(member_source_type, member_id, recipient_notification_email)
@member_source_type = member_source_type @member_source_type = member_source_type
@member_id = member_id @member_id = member_id
admins = member_source.members.owners_and_masters.pluck(:notification_email) mail(to: recipient_notification_email,
# A project in a group can have no explicit owners/masters, in that case
# we fallbacks to the group's owners/masters.
if admins.empty? && member_source.respond_to?(:group) && member_source.group
admins = member_source.group.members.owners_and_masters.pluck(:notification_email)
end
mail(to: admins,
subject: subject("Request to join the #{member_source.human_name} #{member_source.model_name.singular}")) subject: subject("Request to join the #{member_source.human_name} #{member_source.model_name.singular}"))
end end
......
...@@ -417,6 +417,10 @@ class Commit ...@@ -417,6 +417,10 @@ class Commit
!!(title =~ WIP_REGEX) !!(title =~ WIP_REGEX)
end end
def merged_merge_request?(user)
!!merged_merge_request(user)
end
private private
def commit_reference(from, referable_commit_id, full: false) def commit_reference(from, referable_commit_id, full: false)
...@@ -445,10 +449,6 @@ class Commit ...@@ -445,10 +449,6 @@ class Commit
changes changes
end end
def merged_merge_request?(user)
!!merged_merge_request(user)
end
def merged_merge_request_no_cache(user) def merged_merge_request_no_cache(user)
MergeRequestsFinder.new(user, project_id: project.id).find_by(merge_commit_sha: id) if merge_commit? MergeRequestsFinder.new(user, project_id: project.id).find_by(merge_commit_sha: id) if merge_commit?
end end
......
...@@ -61,6 +61,8 @@ class User < ActiveRecord::Base ...@@ -61,6 +61,8 @@ class User < ActiveRecord::Base
# Override Devise::Models::Trackable#update_tracked_fields! # Override Devise::Models::Trackable#update_tracked_fields!
# to limit database writes to at most once every hour # to limit database writes to at most once every hour
def update_tracked_fields!(request) def update_tracked_fields!(request)
return if Gitlab::Database.read_only?
update_tracked_fields(request) update_tracked_fields(request)
lease = Gitlab::ExclusiveLease.new("user_update_tracked_fields:#{id}", timeout: 1.hour.to_i) lease = Gitlab::ExclusiveLease.new("user_update_tracked_fields:#{id}", timeout: 1.hour.to_i)
......
...@@ -231,7 +231,12 @@ class NotificationService ...@@ -231,7 +231,12 @@ class NotificationService
def new_access_request(member) def new_access_request(member)
return true unless member.notifiable?(:subscription) return true unless member.notifiable?(:subscription)
mailer.member_access_requested_email(member.real_source_type, member.id).deliver_later recipients = member.source.members.owners_and_masters
if fallback_to_group_owners_masters?(recipients, member)
recipients = member.source.group.members.owners_and_masters
end
recipients.each { |recipient| deliver_access_request_email(recipient, member) }
end end
def decline_access_request(member) def decline_access_request(member)
...@@ -482,4 +487,14 @@ class NotificationService ...@@ -482,4 +487,14 @@ class NotificationService
def notifiable_users(*args) def notifiable_users(*args)
NotificationRecipientService.notifiable_users(*args) NotificationRecipientService.notifiable_users(*args)
end end
def deliver_access_request_email(recipient, member)
mailer.member_access_requested_email(member.real_source_type, member.id, recipient.user.notification_email).deliver_later
end
def fallback_to_group_owners_masters?(recipients, member)
return false if recipients.present?
member.source.respond_to?(:group) && member.source.group
end
end end
- if inject_u2f_api? - if inject_u2f_api?
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('u2f') = webpack_bundle_tag('u2f')
%div %div
= render 'devise/shared/tab_single', tab_title: 'Two-Factor Authentication' = render 'devise/shared/tab_single', tab_title: 'Two-Factor Authentication'
......
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('profile') = webpack_bundle_tag('profile')
...@@ -6,8 +6,8 @@ ...@@ -6,8 +6,8 @@
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
- if inject_u2f_api? - if inject_u2f_api?
= page_specific_javascript_bundle_tag('u2f') = webpack_bundle_tag('u2f')
= page_specific_javascript_bundle_tag('two_factor_auth') = webpack_bundle_tag('two_factor_auth')
.js-two-factor-auth{ 'data-two-factor-skippable' => "#{two_factor_skippable?}", 'data-two_factor_skip_url' => skip_profile_two_factor_auth_path } .js-two-factor-auth{ 'data-two-factor-skippable' => "#{two_factor_skippable?}", 'data-two_factor_skip_url' => skip_profile_two_factor_auth_path }
.row.prepend-top-default .row.prepend-top-default
......
...@@ -29,4 +29,4 @@ ...@@ -29,4 +29,4 @@
= commit_in_fork_help = commit_in_fork_help
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('blob') = webpack_bundle_tag('blob')
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
- page_title "Edit", @blob.path, @ref - page_title "Edit", @blob.path, @ref
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_tag('lib/ace.js') = page_specific_javascript_tag('lib/ace.js')
= page_specific_javascript_bundle_tag('blob') = webpack_bundle_tag('blob')
%div{ class: container_class } %div{ class: container_class }
- if @conflict - if @conflict
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
- page_title "New File", @path.presence, @ref - page_title "New File", @path.presence, @ref
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_tag('lib/ace.js') = page_specific_javascript_tag('lib/ace.js')
= page_specific_javascript_bundle_tag('blob') = webpack_bundle_tag('blob')
.editor-title-row .editor-title-row
%h3.page-title.blob-new-page-title %h3.page-title.blob-new-page-title
New file New file
......
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('balsamiq_viewer') = webpack_bundle_tag('balsamiq_viewer')
.file-content.balsamiq-viewer#js-balsamiq-viewer{ data: { endpoint: blob_raw_path } } .file-content.balsamiq-viewer#js-balsamiq-viewer{ data: { endpoint: blob_raw_path } }
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_vue') = webpack_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('notebook_viewer') = webpack_bundle_tag('notebook_viewer')
.file-content#js-notebook-viewer{ data: { endpoint: blob_raw_path } } .file-content#js-notebook-viewer{ data: { endpoint: blob_raw_path } }
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_vue') = webpack_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('pdf_viewer') = webpack_bundle_tag('pdf_viewer')
.file-content#js-pdf-viewer{ data: { endpoint: blob_raw_path } } .file-content#js-pdf-viewer{ data: { endpoint: blob_raw_path } }
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_vue') = webpack_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('sketch_viewer') = webpack_bundle_tag('sketch_viewer')
.file-content#js-sketch-viewer{ data: { endpoint: blob_raw_path } } .file-content#js-sketch-viewer{ data: { endpoint: blob_raw_path } }
.js-loading-icon.text-center.prepend-top-default.append-bottom-default.js-loading-icon{ 'aria-label' => 'Loading Sketch preview' } .js-loading-icon.text-center.prepend-top-default.append-bottom-default.js-loading-icon{ 'aria-label' => 'Loading Sketch preview' }
......
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('stl_viewer') = webpack_bundle_tag('stl_viewer')
.file-content.is-stl-loading .file-content.is-stl-loading
.text-center#js-stl-viewer{ data: { endpoint: blob_raw_path } } .text-center#js-stl-viewer{ data: { endpoint: blob_raw_path } }
......
...@@ -8,5 +8,5 @@ ...@@ -8,5 +8,5 @@
} } } }
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_vue') = webpack_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('commit_pipelines') = webpack_bundle_tag('commit_pipelines')
...@@ -7,8 +7,8 @@ ...@@ -7,8 +7,8 @@
- page_title "#{@commit.title} (#{@commit.short_id})", "Commits" - page_title "#{@commit.title} (#{@commit.short_id})", "Commits"
- page_description @commit.description - page_description @commit.description
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_vue') = webpack_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('diff_notes') = webpack_bundle_tag('diff_notes')
.container-fluid{ class: [limited_container_width, container_class] } .container-fluid{ class: [limited_container_width, container_class] }
= render "commit_box" = render "commit_box"
......
- @no_container = true - @no_container = true
- page_title "Cycle Analytics" - page_title "Cycle Analytics"
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_vue') = webpack_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('cycle_analytics') = webpack_bundle_tag('cycle_analytics')
#cycle-analytics{ class: container_class, "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project) } } #cycle-analytics{ class: container_class, "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project) } }
- if @cycle_analytics_no_data - if @cycle_analytics_no_data
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
- page_title "Environments" - page_title "Environments"
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_vue') = webpack_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag("environments_folder") = webpack_bundle_tag("environments_folder")
#environments-folder-list-view{ data: { endpoint: folder_project_environments_path(@project, @folder, format: :json), #environments-folder-list-view{ data: { endpoint: folder_project_environments_path(@project, @folder, format: :json),
"folder-name" => @folder, "folder-name" => @folder,
......
...@@ -3,8 +3,8 @@ ...@@ -3,8 +3,8 @@
- add_to_breadcrumbs("Pipelines", project_pipelines_path(@project)) - add_to_breadcrumbs("Pipelines", project_pipelines_path(@project))
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag("common_vue") = webpack_bundle_tag("common_vue")
= page_specific_javascript_bundle_tag("environments") = webpack_bundle_tag("environments")
#environments-list-view{ data: { environments_data: environments_list_data, #environments-list-view{ data: { environments_data: environments_list_data,
"can-create-deployment" => can?(current_user, :create_deployment, @project).to_s, "can-create-deployment" => can?(current_user, :create_deployment, @project).to_s,
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
- page_title "Metrics for environment", @environment.name - page_title "Metrics for environment", @environment.name
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= webpack_bundle_tag 'common_vue' = webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'common_d3'
.prometheus-container{ class: container_class } .prometheus-container{ class: container_class }
.top-area .top-area
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= stylesheet_link_tag "xterm/xterm" = stylesheet_link_tag "xterm/xterm"
= page_specific_javascript_bundle_tag("terminal") = webpack_bundle_tag("terminal")
%div{ class: container_class } %div{ class: container_class }
.top-area .top-area
......
- @no_container = true - @no_container = true
- page_title "Charts" - page_title "Charts"
- content_for :page_specific_javascripts do
= webpack_bundle_tag('common_d3')
= webpack_bundle_tag('graphs')
= webpack_bundle_tag('graphs_charts')
.repo-charts{ class: container_class } .repo-charts{ class: container_class }
%h4.sub-header %h4.sub-header
......
- @no_container = true - @no_container = true
- page_title _('Contributors') - page_title _('Contributors')
- content_for :page_specific_javascripts do
= webpack_bundle_tag('common_d3')
= webpack_bundle_tag('graphs')
= webpack_bundle_tag('graphs_show')
.js-graphs-show{ class: container_class, 'data-project-graph-path': project_graph_path(@project, current_ref, format: :json) } .js-graphs-show{ class: container_class, 'data-project-graph-path': project_graph_path(@project, current_ref, format: :json) }
.sub-header-block .sub-header-block
......
...@@ -100,5 +100,5 @@ ...@@ -100,5 +100,5 @@
= render 'shared/issuable/sidebar', issuable: @issue = render 'shared/issuable/sidebar', issuable: @issue
= page_specific_javascript_bundle_tag('common_vue') = webpack_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('issue_show') = webpack_bundle_tag('issue_show')
- page_title "Merge Conflicts", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests" - page_title "Merge Conflicts", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests"
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_vue') = webpack_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('merge_conflicts') = webpack_bundle_tag('merge_conflicts')
= page_specific_javascript_tag('lib/ace.js') = page_specific_javascript_tag('lib/ace.js')
= render "projects/merge_requests/mr_title" = render "projects/merge_requests/mr_title"
......
- page_title "Merge Conflicts", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests" - page_title "Merge Conflicts", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests"
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_vue') = webpack_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('merge_conflicts') = webpack_bundle_tag('merge_conflicts')
= page_specific_javascript_tag('lib/ace.js') = page_specific_javascript_tag('lib/ace.js')
= render "projects/merge_requests/mr_title" = render "projects/merge_requests/mr_title"
......
- breadcrumb_title "Graph" - breadcrumb_title "Graph"
- page_title "Graph", @ref - page_title "Graph", @ref
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('network') = webpack_bundle_tag('network')
= render "head" = render "head"
%div{ class: container_class } %div{ class: container_class }
.project-network .project-network
......
...@@ -16,5 +16,5 @@ ...@@ -16,5 +16,5 @@
"ci-lint-path" => ci_lint_path, "ci-lint-path" => ci_lint_path,
"reset-cache-path" => reset_cache_project_settings_ci_cd_path(@project) } } "reset-cache-path" => reset_cache_project_settings_ci_cd_path(@project) } }
= page_specific_javascript_bundle_tag('common_vue') = webpack_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('pipelines') = webpack_bundle_tag('pipelines')
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('protected_branches') = webpack_bundle_tag('protected_branches')
- content_for :create_protected_branch do - content_for :create_protected_branch do
= render 'projects/protected_branches/create_protected_branch' = render 'projects/protected_branches/create_protected_branch'
......
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('protected_tags') = webpack_bundle_tag('protected_tags')
- content_for :create_protected_tag do - content_for :create_protected_tag do
= render 'projects/protected_tags/create_protected_tag' = render 'projects/protected_tags/create_protected_tag'
......
...@@ -14,8 +14,8 @@ ...@@ -14,8 +14,8 @@
.col-lg-12 .col-lg-12
#js-vue-registry-images{ data: { endpoint: project_container_registry_index_path(@project, format: :json) } } #js-vue-registry-images{ data: { endpoint: project_container_registry_index_path(@project, format: :json) } }
= page_specific_javascript_bundle_tag('common_vue') = webpack_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('registry_list') = webpack_bundle_tag('registry_list')
.row.prepend-top-10 .row.prepend-top-10
.col-lg-12 .col-lg-12
......
...@@ -3,8 +3,8 @@ ...@@ -3,8 +3,8 @@
- @content_class = "limit-container-width" unless fluid_layout - @content_class = "limit-container-width" unless fluid_layout
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_vue') = webpack_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('deploy_keys') = webpack_bundle_tag('deploy_keys')
= render "projects/push_rules/index" = render "projects/push_rules/index"
= render "projects/mirrors/show" = render "projects/mirrors/show"
......
- todo = issuable_todo(issuable) - todo = issuable_todo(issuable)
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_vue') = webpack_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('ee_sidebar') = webpack_bundle_tag('ee_sidebar')
%aside.right-sidebar.js-right-sidebar.js-issuable-sidebar{ data: { signed: { in: current_user.present? } }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' } %aside.right-sidebar.js-right-sidebar.js-issuable-sidebar{ data: { signed: { in: current_user.present? } }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' }
.issuable-sidebar{ data: { endpoint: "#{issuable_json_path(issuable)}" } } .issuable-sidebar{ data: { endpoint: "#{issuable_json_path(issuable)}" } }
......
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_tag('lib/ace.js') = page_specific_javascript_tag('lib/ace.js')
= page_specific_javascript_bundle_tag('snippet') = webpack_bundle_tag('snippet')
.snippet-form-holder .snippet-form-holder
= form_for @snippet, url: url, html: { class: "form-horizontal snippet-form js-requires-input js-quick-submit common-note-form" } do |f| = form_for @snippet, url: url, html: { class: "form-horizontal snippet-form js-requires-input js-quick-submit common-note-form" } do |f|
......
...@@ -23,27 +23,25 @@ class ProcessCommitWorker ...@@ -23,27 +23,25 @@ class ProcessCommitWorker
return unless user return unless user
commit = build_commit(project, commit_hash) commit = build_commit(project, commit_hash)
author = commit.author || user author = commit.author || user
process_commit_message(project, commit, user, author, default) process_commit_message(project, commit, user, author, default)
update_issue_metrics(commit, author) update_issue_metrics(commit, author)
end end
def process_commit_message(project, commit, user, author, default = false) def process_commit_message(project, commit, user, author, default = false)
closed_issues = default ? commit.closes_issues(user) : [] # this is a GitLab generated commit message, ignore it.
return if commit.merged_merge_request?(user)
unless closed_issues.empty? closed_issues = default ? commit.closes_issues(user) : []
close_issues(project, user, author, commit, closed_issues)
end
close_issues(project, user, author, commit, closed_issues) if closed_issues.any?
commit.create_cross_references!(author, closed_issues) commit.create_cross_references!(author, closed_issues)
end end
def close_issues(project, user, author, commit, issues) def close_issues(project, user, author, commit, issues)
# We don't want to run permission related queries for every single issue, # We don't want to run permission related queries for every single issue,
# therefor we use IssueCollection here and skip the authorization check in # therefore we use IssueCollection here and skip the authorization check in
# Issues::CloseService#execute. # Issues::CloseService#execute.
IssueCollection.new(issues).updatable_by_user(user).each do |issue| IssueCollection.new(issues).updatable_by_user(user).each do |issue|
Issues::CloseService.new(project, author) Issues::CloseService.new(project, author)
......
---
title: Add basic searching and sorting to Epics API
merge_request:
author:
type: added
---
title: Fix duplicate system notes when merging a merge request.
merge_request: 17035
author:
type: fixed
---
title: Fix long list of recipients on group request membership email
merge_request: 17121
author: Jacopo Beschi @jacopo-beschi
type: fixed
---
title: "[GitHub Import] Create an empty wiki if wiki import failed"
merge_request:
author:
type: fixed
---
title: Increase feature flag cache TTL to one hour
merge_request:
author:
type: performance
---
title: Fix single digit value clipping for stacked progress bar
merge_request: 17217
author:
type: fixed
---
title: Fix squash not working when diff contained non-ASCII data
merge_request:
author:
type: fixed
---
title: Don't attempt to update user tracked fields if database is in read-only
merge_request:
author:
type: fixed
...@@ -8,7 +8,7 @@ Flipper.configure do |config| ...@@ -8,7 +8,7 @@ Flipper.configure do |config|
cached_adapter = Flipper::Adapters::ActiveSupportCacheStore.new( cached_adapter = Flipper::Adapters::ActiveSupportCacheStore.new(
adapter, adapter,
Rails.cache, Rails.cache,
expires_in: 10.seconds) expires_in: 1.hour)
Flipper.new(cached_adapter) Flipper.new(cached_adapter)
end end
......
...@@ -67,9 +67,6 @@ var config = { ...@@ -67,9 +67,6 @@ var config = {
new_epic: 'ee/epics/new_epic/new_epic_bundle.js', new_epic: 'ee/epics/new_epic/new_epic_bundle.js',
filtered_search: './filtered_search/filtered_search_bundle.js', filtered_search: './filtered_search/filtered_search_bundle.js',
geo_nodes: 'ee/geo_nodes', geo_nodes: 'ee/geo_nodes',
graphs: './graphs/graphs_bundle.js',
graphs_charts: './graphs/graphs_charts.js',
graphs_show: './graphs/graphs_show.js',
help: './help/help.js', help: './help/help.js',
issuable: './issuable/issuable_bundle.js', issuable: './issuable/issuable_bundle.js',
issues: './issues/issues_bundle.js', issues: './issues/issues_bundle.js',
...@@ -296,21 +293,6 @@ var config = { ...@@ -296,21 +293,6 @@ var config = {
}, },
}), }),
// create cacheable common library bundle for all d3 chunks
new webpack.optimize.CommonsChunkPlugin({
name: 'common_d3',
chunks: [
'graphs',
'graphs_show',
'monitoring',
'users',
'burndown_chart', // EE
],
minChunks: function (module, count) {
return module.resource && /d3-/.test(module.resource);
},
}),
// create cacheable common library bundles // create cacheable common library bundles
new webpack.optimize.CommonsChunkPlugin({ new webpack.optimize.CommonsChunkPlugin({
names: ['main', 'common', 'webpack_runtime'], names: ['main', 'common', 'webpack_runtime'],
......
...@@ -16,11 +16,16 @@ Gets all epics of the requested group and its subgroups. ...@@ -16,11 +16,16 @@ Gets all epics of the requested group and its subgroups.
``` ```
GET /groups/:id/-/epics GET /groups/:id/-/epics
GET /groups/:id/-/epics?author_id=5
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| ------------------- | ---------------- | ---------- | ---------------------------------------------------------------------------------------| | ------------------- | ---------------- | ---------- | ---------------------------------------------------------------------------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user | | `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `author_id` | integer | no | Return epics created by the given user `id` |
| `order_by` | string | no | Return epics ordered by `created_at` or `updated_at` fields. Default is `created_at` |
| `sort` | string | no | Return epics sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Search epics against their `title` and `description` |
```bash ```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/1/-/epics curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/1/-/epics
...@@ -45,7 +50,9 @@ Example response: ...@@ -45,7 +50,9 @@ Example response:
"web_url": "http://localhost:3001/kam" "web_url": "http://localhost:3001/kam"
}, },
"start_date": null, "start_date": null,
"end_date": null "end_date": null,
"created_at": "2018-01-21T06:21:13.165Z",
"updated_at": "2018-01-22T12:41:41.166Z"
} }
] ]
``` ```
...@@ -85,7 +92,9 @@ Example response: ...@@ -85,7 +92,9 @@ Example response:
"web_url": "http://localhost:3001/arnita" "web_url": "http://localhost:3001/arnita"
}, },
"start_date": null, "start_date": null,
"end_date": null "end_date": null,
"created_at": "2018-01-21T06:21:13.165Z",
"updated_at": "2018-01-22T12:41:41.166Z"
} }
``` ```
...@@ -127,7 +136,9 @@ Example response: ...@@ -127,7 +136,9 @@ Example response:
"username" : "eileen.lowe" "username" : "eileen.lowe"
}, },
"start_date": null, "start_date": null,
"end_date": null "end_date": null,
"created_at": "2018-01-21T06:21:13.165Z",
"updated_at": "2018-01-22T12:41:41.166Z"
} }
``` ```
...@@ -170,7 +181,9 @@ Example response: ...@@ -170,7 +181,9 @@ Example response:
"username" : "eileen.lowe" "username" : "eileen.lowe"
}, },
"start_date": null, "start_date": null,
"end_date": null "end_date": null,
"created_at": "2018-01-21T06:21:13.165Z",
"updated_at": "2018-01-22T12:41:41.166Z"
} }
``` ```
......
...@@ -361,72 +361,82 @@ Additionally, if you have a job that unconditionally recreates the cache without ...@@ -361,72 +361,82 @@ Additionally, if you have a job that unconditionally recreates the cache without
reference to its previous contents, you can use `policy: push` in that job to reference to its previous contents, you can use `policy: push` in that job to
skip the download step. skip the download step.
### include (EEP) ### include
> Introduced in [GitLab Enterprise Edition Premium][ee] 10.5. > Introduced in [GitLab Edition Premium][ee] 10.5.
From 10.5 we can use `include` keyword to allow the inclusion of external YAML files. Using the `include` keyword, you can allow the inclusion of external YAML files.
In the following example, the content of`.before-script-template.yml` will be
automatically fetched and evaluated along with the content of `.gitlab-ci.yml`:
```yaml ```yaml
# Content of https://gitlab.com/awesome-project/raw/master/.before-script-template.yml # Content of https://gitlab.com/awesome-project/raw/master/.before-script-template.yml
before_script: before_script:
- apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs
- ruby -v
- which ruby
- gem install bundler --no-ri --no-rdoc - gem install bundler --no-ri --no-rdoc
- bundle install --jobs $(nproc) "${FLAGS[@]}" - bundle install --jobs $(nproc) "${FLAGS[@]}"
``` ```
```yaml ```yaml
# Content of .gitlab-ci.yml # Content of .gitlab-ci.yml
include: 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml' include: 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
rspec: rspec:
script: script:
- bundle exec rspec - bundle exec rspec
rubocop:
script:
- bundle exec rubocop
``` ```
In the above example `.before-script-template.yml` content will be automatically fetched and evaluated along with the content of `.gitlab-ci.yml`. You can define it either as a single string, or, in case you want to include
more than one files, an array of different values . The following examples
are both valid cases:
`include` supports two types of files: ```yaml
# Single string
- **local** to the same repository, referenced using the paths in the same the repository, i.e: include: '/templates/.after-script-template.yml'
```
```yaml ```yaml
# Within the repository # Array
include: '/templates/.gitlab-ci-template.yml'
include:
- 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
- '/templates/.after-script-template.yml'
``` ```
- **remote** in a different location, accessed using HTTP/HTTPS protocol, referenced using the full URL, i.e: ---
```yaml `include` supports two types of files:
include: 'https://gitlab.com/awesome-project/raw/master/.gitlab-ci-template.yml'
```
Also, `include` supports a single string or an array composed by different values, so - **local** to the same repository, referenced by using full paths in the same
repository, with `/` being the root directory. For example:
```yaml ```yaml
include: '/templates/.gitlab-ci-template.yml' # Within the repository
``` include: '/templates/.gitlab-ci-template.yml'
```
and NOTE: **Note:**
You can only use files that are currently tracked by Git on the same branch
your configuration file is. In other words, when using a **local file**, make
sure that both `.gitlab-ci.yml` and the local file are on the same branch.
```yaml - **remote** in a different location, accessed using HTTP/HTTPS, referenced
include: using the full URL. For example:
- 'https://gitlab.com/awesome-project/raw/master/.gitlab-ci-template.yml'
- '/templates/.gitlab-ci-template.yml'
```
are both valid use cases. ```yaml
include: 'https://gitlab.com/awesome-project/raw/master/.gitlab-ci-template.yml'
```
#### Restrictions ---
- We can only use files that are currently tracked by Git on the same branch your configuration file is. In other words, when using a **local file** make sure that both, `.gitlab-ci.yml` and the local file are on the same branch. Since external files defined by `include` are evaluated first, the content of
- Since external files defined on `include` are evaluated first, the content on `.gitlab-ci.yml` **will always take precedence over the content of the external files, no matters of the position of the `include` keyword, allowing to override values and functions with local definitions**, for example: `.gitlab-ci.yml` will always take precedence over the content of the external
files, no matter of the position of the `include` keyword. This allows you to
override values and functions with local definitions. For example:
```yaml ```yaml
# Content of https://company.com/autodevops-template.yml # Content of https://company.com/autodevops-template.yml
...@@ -434,32 +444,18 @@ are both valid use cases. ...@@ -434,32 +444,18 @@ are both valid use cases.
variables: variables:
POSTGRES_USER: user POSTGRES_USER: user
POSTGRES_PASSWORD: testing_password POSTGRES_PASSWORD: testing_password
POSTGRES_ENABLED: "true"
POSTGRES_DB: $CI_ENVIRONMENT_SLUG POSTGRES_DB: $CI_ENVIRONMENT_SLUG
KUBERNETES_VERSION: 1.8.6
HELM_VERSION: 2.6.1
CODECLIMATE_VERSION: 0.69.0
production: production:
stage: production stage: production
script: script:
- check_kube_domain
- install_dependencies - install_dependencies
- download_chart
- ensure_namespace
- install_tiller
- create_secret
- deploy - deploy
- delete canary
- persist_environment_url
environment: environment:
name: production name: production
url: http://$CI_PROJECT_PATH_SLUG.$AUTO_DEVOPS_DOMAIN url: https://$CI_PROJECT_PATH_SLUG.$AUTO_DEVOPS_DOMAIN
only: only:
refs: - master
- master
kubernetes: active
``` ```
```yaml ```yaml
...@@ -477,35 +473,24 @@ variables: ...@@ -477,35 +473,24 @@ variables:
stages: stages:
- build - build
- test - test
- review
- dast
- staging
- canary
- production - production
- performance
- cleanup
production: production:
stage: production stage: production
script: script:
- check_kube_domain
- install_dependencies - install_dependencies
- download_chart
- ensure_namespace
- install_tiller
- create_secret
- deploy - deploy
environment: environment:
name: production name: production
url: http://auto_devops_domain.com url: https://domain.com
only: only:
refs: - master
- master
# ....
``` ```
In this case, the variables `POSTGRES_USER`, `POSTGRES_PASSWORD` and `POSTGRES_DB` along with the `production` job defined on `autodevops-template.yml` will be overridden by the ones defined on `.gitlab-ci.yml`. In this case, the variables `POSTGRES_USER`, `POSTGRES_PASSWORD` and
`POSTGRES_DB` along with the `production` job defined in
`autodevops-template.yml` will be overridden by the ones defined in
`.gitlab-ci.yml`.
## Jobs ## Jobs
......
/* global Chart */ import Chart from 'chart.js';
export default () => { export default () => {
const dataEl = document.getElementById('js-analytics-data'); const dataEl = document.getElementById('js-analytics-data');
if (dataEl) { if (dataEl) {
......
...@@ -2,10 +2,6 @@ ...@@ -2,10 +2,6 @@
- header_title group_title(@group, "Contribution Analytics", group_analytics_path(@group)) - header_title group_title(@group, "Contribution Analytics", group_analytics_path(@group))
- if @group.feature_available?(:contribution_analytics) - if @group.feature_available?(:contribution_analytics)
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_d3')
= page_specific_javascript_bundle_tag('graphs')
.sub-header-block .sub-header-block
.pull-right .pull-right
.dropdown.inline .dropdown.inline
......
= content_for :sub_nav do
.scrolling-tabs-container.sub-nav-scroll
= render 'shared/nav_scroll'
.nav-links.sub-nav.scrolling-tabs
%ul{ class: (container_class) }
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_d3')
= page_specific_javascript_bundle_tag('graphs')
= nav_link(action: :show) do
= link_to 'Contributors', namespace_project_graph_path
= nav_link(action: :commits) do
= link_to 'Commits', commits_namespace_project_graph_path
= nav_link(action: :languages) do
= link_to 'Languages', languages_namespace_project_graph_path
- if @project.feature_available?(:builds, current_user)
= nav_link(action: :ci) do
= link_to ci_namespace_project_graph_path do
Continuous Integration
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('ee_protected_branches') = webpack_bundle_tag 'ee_protected_branches'
- content_for :create_protected_branch do - content_for :create_protected_branch do
= render 'projects/protected_branches/ee/create_protected_branch' = render 'projects/protected_branches/ee/create_protected_branch'
......
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('ee_protected_tags') = webpack_bundle_tag 'ee_protected_tags'
- content_for :create_protected_tag do - content_for :create_protected_tag do
= render 'projects/protected_tags/ee/create_protected_tag' = render 'projects/protected_tags/ee/create_protected_tag'
......
...@@ -4,8 +4,7 @@ ...@@ -4,8 +4,7 @@
- warning = data_warning_for(burndown) - warning = data_warning_for(burndown)
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_d3') = webpack_bundle_tag 'burndown_chart'
= page_specific_javascript_bundle_tag('burndown_chart')
= warning = warning
......
...@@ -29,6 +29,14 @@ module API ...@@ -29,6 +29,14 @@ module API
def epic def epic
@epic ||= user_group.epics.find_by(iid: params[:epic_iid]) @epic ||= user_group.epics.find_by(iid: params[:epic_iid])
end end
def find_epics(args = {})
args = declared_params.merge(args)
epics = EpicsFinder.new(current_user, args).execute
epics.reorder(args[:order_by] => args[:sort])
end
end end
params do params do
...@@ -39,8 +47,16 @@ module API ...@@ -39,8 +47,16 @@ module API
desc 'Get epics for the group' do desc 'Get epics for the group' do
success Entities::Epic success Entities::Epic
end end
params do
optional :order_by, type: String, values: %w[created_at updated_at], default: 'created_at',
desc: 'Return epics ordered by `created_at` or `updated_at` fields.'
optional :sort, type: String, values: %w[asc desc], default: 'desc',
desc: 'Return epics sorted in `asc` or `desc` order.'
optional :search, type: String, desc: 'Search epics for text present in the title or description'
optional :author_id, type: Integer, desc: 'Return epics which are authored by the user with the given ID'
end
get ':id/-/epics' do get ':id/-/epics' do
present EpicsFinder.new(current_user, group_id: user_group.id).execute, with: Entities::Epic present find_epics(group_id: user_group.id), with: Entities::Epic
end end
desc 'Get details of an epic' do desc 'Get details of an epic' do
......
...@@ -55,6 +55,10 @@ describe 'Epic Issues', :js do ...@@ -55,6 +55,10 @@ describe 'Epic Issues', :js do
def add_issues(references) def add_issues(references)
find('.related-issues-block h3.panel-title button').click find('.related-issues-block h3.panel-title button').click
find('.js-add-issuable-form-input').set(references) find('.js-add-issuable-form-input').set(references)
# When adding long references, for some reason the input gets stuck
# waiting for more text. Send a keystroke before clicking the button to
# get out of this mode.
find('.js-add-issuable-form-input').send_keys(:tab)
find('.js-add-issuable-form-add-button').click find('.js-add-issuable-form-add-button').click
wait_for_requests wait_for_requests
......
...@@ -19,7 +19,9 @@ ...@@ -19,7 +19,9 @@
"additionalProperties": false "additionalProperties": false
}, },
"start_date": { "type": ["string", "null"] }, "start_date": { "type": ["string", "null"] },
"end_date": { "type": ["string", "null"] } "end_date": { "type": ["string", "null"] },
"created_at": { "type": ["string", "null"] },
"updated_at": { "type": ["string", "null"] }
}, },
"required": [ "required": [
"id", "iid", "group_id", "title" "id", "iid", "group_id", "title"
......
...@@ -19,7 +19,9 @@ ...@@ -19,7 +19,9 @@
"description": { "type": ["string", "null"] }, "description": { "type": ["string", "null"] },
"author": { "type": ["object", "null"] }, "author": { "type": ["object", "null"] },
"start_date": { "type": ["string", "null"] }, "start_date": { "type": ["string", "null"] },
"end_date": { "type": ["string", "null"] } "end_date": { "type": ["string", "null"] },
"created_at": { "type": ["string", "null"] },
"updated_at": { "type": ["string", "null"] }
}, },
"additionalProperties": false "additionalProperties": false
}, },
......
...@@ -26,7 +26,7 @@ describe Gitlab::GitAccess do ...@@ -26,7 +26,7 @@ describe Gitlab::GitAccess do
it 'denies push access with primary present' do it 'denies push access with primary present' do
error_message = "You can't push code to a read-only GitLab instance. "\ error_message = "You can't push code to a read-only GitLab instance. "\
"Please use the Primary node URL: https://localhost:3000/gitlab/#{project.full_path}.git. Documentation: https://docs.gitlab.com/ee/gitlab-geo/using_a_geo_server.html" "Please use the Primary node URL: https://localhost:3000/gitlab/#{project.full_path}.git. Documentation: #{EE::Gitlab::GeoGitAccess::GEO_SERVER_DOCS_URL}"
primary_node = create(:geo_node, :primary, url: 'https://localhost:3000/gitlab') primary_node = create(:geo_node, :primary, url: 'https://localhost:3000/gitlab')
allow(Gitlab::Geo).to receive(:primary).and_return(primary_node) allow(Gitlab::Geo).to receive(:primary).and_return(primary_node)
......
...@@ -30,7 +30,7 @@ describe Gitlab::GitAccessWiki do ...@@ -30,7 +30,7 @@ describe Gitlab::GitAccessWiki do
it 'denies push access with primary present' do it 'denies push access with primary present' do
error_message = "You can't push code to a read-only GitLab instance. Please use the Primary node URL: "\ error_message = "You can't push code to a read-only GitLab instance. Please use the Primary node URL: "\
"https://localhost:3000/gitlab/#{project.full_path}.wiki.git. Documentation: https://docs.gitlab.com/ee/gitlab-geo/using_a_geo_server.html" "https://localhost:3000/gitlab/#{project.full_path}.wiki.git. Documentation: #{EE::Gitlab::GeoGitAccess::GEO_SERVER_DOCS_URL}"
primary_node = create(:geo_node, :primary, url: 'https://localhost:3000/gitlab') primary_node = create(:geo_node, :primary, url: 'https://localhost:3000/gitlab')
allow(Gitlab::Geo).to receive(:primary).and_return(primary_node) allow(Gitlab::Geo).to receive(:primary).and_return(primary_node)
......
...@@ -60,6 +60,77 @@ describe API::Epics do ...@@ -60,6 +60,77 @@ describe API::Epics do
expect(response).to match_response_schema('public_api/v4/epics', dir: 'ee') expect(response).to match_response_schema('public_api/v4/epics', dir: 'ee')
end end
end end
context 'with multiple epics' do
let(:user2) { create(:user) }
let!(:epic) do
create(:epic,
group: group,
created_at: 3.days.ago,
updated_at: 2.days.ago)
end
let!(:epic2) do
create(:epic,
author: user2,
group: group,
title: 'foo',
description: 'bar',
created_at: 2.days.ago,
updated_at: 3.days.ago)
end
before do
stub_licensed_features(epics: true)
end
def expect_array_response(expected)
items = json_response.map { |i| i['id'] }
expect(items).to eq(expected)
end
it 'returns epics authored by the given author id' do
get api(url, user), author_id: user2.id
expect_array_response([epic2.id])
end
it 'returns epics matching given search string for title' do
get api(url, user), search: epic2.title
expect_array_response([epic2.id])
end
it 'returns epics matching given search string for description' do
get api(url, user), search: epic2.description
expect_array_response([epic2.id])
end
it 'sorts by created_at descending by default' do
get api(url, user)
expect_array_response([epic2.id, epic.id])
end
it 'sorts ascending when requested' do
get api(url, user), sort: :asc
expect_array_response([epic.id, epic2.id])
end
it 'sorts by updated_at descending when requested' do
get api(url, user), order_by: :updated_at
expect_array_response([epic.id, epic2.id])
end
it 'sorts by updated_at ascending when requested' do
get api(url, user), order_by: :updated_at, sort: :asc
expect_array_response([epic2.id, epic.id])
end
end
end end
describe 'GET /groups/:id/-/epics/:epic_iid' do describe 'GET /groups/:id/-/epics/:epic_iid' do
......
...@@ -506,6 +506,8 @@ module API ...@@ -506,6 +506,8 @@ module API
expose :author, using: Entities::UserBasic expose :author, using: Entities::UserBasic
expose :start_date expose :start_date
expose :end_date expose :end_date
expose :created_at
expose :updated_at
end end
class EpicIssue < Issue class EpicIssue < Issue
......
...@@ -508,7 +508,7 @@ module Gitlab ...@@ -508,7 +508,7 @@ module Gitlab
@committed_date = Time.at(commit.committer.date.seconds).utc @committed_date = Time.at(commit.committer.date.seconds).utc
@committer_name = commit.committer.name.dup @committer_name = commit.committer.name.dup
@committer_email = commit.committer.email.dup @committer_email = commit.committer.email.dup
@parent_ids = commit.parent_ids @parent_ids = Array(commit.parent_ids)
end end
def serialize_keys def serialize_keys
......
...@@ -2195,6 +2195,7 @@ module Gitlab ...@@ -2195,6 +2195,7 @@ module Gitlab
# Apply diff of the `diff_range` to the worktree # Apply diff of the `diff_range` to the worktree
diff = run_git!(%W(diff --binary #{diff_range})) diff = run_git!(%W(diff --binary #{diff_range}))
run_git!(%w(apply --index), chdir: squash_path, env: env) do |stdin| run_git!(%w(apply --index), chdir: squash_path, env: env) do |stdin|
stdin.binmode
stdin.write(diff) stdin.write(diff)
end end
......
...@@ -63,6 +63,7 @@ module Gitlab ...@@ -63,6 +63,7 @@ module Gitlab
true true
rescue Gitlab::Shell::Error => e rescue Gitlab::Shell::Error => e
if e.message !~ /repository not exported/ if e.message !~ /repository not exported/
project.create_wiki
fail_import("Failed to import the wiki: #{e.message}") fail_import("Failed to import the wiki: #{e.message}")
else else
true true
......
...@@ -4,18 +4,6 @@ module QA ...@@ -4,18 +4,6 @@ module QA
class SecretVariable < Factory::Base class SecretVariable < Factory::Base
attr_accessor :key, :value attr_accessor :key, :value
product :key do
Page::Project::Settings::CICD.act do
expand_secret_variables(&:variable_key)
end
end
product :value do
Page::Project::Settings::CICD.act do
expand_secret_variables(&:variable_value)
end
end
dependency Factory::Resource::Project, as: :project do |project| dependency Factory::Resource::Project, as: :project do |project|
project.name = 'project-with-secret-variables' project.name = 'project-with-secret-variables'
project.description = 'project for adding secret variable test' project.description = 'project for adding secret variable test'
......
...@@ -98,6 +98,10 @@ module QA ...@@ -98,6 +98,10 @@ module QA
views.map(&:errors).flatten views.map(&:errors).flatten
end end
def self.elements
views.map(&:elements).flatten
end
class DSL class DSL
attr_reader :views attr_reader :views
......
...@@ -6,39 +6,37 @@ module QA ...@@ -6,39 +6,37 @@ module QA
include Common include Common
view 'app/views/ci/variables/_variable_row.html.haml' do view 'app/views/ci/variables/_variable_row.html.haml' do
element :variable_row, '.ci-variable-row-body'
element :variable_key, '.js-ci-variable-input-key' element :variable_key, '.js-ci-variable-input-key'
element :variable_value, '.js-ci-variable-input-value' element :variable_value, '.js-ci-variable-input-value'
element :key_placeholder, 'Input variable key'
element :value_placeholder, 'Input variable value'
end end
view 'app/views/ci/variables/_index.html.haml' do view 'app/views/ci/variables/_index.html.haml' do
element :save_variables, '.js-secret-variables-save-button' element :save_variables, '.js-secret-variables-save-button'
element :reveal_values, '.js-secret-value-reveal-button'
end end
def fill_variable_key(key) def fill_variable_key(key)
page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do fill_in('Input variable key', with: key, match: :first)
page.find('.js-ci-variable-input-key').set(key)
end
end end
def fill_variable_value(value) def fill_variable_value(value)
page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do fill_in('Input variable value', with: value, match: :first)
page.find('.js-ci-variable-input-value').set(value)
end
end end
def save_variables def save_variables
click_button('Save variables') find('.js-secret-variables-save-button').click
end end
def variable_key def reveal_variables
page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do find('.js-secret-value-reveal-button').click
page.find('.js-ci-variable-input-key').value
end
end end
def variable_value def variable_value(key)
page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do within('.ci-variable-row-body', text: key) do
page.find('.js-ci-variable-input-value').value find('.js-ci-variable-input-value').value
end end
end end
end end
......
...@@ -4,16 +4,21 @@ module QA ...@@ -4,16 +4,21 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login) Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials } Page::Main::Login.act { sign_in_using_credentials }
variable_key = 'VARIABLE_KEY' Factory::Resource::SecretVariable.fabricate! do |resource|
variable_value = 'variable value' resource.key = 'VARIABLE_KEY'
resource.value = 'some secret variable'
variable = Factory::Resource::SecretVariable.fabricate! do |resource|
resource.key = variable_key
resource.value = variable_value
end end
expect(variable.key).to eq(variable_key) Page::Project::Settings::CICD.perform do |settings|
expect(variable.value).to eq(variable_value) settings.expand_secret_variables do |page|
expect(page).to have_field(with: 'VARIABLE_KEY')
expect(page).not_to have_field(with: 'some secret variable')
page.reveal_variables
expect(page).to have_field(with: 'some secret variable')
end
end
end end
end end
end end
...@@ -14,7 +14,7 @@ describe QA::Page::Base do ...@@ -14,7 +14,7 @@ describe QA::Page::Base do
end end
view 'path/to/some/_partial.html.haml' do view 'path/to/some/_partial.html.haml' do
element :something, 'string pattern' element :another_element, 'string pattern'
end end
end end
end end
...@@ -25,11 +25,10 @@ describe QA::Page::Base do ...@@ -25,11 +25,10 @@ describe QA::Page::Base do
end end
it 'populates views objects with data about elements' do it 'populates views objects with data about elements' do
subject.views.first.elements.tap do |elements| expect(subject.elements.size).to eq 3
expect(elements.size).to eq 2 expect(subject.elements).to all(be_an_instance_of QA::Page::Element)
expect(elements).to all(be_an_instance_of QA::Page::Element) expect(subject.elements.map(&:name))
expect(elements.map(&:name)).to eq [:something, :something_else] .to eq [:something, :something_else, :another_element]
end
end end
end end
......
...@@ -13,11 +13,21 @@ WHITELIST = %w[ ...@@ -13,11 +13,21 @@ WHITELIST = %w[
`git remote add canonical-ee https://gitlab.com/gitlab-org/gitlab-ee.git` `git remote add canonical-ee https://gitlab.com/gitlab-org/gitlab-ee.git`
`git remote add canonical-ce https://gitlab.com/gitlab-org/gitlab-ce.git` `git remote add canonical-ce https://gitlab.com/gitlab-org/gitlab-ce.git`
`git fetch canonical-ee master --quiet` `git fetch canonical-ee master --quiet`
`git fetch canonical-ce master --quiet`
new_files_in_this_branch_not_at_the_ee_top_level = new_files_in_this_branch_not_at_the_ee_top_level =
`git diff canonical-ee/master...HEAD --name-status --diff-filter=A -- ./ ':!ee' | cut -f2`.lines.map(&:strip) `git diff canonical-ee/master...HEAD --name-status --diff-filter=A -- ./ ':!ee' | cut -f2`.lines.map(&:strip)
current_branch = ENV.fetch('CI_COMMIT_REF_NAME', `git rev-parse --abbrev-ref HEAD`).strip
ce_branch_name = current_branch.sub(/(\Aee\-|\-ee\z)/, '')
`git fetch canonical-ce #{ce_branch_name} --quiet`
unless $?.success?
`git fetch canonical-ce 'master' --quiet`
end
ee_specific_files_in_ce_master_not_at_the_ee_top_level = ee_specific_files_in_ce_master_not_at_the_ee_top_level =
`git diff canonical-ce/master...HEAD --name-status --diff-filter=A -- ./ ':!ee' | cut -f2`.lines.map(&:strip) `git diff FETCH_HEAD..HEAD --name-status --diff-filter=A -- ./ ':!ee' | cut -f2`.lines.map(&:strip)
new_ee_specific_files_not_at_the_ee_top_level = new_ee_specific_files_not_at_the_ee_top_level =
new_files_in_this_branch_not_at_the_ee_top_level & ee_specific_files_in_ce_master_not_at_the_ee_top_level new_files_in_this_branch_not_at_the_ee_top_level & ee_specific_files_in_ce_master_not_at_the_ee_top_level
......
/* eslint-disable quotes, jasmine/no-suite-dupes, vars-on-top, no-var */ /* eslint-disable quotes, jasmine/no-suite-dupes, vars-on-top, no-var */
import { scaleLinear, scaleTime } from 'd3-scale'; import { scaleLinear, scaleTime } from 'd3-scale';
import { timeParse } from 'd3-time-format'; import { timeParse } from 'd3-time-format';
import { ContributorsGraph, ContributorsMasterGraph } from '~/graphs/stat_graph_contributors_graph'; import { ContributorsGraph, ContributorsMasterGraph } from '~/pages/projects/graphs/show/stat_graph_contributors_graph';
const d3 = { scaleLinear, scaleTime, timeParse }; const d3 = { scaleLinear, scaleTime, timeParse };
......
import ContributorsStatGraph from '~/graphs/stat_graph_contributors'; import ContributorsStatGraph from '~/pages/projects/graphs/show/stat_graph_contributors';
import { ContributorsGraph } from '~/graphs/stat_graph_contributors_graph'; import { ContributorsGraph } from '~/pages/projects/graphs/show/stat_graph_contributors_graph';
import { setLanguage } from '../helpers/locale_helper'; import { setLanguage } from '../helpers/locale_helper';
......
/* eslint-disable quotes, no-var, camelcase, object-property-newline, comma-dangle, max-len, vars-on-top, quote-props */ /* eslint-disable quotes, no-var, camelcase, object-property-newline, comma-dangle, max-len, vars-on-top, quote-props */
import ContributorsStatGraphUtil from '~/graphs/stat_graph_contributors_util'; import ContributorsStatGraphUtil from '~/pages/projects/graphs/show/stat_graph_contributors_util';
describe("ContributorsStatGraphUtil", function () { describe("ContributorsStatGraphUtil", function () {
describe("#parse_log", function () { describe("#parse_log", function () {
......
...@@ -221,7 +221,7 @@ describe Gitlab::Checks::ChangeAccess do ...@@ -221,7 +221,7 @@ describe Gitlab::Checks::ChangeAccess do
context 'with LFS not enabled' do context 'with LFS not enabled' do
it 'skips the validation' do it 'skips the validation' do
expect_any_instance_of(described_class).not_to receive(:lfs_file_locks_validation) expect_any_instance_of(Gitlab::Checks::CommitCheck).not_to receive(:validate)
subject.exec subject.exec
end end
...@@ -238,7 +238,7 @@ describe Gitlab::Checks::ChangeAccess do ...@@ -238,7 +238,7 @@ describe Gitlab::Checks::ChangeAccess do
end end
end end
context 'when change is sent by the author od the lock' do context 'when change is sent by the author of the lock' do
let(:user) { owner } let(:user) { owner }
it "doesn't raise any error" do it "doesn't raise any error" do
......
# coding: utf-8
require "spec_helper" require "spec_helper"
describe Gitlab::Git::Repository, seed_helper: true do describe Gitlab::Git::Repository, seed_helper: true do
...@@ -2221,6 +2222,17 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -2221,6 +2222,17 @@ describe Gitlab::Git::Repository, seed_helper: true do
subject subject
end end
end end
context 'with an ASCII-8BIT diff', :skip_gitaly_mock do
let(:diff) { "diff --git a/README.md b/README.md\nindex faaf198..43c5edf 100644\n--- a/README.md\n+++ b/README.md\n@@ -1,4 +1,4 @@\n-testme\n+✓ testme\n ======\n \n Sample repo for testing gitlab features\n" }
it 'applies a ASCII-8BIT diff' do
allow(repository).to receive(:run_git!).and_call_original
allow(repository).to receive(:run_git!).with(%W(diff --binary #{start_sha}...#{end_sha})).and_return(diff.force_encoding('ASCII-8BIT'))
expect(subject.length).to eq(40)
end
end
end end
end end
......
...@@ -11,7 +11,8 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do ...@@ -11,7 +11,8 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do
import_source: 'foo/bar', import_source: 'foo/bar',
repository_storage_path: 'foo', repository_storage_path: 'foo',
disk_path: 'foo', disk_path: 'foo',
repository: repository repository: repository,
create_wiki: true
) )
end end
...@@ -192,7 +193,7 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do ...@@ -192,7 +193,7 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do
expect(importer.import_wiki_repository).to eq(true) expect(importer.import_wiki_repository).to eq(true)
end end
it 'marks the import as failed if an error was raised' do it 'marks the import as failed and creates an empty repo if an error was raised' do
expect(importer.gitlab_shell) expect(importer.gitlab_shell)
.to receive(:import_repository) .to receive(:import_repository)
.and_raise(Gitlab::Shell::Error) .and_raise(Gitlab::Shell::Error)
...@@ -201,6 +202,9 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do ...@@ -201,6 +202,9 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do
.to receive(:fail_import) .to receive(:fail_import)
.and_return(false) .and_return(false)
expect(project)
.to receive(:create_wiki)
expect(importer.import_wiki_repository).to eq(false) expect(importer.import_wiki_repository).to eq(false)
end end
end end
......
...@@ -578,59 +578,30 @@ describe Notify do ...@@ -578,59 +578,30 @@ describe Notify do
end end
describe 'project access requested' do describe 'project access requested' do
context 'for a project in a user namespace' do let(:project) do
let(:project) do create(:project, :public, :access_requestable) do |project|
create(:project, :public, :access_requestable) do |project| project.add_master(project.owner)
project.add_master(project.owner, current_user: project.owner)
end
end
let(:project_member) do
project.request_access(user)
project.requesters.find_by(user_id: user.id)
end
subject { described_class.member_access_requested_email('project', project_member.id) }
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
it 'contains all the useful information' do
to_emails = subject.header[:to].addrs
expect(to_emails.size).to eq(1)
expect(to_emails[0].address).to eq(project.members.owners_and_masters.first.user.notification_email)
is_expected.to have_subject "Request to join the #{project.name_with_namespace} project"
is_expected.to have_html_escaped_body_text project.name_with_namespace
is_expected.to have_body_text project_project_members_url(project)
is_expected.to have_body_text project_member.human_access
end end
end end
context 'for a project in a group' do let(:project_member) do
let(:group_owner) { create(:user) } project.request_access(user)
let(:group) { create(:group).tap { |g| g.add_owner(group_owner) } } project.requesters.find_by(user_id: user.id)
let(:project) { create(:project, :public, :access_requestable, namespace: group) } end
let(:project_member) do subject { described_class.member_access_requested_email('project', project_member.id, recipient.notification_email) }
project.request_access(user)
project.requesters.find_by(user_id: user.id)
end
subject { described_class.member_access_requested_email('project', project_member.id) }
it_behaves_like 'an email sent from GitLab' it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links' it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link" it_behaves_like "a user cannot unsubscribe through footer link"
it 'contains all the useful information' do it 'contains all the useful information' do
to_emails = subject.header[:to].addrs to_emails = subject.header[:to].addrs.map(&:address)
expect(to_emails.size).to eq(1) expect(to_emails).to eq([recipient.notification_email])
expect(to_emails[0].address).to eq(group.members.owners_and_masters.first.user.notification_email)
is_expected.to have_subject "Request to join the #{project.name_with_namespace} project" is_expected.to have_subject "Request to join the #{project.name_with_namespace} project"
is_expected.to have_html_escaped_body_text project.name_with_namespace is_expected.to have_html_escaped_body_text project.name_with_namespace
is_expected.to have_body_text project_project_members_url(project) is_expected.to have_body_text project_project_members_url(project)
is_expected.to have_body_text project_member.human_access is_expected.to have_body_text project_member.human_access
end
end end
end end
...@@ -1074,13 +1045,16 @@ describe Notify do ...@@ -1074,13 +1045,16 @@ describe Notify do
group.request_access(user) group.request_access(user)
group.requesters.find_by(user_id: user.id) group.requesters.find_by(user_id: user.id)
end end
subject { described_class.member_access_requested_email('group', group_member.id) } subject { described_class.member_access_requested_email('group', group_member.id, recipient.notification_email) }
it_behaves_like 'an email sent from GitLab' it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links' it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link" it_behaves_like "a user cannot unsubscribe through footer link"
it 'contains all the useful information' do it 'contains all the useful information' do
to_emails = subject.header[:to].addrs.map(&:address)
expect(to_emails).to eq([recipient.notification_email])
is_expected.to have_subject "Request to join the #{group.name} group" is_expected.to have_subject "Request to join the #{group.name} group"
is_expected.to have_html_escaped_body_text group.name is_expected.to have_html_escaped_body_text group.name
is_expected.to have_body_text group_group_members_url(group) is_expected.to have_body_text group_group_members_url(group)
......
...@@ -524,6 +524,14 @@ describe User do ...@@ -524,6 +524,14 @@ describe User do
user2.update_tracked_fields!(request) user2.update_tracked_fields!(request)
end.to change { user2.reload.current_sign_in_at } end.to change { user2.reload.current_sign_in_at }
end end
it 'does not write if the DB is in read-only mode' do
expect(Gitlab::Database).to receive(:read_only?).and_return(true)
expect do
user.update_tracked_fields!(request)
end.not_to change { user.reload.current_sign_in_at }
end
end end
shared_context 'user keys' do shared_context 'user keys' do
......
...@@ -1349,6 +1349,33 @@ describe NotificationService, :mailer do ...@@ -1349,6 +1349,33 @@ describe NotificationService, :mailer do
end end
describe 'GroupMember' do describe 'GroupMember' do
let(:added_user) { create(:user) }
describe '#new_access_request' do
let(:master) { create(:user) }
let(:owner) { create(:user) }
let(:developer) { create(:user) }
let!(:group) do
create(:group, :public, :access_requestable) do |group|
group.add_owner(owner)
group.add_master(master)
group.add_developer(developer)
end
end
before do
reset_delivered_emails!
end
it 'sends notification to group owners_and_masters' do
group.request_access(added_user)
should_email(owner)
should_email(master)
should_not_email(developer)
end
end
describe '#decline_group_invite' do describe '#decline_group_invite' do
let(:creator) { create(:user) } let(:creator) { create(:user) }
let(:group) { create(:group) } let(:group) { create(:group) }
...@@ -1370,18 +1397,9 @@ describe NotificationService, :mailer do ...@@ -1370,18 +1397,9 @@ describe NotificationService, :mailer do
describe '#new_group_member' do describe '#new_group_member' do
let(:group) { create(:group) } let(:group) { create(:group) }
let(:added_user) { create(:user) }
def create_member!
GroupMember.create(
group: group,
user: added_user,
access_level: Gitlab::Access::GUEST
)
end
it 'sends a notification' do it 'sends a notification' do
create_member! group.add_guest(added_user)
should_only_email(added_user) should_only_email(added_user)
end end
...@@ -1391,7 +1409,7 @@ describe NotificationService, :mailer do ...@@ -1391,7 +1409,7 @@ describe NotificationService, :mailer do
end end
it 'does not send a notification' do it 'does not send a notification' do
create_member! group.add_guest(added_user)
should_not_email_anyone should_not_email_anyone
end end
end end
...@@ -1399,8 +1417,42 @@ describe NotificationService, :mailer do ...@@ -1399,8 +1417,42 @@ describe NotificationService, :mailer do
end end
describe 'ProjectMember' do describe 'ProjectMember' do
let(:project) { create(:project) }
set(:added_user) { create(:user) }
describe '#new_access_request' do
context 'for a project in a user namespace' do
let(:project) do
create(:project, :public, :access_requestable) do |project|
project.add_master(project.owner)
end
end
it 'sends notification to project owners_and_masters' do
project.request_access(added_user)
should_only_email(project.owner)
end
end
context 'for a project in a group' do
let(:group_owner) { create(:user) }
let(:group) { create(:group).tap { |g| g.add_owner(group_owner) } }
let!(:project) { create(:project, :public, :access_requestable, namespace: group) }
before do
reset_delivered_emails!
end
it 'sends notification to group owners_and_masters' do
project.request_access(added_user)
should_only_email(group_owner)
end
end
end
describe '#decline_group_invite' do describe '#decline_group_invite' do
let(:project) { create(:project) }
let(:member) { create(:user) } let(:member) { create(:user) }
before do before do
...@@ -1417,19 +1469,12 @@ describe NotificationService, :mailer do ...@@ -1417,19 +1469,12 @@ describe NotificationService, :mailer do
end end
describe '#new_project_member' do describe '#new_project_member' do
let(:project) { create(:project) }
let(:added_user) { create(:user) }
def create_member!
create(:project_member, user: added_user, project: project)
end
it do it do
create_member! create_member!
should_only_email(added_user) should_only_email(added_user)
end end
describe 'when notifications are disabled' do context 'when notifications are disabled' do
before do before do
create_global_setting_for(added_user, :disabled) create_global_setting_for(added_user, :disabled)
end end
...@@ -1440,6 +1485,10 @@ describe NotificationService, :mailer do ...@@ -1440,6 +1485,10 @@ describe NotificationService, :mailer do
end end
end end
end end
def create_member!
create(:project_member, user: added_user, project: project)
end
end end
context 'guest user in private project' do context 'guest user in private project' do
......
...@@ -20,6 +20,32 @@ describe ProcessCommitWorker do ...@@ -20,6 +20,32 @@ describe ProcessCommitWorker do
worker.perform(project.id, -1, commit.to_hash) worker.perform(project.id, -1, commit.to_hash)
end end
context 'when commit is a merge request merge commit' do
let(:merge_request) do
create(:merge_request,
description: "Closes #{issue.to_reference}",
source_branch: 'feature-merged',
target_branch: 'master',
source_project: project)
end
let(:commit) do
project.repository.create_branch('feature-merged', 'feature')
sha = project.repository.merge(user,
merge_request.diff_head_sha,
merge_request,
"Closes #{issue.to_reference}")
project.repository.commit(sha)
end
it 'it does not close any issues from the commit message' do
expect(worker).not_to receive(:close_issues)
worker.perform(project.id, user.id, commit.to_hash)
end
end
it 'processes the commit message' do it 'processes the commit message' do
expect(worker).to receive(:process_commit_message).and_call_original expect(worker).to receive(:process_commit_message).and_call_original
...@@ -48,11 +74,9 @@ describe ProcessCommitWorker do ...@@ -48,11 +74,9 @@ describe ProcessCommitWorker do
describe '#process_commit_message' do describe '#process_commit_message' do
context 'when pushing to the default branch' do context 'when pushing to the default branch' do
it 'closes issues that should be closed per the commit message' do it 'closes issues that should be closed per the commit message' do
allow(commit).to receive(:safe_message) allow(commit).to receive(:safe_message).and_return("Closes #{issue.to_reference}")
.and_return("Closes #{issue.to_reference}")
expect(worker).to receive(:close_issues) expect(worker).to receive(:close_issues).with(project, user, user, commit, [issue])
.with(project, user, user, commit, [issue])
worker.process_commit_message(project, commit, user, user, true) worker.process_commit_message(project, commit, user, user, true)
end end
...@@ -60,8 +84,7 @@ describe ProcessCommitWorker do ...@@ -60,8 +84,7 @@ describe ProcessCommitWorker do
context 'when pushing to a non-default branch' do context 'when pushing to a non-default branch' do
it 'does not close any issues' do it 'does not close any issues' do
allow(commit).to receive(:safe_message) allow(commit).to receive(:safe_message).and_return("Closes #{issue.to_reference}")
.and_return("Closes #{issue.to_reference}")
expect(worker).not_to receive(:close_issues) expect(worker).not_to receive(:close_issues)
...@@ -102,8 +125,7 @@ describe ProcessCommitWorker do ...@@ -102,8 +125,7 @@ describe ProcessCommitWorker do
describe '#update_issue_metrics' do describe '#update_issue_metrics' do
it 'updates any existing issue metrics' do it 'updates any existing issue metrics' do
allow(commit).to receive(:safe_message) allow(commit).to receive(:safe_message).and_return("Closes #{issue.to_reference}")
.and_return("Closes #{issue.to_reference}")
worker.update_issue_metrics(commit, user) worker.update_issue_metrics(commit, user)
...@@ -113,10 +135,10 @@ describe ProcessCommitWorker do ...@@ -113,10 +135,10 @@ describe ProcessCommitWorker do
end end
it "doesn't execute any queries with false conditions" do it "doesn't execute any queries with false conditions" do
allow(commit).to receive(:safe_message) allow(commit).to receive(:safe_message).and_return("Lorem Ipsum")
.and_return("Lorem Ipsum")
expect { worker.update_issue_metrics(commit, user) }.not_to make_queries_matching(/WHERE (?:1=0|0=1)/) expect { worker.update_issue_metrics(commit, user) }
.not_to make_queries_matching(/WHERE (?:1=0|0=1)/)
end end
end end
...@@ -128,8 +150,9 @@ describe ProcessCommitWorker do ...@@ -128,8 +150,9 @@ describe ProcessCommitWorker do
end end
it 'parses date strings into Time instances' do it 'parses date strings into Time instances' do
commit = worker commit = worker.build_commit(project,
.build_commit(project, id: '123', authored_date: Time.now.to_s) id: '123',
authored_date: Time.now.to_s)
expect(commit.authored_date).to be_an_instance_of(Time) expect(commit.authored_date).to be_an_instance_of(Time)
end end
......
This diff is collapsed.
...@@ -1443,6 +1443,10 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0: ...@@ -1443,6 +1443,10 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0:
escape-string-regexp "^1.0.5" escape-string-regexp "^1.0.5"
supports-color "^4.0.0" supports-color "^4.0.0"
chart.js@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-1.0.2.tgz#ad57d2229cfd8ccf5955147e8121b4911e69dfe7"
chokidar@^1.4.1, chokidar@^1.4.3, chokidar@^1.6.0, chokidar@^1.7.0: chokidar@^1.4.1, chokidar@^1.4.3, chokidar@^1.6.0, chokidar@^1.7.0:
version "1.7.0" version "1.7.0"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment