Commit a11eedd1 authored by GitLab Bot's avatar GitLab Bot

Merge remote-tracking branch 'upstream/master' into ce-to-ee-2017-12-19

# Conflicts:
#	app/assets/javascripts/dispatcher.js
#	app/helpers/clusters_helper.rb
#	app/serializers/merge_request_widget_entity.rb
#	app/views/projects/merge_requests/show.html.haml
#	app/views/projects/pipelines/_info.html.haml
#	spec/features/issues_spec.rb
#	spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js

[ci skip]
parents 51cd05e9 5b880f0d
...@@ -31,9 +31,12 @@ import projectImport from './project_import'; ...@@ -31,9 +31,12 @@ import projectImport from './project_import';
import Labels from './labels'; import Labels from './labels';
import LabelManager from './label_manager'; import LabelManager from './label_manager';
import Sidebar from './right_sidebar'; import Sidebar from './right_sidebar';
<<<<<<< HEAD
/* global WeightSelect */ /* global WeightSelect */
/* global AdminEmailSelect */ /* global AdminEmailSelect */
=======
>>>>>>> upstream/master
import IssuableTemplateSelectors from './templates/issuable_template_selectors'; import IssuableTemplateSelectors from './templates/issuable_template_selectors';
import Flash from './flash'; import Flash from './flash';
import CommitsList from './commits'; import CommitsList from './commits';
......
import Mousetrap from 'mousetrap';
function addMousetrapClick(el, key) {
el.addEventListener('click', () => Mousetrap.trigger(key));
}
function domContentLoaded() {
addMousetrapClick(document.querySelector('.js-trigger-shortcut'), '?');
addMousetrapClick(document.querySelector('.js-trigger-search-bar'), 's');
}
document.addEventListener('DOMContentLoaded', 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 d3 from 'd3';
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__ } from '../locale'; import { n__, s__, createDateTimeFormat, sprintf } from '../locale';
export default (function() { export default (function() {
function ContributorsStatGraph() {} function ContributorsStatGraph() {
this.dateFormat = createDateTimeFormat({ year: 'numeric', month: 'long', day: 'numeric' });
}
ContributorsStatGraph.prototype.init = function(log) { ContributorsStatGraph.prototype.init = function(log) {
var author_commits, total_commits; var author_commits, total_commits;
...@@ -95,11 +96,15 @@ export default (function() { ...@@ -95,11 +96,15 @@ export default (function() {
}; };
ContributorsStatGraph.prototype.change_date_header = function() { ContributorsStatGraph.prototype.change_date_header = function() {
var print, print_date_format, x_domain; const x_domain = ContributorsGraph.prototype.x_domain;
x_domain = ContributorsGraph.prototype.x_domain; const formattedDateRange = sprintf(
print_date_format = d3.time.format("%B %e %Y"); s__('ContributorsPage|%{startDate} – %{endDate}'),
print = print_date_format(x_domain[0]) + " - " + print_date_format(x_domain[1]); {
return $("#date_header").text(print); startDate: this.dateFormat.format(new Date(x_domain[0])),
endDate: this.dateFormat.format(new Date(x_domain[1])),
},
);
return $('#date_header').text(formattedDateRange);
}; };
ContributorsStatGraph.prototype.redraw_author_commit_info = function(author) { ContributorsStatGraph.prototype.redraw_author_commit_info = function(author) {
......
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, max-len, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, comma-dangle, no-return-assign, prefer-arrow-callback, quotes, prefer-template, newline-per-chained-call, no-else-return, no-shadow */ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, max-len, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, comma-dangle, no-return-assign, prefer-arrow-callback, quotes, prefer-template, newline-per-chained-call, no-else-return, no-shadow */
import _ from 'underscore'; import _ from 'underscore';
import d3 from 'd3'; import d3 from 'd3';
import { dateTickFormat } from '../lib/utils/tick_formats';
const extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; const extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
const hasProp = {}.hasOwnProperty; const hasProp = {}.hasOwnProperty;
...@@ -93,9 +94,12 @@ export const ContributorsMasterGraph = (function(superClass) { ...@@ -93,9 +94,12 @@ export const ContributorsMasterGraph = (function(superClass) {
extend(ContributorsMasterGraph, superClass); extend(ContributorsMasterGraph, superClass);
function ContributorsMasterGraph(data1) { function ContributorsMasterGraph(data1) {
const $parentElement = $('#contributors-master');
const parentPadding = parseFloat($parentElement.css('padding-left')) + parseFloat($parentElement.css('padding-right'));
this.data = data1; this.data = data1;
this.update_content = this.update_content.bind(this); this.update_content = this.update_content.bind(this);
this.width = $('.content').width() - 70; this.width = $('.content').width() - parentPadding - (this.MARGIN.left + this.MARGIN.right);
this.height = 200; this.height = 200;
this.x = null; this.x = null;
this.y = null; this.y = null;
...@@ -131,7 +135,10 @@ export const ContributorsMasterGraph = (function(superClass) { ...@@ -131,7 +135,10 @@ export const ContributorsMasterGraph = (function(superClass) {
}; };
ContributorsMasterGraph.prototype.create_axes = function() { ContributorsMasterGraph.prototype.create_axes = function() {
this.x_axis = d3.svg.axis().scale(this.x).orient("bottom"); this.x_axis = d3.svg.axis()
.scale(this.x)
.orient('bottom')
.tickFormat(dateTickFormat);
return this.y_axis = d3.svg.axis().scale(this.y).orient("left").ticks(5); return this.y_axis = d3.svg.axis().scale(this.y).orient("left").ticks(5);
}; };
...@@ -219,7 +226,11 @@ export const ContributorsAuthorGraph = (function(superClass) { ...@@ -219,7 +226,11 @@ export const ContributorsAuthorGraph = (function(superClass) {
}; };
ContributorsAuthorGraph.prototype.create_axes = function() { ContributorsAuthorGraph.prototype.create_axes = function() {
this.x_axis = d3.svg.axis().scale(this.x).orient("bottom").ticks(8); this.x_axis = d3.svg.axis()
.scale(this.x)
.orient('bottom')
.ticks(8)
.tickFormat(dateTickFormat);
return this.y_axis = d3.svg.axis().scale(this.y).orient("left").ticks(5); return this.y_axis = d3.svg.axis().scale(this.y).orient("left").ticks(5);
}; };
......
import { createDateTimeFormat } from '../../locale';
let dateTimeFormats;
export const initDateFormats = () => {
const dayFormat = createDateTimeFormat({ month: 'short', day: 'numeric' });
const monthFormat = createDateTimeFormat({ month: 'long' });
const yearFormat = createDateTimeFormat({ year: 'numeric' });
dateTimeFormats = {
dayFormat,
monthFormat,
yearFormat,
};
};
initDateFormats();
/**
Formats a localized date in way that it can be used for d3.js axis.tickFormat().
That is, it displays
- 4-digit for first of January
- full month name for first of every month
- day and abbreviated month otherwise
see also https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Axes.md#tickFormat
*/
export const dateTickFormat = (date) => {
if (date.getDate() !== 1) {
return dateTimeFormats.dayFormat.format(date);
}
if (date.getMonth() > 0) {
return dateTimeFormats.monthFormat.format(date);
}
return dateTimeFormats.yearFormat.format(date);
};
import Jed from 'jed'; import Jed from 'jed';
import sprintf from './sprintf'; import sprintf from './sprintf';
const langAttribute = document.querySelector('html').getAttribute('lang'); const languageCode = () => document.querySelector('html').getAttribute('lang') || 'en';
const lang = (langAttribute || 'en').replace(/-/g, '_');
const locale = new Jed(window.translations || {}); const locale = new Jed(window.translations || {});
delete window.translations; delete window.translations;
...@@ -47,9 +46,19 @@ const pgettext = (keyOrContext, key) => { ...@@ -47,9 +46,19 @@ const pgettext = (keyOrContext, key) => {
return translated[translated.length - 1]; return translated[translated.length - 1];
}; };
export { lang }; /**
Creates an instance of Intl.DateTimeFormat for the current locale.
@param formatOptions for available options, please see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat
@returns {Intl.DateTimeFormat}
*/
const createDateTimeFormat =
formatOptions => Intl.DateTimeFormat(languageCode(), formatOptions);
export { languageCode };
export { gettext as __ }; export { gettext as __ };
export { ngettext as n__ }; export { ngettext as n__ };
export { pgettext as s__ }; export { pgettext as s__ };
export { sprintf }; export { sprintf };
export { createDateTimeFormat };
export default locale; export default locale;
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, no-underscore-dangle, one-var, one-var-declaration-per-line, consistent-return, dot-notation, quote-props, comma-dangle, object-shorthand, max-len, prefer-arrow-callback */ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, no-underscore-dangle, one-var, one-var-declaration-per-line, consistent-return, dot-notation, quote-props, comma-dangle, object-shorthand, max-len, prefer-arrow-callback */
/* global MergeRequestTabs */
import 'vendor/jquery.waitforimages'; import 'vendor/jquery.waitforimages';
import TaskList from './task_list'; import TaskList from './task_list';
......
...@@ -51,7 +51,10 @@ export default class Shortcuts { ...@@ -51,7 +51,10 @@ export default class Shortcuts {
} }
onToggleHelp(e) { onToggleHelp(e) {
if (e.preventDefault) {
e.preventDefault(); e.preventDefault();
}
Shortcuts.toggleHelp(this.enabledHelp); Shortcuts.toggleHelp(this.enabledHelp);
} }
...@@ -112,6 +115,9 @@ export default class Shortcuts { ...@@ -112,6 +115,9 @@ export default class Shortcuts {
static focusSearch(e) { static focusSearch(e) {
$('#search').focus(); $('#search').focus();
if (e.preventDefault) {
e.preventDefault(); e.preventDefault();
} }
}
} }
...@@ -320,13 +320,14 @@ ...@@ -320,13 +320,14 @@
transition: width $sidebar-transition-duration; transition: width $sidebar-transition-duration;
position: fixed; position: fixed;
bottom: 0; bottom: 0;
padding: 16px; padding: $gl-padding;
background-color: $gray-light; background-color: $gray-light;
border: 0; border: 0;
border-top: 2px solid $border-color; border-top: 2px solid $border-color;
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
display: flex; display: flex;
align-items: center; align-items: center;
line-height: 1;
svg { svg {
margin-right: 8px; margin-right: 8px;
......
...@@ -55,7 +55,6 @@ module IssuableActions ...@@ -55,7 +55,6 @@ module IssuableActions
def destroy def destroy
Issuable::DestroyService.new(issuable.project, current_user).execute(issuable) Issuable::DestroyService.new(issuable.project, current_user).execute(issuable)
TodoService.new.destroy_issuable(issuable, current_user)
name = issuable.human_class_name name = issuable.human_class_name
flash[:notice] = "The #{name} was successfully deleted." flash[:notice] = "The #{name} was successfully deleted."
......
class Projects::PipelineSchedulesController < Projects::ApplicationController class Projects::PipelineSchedulesController < Projects::ApplicationController
before_action :schedule, except: [:index, :new, :create] before_action :schedule, except: [:index, :new, :create]
before_action :play_rate_limit, only: [:play]
before_action :authorize_play_pipeline_schedule!, only: [:play]
before_action :authorize_read_pipeline_schedule! before_action :authorize_read_pipeline_schedule!
before_action :authorize_create_pipeline_schedule!, only: [:new, :create] before_action :authorize_create_pipeline_schedule!, only: [:new, :create]
before_action :authorize_update_pipeline_schedule!, except: [:index, :new, :create] before_action :authorize_update_pipeline_schedule!, except: [:index, :new, :create, :play]
before_action :authorize_admin_pipeline_schedule!, only: [:destroy] before_action :authorize_admin_pipeline_schedule!, only: [:destroy]
def index def index
...@@ -40,6 +42,18 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController ...@@ -40,6 +42,18 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
end end
end end
def play
job_id = RunPipelineScheduleWorker.perform_async(schedule.id, current_user.id)
if job_id
flash[:notice] = "Successfully scheduled a pipeline to run. Go to the <a href=\"#{project_pipelines_path(@project)}\">Pipelines page</a> for details.".html_safe
else
flash[:alert] = 'Unable to schedule a pipeline to run immediately'
end
redirect_to pipeline_schedules_path(@project)
end
def take_ownership def take_ownership
if schedule.update(owner: current_user) if schedule.update(owner: current_user)
redirect_to pipeline_schedules_path(@project) redirect_to pipeline_schedules_path(@project)
...@@ -60,6 +74,17 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController ...@@ -60,6 +74,17 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
private private
def play_rate_limit
return unless current_user
limiter = ::Gitlab::ActionRateLimiter.new(action: :play_pipeline_schedule)
return unless limiter.throttled?([current_user, schedule], 1)
flash[:alert] = 'You cannot play this scheduled pipeline at the moment. Please wait a minute.'
redirect_to pipeline_schedules_path(@project)
end
def schedule def schedule
@schedule ||= project.pipeline_schedules.find(params[:id]) @schedule ||= project.pipeline_schedules.find(params[:id])
end end
...@@ -70,6 +95,10 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController ...@@ -70,6 +95,10 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
variables_attributes: [:id, :key, :value, :_destroy] ) variables_attributes: [:id, :key, :value, :_destroy] )
end end
def authorize_play_pipeline_schedule!
return access_denied! unless can?(current_user, :play_pipeline_schedule, schedule)
end
def authorize_update_pipeline_schedule! def authorize_update_pipeline_schedule!
return access_denied! unless can?(current_user, :update_pipeline_schedule, schedule) return access_denied! unless can?(current_user, :update_pipeline_schedule, schedule)
end end
......
module ClustersHelper module ClustersHelper
def has_multiple_clusters?(project) def has_multiple_clusters?(project)
<<<<<<< HEAD
project.feature_available?(:multiple_clusters) project.feature_available?(:multiple_clusters)
=======
false
>>>>>>> upstream/master
end end
end end
...@@ -184,6 +184,11 @@ module GitlabRoutingHelper ...@@ -184,6 +184,11 @@ module GitlabRoutingHelper
edit_project_pipeline_schedule_path(project, schedule) edit_project_pipeline_schedule_path(project, schedule)
end end
def play_pipeline_schedule_path(schedule, *args)
project = schedule.project
play_project_pipeline_schedule_path(project, schedule, *args)
end
def take_ownership_pipeline_schedule_path(schedule, *args) def take_ownership_pipeline_schedule_path(schedule, *args)
project = schedule.project project = schedule.project
take_ownership_project_pipeline_schedule_path(project, schedule, *args) take_ownership_project_pipeline_schedule_path(project, schedule, *args)
......
...@@ -27,10 +27,17 @@ module BlobViewer ...@@ -27,10 +27,17 @@ module BlobViewer
private private
def package_name_from_json(key) def json_data
@json_data ||= begin
prepare! prepare!
JSON.parse(blob.data)
rescue
{}
end
end
JSON.parse(blob.data)[key] rescue nil def package_name_from_json(key)
json_data[key]
end end
def package_name_from_method_call(name) def package_name_from_method_call(name)
......
...@@ -16,7 +16,25 @@ module BlobViewer ...@@ -16,7 +16,25 @@ module BlobViewer
@package_name ||= package_name_from_json('name') @package_name ||= package_name_from_json('name')
end end
def package_type
private? ? 'private package' : super
end
def package_url def package_url
private? ? homepage : npm_url
end
private
def private?
!!json_data['private']
end
def homepage
json_data['homepage']
end
def npm_url
"https://www.npmjs.com/package/#{package_name}" "https://www.npmjs.com/package/#{package_name}"
end end
end end
......
...@@ -241,6 +241,10 @@ module Ci ...@@ -241,6 +241,10 @@ module Ci
statuses.select(:stage).distinct.count statuses.select(:stage).distinct.count
end end
def total_size
statuses.count(:id)
end
def stages_names def stages_names
statuses.order(:stage_idx).distinct statuses.order(:stage_idx).distinct
.pluck(:stage, :stage_idx).map(&:first) .pluck(:stage, :stage_idx).map(&:first)
......
...@@ -22,12 +22,9 @@ class DiffDiscussion < Discussion ...@@ -22,12 +22,9 @@ class DiffDiscussion < Discussion
def merge_request_version_params def merge_request_version_params
return unless for_merge_request? return unless for_merge_request?
return {} if active?
if on_merge_request_commit? version_params.tap do |params|
{ commit_id: commit_id } params[:commit_id] = commit_id if on_merge_request_commit?
else
noteable.version_params_for(position.diff_refs)
end end
end end
...@@ -37,4 +34,12 @@ class DiffDiscussion < Discussion ...@@ -37,4 +34,12 @@ class DiffDiscussion < Discussion
position: position.to_json position: position.to_json
) )
end end
private
def version_params
return {} if active?
noteable.version_params_for(position.diff_refs)
end
end end
...@@ -46,6 +46,8 @@ class JiraService < IssueTrackerService ...@@ -46,6 +46,8 @@ class JiraService < IssueTrackerService
context_path: url.path, context_path: url.path,
auth_type: :basic, auth_type: :basic,
read_timeout: 120, read_timeout: 120,
use_cookies: true,
additional_cookies: ['OBBasicAuth=fromDialog'],
use_ssl: url.scheme == 'https' use_ssl: url.scheme == 'https'
} }
end end
......
...@@ -240,6 +240,12 @@ class Repository ...@@ -240,6 +240,12 @@ class Repository
branch_names.include?(branch_name) branch_names.include?(branch_name)
end end
def tag_exists?(tag_name)
return false unless raw_repository
tag_names.include?(tag_name)
end
def ref_exists?(ref) def ref_exists?(ref)
!!raw_repository&.ref_exists?(ref) !!raw_repository&.ref_exists?(ref)
rescue ArgumentError rescue ArgumentError
......
...@@ -2,16 +2,18 @@ module Ci ...@@ -2,16 +2,18 @@ module Ci
class PipelinePolicy < BasePolicy class PipelinePolicy < BasePolicy
delegate { @subject.project } delegate { @subject.project }
condition(:protected_ref) do condition(:protected_ref) { ref_protected?(@user, @subject.project, @subject.tag?, @subject.ref) }
access = ::Gitlab::UserAccess.new(@user, project: @subject.project)
if @subject.tag? rule { protected_ref }.prevent :update_pipeline
!access.can_create_tag?(@subject.ref)
def ref_protected?(user, project, tag, ref)
access = ::Gitlab::UserAccess.new(user, project: project)
if tag
!access.can_create_tag?(ref)
else else
!access.can_update_branch?(@subject.ref) !access.can_update_branch?(ref)
end end
end end
rule { protected_ref }.prevent :update_pipeline
end end
end end
...@@ -2,13 +2,23 @@ module Ci ...@@ -2,13 +2,23 @@ module Ci
class PipelineSchedulePolicy < PipelinePolicy class PipelineSchedulePolicy < PipelinePolicy
alias_method :pipeline_schedule, :subject alias_method :pipeline_schedule, :subject
condition(:protected_ref) do
ref_protected?(@user, @subject.project, @subject.project.repository.tag_exists?(@subject.ref), @subject.ref)
end
condition(:owner_of_schedule) do condition(:owner_of_schedule) do
can?(:developer_access) && pipeline_schedule.owned_by?(@user) can?(:developer_access) && pipeline_schedule.owned_by?(@user)
end end
rule { can?(:developer_access) }.policy do
enable :play_pipeline_schedule
end
rule { can?(:master_access) | owner_of_schedule }.policy do rule { can?(:master_access) | owner_of_schedule }.policy do
enable :update_pipeline_schedule enable :update_pipeline_schedule
enable :admin_pipeline_schedule enable :admin_pipeline_schedule
end end
rule { protected_ref }.prevent :play_pipeline_schedule
end end
end end
class MergeRequestWidgetEntity < IssuableEntity class MergeRequestWidgetEntity < IssuableEntity
<<<<<<< HEAD:app/serializers/merge_request_widget_entity.rb
prepend ::EE::MergeRequestWidgetEntity prepend ::EE::MergeRequestWidgetEntity
=======
>>>>>>> upstream/master:app/serializers/merge_request_widget_entity.rb
expose :state expose :state
expose :in_progress_merge_commit_sha expose :in_progress_merge_commit_sha
expose :merge_commit_sha expose :merge_commit_sha
......
module Issuable module Issuable
class DestroyService < IssuableBaseService class DestroyService < IssuableBaseService
def execute(issuable) def execute(issuable)
TodoService.new.destroy_target(issuable) do |issuable|
if issuable.destroy if issuable.destroy
issuable.update_project_counter_caches issuable.update_project_counter_caches
end end
end end
end end
end
end end
module Notes module Notes
class DestroyService < BaseService class DestroyService < BaseService
def execute(note) def execute(note)
TodoService.new.destroy_target(note) do |note|
note.destroy note.destroy
end end
end end
end
end end
...@@ -31,12 +31,20 @@ class TodoService ...@@ -31,12 +31,20 @@ class TodoService
mark_pending_todos_as_done(issue, current_user) mark_pending_todos_as_done(issue, current_user)
end end
# When we destroy an issuable we should: # When we destroy a todo target we should:
# #
# * refresh the todos count cache for the current user # * refresh the todos count cache for all users with todos on the target
# #
def destroy_issuable(issuable, user) # This needs to yield back to the caller to destroy the target, because it
user.update_todos_count_cache # collects the todo users before the todos themselves are deleted, then
# updates the todo counts for those users.
#
def destroy_target(target)
todo_users = User.where(id: target.todos.pending.select(:user_id)).to_a
yield target
todo_users.each(&:update_todos_count_cache)
end end
# When we reassign an issue we should: # When we reassign an issue we should:
......
...@@ -7,7 +7,8 @@ ...@@ -7,7 +7,8 @@
%span.pushed #{event.action_name} #{event.ref_type} %span.pushed #{event.action_name} #{event.ref_type}
%strong %strong
- commits_link = project_commits_path(project, event.ref_name) - commits_link = project_commits_path(project, event.ref_name)
= link_to_if project.repository.branch_exists?(event.ref_name), event.ref_name, commits_link, class: 'ref-name' - should_link = event.tag? ? project.repository.tag_exists?(event.ref_name) : project.repository.branch_exists?(event.ref_name)
= link_to_if should_link, event.ref_name, commits_link, class: 'ref-name'
= render "events/event_scope", event: event = render "events/event_scope", event: event
......
= webpack_bundle_tag 'docs'
%div %div
- if current_application_settings.help_page_text.present? - if current_application_settings.help_page_text.present?
= markdown(current_application_settings.help_page_text) = markdown(current_application_settings.help_page_text)
...@@ -38,8 +40,12 @@ ...@@ -38,8 +40,12 @@
Quick help Quick help
%ul.well-list %ul.well-list
%li= link_to 'See our website for getting help', support_url %li= link_to 'See our website for getting help', support_url
%li= link_to 'Use the search bar on the top of this page', '#', onclick: 'Shortcuts.focusSearch(event)' %li
%li= link_to 'Use shortcuts', '#', onclick: 'Shortcuts.toggleHelp()' %button.btn-blank.btn-link.js-trigger-search-bar{ type: 'button' }
Use the search bar on the top of this page
%li
%button.btn-blank.btn-link.js-trigger-shortcut{ type: 'button' }
Use shortcuts
- unless current_application_settings.help_page_hide_commercial_content? - unless current_application_settings.help_page_hide_commercial_content?
%li= link_to 'Get a support subscription', 'https://about.gitlab.com/pricing/' %li= link_to 'Get a support subscription', 'https://about.gitlab.com/pricing/'
%li= link_to 'Compare GitLab editions', 'https://about.gitlab.com/features/#compare' %li= link_to 'Compare GitLab editions', 'https://about.gitlab.com/features/#compare'
...@@ -109,7 +109,7 @@ ...@@ -109,7 +109,7 @@
API API
%tr %tr
%td{ colspan: 2, style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:300;line-height:1.4;padding:15px 5px;text-align:center;" } %td{ colspan: 2, style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:300;line-height:1.4;padding:15px 5px;text-align:center;" }
- job_count = @pipeline.statuses.latest.size - job_count = @pipeline.total_size
- stage_count = @pipeline.stages_count - stage_count = @pipeline.stages_count
successfully completed successfully completed
#{job_count} #{'job'.pluralize(job_count)} #{job_count} #{'job'.pluralize(job_count)}
......
...@@ -22,11 +22,11 @@ Committed by: <%= commit.committer_name %> ...@@ -22,11 +22,11 @@ Committed by: <%= commit.committer_name %>
<% end -%> <% end -%>
<% end -%> <% end -%>
<% build_count = @pipeline.statuses.latest.size -%> <% job_count = @pipeline.total_size -%>
<% stage_count = @pipeline.stages_count -%> <% stage_count = @pipeline.stages_count -%>
<% if @pipeline.user -%> <% if @pipeline.user -%>
Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) triggered by <%= @pipeline.user.name %> ( <%= user_url(@pipeline.user) %> ) Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) triggered by <%= @pipeline.user.name %> ( <%= user_url(@pipeline.user) %> )
<% else -%> <% else -%>
Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) triggered by API Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) triggered by API
<% end -%> <% end -%>
successfully completed <%= build_count %> <%= 'build'.pluralize(build_count) %> in <%= stage_count %> <%= 'stage'.pluralize(stage_count) %>. successfully completed <%= job_count %> <%= 'job'.pluralize(job_count) %> in <%= stage_count %> <%= 'stage'.pluralize(stage_count) %>.
...@@ -6,6 +6,6 @@ ...@@ -6,6 +6,6 @@
- if viewer.package_name - if viewer.package_name
and defines a #{viewer.package_type} named and defines a #{viewer.package_type} named
%strong< %strong<
= link_to viewer.package_name, viewer.package_url, target: '_blank', rel: 'noopener noreferrer' = link_to_if viewer.package_url.present?, viewer.package_name, viewer.package_url, target: '_blank', rel: 'noopener noreferrer'
= link_to 'Learn more', viewer.manager_url, target: '_blank', rel: 'noopener noreferrer' = link_to 'Learn more', viewer.manager_url, target: '_blank', rel: 'noopener noreferrer'
...@@ -7,6 +7,9 @@ ...@@ -7,6 +7,9 @@
.form-group .form-group
= field.label :name, s_('ClusterIntegration|Cluster name') = field.label :name, s_('ClusterIntegration|Cluster name')
= field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Cluster name') = field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Cluster name')
.form-group
= field.label :environment_scope, s_('ClusterIntegration|Environment scope')
= field.text_field :environment_scope, class: 'form-control', readonly: !has_multiple_clusters?(@project), placeholder: s_('ClusterIntegration|Environment scope')
.form-group .form-group
= field.label :environment_scope, s_('ClusterIntegration|Environment scope') = field.label :environment_scope, s_('ClusterIntegration|Environment scope')
......
...@@ -3,6 +3,9 @@ ...@@ -3,6 +3,9 @@
.form-group .form-group
= field.label :name, s_('ClusterIntegration|Cluster name') = field.label :name, s_('ClusterIntegration|Cluster name')
= field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Cluster name') = field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Cluster name')
.form-group
= field.label :environment_scope, s_('ClusterIntegration|Environment scope')
= field.text_field :environment_scope, class: 'form-control', readonly: !has_multiple_clusters?(@project), placeholder: s_('ClusterIntegration|Environment scope')
.form-group .form-group
= field.label :environment_scope, s_('ClusterIntegration|Environment scope') = field.label :environment_scope, s_('ClusterIntegration|Environment scope')
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
:javascript :javascript
window.gl = window.gl || {}; window.gl = window.gl || {};
window.gl.mrWidgetData = #{serialize_issuable(@merge_request, serializer: 'widget')} window.gl.mrWidgetData = #{serialize_issuable(@merge_request, serializer: 'widget')}
<<<<<<< HEAD
// Append static, server-generated data not included in merge request entity (EE-Only) // Append static, server-generated data not included in merge request entity (EE-Only)
// Object.assign would be useful here, but it blows up Phantom.js in tests // Object.assign would be useful here, but it blows up Phantom.js in tests
...@@ -29,6 +30,8 @@ ...@@ -29,6 +30,8 @@
window.gl.mrWidgetData.geo_secondary_help_path = '#{help_page_path("/gitlab-geo/configuration.md")}'; window.gl.mrWidgetData.geo_secondary_help_path = '#{help_page_path("/gitlab-geo/configuration.md")}';
window.gl.mrWidgetData.enable_squash_before_merge = '#{@merge_request.project.feature_available?(:merge_request_squash)}' === 'true'; window.gl.mrWidgetData.enable_squash_before_merge = '#{@merge_request.project.feature_available?(:merge_request_squash)}' === 'true';
window.gl.mrWidgetData.squash_before_merge_help_path = '#{help_page_path("user/project/merge_requests/squash_and_merge")}'; window.gl.mrWidgetData.squash_before_merge_help_path = '#{help_page_path("user/project/merge_requests/squash_and_merge")}';
=======
>>>>>>> upstream/master
#js-vue-mr-widget.mr-widget #js-vue-mr-widget.mr-widget
......
...@@ -26,10 +26,12 @@ ...@@ -26,10 +26,12 @@
= pipeline_schedule.owner&.name = pipeline_schedule.owner&.name
%td %td
.pull-right.btn-group .pull-right.btn-group
- if can?(current_user, :play_pipeline_schedule, pipeline_schedule)
= link_to play_pipeline_schedule_path(pipeline_schedule), method: :post, title: s_('Play'), class: 'btn' do
= icon('play')
- if can?(current_user, :update_pipeline_schedule, pipeline_schedule) - if can?(current_user, :update_pipeline_schedule, pipeline_schedule)
= link_to take_ownership_pipeline_schedule_path(pipeline_schedule), method: :post, title: s_('PipelineSchedules|Take ownership'), class: 'btn' do = link_to take_ownership_pipeline_schedule_path(pipeline_schedule), method: :post, title: s_('PipelineSchedules|Take ownership'), class: 'btn' do
= s_('PipelineSchedules|Take ownership') = s_('PipelineSchedules|Take ownership')
- if can?(current_user, :update_pipeline_schedule, pipeline_schedule)
= link_to edit_pipeline_schedule_path(pipeline_schedule), title: _('Edit'), class: 'btn' do = link_to edit_pipeline_schedule_path(pipeline_schedule), title: _('Edit'), class: 'btn' do
= icon('pencil') = icon('pencil')
- if can?(current_user, :admin_pipeline_schedule, pipeline_schedule) - if can?(current_user, :admin_pipeline_schedule, pipeline_schedule)
......
...@@ -13,7 +13,11 @@ ...@@ -13,7 +13,11 @@
.well-segment.pipeline-info .well-segment.pipeline-info
.icon-container .icon-container
= icon('clock-o') = icon('clock-o')
<<<<<<< HEAD
= pluralize @pipeline.statuses.count(:id), "job" = pluralize @pipeline.statuses.count(:id), "job"
=======
= pluralize @pipeline.total_size, "job"
>>>>>>> upstream/master
- if @pipeline.ref - if @pipeline.ref
from from
= link_to @pipeline.ref, project_ref_path(@project, @pipeline.ref), class: "ref-name" = link_to @pipeline.ref, project_ref_path(@project, @pipeline.ref), class: "ref-name"
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
%li.js-builds-tab-link %li.js-builds-tab-link
= link_to builds_project_pipeline_path(@project, @pipeline), data: {target: 'div#js-tab-builds', action: 'builds', toggle: 'tab' }, class: 'builds-tab' do = link_to builds_project_pipeline_path(@project, @pipeline), data: {target: 'div#js-tab-builds', action: 'builds', toggle: 'tab' }, class: 'builds-tab' do
Jobs Jobs
%span.badge.js-builds-counter= pipeline.statuses.count %span.badge.js-builds-counter= pipeline.total_size
- if failed_builds.present? - if failed_builds.present?
%li.js-failures-tab-link %li.js-failures-tab-link
= link_to failures_project_pipeline_path(@project, @pipeline), data: {target: 'div#js-tab-failures', action: 'failures', toggle: 'tab' }, class: 'failures-tab' do = link_to failures_project_pipeline_path(@project, @pipeline), data: {target: 'div#js-tab-failures', action: 'failures', toggle: 'tab' }, class: 'failures-tab' do
......
- max_render = 3 - max_render = 4
- max = [max_render, issue.assignees.length].min - assignees_rendering_overflow = issue.assignees.size > max_render
- render_count = assignees_rendering_overflow ? max_render - 1 : max_render
- more_assignees_count = issue.assignees.size - render_count
- issue.assignees.take(max).each do |assignee| - issue.assignees.take(render_count).each do |assignee|
= link_to_member(@project, assignee, name: false, title: "Assigned to :name") = link_to_member(@project, assignee, name: false, title: "Assigned to :name")
- if issue.assignees.length > max_render - if more_assignees_count.positive?
- counter = issue.assignees.length - max_render %span{ class: 'avatar-counter has-tooltip', data: { container: 'body', placement: 'bottom', 'line-type' => 'old', 'original-title' => "+#{more_assignees_count} more assignees" } } +#{more_assignees_count}
%span{ class: 'avatar-counter has-tooltip', data: { container: 'body', placement: 'bottom', 'line-type' => 'old', 'original-title' => "+#{counter} more assignees" } }
- if counter < 99
= "+#{counter}"
- else
99+
...@@ -39,6 +39,7 @@ ...@@ -39,6 +39,7 @@
- pipeline_cache:expire_job_cache - pipeline_cache:expire_job_cache
- pipeline_cache:expire_pipeline_cache - pipeline_cache:expire_pipeline_cache
- pipeline_creation:create_pipeline - pipeline_creation:create_pipeline
- pipeline_creation:run_pipeline_schedule
- pipeline_default:build_coverage - pipeline_default:build_coverage
- pipeline_default:build_trace_sections - pipeline_default:build_trace_sections
- pipeline_default:pipeline_metrics - pipeline_default:pipeline_metrics
......
class RunPipelineScheduleWorker
include ApplicationWorker
include PipelineQueue
queue_namespace :pipeline_creation
def perform(schedule_id, user_id)
schedule = Ci::PipelineSchedule.find_by(id: schedule_id)
user = User.find_by(id: user_id)
return unless schedule && user
run_pipeline_schedule(schedule, user)
end
def run_pipeline_schedule(schedule, user)
Ci::CreatePipelineService.new(schedule.project,
user,
ref: schedule.ref)
.execute(:schedule, ignore_skip_ci: true, save_on_errors: false, schedule: schedule)
end
end
---
title: Fix tags in the Activity tab not being clickable
merge_request: 15996
author: Mario de la Ossa
type: fixed
---
title: Do not generate NPM links for private NPM modules in blob view
merge_request: 16002
author: Mario de la Ossa
type: added
---
title: List of avatars should never show +1
merge_request: 15972
author: Jacopo Beschi @jacopo-beschi
type: added
---
title: Reset todo counters when the target is deleted
merge_request: 15807
author:
type: fixed
---
title: Provide additional cookies to JIRA service requests to allow Oracle WebGates
Basic Auth
merge_request:
author: Stanislaw Wozniak
type: changed
---
title: Fix shortcut links on help page
merge_request:
author:
type: fixed
---
title: Fix onion-skin re-entering state
merge_request:
author:
type: fixed
---
title: fix build count in pipeline success mail
merge_request: 15827
author: Christiaan Van den Poel
type: fixed
---
title: Remove related links in MR widget when empty state
merge_request:
author:
type: fixed
---
title: Add button to run scheduled pipeline immediately
merge_request:
author:
type: added
---
title: Move edit button to second row on issue page (and change it to a pencil icon)
merge_request:
author:
type: changed
---
title: Translate date ranges on contributors page
merge_request: 15846
author:
type: changed
...@@ -214,6 +214,7 @@ constraints(ProjectUrlConstrainer.new) do ...@@ -214,6 +214,7 @@ constraints(ProjectUrlConstrainer.new) do
resources :pipeline_schedules, except: [:show] do resources :pipeline_schedules, except: [:show] do
member do member do
post :play
post :take_ownership post :take_ownership
end end
end end
......
...@@ -38,6 +38,7 @@ var config = { ...@@ -38,6 +38,7 @@ var config = {
cycle_analytics: './cycle_analytics/cycle_analytics_bundle.js', cycle_analytics: './cycle_analytics/cycle_analytics_bundle.js',
commit_pipelines: './commit/pipelines/pipelines_bundle.js', commit_pipelines: './commit/pipelines/pipelines_bundle.js',
deploy_keys: './deploy_keys/index.js', deploy_keys: './deploy_keys/index.js',
docs: './docs/docs_bundle.js',
diff_notes: './diff_notes/diff_notes_bundle.js', diff_notes: './diff_notes/diff_notes_bundle.js',
environments: './environments/environments_bundle.js', environments: './environments/environments_bundle.js',
environments_folder: './environments/folder/environments_folder_bundle.js', environments_folder: './environments/folder/environments_folder_bundle.js',
......
...@@ -16,6 +16,7 @@ class IssuesMilestoneIdForeignKey < ActiveRecord::Migration ...@@ -16,6 +16,7 @@ class IssuesMilestoneIdForeignKey < ActiveRecord::Migration
def self.with_orphaned_milestones def self.with_orphaned_milestones
where('NOT EXISTS (SELECT true FROM milestones WHERE milestones.id = issues.milestone_id)') where('NOT EXISTS (SELECT true FROM milestones WHERE milestones.id = issues.milestone_id)')
.where('milestone_id IS NOT NULL')
end end
end end
......
# Configuring GitLab for HA # Configuring GitLab for HA
Assuming you have already configured a database, Redis, and NFS, you can Assuming you have already configured a [database](database.md), [Redis](redis.md), and [NFS](nfs.md), you can
configure the GitLab application server(s) now. Complete the steps below configure the GitLab application server(s) now. Complete the steps below
for each GitLab application server in your environment. for each GitLab application server in your environment.
...@@ -56,8 +56,7 @@ for each GitLab application server in your environment. ...@@ -56,8 +56,7 @@ for each GitLab application server in your environment.
high_availability['mountpoint'] = '/var/opt/gitlab/git-data' high_availability['mountpoint'] = '/var/opt/gitlab/git-data'
# Disable components that will not be on the GitLab application server # Disable components that will not be on the GitLab application server
postgresql['enable'] = false roles ['application_role']
redis['enable'] = false
# PostgreSQL connection details # PostgreSQL connection details
gitlab_rails['db_adapter'] = 'postgresql' gitlab_rails['db_adapter'] = 'postgresql'
......
...@@ -11,7 +11,7 @@ This exported module should be used instead of directly using `axios` to ensure ...@@ -11,7 +11,7 @@ This exported module should be used instead of directly using `axios` to ensure
## Usage ## Usage
```javascript ```javascript
import axios from '~/lib/utils/axios_utils'; import axios from './lib/utils/axios_utils';
axios.get(url) axios.get(url)
.then((response) => { .then((response) => {
......
...@@ -262,6 +262,21 @@ Sometimes you need to add some context to the text that you want to translate ...@@ -262,6 +262,21 @@ Sometimes you need to add some context to the text that you want to translate
s__('OpenedNDaysAgo|Opened') s__('OpenedNDaysAgo|Opened')
``` ```
### Dates / times
- In JavaScript:
```js
import { createDateTimeFormat } from '.../locale';
const dateFormat = createDateTimeFormat({ year: 'numeric', month: 'long', day: 'numeric' });
console.log(dateFormat.format(new Date('2063-04-05'))) // April 5, 2063
```
This makes use of [`Intl.DateTimeFormat`].
[`Intl.DateTimeFormat`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat
## Adding a new language ## Adding a new language
Let's suppose you want to add translations for a new language, let's say French. Let's suppose you want to add translations for a new language, let's say French.
......
...@@ -59,6 +59,7 @@ Requests to become a proof reader will be considered on the merits of previous t ...@@ -59,6 +59,7 @@ Requests to become a proof reader will be considered on the merits of previous t
- French - French
- German - German
- Italian - Italian
- [Paolo Falomo](https://crowdin.com/profile/paolo.falomo)
- Japanese - Japanese
- Korean - Korean
- [Huang Tao](https://crowdin.com/profile/htve) - [Huang Tao](https://crowdin.com/profile/htve)
......
...@@ -95,6 +95,9 @@ password as they will be needed when configuring GitLab in the next section. ...@@ -95,6 +95,9 @@ password as they will be needed when configuring GitLab in the next section.
- GitLab 8.14 introduced a new way to integrate with JIRA which greatly simplified - GitLab 8.14 introduced a new way to integrate with JIRA which greatly simplified
the configuration options you have to enter. If you are using an older version, the configuration options you have to enter. If you are using an older version,
[follow this documentation][jira-repo-old-docs]. [follow this documentation][jira-repo-old-docs].
- In order to support Oracle's Access Manager, GitLab will send additional cookies
to enable Basic Auth. The cookie being added to each request is `OBBasicAuth` with
a value of `fromDialog`.
To enable JIRA integration in a project, navigate to the To enable JIRA integration in a project, navigate to the
[Integrations page](project_services.md#accessing-the-project-services), click [Integrations page](project_services.md#accessing-the-project-services), click
......
...@@ -163,3 +163,11 @@ For Windows, you can use `wincred` or Microsoft's [Git Credential Manager for Wi ...@@ -163,3 +163,11 @@ For Windows, you can use `wincred` or Microsoft's [Git Credential Manager for Wi
More details about various methods of storing the user credentials can be found More details about various methods of storing the user credentials can be found
on [Git Credential Storage documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage). on [Git Credential Storage documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage).
### LFS objects are missing on push
GitLab checks files to detect LFS pointers on push. If LFS pointers are detected, GitLab tries to verify that those files already exist in LFS on GitLab.
Verify that LFS in installed locally and consider a manual push with `git lfs push --all`.
If you are storing LFS files outside of GitLab you can disable LFS on the project by settting `lfs_enabled: false` with the [projets api](../../api/projects.md#edit-project).
module Gitlab
# This class implements a simple rate limiter that can be used to throttle
# certain actions. Unlike Rack Attack and Rack::Throttle, which operate at
# the middleware level, this can be used at the controller level.
class ActionRateLimiter
TIME_TO_EXPIRE = 60 # 1 min
attr_accessor :action, :expiry_time
def initialize(action:, expiry_time: TIME_TO_EXPIRE)
@action = action
@expiry_time = expiry_time
end
# Increments the given cache key and increments the value by 1 with the
# given expiration time. Returns the incremented value.
#
# key - An array of ActiveRecord instances
def increment(key)
value = 0
Gitlab::Redis::Cache.with do |redis|
cache_key = action_key(key)
value = redis.incr(cache_key)
redis.expire(cache_key, expiry_time) if value == 1
end
value
end
# Increments the given key and returns true if the action should
# be throttled.
#
# key - An array of ActiveRecord instances
# threshold_value - The maximum number of times this action should occur in the given time interval
def throttled?(key, threshold_value)
self.increment(key) > threshold_value
end
private
def action_key(key)
serialized = key.map { |obj| "#{obj.class.model_name.to_s.underscore}:#{obj.id}" }.join(":")
"action_rate_limiter:#{action}:#{serialized}"
end
end
end
...@@ -6,7 +6,13 @@ module QA ...@@ -6,7 +6,13 @@ module QA
click_link name click_link name
end end
def filter_by_name(name)
fill_in 'Filter by name...', with: name
end
def has_subgroup?(name) def has_subgroup?(name)
filter_by_name(name)
page.has_link?(name) page.has_link?(name)
end end
......
...@@ -51,6 +51,9 @@ module QA ...@@ -51,6 +51,9 @@ module QA
driver.browser.save_screenshot(path) driver.browser.save_screenshot(path)
end end
# Keep only the screenshots generated from the last failing test suite
Capybara::Screenshot.prune_strategy = :keep_last_run
Capybara.configure do |config| Capybara.configure do |config|
config.default_driver = :chrome config.default_driver = :chrome
config.javascript_driver = :chrome config.javascript_driver = :chrome
......
#!/usr/bin/env ruby #!/usr/bin/env ruby
gitaly_dir = 'tmp/tests/gitaly' gitaly_dir = 'tmp/tests/gitaly'
env = { 'HOME' => File.expand_path('tmp/tests') } env = { 'HOME' => File.expand_path('tmp/tests'),
'GEM_PATH' => Gem.path.join(':') }
args = %W[#{gitaly_dir}/gitaly #{gitaly_dir}/config.toml] args = %W[#{gitaly_dir}/gitaly #{gitaly_dir}/config.toml]
# Print the PID of the spawned process # Print the PID of the spawned process
......
...@@ -874,7 +874,7 @@ describe Projects::IssuesController do ...@@ -874,7 +874,7 @@ describe Projects::IssuesController do
end end
it 'delegates the update of the todos count cache to TodoService' do it 'delegates the update of the todos count cache to TodoService' do
expect_any_instance_of(TodoService).to receive(:destroy_issuable).with(issue, owner).once expect_any_instance_of(TodoService).to receive(:destroy_target).with(issue).once
delete :destroy, namespace_id: project.namespace, project_id: project, id: issue.iid delete :destroy, namespace_id: project.namespace, project_id: project, id: issue.iid
end end
......
...@@ -469,7 +469,7 @@ describe Projects::MergeRequestsController do ...@@ -469,7 +469,7 @@ describe Projects::MergeRequestsController do
end end
it 'delegates the update of the todos count cache to TodoService' do it 'delegates the update of the todos count cache to TodoService' do
expect_any_instance_of(TodoService).to receive(:destroy_issuable).with(merge_request, owner).once expect_any_instance_of(TodoService).to receive(:destroy_target).with(merge_request).once
delete :destroy, namespace_id: project.namespace, project_id: project, id: merge_request.iid delete :destroy, namespace_id: project.namespace, project_id: project, id: merge_request.iid
end end
......
...@@ -3,10 +3,12 @@ require 'spec_helper' ...@@ -3,10 +3,12 @@ require 'spec_helper'
describe Projects::PipelineSchedulesController do describe Projects::PipelineSchedulesController do
include AccessMatchersForController include AccessMatchersForController
set(:project) { create(:project, :public) } set(:project) { create(:project, :public, :repository) }
let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project) } set(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project) }
describe 'GET #index' do describe 'GET #index' do
render_views
let(:scope) { nil } let(:scope) { nil }
let!(:inactive_pipeline_schedule) do let!(:inactive_pipeline_schedule) do
create(:ci_pipeline_schedule, :inactive, project: project) create(:ci_pipeline_schedule, :inactive, project: project)
...@@ -96,7 +98,7 @@ describe Projects::PipelineSchedulesController do ...@@ -96,7 +98,7 @@ describe Projects::PipelineSchedulesController do
end end
end end
context 'when variables_attributes has two variables and duplicted' do context 'when variables_attributes has two variables and duplicated' do
let(:schedule) do let(:schedule) do
basic_param.merge({ basic_param.merge({
variables_attributes: [{ key: 'AAA', value: 'AAA123' }, { key: 'AAA', value: 'BBB123' }] variables_attributes: [{ key: 'AAA', value: 'AAA123' }, { key: 'AAA', value: 'BBB123' }]
...@@ -364,6 +366,65 @@ describe Projects::PipelineSchedulesController do ...@@ -364,6 +366,65 @@ describe Projects::PipelineSchedulesController do
end end
end end
describe 'POST #play', :clean_gitlab_redis_cache do
set(:user) { create(:user) }
let(:ref) { 'master' }
before do
project.add_developer(user)
sign_in(user)
end
context 'when an anonymous user makes the request' do
before do
sign_out(user)
end
it 'does not allow pipeline to be executed' do
expect(RunPipelineScheduleWorker).not_to receive(:perform_async)
post :play, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id
expect(response).to have_gitlab_http_status(404)
end
end
context 'when a developer makes the request' do
it 'executes a new pipeline' do
expect(RunPipelineScheduleWorker).to receive(:perform_async).with(pipeline_schedule.id, user.id).and_return('job-123')
post :play, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id
expect(flash[:notice]).to start_with 'Successfully scheduled a pipeline to run'
expect(response).to have_gitlab_http_status(302)
end
it 'prevents users from scheduling the same pipeline repeatedly' do
2.times do
post :play, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id
end
expect(flash.to_a.size).to eq(2)
expect(flash[:alert]).to eq 'You cannot play this scheduled pipeline at the moment. Please wait a minute.'
expect(response).to have_gitlab_http_status(302)
end
end
context 'when a developer attempts to schedule a protected ref' do
it 'does not allow pipeline to be executed' do
create(:protected_branch, project: project, name: ref)
protected_schedule = create(:ci_pipeline_schedule, project: project, ref: ref)
expect(RunPipelineScheduleWorker).not_to receive(:perform_async)
post :play, namespace_id: project.namespace.to_param, project_id: project, id: protected_schedule.id
expect(response).to have_gitlab_http_status(404)
end
end
end
describe 'DELETE #destroy' do describe 'DELETE #destroy' do
set(:user) { create(:user) } set(:user) { create(:user) }
......
...@@ -32,6 +32,24 @@ describe 'Help Pages' do ...@@ -32,6 +32,24 @@ describe 'Help Pages' do
it_behaves_like 'help page', prefix: '/gitlab' it_behaves_like 'help page', prefix: '/gitlab'
end end
context 'quick link shortcuts', :js do
before do
visit help_path
end
it 'focuses search bar' do
find('.js-trigger-search-bar').click
expect(page).to have_selector('#search:focus')
end
it 'opens shortcuts help dialog' do
find('.js-trigger-shortcut').click
expect(page).to have_selector('#modal-shortcuts')
end
end
end end
context 'in a production environment with version check enabled', :js do context 'in a production environment with version check enabled', :js do
......
This diff is collapsed.
...@@ -2,15 +2,15 @@ require 'spec_helper' ...@@ -2,15 +2,15 @@ require 'spec_helper'
feature 'project owner sees a link to create a license file in empty project', :js do feature 'project owner sees a link to create a license file in empty project', :js do
let(:project_master) { create(:user) } let(:project_master) { create(:user) }
let(:project) { create(:project) } let(:project) { create(:project_empty_repo) }
background do background do
project.team << [project_master, :master] project.add_master(project_master)
sign_in(project_master) sign_in(project_master)
end end
scenario 'project master creates a license file from a template' do scenario 'project master creates a license file from a template' do
visit project_path(project) visit project_path(project)
click_link 'Create empty bare repository'
click_on 'LICENSE' click_on 'LICENSE'
expect(page).to have_content('New file') expect(page).to have_content('New file')
...@@ -26,8 +26,6 @@ feature 'project owner sees a link to create a license file in empty project', : ...@@ -26,8 +26,6 @@ feature 'project owner sees a link to create a license file in empty project', :
expect(file_content).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}") expect(file_content).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}")
fill_in :commit_message, with: 'Add a LICENSE file', visible: true fill_in :commit_message, with: 'Add a LICENSE file', visible: true
# Remove pre-receive hook so we can push without auth
FileUtils.rm_f(File.join(project.repository.path, 'hooks', 'pre-receive'))
click_button 'Commit changes' click_button 'Commit changes'
expect(current_path).to eq( expect(current_path).to eq(
......
...@@ -152,7 +152,7 @@ describe 'Pipeline', :js do ...@@ -152,7 +152,7 @@ describe 'Pipeline', :js do
end end
it 'shows counter in Jobs tab' do it 'shows counter in Jobs tab' do
expect(page.find('.js-builds-counter').text).to eq(pipeline.statuses.count.to_s) expect(page.find('.js-builds-counter').text).to eq(pipeline.total_size.to_s)
end end
it 'shows Pipeline tab as active' do it 'shows Pipeline tab as active' do
...@@ -248,7 +248,7 @@ describe 'Pipeline', :js do ...@@ -248,7 +248,7 @@ describe 'Pipeline', :js do
end end
it 'shows counter in Jobs tab' do it 'shows counter in Jobs tab' do
expect(page.find('.js-builds-counter').text).to eq(pipeline.statuses.count.to_s) expect(page.find('.js-builds-counter').text).to eq(pipeline.total_size.to_s)
end end
it 'shows Jobs tab as active' do it 'shows Jobs tab as active' do
......
...@@ -4,18 +4,17 @@ feature 'Master views tags' do ...@@ -4,18 +4,17 @@ feature 'Master views tags' do
let(:user) { create(:user) } let(:user) { create(:user) }
before do before do
project.team << [user, :master] project.add_master(user)
sign_in(user) sign_in(user)
end end
context 'when project has no tags' do context 'when project has no tags' do
let(:project) { create(:project_empty_repo) } let(:project) { create(:project_empty_repo) }
before do before do
visit project_path(project) visit project_path(project)
click_on 'README' click_on 'README'
fill_in :commit_message, with: 'Add a README file', visible: true fill_in :commit_message, with: 'Add a README file', visible: true
# Remove pre-receive hook so we can push without auth
FileUtils.rm_f(File.join(project.repository.path, 'hooks', 'pre-receive'))
click_button 'Commit changes' click_button 'Commit changes'
visit project_tags_path(project) visit project_tags_path(project)
end end
......
...@@ -41,6 +41,7 @@ describe NotesHelper do ...@@ -41,6 +41,7 @@ describe NotesHelper do
describe '#discussion_path' do describe '#discussion_path' do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:anchor) { discussion.line_code }
context 'for a merge request discusion' do context 'for a merge request discusion' do
let(:merge_request) { create(:merge_request, source_project: project, target_project: project, importing: true) } let(:merge_request) { create(:merge_request, source_project: project, target_project: project, importing: true) }
...@@ -151,6 +152,15 @@ describe NotesHelper do ...@@ -151,6 +152,15 @@ describe NotesHelper do
expect(helper.discussion_path(discussion)).to be_nil expect(helper.discussion_path(discussion)).to be_nil
end end
end end
context 'for a contextual commit discussion' do
let(:commit) { merge_request.commits.last }
let(:discussion) { create(:diff_note_on_merge_request, noteable: merge_request, project: project, commit_id: commit.id).to_discussion }
it 'returns the merge request diff discussion scoped in the commit' do
expect(helper.discussion_path(discussion)).to eq(diffs_project_merge_request_path(project, merge_request, commit_id: commit.id, anchor: anchor))
end
end
end end
context 'for a commit discussion' do context 'for a commit discussion' do
...@@ -160,7 +170,7 @@ describe NotesHelper do ...@@ -160,7 +170,7 @@ describe NotesHelper do
let(:discussion) { create(:diff_note_on_commit, project: project).to_discussion } let(:discussion) { create(:diff_note_on_commit, project: project).to_discussion }
it 'returns the commit path with the line code' do it 'returns the commit path with the line code' do
expect(helper.discussion_path(discussion)).to eq(project_commit_path(project, commit, anchor: discussion.line_code)) expect(helper.discussion_path(discussion)).to eq(project_commit_path(project, commit, anchor: anchor))
end end
end end
...@@ -168,7 +178,7 @@ describe NotesHelper do ...@@ -168,7 +178,7 @@ describe NotesHelper do
let(:discussion) { create(:legacy_diff_note_on_commit, project: project).to_discussion } let(:discussion) { create(:legacy_diff_note_on_commit, project: project).to_discussion }
it 'returns the commit path with the line code' do it 'returns the commit path with the line code' do
expect(helper.discussion_path(discussion)).to eq(project_commit_path(project, commit, anchor: discussion.line_code)) expect(helper.discussion_path(discussion)).to eq(project_commit_path(project, commit, anchor: anchor))
end end
end end
......
import ContributorsStatGraph from '~/graphs/stat_graph_contributors';
import { ContributorsGraph } from '~/graphs/stat_graph_contributors_graph';
import { setLanguage } from '../helpers/locale_helper';
describe('ContributorsStatGraph', () => {
describe('change_date_header', () => {
beforeAll(() => {
setLanguage('de');
});
afterAll(() => {
setLanguage(null);
});
it('uses the locale to display date ranges', () => {
ContributorsGraph.init_x_domain([{ date: '2013-01-31' }, { date: '2012-01-31' }]);
setFixtures('<div id="date_header"></div>');
const graph = new ContributorsStatGraph();
graph.change_date_header();
expect(document.getElementById('date_header').innerText).toBe('31. Januar 2012 – 31. Januar 2013');
});
});
});
/* eslint-disable import/prefer-default-export */
export const setLanguage = (languageCode) => {
const htmlElement = document.querySelector('html');
if (languageCode) {
htmlElement.setAttribute('lang', languageCode);
} else {
htmlElement.removeAttribute('lang');
}
};
import { dateTickFormat, initDateFormats } from '~/lib/utils/tick_formats';
import { setLanguage } from '../../helpers/locale_helper';
describe('tick formats', () => {
describe('dateTickFormat', () => {
beforeAll(() => {
setLanguage('de');
initDateFormats();
});
afterAll(() => {
setLanguage(null);
});
it('returns year for first of January', () => {
const tick = dateTickFormat(new Date('2001-01-01'));
expect(tick).toBe('2001');
});
it('returns month for first of February', () => {
const tick = dateTickFormat(new Date('2001-02-01'));
expect(tick).toBe('Februar');
});
it('returns day and month for second of February', () => {
const tick = dateTickFormat(new Date('2001-02-02'));
expect(tick).toBe('2. Feb.');
});
it('ignores time', () => {
const tick = dateTickFormat(new Date('2001-02-02 12:34:56'));
expect(tick).toBe('2. Feb.');
});
});
});
import { createDateTimeFormat, languageCode } from '~/locale';
import { setLanguage } from '../helpers/locale_helper';
describe('locale', () => {
afterEach(() => {
setLanguage(null);
});
describe('languageCode', () => {
it('parses the lang attribute', () => {
setLanguage('ja');
expect(languageCode()).toBe('ja');
});
it('falls back to English', () => {
setLanguage(null);
expect(languageCode()).toBe('en');
});
});
describe('createDateTimeFormat', () => {
beforeEach(() => {
setLanguage('de');
});
it('creates an instance of Intl.DateTimeFormat', () => {
const dateFormat = createDateTimeFormat({ year: 'numeric', month: 'long', day: 'numeric' });
expect(dateFormat.format(new Date(2015, 6, 3))).toBe('3. Juli 2015');
});
});
});
<<<<<<< HEAD
import MergeRequestStore from 'ee/vue_merge_request_widget/stores/mr_widget_store'; import MergeRequestStore from 'ee/vue_merge_request_widget/stores/mr_widget_store';
import { stateKey } from '~/vue_merge_request_widget/stores/state_maps'; import { stateKey } from '~/vue_merge_request_widget/stores/state_maps';
import mockData, { import mockData, {
...@@ -10,6 +11,11 @@ import mockData, { ...@@ -10,6 +11,11 @@ import mockData, {
dockerReport, dockerReport,
dockerReportParsed, dockerReportParsed,
} from '../mock_data'; } from '../mock_data';
=======
import MergeRequestStore from '~/vue_merge_request_widget/stores/mr_widget_store';
import { stateKey } from '~/vue_merge_request_widget/stores/state_maps';
import mockData from '../mock_data';
>>>>>>> upstream/master
describe('MergeRequestStore', () => { describe('MergeRequestStore', () => {
let store; let store;
...@@ -62,6 +68,18 @@ describe('MergeRequestStore', () => { ...@@ -62,6 +68,18 @@ describe('MergeRequestStore', () => {
expect(store.isPipelineSkipped).toBe(false); expect(store.isPipelineSkipped).toBe(false);
}); });
}); });
describe('isNothingToMergeState', () => {
it('returns true when nothingToMerge', () => {
store.state = stateKey.nothingToMerge;
expect(store.isNothingToMergeState).toEqual(true);
});
it('returns false when not nothingToMerge', () => {
store.state = 'state';
expect(store.isNothingToMergeState).toEqual(false);
});
});
}); });
describe('compareCodeclimateMetrics', () => { describe('compareCodeclimateMetrics', () => {
......
require 'spec_helper'
describe Gitlab::ActionRateLimiter do
let(:redis) { double('redis') }
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:key) { [user, project] }
let(:cache_key) { "action_rate_limiter:test_action:user:#{user.id}:project:#{project.id}" }
subject { described_class.new(action: :test_action, expiry_time: 100) }
before do
allow(Gitlab::Redis::Cache).to receive(:with).and_yield(redis)
end
it 'increases the throttle count and sets the expire time' do
expect(redis).to receive(:incr).with(cache_key).and_return(1)
expect(redis).to receive(:expire).with(cache_key, 100)
expect(subject.throttled?(key, 1)).to be false
end
it 'returns true if the key is throttled' do
expect(redis).to receive(:incr).with(cache_key).and_return(2)
expect(redis).not_to receive(:expire)
expect(subject.throttled?(key, 1)).to be true
end
end
...@@ -22,4 +22,51 @@ describe BlobViewer::PackageJson do ...@@ -22,4 +22,51 @@ describe BlobViewer::PackageJson do
expect(subject.package_name).to eq('module-name') expect(subject.package_name).to eq('module-name')
end end
end end
describe '#package_url' do
it 'returns the package URL' do
expect(subject).to receive(:prepare!)
expect(subject.package_url).to eq("https://www.npmjs.com/package/#{subject.package_name}")
end
end
describe '#package_type' do
it 'returns "package"' do
expect(subject).to receive(:prepare!)
expect(subject.package_type).to eq('package')
end
end
context 'when package.json has "private": true' do
let(:data) do
<<-SPEC.strip_heredoc
{
"name": "module-name",
"version": "10.3.1",
"private": true,
"homepage": "myawesomepackage.com"
}
SPEC
end
let(:blob) { fake_blob(path: 'package.json', data: data) }
subject { described_class.new(blob) }
describe '#package_url' do
it 'returns homepage if any' do
expect(subject).to receive(:prepare!)
expect(subject.package_url).to eq('myawesomepackage.com')
end
end
describe '#package_type' do
it 'returns "private package"' do
expect(subject).to receive(:prepare!)
expect(subject.package_type).to eq('private package')
end
end
end
end end
...@@ -1534,4 +1534,16 @@ describe Ci::Pipeline, :mailer do ...@@ -1534,4 +1534,16 @@ describe Ci::Pipeline, :mailer do
expect(query_count).to eq(1) expect(query_count).to eq(1)
end end
end end
describe '#total_size' do
let!(:build_job1) { create(:ci_build, pipeline: pipeline, stage_idx: 0) }
let!(:build_job2) { create(:ci_build, pipeline: pipeline, stage_idx: 0) }
let!(:test_job_failed_and_retried) { create(:ci_build, :failed, :retried, pipeline: pipeline, stage_idx: 1) }
let!(:second_test_job) { create(:ci_build, pipeline: pipeline, stage_idx: 1) }
let!(:deploy_job) { create(:ci_build, pipeline: pipeline, stage_idx: 2) }
it 'returns all jobs (including failed and retried)' do
expect(pipeline.total_size).to eq(5)
end
end
end end
...@@ -395,6 +395,26 @@ describe JiraService do ...@@ -395,6 +395,26 @@ describe JiraService do
end end
end end
describe 'additional cookies' do
let(:project) { create(:project) }
context 'provides additional cookies to allow basic auth with oracle webgate' do
before do
@service = project.create_jira_service(
active: true, properties: { url: 'http://jira.com' })
end
after do
@service.destroy!
end
it 'is initialized' do
expect(@service.options[:use_cookies]).to eq(true)
expect(@service.options[:additional_cookies]).to eq(["OBBasicAuth=fromDialog"])
end
end
end
describe 'project and issue urls' do describe 'project and issue urls' do
let(:project) { create(:project) } let(:project) { create(:project) }
......
...@@ -1211,6 +1211,15 @@ describe Repository do ...@@ -1211,6 +1211,15 @@ describe Repository do
end end
end end
describe '#tag_exists?' do
it 'uses tag_names' do
allow(repository).to receive(:tag_names).and_return(['foobar'])
expect(repository.tag_exists?('foobar')).to eq(true)
expect(repository.tag_exists?('master')).to eq(false)
end
end
describe '#branch_names', :use_clean_rails_memory_store_caching do describe '#branch_names', :use_clean_rails_memory_store_caching do
let(:fake_branch_names) { ['foobar'] } let(:fake_branch_names) { ['foobar'] }
......
require 'spec_helper'
describe Ci::PipelineSchedulePolicy, :models do
set(:user) { create(:user) }
set(:project) { create(:project, :repository) }
set(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly, project: project) }
let(:policy) do
described_class.new(user, pipeline_schedule)
end
describe 'rules' do
describe 'rules for protected ref' do
before do
project.add_developer(user)
end
context 'when no one can push or merge to the branch' do
before do
create(:protected_branch, :no_one_can_push,
name: pipeline_schedule.ref, project: project)
end
it 'does not include ability to play pipeline schedule' do
expect(policy).to be_disallowed :play_pipeline_schedule
end
end
context 'when developers can push to the branch' do
before do
create(:protected_branch, :developers_can_merge,
name: pipeline_schedule.ref, project: project)
end
it 'includes ability to update pipeline' do
expect(policy).to be_allowed :play_pipeline_schedule
end
end
context 'when no one can create the tag' do
let(:tag) { 'v1.0.0' }
before do
pipeline_schedule.update(ref: tag)
create(:protected_tag, :no_one_can_create,
name: pipeline_schedule.ref, project: project)
end
it 'does not include ability to play pipeline schedule' do
expect(policy).to be_disallowed :play_pipeline_schedule
end
end
context 'when no one can create the tag but it is not a tag' do
before do
create(:protected_tag, :no_one_can_create,
name: pipeline_schedule.ref, project: project)
end
it 'includes ability to play pipeline schedule' do
expect(policy).to be_allowed :play_pipeline_schedule
end
end
end
describe 'rules for owner of schedule' do
before do
project.add_developer(user)
pipeline_schedule.update(owner: user)
end
it 'includes abilities to do do all operations on pipeline schedule' do
expect(policy).to be_allowed :play_pipeline_schedule
expect(policy).to be_allowed :update_pipeline_schedule
expect(policy).to be_allowed :admin_pipeline_schedule
end
end
describe 'rules for a master' do
before do
project.add_master(user)
end
it 'includes abilities to do do all operations on pipeline schedule' do
expect(policy).to be_allowed :play_pipeline_schedule
expect(policy).to be_allowed :update_pipeline_schedule
expect(policy).to be_allowed :admin_pipeline_schedule
end
end
end
end
...@@ -2,7 +2,7 @@ require 'spec_helper' ...@@ -2,7 +2,7 @@ require 'spec_helper'
describe Issuable::DestroyService do describe Issuable::DestroyService do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project) } let(:project) { create(:project, :public) }
subject(:service) { described_class.new(project, user) } subject(:service) { described_class.new(project, user) }
...@@ -19,6 +19,13 @@ describe Issuable::DestroyService do ...@@ -19,6 +19,13 @@ describe Issuable::DestroyService do
service.execute(issue) service.execute(issue)
end end
it 'updates the todo caches for users with todos on the issue' do
create(:todo, target: issue, user: user, author: user, project: project)
expect { service.execute(issue) }
.to change { user.todos_pending_count }.from(1).to(0)
end
end end
context 'when issuable is a merge request' do context 'when issuable is a merge request' do
...@@ -33,6 +40,13 @@ describe Issuable::DestroyService do ...@@ -33,6 +40,13 @@ describe Issuable::DestroyService do
service.execute(merge_request) service.execute(merge_request)
end end
it 'updates the todo caches for users with todos on the merge request' do
create(:todo, target: merge_request, user: user, author: user, project: project)
expect { service.execute(merge_request) }
.to change { user.todos_pending_count }.from(1).to(0)
end
end end
end end
end end
require 'spec_helper' require 'spec_helper'
describe Notes::DestroyService do describe Notes::DestroyService do
set(:project) { create(:project, :public) }
set(:issue) { create(:issue, project: project) }
let(:user) { issue.author }
describe '#execute' do describe '#execute' do
it 'deletes a note' do it 'deletes a note' do
project = create(:project)
issue = create(:issue, project: project)
note = create(:note, project: project, noteable: issue) note = create(:note, project: project, noteable: issue)
described_class.new(project, note.author).execute(note) described_class.new(project, user).execute(note)
expect(project.issues.find(issue.id).notes).not_to include(note) expect(project.issues.find(issue.id).notes).not_to include(note)
end end
it 'updates the todo counts for users with todos for the note' do
note = create(:note, project: project, noteable: issue)
create(:todo, note: note, target: issue, user: user, author: user, project: project)
expect { described_class.new(project, user).execute(note) }
.to change { user.todos_pending_count }.from(1).to(0)
end
end end
end end
...@@ -248,11 +248,26 @@ describe TodoService do ...@@ -248,11 +248,26 @@ describe TodoService do
end end
end end
describe '#destroy_issuable' do describe '#destroy_target' do
it 'refresh the todos count cache for the user' do it 'refreshes the todos count cache for users with todos on the target' do
expect(john_doe).to receive(:update_todos_count_cache).and_call_original create(:todo, target: issue, user: john_doe, author: john_doe, project: issue.project)
expect_any_instance_of(User).to receive(:update_todos_count_cache).and_call_original
service.destroy_target(issue) { }
end
it 'does not refresh the todos count cache for users with only done todos on the target' do
create(:todo, :done, target: issue, user: john_doe, author: john_doe, project: issue.project)
expect_any_instance_of(User).not_to receive(:update_todos_count_cache)
service.destroy_target(issue) { }
end
service.destroy_issuable(issue, john_doe) it 'yields the target to the caller' do
expect { |b| service.destroy_target(issue, &b) }
.to yield_with_args(issue)
end end
end end
......
require 'spec_helper'
describe 'events/event/_push.html.haml' do
let(:event) { build_stubbed(:push_event) }
context 'with a branch' do
let(:payload) { build_stubbed(:push_event_payload, event: event) }
before do
allow(event).to receive(:push_event_payload).and_return(payload)
end
it 'links to the branch' do
allow(event.project.repository).to receive(:branch_exists?).with(event.ref_name).and_return(true)
link = project_commits_path(event.project, event.ref_name)
render partial: 'events/event/push', locals: { event: event }
expect(rendered).to have_link(event.ref_name, href: link)
end
context 'that has been deleted' do
it 'does not link to the branch' do
render partial: 'events/event/push', locals: { event: event }
expect(rendered).not_to have_link(event.ref_name)
end
end
end
context 'with a tag' do
let(:payload) { build_stubbed(:push_event_payload, event: event, ref_type: :tag, ref: 'v0.1.0') }
before do
allow(event).to receive(:push_event_payload).and_return(payload)
end
it 'links to the tag' do
allow(event.project.repository).to receive(:tag_exists?).with(event.ref_name).and_return(true)
link = project_commits_path(event.project, event.ref_name)
render partial: 'events/event/push', locals: { event: event }
expect(rendered).to have_link(event.ref_name, href: link)
end
context 'that has been deleted' do
it 'does not link to the tag' do
render partial: 'events/event/push', locals: { event: event }
expect(rendered).not_to have_link(event.ref_name)
end
end
end
end
require 'spec_helper'
describe RunPipelineScheduleWorker do
describe '#perform' do
set(:project) { create(:project) }
set(:user) { create(:user) }
set(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly, project: project ) }
let(:worker) { described_class.new }
context 'when a project not found' do
it 'does not call the Service' do
expect(Ci::CreatePipelineService).not_to receive(:new)
expect(worker).not_to receive(:run_pipeline_schedule)
worker.perform(100000, user.id)
end
end
context 'when a user not found' do
it 'does not call the Service' do
expect(Ci::CreatePipelineService).not_to receive(:new)
expect(worker).not_to receive(:run_pipeline_schedule)
worker.perform(pipeline_schedule.id, 10000)
end
end
context 'when everything is ok' do
let(:create_pipeline_service) { instance_double(Ci::CreatePipelineService) }
it 'calls the Service' do
expect(Ci::CreatePipelineService).to receive(:new).with(project, user, ref: pipeline_schedule.ref).and_return(create_pipeline_service)
expect(create_pipeline_service).to receive(:execute).with(:schedule, ignore_skip_ci: true, save_on_errors: false, schedule: pipeline_schedule)
worker.perform(pipeline_schedule.id, user.id)
end
end
end
end
...@@ -89,7 +89,7 @@ sast: ...@@ -89,7 +89,7 @@ sast:
POSTGRES_DB: "false" POSTGRES_DB: "false"
allow_failure: true allow_failure: true
script: script:
- /app/bin/run . - sast .
artifacts: artifacts:
paths: [gl-sast-report.json] paths: [gl-sast-report.json]
...@@ -232,6 +232,17 @@ production: ...@@ -232,6 +232,17 @@ production:
docker run ${cc_opts} codeclimate/codeclimate:0.69.0 analyze -f json > codeclimate.json docker run ${cc_opts} codeclimate/codeclimate:0.69.0 analyze -f json > codeclimate.json
} }
function sast() {
case "$CI_SERVER_VERSION" in
*-ee)
/app/bin/run "$@"
;;
*)
echo "GitLab EE is required"
;;
esac
}
function deploy() { function deploy() {
track="${1-stable}" track="${1-stable}"
name="$CI_ENVIRONMENT_SLUG" name="$CI_ENVIRONMENT_SLUG"
......
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