Commit dfaa942f authored by Stan Hu's avatar Stan Hu

Merge branch 'ce-to-ee-2018-12-07' into 'master'

CE upstream - 2018-12-07 20:59 UTC

Closes gitlab-ce#54862 and gitlab-runner#3807

See merge request gitlab-org/gitlab-ee!8762
parents f9f8ebea b4c2ab99
...@@ -84,6 +84,9 @@ export default { ...@@ -84,6 +84,9 @@ export default {
ingressExternalIp() { ingressExternalIp() {
return this.applications.ingress.externalIp; return this.applications.ingress.externalIp;
}, },
certManagerInstalled() {
return this.applications.cert_manager.status === APPLICATION_STATUS.INSTALLED;
},
ingressDescription() { ingressDescription() {
const extraCostParagraph = sprintf( const extraCostParagraph = sprintf(
_.escape( _.escape(
...@@ -130,9 +133,9 @@ export default { ...@@ -130,9 +133,9 @@ export default {
return sprintf( return sprintf(
_.escape( _.escape(
s__( s__(
`ClusterIntegration|cert-manager is a native Kubernetes certificate management controller that helps with issuing certificates. `ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates.
Installing cert-manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates
are valid and up to date.`, are valid and up-to-date.`,
), ),
), ),
{ {
...@@ -259,6 +262,16 @@ export default { ...@@ -259,6 +262,16 @@ export default {
</span> </span>
</div> </div>
<input v-else type="text" class="form-control js-ip-address" readonly value="?" /> <input v-else type="text" class="form-control js-ip-address" readonly value="?" />
<p class="form-text text-muted">
{{
s__(`ClusterIntegration|Point a wildcard DNS to this
generated IP address in order to access
your application after it has been deployed.`)
}}
<a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
{{ __('More information') }}
</a>
</p>
</div> </div>
<p v-if="!ingressExternalIp" class="settings-message js-no-ip-message"> <p v-if="!ingressExternalIp" class="settings-message js-no-ip-message">
...@@ -272,17 +285,6 @@ export default { ...@@ -272,17 +285,6 @@ export default {
{{ __('More information') }} {{ __('More information') }}
</a> </a>
</p> </p>
<p>
{{
s__(`ClusterIntegration|Point a wildcard DNS to this
generated IP address in order to access
your application after it has been deployed.`)
}}
<a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
{{ __('More information') }}
</a>
</p>
</template> </template>
<div v-html="ingressDescription"></div> <div v-html="ingressDescription"></div>
</div> </div>
...@@ -295,10 +297,41 @@ export default { ...@@ -295,10 +297,41 @@ export default {
:status-reason="applications.cert_manager.statusReason" :status-reason="applications.cert_manager.statusReason"
:request-status="applications.cert_manager.requestStatus" :request-status="applications.cert_manager.requestStatus"
:request-reason="applications.cert_manager.requestReason" :request-reason="applications.cert_manager.requestReason"
:install-application-request-params="{ email: applications.cert_manager.email }"
:disabled="!helmInstalled" :disabled="!helmInstalled"
title-link="https://cert-manager.readthedocs.io/en/latest/#" title-link="https://cert-manager.readthedocs.io/en/latest/#"
> >
<div slot="description" v-html="certManagerDescription"></div> <template>
<div slot="description">
<p v-html="certManagerDescription"></p>
<div class="form-group">
<label for="cert-manager-issuer-email">
{{ s__('ClusterIntegration|Issuer Email') }}
</label>
<div class="input-group">
<input
v-model="applications.cert_manager.email"
:readonly="certManagerInstalled"
type="text"
class="form-control js-email"
/>
</div>
<p class="form-text text-muted">
{{
s__(`ClusterIntegration|Issuers represent a certificate authority.
You must provide an email address for your Issuer. `)
}}
<a
href="http://docs.cert-manager.io/en/latest/reference/issuers.html?highlight=email"
target="_blank"
rel="noopener noreferrer"
>
{{ __('More information') }}
</a>
</p>
</div>
</div>
</template>
</application-row> </application-row>
<application-row <application-row
v-if="isProjectCluster" v-if="isProjectCluster"
...@@ -381,16 +414,17 @@ export default { ...@@ -381,16 +414,17 @@ export default {
/> />
</span> </span>
</div> </div>
<p v-if="ingressInstalled" class="form-text text-muted">
{{
s__(`ClusterIntegration|Replace this with your own hostname if you want.
If you do so, point hostname to Ingress IP Address from above.`)
}}
<a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
{{ __('More information') }}
</a>
</p>
</div> </div>
<p v-if="ingressInstalled">
{{
s__(`ClusterIntegration|Replace this with your own hostname if you want.
If you do so, point hostname to Ingress IP Address from above.`)
}}
<a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
{{ __('More information') }}
</a>
</p>
</template> </template>
</div> </div>
</application-row> </application-row>
......
...@@ -24,3 +24,4 @@ export const REQUEST_FAILURE = 'request-failure'; ...@@ -24,3 +24,4 @@ export const REQUEST_FAILURE = 'request-failure';
export const INGRESS = 'ingress'; export const INGRESS = 'ingress';
export const JUPYTER = 'jupyter'; export const JUPYTER = 'jupyter';
export const KNATIVE = 'knative'; export const KNATIVE = 'knative';
export const CERT_MANAGER = 'cert_manager';
import { s__ } from '../../locale'; import { s__ } from '../../locale';
import { INGRESS, JUPYTER, KNATIVE } from '../constants'; import { INGRESS, JUPYTER, KNATIVE, CERT_MANAGER } from '../constants';
export default class ClusterStore { export default class ClusterStore {
constructor() { constructor() {
...@@ -30,6 +30,7 @@ export default class ClusterStore { ...@@ -30,6 +30,7 @@ export default class ClusterStore {
statusReason: null, statusReason: null,
requestStatus: null, requestStatus: null,
requestReason: null, requestReason: null,
email: null,
}, },
runner: { runner: {
title: s__('ClusterIntegration|GitLab Runner'), title: s__('ClusterIntegration|GitLab Runner'),
...@@ -103,6 +104,9 @@ export default class ClusterStore { ...@@ -103,6 +104,9 @@ export default class ClusterStore {
if (appId === INGRESS) { if (appId === INGRESS) {
this.state.applications.ingress.externalIp = serverAppEntry.external_ip; this.state.applications.ingress.externalIp = serverAppEntry.external_ip;
} else if (appId === CERT_MANAGER) {
this.state.applications.cert_manager.email =
this.state.applications.cert_manager.email || serverAppEntry.email;
} else if (appId === JUPYTER) { } else if (appId === JUPYTER) {
this.state.applications.jupyter.hostname = this.state.applications.jupyter.hostname =
serverAppEntry.hostname || serverAppEntry.hostname ||
......
...@@ -218,8 +218,9 @@ export const toggleFileDiscussions = ({ getters, dispatch }, diff) => { ...@@ -218,8 +218,9 @@ export const toggleFileDiscussions = ({ getters, dispatch }, diff) => {
}); });
}; };
export const saveDiffDiscussion = ({ dispatch }, { note, formData }) => { export const saveDiffDiscussion = ({ state, dispatch }, { note, formData }) => {
const postData = getNoteFormData({ const postData = getNoteFormData({
commit: state.commit,
note, note,
...formData, ...formData,
}); });
......
...@@ -27,6 +27,7 @@ export const getReversePosition = linePosition => { ...@@ -27,6 +27,7 @@ export const getReversePosition = linePosition => {
export function getFormData(params) { export function getFormData(params) {
const { const {
commit,
note, note,
noteableType, noteableType,
noteableData, noteableData,
...@@ -66,7 +67,7 @@ export function getFormData(params) { ...@@ -66,7 +67,7 @@ export function getFormData(params) {
position, position,
noteable_type: noteableType, noteable_type: noteableType,
noteable_id: noteableData.id, noteable_id: noteableData.id,
commit_id: '', commit_id: commit && commit.id,
type: type:
diffFile.diff_refs.start_sha && diffFile.diff_refs.head_sha diffFile.diff_refs.start_sha && diffFile.diff_refs.head_sha
? DIFF_NOTE_TYPE ? DIFF_NOTE_TYPE
......
<script> <script>
import { __ } from '~/locale';
import { GlButton } from '@gitlab/ui'; import { GlButton } from '@gitlab/ui';
const HIDDEN_VALUE = '••••••';
export default { export default {
components: { components: {
GlButton, GlButton,
...@@ -13,17 +16,26 @@ export default { ...@@ -13,17 +16,26 @@ export default {
}, },
data() { data() {
return { return {
areVariablesVisible: false, showVariableValues: false,
}; };
}, },
computed: { computed: {
hasVariables() { hasVariables() {
return this.trigger.variables && this.trigger.variables.length > 0; return this.trigger.variables && this.trigger.variables.length > 0;
}, },
getToggleButtonText() {
return this.showVariableValues ? __('Hide values') : __('Reveal values');
},
hasValues() {
return this.trigger.variables.some(v => v.value);
},
}, },
methods: { methods: {
revealVariables() { toggleValues() {
this.areVariablesVisible = true; this.showVariableValues = !this.showVariableValues;
},
getDisplayValue(value) {
return this.showVariableValues ? value : HIDDEN_VALUE;
}, },
}, },
}; };
...@@ -33,31 +45,33 @@ export default { ...@@ -33,31 +45,33 @@ export default {
<div class="build-widget block"> <div class="build-widget block">
<h4 class="title">{{ __('Trigger') }}</h4> <h4 class="title">{{ __('Trigger') }}</h4>
<p v-if="trigger.short_token" class="js-short-token"> <p
v-if="trigger.short_token"
class="js-short-token"
:class="{ 'append-bottom-0': !hasVariables }"
>
<span class="build-light-text"> {{ __('Token') }} </span> {{ trigger.short_token }} <span class="build-light-text"> {{ __('Token') }} </span> {{ trigger.short_token }}
</p> </p>
<p v-if="hasVariables"> <template v-if="hasVariables">
<gl-button <p class="trigger-variables-btn-container">
v-if="!areVariablesVisible" <span class="build-light-text"> {{ __('Variables:') }} </span>
type="button"
class="btn btn-default group js-reveal-variables"
@click="revealVariables"
>
{{ __('Reveal Variables') }}
</gl-button>
</p>
<dl v-if="areVariablesVisible" class="js-build-variables trigger-build-variables"> <gl-button v-if="hasValues" class="group js-reveal-variables" @click="toggleValues">
<template v-for="variable in trigger.variables"> {{ getToggleButtonText }}
<dt :key="`${variable.key}-variable`" class="js-build-variable trigger-build-variable"> </gl-button>
{{ variable.key }} </p>
</dt>
<dd :key="`${variable.key}-value`" class="js-build-value trigger-build-value"> <table class="js-build-variables trigger-build-variables">
{{ variable.value }} <tr v-for="(variable, index) in trigger.variables" :key="`${variable.key}-${index}`">
</dd> <td class="js-build-variable trigger-build-variable trigger-variables-table-cell">
</template> {{ variable.key }}
</dl> </td>
<td class="js-build-value trigger-build-value trigger-variables-table-cell">
{{ getDisplayValue(variable.value) }}
</td>
</tr>
</table>
</template>
</div> </div>
</template> </template>
...@@ -155,7 +155,7 @@ export default class MilestoneSelect { ...@@ -155,7 +155,7 @@ export default class MilestoneSelect {
const { $el, e } = clickEvent; const { $el, e } = clickEvent;
let selected = clickEvent.selectedObj; let selected = clickEvent.selectedObj;
let data, boardsStore; let data, modalStoreFilter;
if (!selected) return; if (!selected) return;
if (options.handleClick) { if (options.handleClick) {
...@@ -179,11 +179,11 @@ export default class MilestoneSelect { ...@@ -179,11 +179,11 @@ export default class MilestoneSelect {
} }
if ($dropdown.closest('.add-issues-modal').length) { if ($dropdown.closest('.add-issues-modal').length) {
boardsStore = ModalStore.store.filter; modalStoreFilter = ModalStore.store.filter;
} }
if (boardsStore) { if (modalStoreFilter) {
boardsStore[$dropdown.data('fieldName')] = selected.name; modalStoreFilter[$dropdown.data('fieldName')] = selected.name;
e.preventDefault(); e.preventDefault();
} else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) { } else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
return Issuable.filterResults($dropdown.closest('form')); return Issuable.filterResults($dropdown.closest('form'));
......
...@@ -247,15 +247,19 @@ Please check your network connection and try again.`; ...@@ -247,15 +247,19 @@ Please check your network connection and try again.`;
} else { } else {
this.reopenIssue() this.reopenIssue()
.then(() => this.enableButton()) .then(() => this.enableButton())
.catch(() => { .catch(({ data }) => {
this.enableButton(); this.enableButton();
this.toggleStateButtonLoading(false); this.toggleStateButtonLoading(false);
Flash( let errorMessage = sprintf(
sprintf( __('Something went wrong while reopening the %{issuable}. Please try again later'),
__('Something went wrong while reopening the %{issuable}. Please try again later'), { issuable: this.noteableDisplayName },
{ issuable: this.noteableDisplayName },
),
); );
if (data) {
errorMessage = Object.values(data).join('\n');
}
Flash(errorMessage);
}); });
} }
}, },
......
...@@ -46,6 +46,7 @@ export default { ...@@ -46,6 +46,7 @@ export default {
class="sidebar-collapsed-icon" class="sidebar-collapsed-icon"
data-placement="left" data-placement="left"
data-container="body" data-container="body"
data-boundary="viewport"
@click="handleClick" @click="handleClick"
> >
<i aria-hidden="true" data-hidden="true" class="fa fa-tags"> </i> <i aria-hidden="true" data-hidden="true" class="fa fa-tags"> </i>
......
...@@ -383,6 +383,16 @@ ...@@ -383,6 +383,16 @@
top: 1px; top: 1px;
} }
} }
.dropdown-menu li a .identicon {
width: 17px;
height: 17px;
font-size: $gl-font-size-xs;
vertical-align: middle;
text-indent: 0;
line-height: $gl-font-size-xs + 2px;
display: inline-block;
}
} }
.breadcrumbs-list { .breadcrumbs-list {
......
...@@ -221,9 +221,16 @@ ...@@ -221,9 +221,16 @@
padding: 16px 0; padding: 16px 0;
} }
.trigger-variables-btn-container {
@extend .d-flex;
justify-content: space-between;
align-items: center;
}
.trigger-build-variables { .trigger-build-variables {
margin: 0; margin: 0;
overflow-x: auto; overflow-x: auto;
width: 100%;
-ms-overflow-style: scrollbar; -ms-overflow-style: scrollbar;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
} }
...@@ -236,7 +243,15 @@ ...@@ -236,7 +243,15 @@
.trigger-build-value { .trigger-build-value {
padding: 2px 4px; padding: 2px 4px;
color: $black; color: $black;
background-color: $white-light; }
.trigger-variables-table-cell {
font-size: $gl-font-size-small;
line-height: $gl-line-height;
border: 1px solid $theme-gray-200;
padding: $gl-padding-4 6px;
width: 50%;
vertical-align: top;
} }
.badge.badge-pill { .badge.badge-pill {
......
...@@ -8,7 +8,6 @@ class ApplicationController < ActionController::Base ...@@ -8,7 +8,6 @@ class ApplicationController < ActionController::Base
include GitlabRoutingHelper include GitlabRoutingHelper
include PageLayoutHelper include PageLayoutHelper
include SafeParamsHelper include SafeParamsHelper
include SentryHelper
include WorkhorseHelper include WorkhorseHelper
include EnforcesTwoFactorAuthentication include EnforcesTwoFactorAuthentication
include WithPerformanceBar include WithPerformanceBar
...@@ -129,6 +128,7 @@ class ApplicationController < ActionController::Base ...@@ -129,6 +128,7 @@ class ApplicationController < ActionController::Base
payload[:ua] = request.env["HTTP_USER_AGENT"] payload[:ua] = request.env["HTTP_USER_AGENT"]
payload[:remote_ip] = request.remote_ip payload[:remote_ip] = request.remote_ip
payload[Gitlab::CorrelationId::LOG_KEY] = Gitlab::CorrelationId.current_id
logged_user = auth_user logged_user = auth_user
...@@ -159,7 +159,7 @@ class ApplicationController < ActionController::Base ...@@ -159,7 +159,7 @@ class ApplicationController < ActionController::Base
end end
def log_exception(exception) def log_exception(exception)
Raven.capture_exception(exception) if sentry_enabled? Gitlab::Sentry.track_acceptable_exception(exception)
backtrace_cleaner = Gitlab.rails5? ? request.env["action_dispatch.backtrace_cleaner"] : env backtrace_cleaner = Gitlab.rails5? ? request.env["action_dispatch.backtrace_cleaner"] : env
application_trace = ActionDispatch::ExceptionWrapper.new(backtrace_cleaner, exception).application_trace application_trace = ActionDispatch::ExceptionWrapper.new(backtrace_cleaner, exception).application_trace
...@@ -495,4 +495,8 @@ class ApplicationController < ActionController::Base ...@@ -495,4 +495,8 @@ class ApplicationController < ActionController::Base
def impersonator def impersonator
@impersonator ||= User.find(session[:impersonator_id]) if session[:impersonator_id] @impersonator ||= User.find(session[:impersonator_id]) if session[:impersonator_id]
end end
def sentry_context
Gitlab::Sentry.context(current_user)
end
end end
...@@ -23,6 +23,6 @@ class Clusters::ApplicationsController < Clusters::BaseController ...@@ -23,6 +23,6 @@ class Clusters::ApplicationsController < Clusters::BaseController
end end
def create_cluster_application_params def create_cluster_application_params
params.permit(:application, :hostname) params.permit(:application, :hostname, :email)
end end
end end
...@@ -102,7 +102,7 @@ module IssuableCollections ...@@ -102,7 +102,7 @@ module IssuableCollections
elsif @group elsif @group
options[:group_id] = @group.id options[:group_id] = @group.id
options[:include_subgroups] = true options[:include_subgroups] = true
options[:use_cte_for_search] = true options[:attempt_group_search_optimizations] = true
end end
params.permit(finder_type.valid_params).merge(options) params.permit(finder_type.valid_params).merge(options)
......
...@@ -124,17 +124,21 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -124,17 +124,21 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
respond_to do |format| respond_to do |format|
format.html do format.html do
if @merge_request.valid? if @merge_request.errors.present?
redirect_to([@merge_request.target_project.namespace.becomes(Namespace), @merge_request.target_project, @merge_request])
else
define_edit_vars define_edit_vars
render :edit render :edit
else
redirect_to project_merge_request_path(@merge_request.target_project, @merge_request)
end end
end end
format.json do format.json do
render json: serializer.represent(@merge_request, serializer: 'basic') if merge_request.errors.present?
render json: @merge_request.errors, status: :bad_request
else
render json: serializer.represent(@merge_request, serializer: 'basic')
end
end end
end end
rescue ActiveRecord::StaleObjectError rescue ActiveRecord::StaleObjectError
......
...@@ -27,12 +27,13 @@ ...@@ -27,12 +27,13 @@
# created_before: datetime # created_before: datetime
# updated_after: datetime # updated_after: datetime
# updated_before: datetime # updated_before: datetime
# use_cte_for_search: boolean # attempt_group_search_optimizations: boolean
# #
class IssuableFinder class IssuableFinder
prepend FinderWithCrossProjectAccess prepend FinderWithCrossProjectAccess
include FinderMethods include FinderMethods
include CreatedAtFilter include CreatedAtFilter
include Gitlab::Utils::StrongMemoize
requires_cross_project_access unless: -> { project? } requires_cross_project_access unless: -> { project? }
...@@ -75,8 +76,9 @@ class IssuableFinder ...@@ -75,8 +76,9 @@ class IssuableFinder
items = init_collection items = init_collection
items = filter_items(items) items = filter_items(items)
# This has to be last as we may use a CTE as an optimization fence by # This has to be last as we may use a CTE as an optimization fence
# passing the use_cte_for_search param # by passing the attempt_group_search_optimizations param and
# enabling the use_cte_for_group_issues_search feature flag
# https://www.postgresql.org/docs/current/static/queries-with.html # https://www.postgresql.org/docs/current/static/queries-with.html
items = by_search(items) items = by_search(items)
...@@ -85,6 +87,8 @@ class IssuableFinder ...@@ -85,6 +87,8 @@ class IssuableFinder
def filter_items(items) def filter_items(items)
items = by_project(items) items = by_project(items)
items = by_group(items)
items = by_subquery(items)
items = by_scope(items) items = by_scope(items)
items = by_created_at(items) items = by_created_at(items)
items = by_updated_at(items) items = by_updated_at(items)
...@@ -282,12 +286,31 @@ class IssuableFinder ...@@ -282,12 +286,31 @@ class IssuableFinder
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
def use_subquery_for_search?
strong_memoize(:use_subquery_for_search) do
attempt_group_search_optimizations? &&
Feature.enabled?(:use_subquery_for_group_issues_search, default_enabled: false)
end
end
def use_cte_for_search?
strong_memoize(:use_cte_for_search) do
attempt_group_search_optimizations? &&
!use_subquery_for_search? &&
Feature.enabled?(:use_cte_for_group_issues_search, default_enabled: true)
end
end
private private
def init_collection def init_collection
klass.all klass.all
end end
def attempt_group_search_optimizations?
search && Gitlab::Database.postgresql? && params[:attempt_group_search_optimizations]
end
def count_key(value) def count_key(value)
Array(value).last.to_sym Array(value).last.to_sym
end end
...@@ -351,12 +374,13 @@ class IssuableFinder ...@@ -351,12 +374,13 @@ class IssuableFinder
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
def use_cte_for_search? # Wrap projects and groups in a subquery if the conditions are met.
return false unless search def by_subquery(items)
return false unless Gitlab::Database.postgresql? if use_subquery_for_search?
return false unless Feature.enabled?(:use_cte_for_group_issues_search, default_enabled: true) klass.where(id: items.select(:id)) # rubocop: disable CodeReuse/ActiveRecord
else
params[:use_cte_for_search] items
end
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
......
...@@ -42,7 +42,7 @@ module IconsHelper ...@@ -42,7 +42,7 @@ module IconsHelper
end end
def sprite_icon(icon_name, size: nil, css_class: nil) def sprite_icon(icon_name, size: nil, css_class: nil)
if Gitlab::Sentry.should_raise? if Gitlab::Sentry.should_raise_for_dev?
unless known_sprites.include?(icon_name) unless known_sprites.include?(icon_name)
exception = ArgumentError.new("#{icon_name} is not a known icon in @gitlab-org/gitlab-svg") exception = ArgumentError.new("#{icon_name} is not a known icon in @gitlab-org/gitlab-svg")
raise exception raise exception
......
# frozen_string_literal: true
module SentryHelper
def sentry_enabled?
Gitlab::Sentry.enabled?
end
def sentry_context
Gitlab::Sentry.context(current_user)
end
end
...@@ -14,6 +14,10 @@ module Clusters ...@@ -14,6 +14,10 @@ module Clusters
default_value_for :version, VERSION default_value_for :version, VERSION
default_value_for :email do |cert_manager|
cert_manager.cluster&.user&.email
end
validates :email, presence: true validates :email, presence: true
def chart def chart
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
module Clusters module Clusters
module Applications module Applications
class Runner < ActiveRecord::Base class Runner < ActiveRecord::Base
VERSION = '0.1.38'.freeze VERSION = '0.1.39'.freeze
self.table_name = 'clusters_applications_runners' self.table_name = 'clusters_applications_runners'
......
...@@ -177,7 +177,9 @@ class Commit ...@@ -177,7 +177,9 @@ class Commit
def title def title
return full_title if full_title.length < 100 return full_title if full_title.length < 100
full_title.truncate(81, separator: ' ', omission: '…') # Use three dots instead of the ellipsis Unicode character because
# some clients show the raw Unicode value in the merge commit.
full_title.truncate(81, separator: ' ', omission: '...')
end end
# Returns the full commits title # Returns the full commits title
......
...@@ -38,12 +38,13 @@ module DiscussionOnDiff ...@@ -38,12 +38,13 @@ module DiscussionOnDiff
end end
# Returns an array of at most 16 highlighted lines above a diff note # Returns an array of at most 16 highlighted lines above a diff note
def truncated_diff_lines(highlight: true) def truncated_diff_lines(highlight: true, diff_limit: nil)
return [] if diff_line.nil? && first_note.is_a?(LegacyDiffNote) return [] if diff_line.nil? && first_note.is_a?(LegacyDiffNote)
diff_limit = [diff_limit, NUMBER_OF_TRUNCATED_DIFF_LINES].compact.min
lines = highlight ? highlighted_diff_lines : diff_lines lines = highlight ? highlighted_diff_lines : diff_lines
initial_line_index = [diff_line.index - NUMBER_OF_TRUNCATED_DIFF_LINES + 1, 0].max initial_line_index = [diff_line.index - diff_limit + 1, 0].max
prev_lines = [] prev_lines = []
......
...@@ -541,15 +541,26 @@ class MergeRequest < ActiveRecord::Base ...@@ -541,15 +541,26 @@ class MergeRequest < ActiveRecord::Base
def validate_branches def validate_branches
if target_project == source_project && target_branch == source_branch if target_project == source_project && target_branch == source_branch
errors.add :branch_conflict, "You can not use same project/branch for source and target" errors.add :branch_conflict, "You can't use same project/branch for source and target"
return
end end
if opened? if opened?
similar_mrs = self.target_project.merge_requests.where(source_branch: source_branch, target_branch: target_branch, source_project_id: source_project.try(:id)).opened similar_mrs = target_project
similar_mrs = similar_mrs.where('id not in (?)', self.id) if self.id .merge_requests
if similar_mrs.any? .where(source_branch: source_branch, target_branch: target_branch)
errors.add :validate_branches, .where(source_project_id: source_project&.id)
"Cannot Create: This merge request already exists: #{similar_mrs.pluck(:title)}" .opened
similar_mrs = similar_mrs.where.not(id: id) if persisted?
conflict = similar_mrs.first
if conflict.present?
errors.add(
:validate_branches,
"Another open merge request already exists for this source branch: #{conflict.to_reference}"
)
end end
end end
end end
......
...@@ -6,4 +6,5 @@ class ClusterApplicationEntity < Grape::Entity ...@@ -6,4 +6,5 @@ class ClusterApplicationEntity < Grape::Entity
expose :status_reason expose :status_reason
expose :external_ip, if: -> (e, _) { e.respond_to?(:external_ip) } expose :external_ip, if: -> (e, _) { e.respond_to?(:external_ip) }
expose :hostname, if: -> (e, _) { e.respond_to?(:hostname) } expose :hostname, if: -> (e, _) { e.respond_to?(:hostname) }
expose :email, if: -> (e, _) { e.respond_to?(:email) }
end end
# frozen_string_literal: true
class DiffFileBaseEntity < Grape::Entity
include RequestAwareEntity
include BlobHelper
include SubmoduleHelper
include DiffHelper
include TreeHelper
include ChecksCollaboration
include Gitlab::Utils::StrongMemoize
expose :content_sha
expose :submodule?, as: :submodule
expose :submodule_link do |diff_file|
memoized_submodule_links(diff_file).first
end
expose :submodule_tree_url do |diff_file|
memoized_submodule_links(diff_file).last
end
expose :edit_path, if: -> (_, options) { options[:merge_request] } do |diff_file|
merge_request = options[:merge_request]
options = merge_request.persisted? ? { from_merge_request_iid: merge_request.iid } : {}
next unless merge_request.source_project
project_edit_blob_path(merge_request.source_project,
tree_join(merge_request.source_branch, diff_file.new_path),
options)
end
expose :old_path_html do |diff_file|
old_path, _ = mark_inline_diffs(diff_file.old_path, diff_file.new_path)
old_path
end
expose :new_path_html do |diff_file|
_, new_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path)
new_path
end
expose :formatted_external_url, if: -> (_, options) { options[:environment] } do |diff_file|
options[:environment].formatted_external_url
end
expose :external_url, if: -> (_, options) { options[:environment] } do |diff_file|
options[:environment].external_url_for(diff_file.new_path, diff_file.content_sha)
end
expose :blob, using: BlobEntity
expose :can_modify_blob do |diff_file|
merge_request = options[:merge_request]
next unless diff_file.blob
if merge_request&.source_project && current_user
can_modify_blob?(diff_file.blob, merge_request.source_project, merge_request.source_branch)
else
false
end
end
expose :file_hash do |diff_file|
Digest::SHA1.hexdigest(diff_file.file_path)
end
expose :file_path
expose :old_path
expose :new_path
expose :new_file?, as: :new_file
expose :collapsed?, as: :collapsed
expose :text?, as: :text
expose :diff_refs
expose :stored_externally?, as: :stored_externally
expose :external_storage
expose :renamed_file?, as: :renamed_file
expose :deleted_file?, as: :deleted_file
expose :mode_changed?, as: :mode_changed
expose :a_mode
expose :b_mode
private
def memoized_submodule_links(diff_file)
strong_memoize(:submodule_links) do
if diff_file.submodule?
submodule_links(diff_file.blob, diff_file.content_sha, diff_file.repository)
else
[]
end
end
end
def current_user
request.current_user
end
end
# frozen_string_literal: true # frozen_string_literal: true
class DiffFileEntity < Grape::Entity class DiffFileEntity < DiffFileBaseEntity
include RequestAwareEntity
include CommitsHelper include CommitsHelper
include DiffHelper
include SubmoduleHelper
include BlobHelper
include IconsHelper include IconsHelper
include TreeHelper
include ChecksCollaboration
include Gitlab::Utils::StrongMemoize
expose :submodule?, as: :submodule
expose :submodule_link do |diff_file|
memoized_submodule_links(diff_file).first
end
expose :submodule_tree_url do |diff_file|
memoized_submodule_links(diff_file).last
end
expose :blob, using: BlobEntity
expose :can_modify_blob do |diff_file|
merge_request = options[:merge_request]
next unless diff_file.blob
if merge_request&.source_project && current_user
can_modify_blob?(diff_file.blob, merge_request.source_project, merge_request.source_branch)
else
false
end
end
expose :file_hash do |diff_file|
Digest::SHA1.hexdigest(diff_file.file_path)
end
expose :file_path
expose :too_large?, as: :too_large expose :too_large?, as: :too_large
expose :collapsed?, as: :collapsed
expose :new_file?, as: :new_file
expose :deleted_file?, as: :deleted_file
expose :renamed_file?, as: :renamed_file
expose :mode_changed?, as: :mode_changed
expose :old_path
expose :new_path
expose :mode_changed?, as: :mode_changed
expose :a_mode
expose :b_mode
expose :text?, as: :text
expose :added_lines expose :added_lines
expose :removed_lines expose :removed_lines
expose :diff_refs
expose :content_sha
expose :stored_externally?, as: :stored_externally
expose :external_storage
expose :load_collapsed_diff_url, if: -> (diff_file, options) { diff_file.text? && options[:merge_request] } do |diff_file| expose :load_collapsed_diff_url, if: -> (diff_file, options) { diff_file.text? && options[:merge_request] } do |diff_file|
merge_request = options[:merge_request] merge_request = options[:merge_request]
...@@ -76,36 +24,6 @@ class DiffFileEntity < Grape::Entity ...@@ -76,36 +24,6 @@ class DiffFileEntity < Grape::Entity
) )
end end
expose :formatted_external_url, if: -> (_, options) { options[:environment] } do |diff_file|
options[:environment].formatted_external_url
end
expose :external_url, if: -> (_, options) { options[:environment] } do |diff_file|
options[:environment].external_url_for(diff_file.new_path, diff_file.content_sha)
end
expose :old_path_html do |diff_file|
old_path, _ = mark_inline_diffs(diff_file.old_path, diff_file.new_path)
old_path
end
expose :new_path_html do |diff_file|
_, new_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path)
new_path
end
expose :edit_path, if: -> (_, options) { options[:merge_request] } do |diff_file|
merge_request = options[:merge_request]
options = merge_request.persisted? ? { from_merge_request_iid: merge_request.iid } : {}
next unless merge_request.source_project
project_edit_blob_path(merge_request.source_project,
tree_join(merge_request.source_branch, diff_file.new_path),
options)
end
expose :view_path, if: -> (_, options) { options[:merge_request] } do |diff_file| expose :view_path, if: -> (_, options) { options[:merge_request] } do |diff_file|
merge_request = options[:merge_request] merge_request = options[:merge_request]
...@@ -146,18 +64,4 @@ class DiffFileEntity < Grape::Entity ...@@ -146,18 +64,4 @@ class DiffFileEntity < Grape::Entity
# Used for parallel diffs # Used for parallel diffs
expose :parallel_diff_lines, using: DiffLineParallelEntity, if: -> (diff_file, _) { diff_file.text? } expose :parallel_diff_lines, using: DiffLineParallelEntity, if: -> (diff_file, _) { diff_file.text? }
def current_user
request.current_user
end
def memoized_submodule_links(diff_file)
strong_memoize(:submodule_links) do
if diff_file.submodule?
submodule_links(diff_file.blob, diff_file.content_sha, diff_file.repository)
else
[]
end
end
end
end end
# frozen_string_literal: true
class DiscussionDiffFileEntity < DiffFileBaseEntity
end
...@@ -36,7 +36,7 @@ class DiscussionEntity < Grape::Entity ...@@ -36,7 +36,7 @@ class DiscussionEntity < Grape::Entity
new_project_issue_path(discussion.project, merge_request_to_resolve_discussions_of: discussion.noteable.iid, discussion_to_resolve: discussion.id) new_project_issue_path(discussion.project, merge_request_to_resolve_discussions_of: discussion.noteable.iid, discussion_to_resolve: discussion.id)
end end
expose :diff_file, using: DiffFileEntity, if: -> (d, _) { d.diff_discussion? } expose :diff_file, using: DiscussionDiffFileEntity, if: -> (d, _) { d.diff_discussion? }
expose :diff_discussion?, as: :diff_discussion expose :diff_discussion?, as: :diff_discussion
...@@ -46,19 +46,6 @@ class DiscussionEntity < Grape::Entity ...@@ -46,19 +46,6 @@ class DiscussionEntity < Grape::Entity
expose :truncated_diff_lines, using: DiffLineEntity, if: -> (d, _) { d.diff_discussion? && d.on_text? && (d.expanded? || render_truncated_diff_lines?) } expose :truncated_diff_lines, using: DiffLineEntity, if: -> (d, _) { d.diff_discussion? && d.on_text? && (d.expanded? || render_truncated_diff_lines?) }
expose :image_diff_html, if: -> (d, _) { d.diff_discussion? && d.on_image? } do |discussion|
diff_file = discussion.diff_file
partial = diff_file.new_file? || diff_file.deleted_file? ? 'single_image_diff' : 'replaced_image_diff'
options[:context].render_to_string(
partial: "projects/diffs/#{partial}",
locals: { diff_file: diff_file,
position: discussion.position.to_json,
click_to_comment: false },
layout: false,
formats: [:html]
)
end
expose :for_commit?, as: :for_commit expose :for_commit?, as: :for_commit
expose :commit_id expose :commit_id
......
...@@ -3,5 +3,6 @@ ...@@ -3,5 +3,6 @@
class TriggerVariableEntity < Grape::Entity class TriggerVariableEntity < Grape::Entity
include RequestAwareEntity include RequestAwareEntity
expose :key, :value, :public expose :key, :public
expose :value, if: ->(_, _) { can?(request.current_user, :admin_build, request.project) }
end end
...@@ -20,7 +20,7 @@ module Clusters ...@@ -20,7 +20,7 @@ module Clusters
end end
if application.has_attribute?(:email) if application.has_attribute?(:email)
application.email = current_user.email application.email = params[:email]
end end
if application.respond_to?(:oauth_application) if application.respond_to?(:oauth_application)
......
...@@ -58,13 +58,27 @@ module MergeRequests ...@@ -58,13 +58,27 @@ module MergeRequests
.preload(:latest_merge_request_diff) .preload(:latest_merge_request_diff)
.where(target_branch: @push.branch_name).to_a .where(target_branch: @push.branch_name).to_a
.select(&:diff_head_commit) .select(&:diff_head_commit)
.select do |merge_request|
commit_ids.include?(merge_request.diff_head_sha) &&
merge_request.merge_request_diff.state != 'empty'
end
merge_requests = filter_merge_requests(merge_requests)
return if merge_requests.empty?
merge_requests = merge_requests.select do |merge_request| commit_analyze_enabled = Feature.enabled?(:branch_push_merge_commit_analyze, @project, default_enabled: true)
commit_ids.include?(merge_request.diff_head_sha) && if commit_analyze_enabled
merge_request.merge_request_diff.state != 'empty' analyzer = Gitlab::BranchPushMergeCommitAnalyzer.new(
@commits.reverse,
relevant_commit_ids: merge_requests.map(&:diff_head_sha)
)
end end
filter_merge_requests(merge_requests).each do |merge_request| merge_requests.each do |merge_request|
if commit_analyze_enabled
merge_request.merge_commit_sha = analyzer.get_merge_commit(merge_request.diff_head_sha)
end
MergeRequests::PostMergeService MergeRequests::PostMergeService
.new(merge_request.target_project, @current_user) .new(merge_request.target_project, @current_user)
.execute(merge_request) .execute(merge_request)
......
- discussion = @note.discussion if @note.part_of_discussion? - note = local_assigns.fetch(:note, @note)
- diff_limit = local_assigns.fetch(:diff_limit, nil)
- target_url = local_assigns.fetch(:target_url, @target_url)
- note_style = local_assigns.fetch(:note_style, "")
- discussion = note.discussion if note.part_of_discussion?
- diff_discussion = discussion&.diff_discussion? - diff_discussion = discussion&.diff_discussion?
- on_image = discussion.on_image? if diff_discussion - on_image = discussion.on_image? if diff_discussion
- if discussion - if discussion
- phrase_end_char = on_image ? "." : ":" - phrase_end_char = on_image ? "." : ":"
%p.details %p{ style: "color: #777777;" }
= succeed phrase_end_char do = succeed phrase_end_char do
= link_to @note.author_name, user_url(@note.author) = link_to note.author_name, user_url(note.author)
- if diff_discussion - if diff_discussion
- if discussion.new_discussion? - if discussion.new_discussion?
...@@ -15,16 +20,16 @@ ...@@ -15,16 +20,16 @@
- else - else
commented on a discussion commented on a discussion
on #{link_to discussion.file_path, @target_url} on #{link_to discussion.file_path, target_url}
- else - else
- if discussion.new_discussion? - if discussion.new_discussion?
started a new discussion started a new discussion
- else - else
commented on a #{link_to 'discussion', @target_url} commented on a #{link_to 'discussion', target_url}
- elsif Gitlab::CurrentSettings.email_author_in_body - elsif Gitlab::CurrentSettings.email_author_in_body
%p.details %p.details
#{link_to @note.author_name, user_url(@note.author)} commented: #{link_to note.author_name, user_url(note.author)} commented:
- if diff_discussion && !on_image - if diff_discussion && !on_image
= content_for :head do = content_for :head do
...@@ -32,11 +37,11 @@ ...@@ -32,11 +37,11 @@
%table %table
= render partial: "projects/diffs/line", = render partial: "projects/diffs/line",
collection: discussion.truncated_diff_lines, collection: discussion.truncated_diff_lines(diff_limit: diff_limit),
as: :line, as: :line,
locals: { diff_file: discussion.diff_file, locals: { diff_file: discussion.diff_file,
plain: true, plain: true,
email: true } email: true }
%div %div{ style: note_style }
= markdown(@note.note, pipeline: :email, author: @note.author) = markdown(note.note, pipeline: :email, author: note.author)
<% discussion = @note.discussion if @note.part_of_discussion? -%> <% note = local_assigns.fetch(:note, @note) -%>
<% diff_limit = local_assigns.fetch(:diff_limit, nil) -%>
<% discussion = note.discussion if note.part_of_discussion? -%>
<% if discussion && !discussion.individual_note? -%> <% if discussion && !discussion.individual_note? -%>
<%= @note.author_name -%> <%= note.author_name -%>
<% if discussion.new_discussion? -%> <% if discussion.new_discussion? -%>
<%= " started a new discussion" -%> <%= " started a new discussion" -%>
<% else -%> <% else -%>
...@@ -13,14 +16,14 @@ ...@@ -13,14 +16,14 @@
<% elsif Gitlab::CurrentSettings.email_author_in_body -%> <% elsif Gitlab::CurrentSettings.email_author_in_body -%>
<%= "#{@note.author_name} commented:" -%> <%= "#{note.author_name} commented:" -%>
<% end -%> <% end -%>
<% if discussion&.diff_discussion? -%> <% if discussion&.diff_discussion? -%>
<% discussion.truncated_diff_lines(highlight: false).each do |line| -%> <% discussion.truncated_diff_lines(highlight: false, diff_limit: diff_limit).each do |line| -%>
<%= "> #{line.text}\n" -%> <%= "> #{line.text}\n" -%>
<% end -%> <% end -%>
<% end -%> <% end -%>
<%= @note.note -%> <%= note.note -%>
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
- subscribed = params[:subscribed] - subscribed = params[:subscribed]
- labels_or_filters = @labels.exists? || @prioritized_labels.exists? || search.present? || subscribed.present? - labels_or_filters = @labels.exists? || @prioritized_labels.exists? || search.present? || subscribed.present?
- if @labels.present? && can_admin_label - if labels_or_filters && can_admin_label
- content_for(:header_content) do - content_for(:header_content) do
.nav-controls .nav-controls
= link_to _('New label'), new_project_label_path(@project), class: "btn btn-success qa-label-create-new" = link_to _('New label'), new_project_label_path(@project), class: "btn btn-success qa-label-create-new"
......
...@@ -8,11 +8,18 @@ class NewNoteWorker ...@@ -8,11 +8,18 @@ class NewNoteWorker
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def perform(note_id, _params = {}) def perform(note_id, _params = {})
if note = Note.find_by(id: note_id) if note = Note.find_by(id: note_id)
NotificationService.new.new_note(note) NotificationService.new.new_note(note) unless skip_notification?(note)
Notes::PostProcessService.new(note).execute Notes::PostProcessService.new(note).execute
else else
Rails.logger.error("NewNoteWorker: couldn't find note with ID=#{note_id}, skipping job") Rails.logger.error("NewNoteWorker: couldn't find note with ID=#{note_id}, skipping job")
end end
end end
private
# EE-only method
def skip_notification?(note)
false
end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
end end
---
title: Pipeline trigger variable values are hidden in the UI by default. Maintainers
have the option to reveal them.
merge_request: 23518
author: jhampton
type: added
---
title: Show error message when attempting to reopen an MR and there is an open MR
for the same branch
merge_request: 16447
author: Akos Gyimesi
type: fixed
---
title: Fix "merged with [commit]" info for merge requests being merged automatically
by other actions
merge_request: 22794
author:
type: fixed
---
title: "Make auto-generated icons for subgroups in the breadcrumb dropdown display as a circle"
merge_request: 23062
author: Thomas Pathier
type: fix
\ No newline at end of file
---
title: Ability to override email for cert-manager
merge_request: 23503
author: Amit Rathi
type: added
---
title: Show primary button when all labels are prioritized
merge_request: 23648
author: George Tsiolis
type: other
---
title: Fix error when searching for group issues with priority or popularity sort
merge_request: 23445
author:
type: fixed
---
title: Remove unused data from discussions endpoint
merge_request: 23570
author:
type: performance
---
title: Truncate merge request titles with periods instead of ellipsis
merge_request: 23558
author:
type: changed
---
title: Log and pass correlation-id between Unicorn, Sidekiq and Gitaly
merge_request:
author:
type: added
---
title: Update used version of Runner Helm Chart to 0.1.39
merge_request: 23633
author:
type: other
---
title: Pass commit when posting diff discussions
merge_request: 23371
author:
type: fixed
---
title: Fix milestone select in issue sidebar of issue boards
merge_request: 23625
author:
type: fixed
# frozen_string_literal: true
Rails.application.config.middleware.use(Gitlab::Middleware::CorrelationId)
...@@ -29,6 +29,7 @@ unless Sidekiq.server? ...@@ -29,6 +29,7 @@ unless Sidekiq.server?
gitaly_calls = Gitlab::GitalyClient.get_request_count gitaly_calls = Gitlab::GitalyClient.get_request_count
payload[:gitaly_calls] = gitaly_calls if gitaly_calls > 0 payload[:gitaly_calls] = gitaly_calls if gitaly_calls > 0
payload[:response] = event.payload[:response] if event.payload[:response] payload[:response] = event.payload[:response] if event.payload[:response]
payload[Gitlab::CorrelationId::LOG_KEY] = Gitlab::CorrelationId.current_id
payload payload
end end
......
...@@ -24,4 +24,4 @@ def configure_sentry ...@@ -24,4 +24,4 @@ def configure_sentry
end end
end end
configure_sentry if Rails.env.production? configure_sentry if Rails.env.production? || Rails.env.development?
...@@ -21,6 +21,7 @@ Sidekiq.configure_server do |config| ...@@ -21,6 +21,7 @@ Sidekiq.configure_server do |config|
chain.add Gitlab::SidekiqMiddleware::Shutdown chain.add Gitlab::SidekiqMiddleware::Shutdown
chain.add Gitlab::SidekiqMiddleware::RequestStoreMiddleware unless ENV['SIDEKIQ_REQUEST_STORE'] == '0' chain.add Gitlab::SidekiqMiddleware::RequestStoreMiddleware unless ENV['SIDEKIQ_REQUEST_STORE'] == '0'
chain.add Gitlab::SidekiqMiddleware::BatchLoader chain.add Gitlab::SidekiqMiddleware::BatchLoader
chain.add Gitlab::SidekiqMiddleware::CorrelationLogger
chain.add Gitlab::SidekiqStatus::ServerMiddleware chain.add Gitlab::SidekiqStatus::ServerMiddleware
end end
...@@ -31,6 +32,7 @@ Sidekiq.configure_server do |config| ...@@ -31,6 +32,7 @@ Sidekiq.configure_server do |config|
config.client_middleware do |chain| config.client_middleware do |chain|
chain.add Gitlab::SidekiqStatus::ClientMiddleware chain.add Gitlab::SidekiqStatus::ClientMiddleware
chain.add Gitlab::SidekiqMiddleware::CorrelationInjector
end end
config.on :startup do config.on :startup do
...@@ -87,6 +89,7 @@ Sidekiq.configure_client do |config| ...@@ -87,6 +89,7 @@ Sidekiq.configure_client do |config|
config.redis = queues_config_hash config.redis = queues_config_hash
config.client_middleware do |chain| config.client_middleware do |chain|
chain.add Gitlab::SidekiqMiddleware::CorrelationInjector
chain.add Gitlab::SidekiqStatus::ClientMiddleware chain.add Gitlab::SidekiqStatus::ClientMiddleware
end end
end end
...@@ -24,23 +24,24 @@ The following files require a review from the Documentation team: ...@@ -24,23 +24,24 @@ The following files require a review from the Documentation team:
* #{docs_paths_to_review.map { |path| "`#{path}`" }.join("\n* ")} * #{docs_paths_to_review.map { |path| "`#{path}`" }.join("\n* ")}
When your content is ready for review, mention a technical writer in a separate When your content is ready for review, assign the MR to a technical writer
comment and explain what needs to be reviewed. according to the [DevOps stages](https://about.gitlab.com/handbook/product/categories/#devops-stages)
in the table below. If necessary, mention them in a comment explaining what needs
You are welcome to mention them sooner if you have questions about writing or updating to be reviewed.
the documentation. GitLabbers are also welcome to use the [#docs](https://gitlab.slack.com/archives/C16HYA2P5) channel on Slack.
Who to ping [based on DevOps stages](https://about.gitlab.com/handbook/product/categories/#devops-stages):
| Tech writer | Stage(s) | | Tech writer | Stage(s) |
| ------------ | ------------------------------------------------------------ | | ------------ | ------------------------------------------------------------ |
| `@marcia` | ~Create ~Release | | `@marcia` | ~Create ~Release + ~"development guidelines" |
| `@axil` | ~Distribution ~Gitaly ~Gitter ~Monitoring ~Packaging ~Secure | | `@axil` | ~Distribution ~Gitaly ~Gitter ~Monitoring ~Packaging ~Secure |
| `@eread` | ~Manage ~Configure ~Geo ~Verify | | `@eread` | ~Manage ~Configure ~Geo ~Verify |
| `@mikelewis` | ~Plan | | `@mikelewis` | ~Plan |
You are welcome to mention them sooner if you have questions about writing or
updating the documentation. GitLabbers are also welcome to use the
[#docs](https://gitlab.slack.com/archives/C16HYA2P5) channel on Slack.
If you are not sure which category the change falls within, or the change is not If you are not sure which category the change falls within, or the change is not
part of one of these categories, you can mention one of the usernames above. part of one of these categories, mention one of the usernames above.
MARKDOWN MARKDOWN
unless gitlab.mr_labels.include?('Documentation') unless gitlab.mr_labels.include?('Documentation')
......
...@@ -71,13 +71,17 @@ Sidekiq::Testing.inline! do ...@@ -71,13 +71,17 @@ Sidekiq::Testing.inline! do
params[:storage_version] = Project::LATEST_STORAGE_VERSION params[:storage_version] = Project::LATEST_STORAGE_VERSION
end end
project = Projects::CreateService.new(User.first, params).execute project = nil
# Seed-Fu runs this entire fixture in a transaction, so the `after_commit`
# hook won't run until after the fixture is loaded. That is too late
# since the Sidekiq::Testing block has already exited. Force clearing
# the `after_commit` queue to ensure the job is run now.
Sidekiq::Worker.skipping_transaction_check do Sidekiq::Worker.skipping_transaction_check do
project = Projects::CreateService.new(User.first, params).execute
# Seed-Fu runs this entire fixture in a transaction, so the `after_commit`
# hook won't run until after the fixture is loaded. That is too late
# since the Sidekiq::Testing block has already exited. Force clearing
# the `after_commit` queue to ensure the job is run now.
project.send(:_run_after_commit_queue) project.send(:_run_after_commit_queue)
project.import_state.send(:_run_after_commit_queue)
end end
if project.valid? && project.valid_repo? if project.valid? && project.valid_repo?
......
...@@ -25,7 +25,9 @@ Gitlab::Seeder.quiet do ...@@ -25,7 +25,9 @@ Gitlab::Seeder.quiet do
developer = project.team.developers.sample developer = project.team.developers.sample
break unless developer break unless developer
MergeRequests::CreateService.new(project, developer, params).execute Sidekiq::Worker.skipping_transaction_check do
MergeRequests::CreateService.new(project, developer, params).execute
end
print '.' print '.'
end end
end end
...@@ -39,7 +41,9 @@ Gitlab::Seeder.quiet do ...@@ -39,7 +41,9 @@ Gitlab::Seeder.quiet do
target_branch: 'master', target_branch: 'master',
title: 'Can be automatically merged' title: 'Can be automatically merged'
} }
MergeRequests::CreateService.new(project, User.admins.first, params).execute Sidekiq::Worker.skipping_transaction_check do
MergeRequests::CreateService.new(project, User.admins.first, params).execute
end
print '.' print '.'
params = { params = {
...@@ -47,6 +51,8 @@ Gitlab::Seeder.quiet do ...@@ -47,6 +51,8 @@ Gitlab::Seeder.quiet do
target_branch: 'feature', target_branch: 'feature',
title: 'Cannot be automatically merged' title: 'Cannot be automatically merged'
} }
MergeRequests::CreateService.new(project, User.admins.first, params).execute Sidekiq::Worker.skipping_transaction_check do
MergeRequests::CreateService.new(project, User.admins.first, params).execute
end
print '.' print '.'
end end
...@@ -989,10 +989,9 @@ curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://git ...@@ -989,10 +989,9 @@ curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://git
Merge changes submitted with MR using this API. Merge changes submitted with MR using this API.
If merge request is unable to be accepted (ie: Work in Progress, Closed, Pipeline Pending Completion, or Failed while requiring Success) - you'll get a `405` and the error message 'Method Not Allowed'
If it has some conflicts and can not be merged - you'll get a `405` and the error message 'Branch cannot be merged' If it has some conflicts and can not be merged - you'll get a `406` and the error message 'Branch cannot be merged'
If merge request is already merged or closed - you'll get a `406` and the error message 'Method Not Allowed'
If the `sha` parameter is passed and does not match the HEAD of the source - you'll get a `409` and the error message 'SHA does not match HEAD of source branch' If the `sha` parameter is passed and does not match the HEAD of the source - you'll get a `409` and the error message 'SHA does not match HEAD of source branch'
......
...@@ -910,16 +910,22 @@ Example response: ...@@ -910,16 +910,22 @@ Example response:
### Scope: wiki_blobs ### Scope: wiki_blobs
Filters are available for this scope:
- filename
- path
- extension
To use a filter simply include it in your query like: `a query filename:some_name*`.
You may use wildcards (`*`) to use glob matching.
Wiki blobs searches are performed on both filenames and contents. Search Wiki blobs searches are performed on both filenames and contents. Search
results: results:
- Found in filenames are displayed before results found in contents. - Found in filenames are displayed before results found in contents.
- May contain multiple matches for the same blob because the search string - May contain multiple matches for the same blob because the search string
might be found in both the filename and content, and matches of the different might be found in both the filename and content, or might appear multiple
types are displayed separately. times in the content.
- May contain multiple matches for the same blob because the search string
might be found if the search string appears multiple times in the content.
```bash ```bash
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/search?scope=wiki_blobs&search=bye curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/search?scope=wiki_blobs&search=bye
...@@ -976,22 +982,20 @@ Example response: ...@@ -976,22 +982,20 @@ Example response:
### Scope: blobs ### Scope: blobs
Filters are available for this scope: Filters are available for this scope:
- filename - filename
- path - path
- extension - extension
to use a filter simply include it in your query like so: `a query filename:some_name*`. To use a filter simply include it in your query like: `a query filename:some_name*`.
You may use wildcards (`*`) to use glob matching.
Blobs searches are performed on both filenames and contents. Search results: Blobs searches are performed on both filenames and contents. Search results:
- Found in filenames are displayed before results found in contents. - Found in filenames are displayed before results found in contents.
- May contain multiple matches for the same blob because the search string - May contain multiple matches for the same blob because the search string
might be found in both the filename and content, and matches of the different might be found in both the filename and content, or might appear multiple
types are displayed separately. times in the content.
- May contain multiple matches for the same blob because the search string
might be found if the search string appears multiple times in the content.
You may use wildcards (`*`) to use glob matching.
```bash ```bash
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/search?scope=blobs&search=installation curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/search?scope=blobs&search=installation
......
...@@ -204,7 +204,7 @@ file. The parameter is of the form: ...@@ -204,7 +204,7 @@ file. The parameter is of the form:
variables[key]=value variables[key]=value
``` ```
This information is also exposed in the UI. This information is also exposed in the UI. Please note that _values_ are only viewable by Owners and Maintainers.
![Job variables in UI](img/trigger_variables.png) ![Job variables in UI](img/trigger_variables.png)
......
...@@ -368,6 +368,16 @@ You can combine one or more of the following: ...@@ -368,6 +368,16 @@ You can combine one or more of the following:
= link_to 'Help page', help_page_path('user/permissions') = link_to 'Help page', help_page_path('user/permissions')
``` ```
### GitLab `/help` tests
Several [rspec tests](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/features/help_pages_spec.rb)
are run to ensure GitLab documentation renders and works correctly. In particular, that [main docs landing page](../../README.md) will work correctly from `/help`.
For example, [GitLab.com's `/help`](https://gitlab.com/help).
CAUTION: **Caution:**
Because the rspec tests only run in a full pipeline, and not a special [docs-only pipeline](#branch-naming), it is possible
to merge changes that will break `master` from a merge request with a successful docs-only pipeline run.
## General Documentation vs Technical Articles ## General Documentation vs Technical Articles
### General documentation ### General documentation
...@@ -552,6 +562,7 @@ Currently, the following tests are in place: ...@@ -552,6 +562,7 @@ Currently, the following tests are in place:
As CE is merged into EE once a day, it's important to avoid merge conflicts. As CE is merged into EE once a day, it's important to avoid merge conflicts.
Submitting an EE-equivalent merge request cherry-picking all commits from CE to EE is Submitting an EE-equivalent merge request cherry-picking all commits from CE to EE is
essential to avoid them. essential to avoid them.
1. In a full pipeline, tests for [`/help`](#gitlab-help-tests).
### Linting ### Linting
......
...@@ -113,7 +113,15 @@ feature flag. You can stub a feature flag as follows: ...@@ -113,7 +113,15 @@ feature flag. You can stub a feature flag as follows:
stub_feature_flags(my_feature_flag: false) stub_feature_flags(my_feature_flag: false)
``` ```
## Enabling a feature flag ## Enabling a feature flag (in development)
In the rails console (`rails c`), enter the following command to enable your feature flag
```ruby
Feature.enable(:feature_flag_name)
```
## Enabling a feature flag (in production)
Check how to [roll out changes using feature flags](rolling_out_changes_using_feature_flags.md). Check how to [roll out changes using feature flags](rolling_out_changes_using_feature_flags.md).
...@@ -268,7 +268,7 @@ deployments. ...@@ -268,7 +268,7 @@ deployments.
| ----------- | :------------: | ----------- | --------------- | | ----------- | :------------: | ----------- | --------------- |
| [Helm Tiller](https://docs.helm.sh/) | 10.2+ | Helm is a package manager for Kubernetes and is required to install all the other applications. It is installed in its own pod inside the cluster which can run the `helm` CLI in a safe environment. | n/a | | [Helm Tiller](https://docs.helm.sh/) | 10.2+ | Helm is a package manager for Kubernetes and is required to install all the other applications. It is installed in its own pod inside the cluster which can run the `helm` CLI in a safe environment. | n/a |
| [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) | 10.2+ | Ingress can provide load balancing, SSL termination, and name-based virtual hosting. It acts as a web proxy for your applications and is useful if you want to use [Auto DevOps] or deploy your own web apps. | [stable/nginx-ingress](https://github.com/helm/charts/tree/master/stable/nginx-ingress) | | [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) | 10.2+ | Ingress can provide load balancing, SSL termination, and name-based virtual hosting. It acts as a web proxy for your applications and is useful if you want to use [Auto DevOps] or deploy your own web apps. | [stable/nginx-ingress](https://github.com/helm/charts/tree/master/stable/nginx-ingress) |
| [Cert Manager](http://docs.cert-manager.io/en/latest/) | 11.6+ | Cert Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert Manager on your cluster will issue a certificate by [Let's Encrypt](https://letsencrypt.org/) and ensure that certificates are valid and up to date. The email address used by Let's Encrypt registration will be taken from the GitLab user that installed Cert Manager on the cluster. | [stable/cert-manager](https://github.com/helm/charts/tree/master/stable/cert-manager) | | [Cert Manager](http://docs.cert-manager.io/en/latest/) | 11.6+ | Cert Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert Manager on your cluster will issue a certificate by [Let's Encrypt](https://letsencrypt.org/) and ensure that certificates are valid and up-to-date. | [stable/cert-manager](https://github.com/helm/charts/tree/master/stable/cert-manager) |
| [Prometheus](https://prometheus.io/docs/introduction/overview/) | 10.4+ | Prometheus is an open-source monitoring and alerting system useful to supervise your deployed applications. | [stable/prometheus](https://github.com/helm/charts/tree/master/stable/prometheus) | | [Prometheus](https://prometheus.io/docs/introduction/overview/) | 10.4+ | Prometheus is an open-source monitoring and alerting system useful to supervise your deployed applications. | [stable/prometheus](https://github.com/helm/charts/tree/master/stable/prometheus) |
| [GitLab Runner](https://docs.gitlab.com/runner/) | 10.6+ | GitLab Runner is the open source project that is used to run your jobs and send the results back to GitLab. It is used in conjunction with [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/), the open-source continuous integration service included with GitLab that coordinates the jobs. When installing the GitLab Runner via the applications, it will run in **privileged mode** by default. Make sure you read the [security implications](#security-implications) before doing so. | [runner/gitlab-runner](https://gitlab.com/charts/gitlab-runner) | | [GitLab Runner](https://docs.gitlab.com/runner/) | 10.6+ | GitLab Runner is the open source project that is used to run your jobs and send the results back to GitLab. It is used in conjunction with [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/), the open-source continuous integration service included with GitLab that coordinates the jobs. When installing the GitLab Runner via the applications, it will run in **privileged mode** by default. Make sure you read the [security implications](#security-implications) before doing so. | [runner/gitlab-runner](https://gitlab.com/charts/gitlab-runner) |
| [JupyterHub](http://jupyter.org/) | 11.0+ | [JupyterHub](https://jupyterhub.readthedocs.io/en/stable/) is a multi-user service for managing notebooks across a team. [Jupyter Notebooks](https://jupyter-notebook.readthedocs.io/en/latest/) provide a web-based interactive programming environment used for data analysis, visualization, and machine learning. We use [this](https://gitlab.com/gitlab-org/jupyterhub-user-image/blob/master/Dockerfile) custom Jupyter image that installs additional useful packages on top of the base Jupyter. You will also see ready-to-use DevOps Runbooks built with Nurtch's [Rubix library](https://github.com/amit1rrr/rubix). More information on creating executable runbooks can be found at [Nurtch Documentation](http://docs.nurtch.com/en/latest). **Note**: Authentication will be enabled for any user of the GitLab server via OAuth2. HTTPS will be supported in a future release. | [jupyter/jupyterhub](https://jupyterhub.github.io/helm-chart/) | | [JupyterHub](http://jupyter.org/) | 11.0+ | [JupyterHub](https://jupyterhub.readthedocs.io/en/stable/) is a multi-user service for managing notebooks across a team. [Jupyter Notebooks](https://jupyter-notebook.readthedocs.io/en/latest/) provide a web-based interactive programming environment used for data analysis, visualization, and machine learning. We use [this](https://gitlab.com/gitlab-org/jupyterhub-user-image/blob/master/Dockerfile) custom Jupyter image that installs additional useful packages on top of the base Jupyter. You will also see ready-to-use DevOps Runbooks built with Nurtch's [Rubix library](https://github.com/amit1rrr/rubix). More information on creating executable runbooks can be found at [Nurtch Documentation](http://docs.nurtch.com/en/latest). **Note**: Authentication will be enabled for any user of the GitLab server via OAuth2. HTTPS will be supported in a future release. | [jupyter/jupyterhub](https://jupyterhub.github.io/helm-chart/) |
......
...@@ -20,7 +20,8 @@ module API ...@@ -20,7 +20,8 @@ module API
Gitlab::GrapeLogging::Loggers::RouteLogger.new, Gitlab::GrapeLogging::Loggers::RouteLogger.new,
Gitlab::GrapeLogging::Loggers::UserLogger.new, Gitlab::GrapeLogging::Loggers::UserLogger.new,
Gitlab::GrapeLogging::Loggers::QueueDurationLogger.new, Gitlab::GrapeLogging::Loggers::QueueDurationLogger.new,
Gitlab::GrapeLogging::Loggers::PerfLogger.new Gitlab::GrapeLogging::Loggers::PerfLogger.new,
Gitlab::GrapeLogging::Loggers::CorrelationIdLogger.new
] ]
allow_access_with_scope :api allow_access_with_scope :api
...@@ -91,7 +92,6 @@ module API ...@@ -91,7 +92,6 @@ module API
content_type :txt, "text/plain" content_type :txt, "text/plain"
# Ensure the namespace is right, otherwise we might load Grape::API::Helpers # Ensure the namespace is right, otherwise we might load Grape::API::Helpers
helpers ::SentryHelper
helpers ::API::Helpers helpers ::API::Helpers
helpers ::API::Helpers::CommonHelpers helpers ::API::Helpers::CommonHelpers
......
...@@ -381,10 +381,10 @@ module API ...@@ -381,10 +381,10 @@ module API
end end
def handle_api_exception(exception) def handle_api_exception(exception)
if sentry_enabled? && report_exception?(exception) if report_exception?(exception)
define_params_for_grape_middleware define_params_for_grape_middleware
sentry_context Gitlab::Sentry.context(current_user)
Raven.capture_exception(exception, extra: params) Gitlab::Sentry.track_acceptable_exception(exception, extra: params)
end end
# lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60 # lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60
......
# frozen_string_literal: true
module Gitlab
# Analyse a graph of commits from a push to a branch,
# for each commit, analyze that if it is the head of a merge request,
# then what should its merge_commit be, relative to the branch.
#
# A----->B----->C----->D target branch
# | ^
# | |
# +-->E----->F--+ merged branch
# | ^
# | |
# +->G--+
#
# (See merge-commit-analyze-after branch in gitlab-test)
#
# Assuming
# - A is already in remote
# - B~D are all in its own branch with its own merge request, targeting the target branch
#
# When D is finally pushed to the target branch,
# what are the merge commits for all the other merge requests?
#
# We can walk backwards from the HEAD commit D,
# and find status of its parents.
# First we determine if commit belongs to the target branch (i.e. A, B, C, D),
# and then determine its merge commit.
#
# +--------+-----------------+--------------+
# | Commit | Direct ancestor | Merge commit |
# +--------+-----------------+--------------+
# | D | Y | D |
# +--------+-----------------+--------------+
# | C | Y | C |
# +--------+-----------------+--------------+
# | F | | C |
# +--------+-----------------+--------------+
# | B | Y | B |
# +--------+-----------------+--------------+
# | E | | C |
# +--------+-----------------+--------------+
# | G | | C |
# +--------+-----------------+--------------+
#
# By examining the result, it can be said that
#
# - If commit is direct ancestor of HEAD, its merge commit is itself.
# - Otherwise, the merge commit is the same as its child's merge commit.
#
class BranchPushMergeCommitAnalyzer
class CommitDecorator < SimpleDelegator
attr_accessor :merge_commit
attr_writer :direct_ancestor # boolean
def direct_ancestor?
@direct_ancestor
end
# @param child_commit [CommitDecorator]
# @param first_parent [Boolean] whether `self` is the first parent of `child_commit`
def set_merge_commit(child_commit:)
@merge_commit ||= direct_ancestor? ? self : child_commit.merge_commit
end
end
# @param commits [Array] list of commits, must be ordered from the child (tip) of the graph back to the ancestors
def initialize(commits, relevant_commit_ids: nil)
@commits = commits
@id_to_commit = {}
@commits.each do |commit|
@id_to_commit[commit.id] = CommitDecorator.new(commit)
if relevant_commit_ids
relevant_commit_ids.delete(commit.id)
break if relevant_commit_ids.empty? # Only limit the analyze up to relevant_commit_ids
end
end
analyze
end
def get_merge_commit(id)
get_commit(id).merge_commit.id
end
private
def analyze
head_commit = get_commit(@commits.first.id)
head_commit.direct_ancestor = true
head_commit.merge_commit = head_commit
mark_all_direct_ancestors(head_commit)
# Analyzing a commit requires its child commit be analyzed first,
# which is the case here since commits are ordered from child to parent.
@id_to_commit.each_value do |commit|
analyze_parents(commit)
end
end
def analyze_parents(commit)
commit.parent_ids.each do |parent_commit_id|
parent_commit = get_commit(parent_commit_id)
next unless parent_commit # parent commit may not be part of new commits
parent_commit.set_merge_commit(child_commit: commit)
end
end
# Mark all direct ancestors.
# If child commit is a direct ancestor, its first parent is also a direct ancestor.
# We assume direct ancestors matches the trail of the target branch over time,
# This assumption is correct most of the time, especially for gitlab managed merges,
# but there are exception cases which can't be solved (https://stackoverflow.com/a/49754723/474597)
def mark_all_direct_ancestors(commit)
loop do
commit = get_commit(commit.parent_ids.first)
break unless commit
commit.direct_ancestor = true
end
end
def get_commit(id)
@id_to_commit[id]
end
end
end
# frozen_string_literal: true
module Gitlab
module CorrelationId
LOG_KEY = 'correlation_id'.freeze
class << self
def use_id(correlation_id, &blk)
# always generate a id if null is passed
correlation_id ||= new_id
ids.push(correlation_id || new_id)
begin
yield(current_id)
ensure
ids.pop
end
end
def current_id
ids.last
end
def current_or_new_id
current_id || new_id
end
private
def ids
Thread.current[:correlation_id] ||= []
end
def new_id
SecureRandom.uuid
end
end
end
end
...@@ -193,6 +193,7 @@ module Gitlab ...@@ -193,6 +193,7 @@ module Gitlab
feature = feature_stack && feature_stack[0] feature = feature_stack && feature_stack[0]
metadata['call_site'] = feature.to_s if feature metadata['call_site'] = feature.to_s if feature
metadata['gitaly-servers'] = address_metadata(remote_storage) if remote_storage metadata['gitaly-servers'] = address_metadata(remote_storage) if remote_storage
metadata['correlation_id'] = Gitlab::CorrelationId.current_id if Gitlab::CorrelationId.current_id
metadata.merge!(server_feature_flags) metadata.merge!(server_feature_flags)
......
# frozen_string_literal: true
# This module adds additional correlation id the grape logger
module Gitlab
module GrapeLogging
module Loggers
class CorrelationIdLogger < ::GrapeLogging::Loggers::Base
def parameters(_, _)
{ Gitlab::CorrelationId::LOG_KEY => Gitlab::CorrelationId.current_id }
end
end
end
end
end
...@@ -10,6 +10,7 @@ module Gitlab ...@@ -10,6 +10,7 @@ module Gitlab
data = {} data = {}
data[:severity] = severity data[:severity] = severity
data[:time] = timestamp.utc.iso8601(3) data[:time] = timestamp.utc.iso8601(3)
data[Gitlab::CorrelationId::LOG_KEY] = Gitlab::CorrelationId.current_id
case message case message
when String when String
......
# frozen_string_literal: true
# A dumb middleware that steals correlation id
# and sets it as a global context for the request
module Gitlab
module Middleware
class CorrelationId
include ActionView::Helpers::TagHelper
def initialize(app)
@app = app
end
def call(env)
::Gitlab::CorrelationId.use_id(correlation_id(env)) do
@app.call(env)
end
end
private
def correlation_id(env)
if Gitlab.rails5?
request(env).request_id
else
request(env).uuid
end
end
def request(env)
ActionDispatch::Request.new(env)
end
end
end
end
...@@ -3,7 +3,8 @@ ...@@ -3,7 +3,8 @@
module Gitlab module Gitlab
module Sentry module Sentry
def self.enabled? def self.enabled?
Rails.env.production? && Gitlab::CurrentSettings.sentry_enabled? (Rails.env.production? || Rails.env.development?) &&
Gitlab::CurrentSettings.sentry_enabled?
end end
def self.context(current_user = nil) def self.context(current_user = nil)
...@@ -31,7 +32,7 @@ module Gitlab ...@@ -31,7 +32,7 @@ module Gitlab
def self.track_exception(exception, issue_url: nil, extra: {}) def self.track_exception(exception, issue_url: nil, extra: {})
track_acceptable_exception(exception, issue_url: issue_url, extra: extra) track_acceptable_exception(exception, issue_url: issue_url, extra: extra)
raise exception if should_raise? raise exception if should_raise_for_dev?
end end
# This should be used when you do not want to raise an exception in # This should be used when you do not want to raise an exception in
...@@ -43,7 +44,11 @@ module Gitlab ...@@ -43,7 +44,11 @@ module Gitlab
extra[:issue_url] = issue_url if issue_url extra[:issue_url] = issue_url if issue_url
context # Make sure we've set everything we know in the context context # Make sure we've set everything we know in the context
Raven.capture_exception(exception, extra: extra) tags = {
Gitlab::CorrelationId::LOG_KEY.to_sym => Gitlab::CorrelationId.current_id
}
Raven.capture_exception(exception, tags: tags, extra: extra)
end end
end end
...@@ -55,7 +60,7 @@ module Gitlab ...@@ -55,7 +60,7 @@ module Gitlab
end end
end end
def self.should_raise? def self.should_raise_for_dev?
Rails.env.development? || Rails.env.test? Rails.env.development? || Rails.env.test?
end end
end end
......
# frozen_string_literal: true
module Gitlab
module SidekiqMiddleware
class CorrelationInjector
def call(worker_class, job, queue, redis_pool)
job[Gitlab::CorrelationId::LOG_KEY] ||=
Gitlab::CorrelationId.current_or_new_id
yield
end
end
end
end
# frozen_string_literal: true
module Gitlab
module SidekiqMiddleware
class CorrelationLogger
def call(worker, job, queue)
correlation_id = job[Gitlab::CorrelationId::LOG_KEY]
Gitlab::CorrelationId.use_id(correlation_id) do
yield
end
end
end
end
end
...@@ -1838,6 +1838,9 @@ msgstr "" ...@@ -1838,6 +1838,9 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager" msgid "ClusterIntegration|Cert-Manager"
msgstr "" msgstr ""
msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
msgstr "" msgstr ""
...@@ -1961,6 +1964,12 @@ msgstr "" ...@@ -1961,6 +1964,12 @@ msgstr ""
msgid "ClusterIntegration|Integration status" msgid "ClusterIntegration|Integration status"
msgstr "" msgstr ""
msgid "ClusterIntegration|Issuer Email"
msgstr ""
msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname" msgid "ClusterIntegration|Jupyter Hostname"
msgstr "" msgstr ""
...@@ -2183,9 +2192,6 @@ msgstr "" ...@@ -2183,9 +2192,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine" msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr "" msgstr ""
msgid "ClusterIntegration|cert-manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing cert-manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up to date."
msgstr ""
msgid "ClusterIntegration|check the pricing here" msgid "ClusterIntegration|check the pricing here"
msgstr "" msgstr ""
...@@ -4487,6 +4493,9 @@ msgid_plural "Hide values" ...@@ -4487,6 +4493,9 @@ msgid_plural "Hide values"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
msgid "Hide values"
msgstr ""
msgid "Hide whitespace changes" msgid "Hide whitespace changes"
msgstr "" msgstr ""
...@@ -7316,14 +7325,14 @@ msgstr "" ...@@ -7316,14 +7325,14 @@ msgstr ""
msgid "Retry verification" msgid "Retry verification"
msgstr "" msgstr ""
msgid "Reveal Variables"
msgstr ""
msgid "Reveal value" msgid "Reveal value"
msgid_plural "Reveal values" msgid_plural "Reveal values"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
msgid "Reveal values"
msgstr ""
msgid "Revert this commit" msgid "Revert this commit"
msgstr "" msgstr ""
...@@ -9439,6 +9448,9 @@ msgstr "" ...@@ -9439,6 +9448,9 @@ msgstr ""
msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want."
msgstr "" msgstr ""
msgid "Variables:"
msgstr ""
msgid "Various container registry settings." msgid "Various container registry settings."
msgstr "" msgstr ""
......
...@@ -460,6 +460,14 @@ describe ApplicationController do ...@@ -460,6 +460,14 @@ describe ApplicationController do
expect(controller.last_payload.has_key?(:response)).to be_falsey expect(controller.last_payload.has_key?(:response)).to be_falsey
end end
it 'does log correlation id' do
Gitlab::CorrelationId.use_id('new-id') do
get :index
end
expect(controller.last_payload).to include('correlation_id' => 'new-id')
end
context '422 errors' do context '422 errors' do
it 'logs a response with a string' do it 'logs a response with a string' do
response = spy(ActionDispatch::Response, status: 422, body: 'Hello world', content_type: 'application/json', cookies: {}) response = spy(ActionDispatch::Response, status: 422, body: 'Hello world', content_type: 'application/json', cookies: {})
......
...@@ -258,9 +258,10 @@ describe GroupsController do ...@@ -258,9 +258,10 @@ describe GroupsController do
end end
context 'searching' do context 'searching' do
# Remove as part of https://gitlab.com/gitlab-org/gitlab-ce/issues/52271
before do before do
# Remove in https://gitlab.com/gitlab-org/gitlab-ce/issues/54643
stub_feature_flags(use_cte_for_group_issues_search: false) stub_feature_flags(use_cte_for_group_issues_search: false)
stub_feature_flags(use_subquery_for_group_issues_search: true)
end end
it 'works with popularity sort' do it 'works with popularity sort' do
......
...@@ -401,18 +401,56 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do ...@@ -401,18 +401,56 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
context 'with variables' do context 'with variables' do
before do before do
create(:ci_pipeline_variable, pipeline: pipeline, key: :TRIGGER_KEY_1, value: 'TRIGGER_VALUE_1') create(:ci_pipeline_variable, pipeline: pipeline, key: :TRIGGER_KEY_1, value: 'TRIGGER_VALUE_1')
end
get_show(id: job.id, format: :json) context 'user is a maintainer' do
before do
project.add_maintainer(user)
get_show(id: job.id, format: :json)
end
it 'returns a job_detail' do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('job/job_details')
end
it 'exposes trigger information and variables' do
expect(json_response['trigger']['short_token']).to eq 'toke'
expect(json_response['trigger']['variables'].length).to eq 1
end
it 'exposes correct variable properties' do
first_variable = json_response['trigger']['variables'].first
expect(first_variable['key']).to eq "TRIGGER_KEY_1"
expect(first_variable['value']).to eq "TRIGGER_VALUE_1"
expect(first_variable['public']).to eq false
end
end end
it 'exposes trigger information and variables' do context 'user is not a mantainer' do
expect(response).to have_gitlab_http_status(:ok) before do
expect(response).to match_response_schema('job/job_details') get_show(id: job.id, format: :json)
expect(json_response['trigger']['short_token']).to eq 'toke' end
expect(json_response['trigger']['variables'].length).to eq 1
expect(json_response['trigger']['variables'].first['key']).to eq "TRIGGER_KEY_1" it 'returns a job_detail' do
expect(json_response['trigger']['variables'].first['value']).to eq "TRIGGER_VALUE_1" expect(response).to have_gitlab_http_status(:ok)
expect(json_response['trigger']['variables'].first['public']).to eq false expect(response).to match_response_schema('job/job_details')
end
it 'exposes trigger information and variables' do
expect(json_response['trigger']['short_token']).to eq 'toke'
expect(json_response['trigger']['variables'].length).to eq 1
end
it 'exposes correct variable properties' do
first_variable = json_response['trigger']['variables'].first
expect(first_variable['key']).to eq "TRIGGER_KEY_1"
expect(first_variable['value']).to be_nil
expect(first_variable['public']).to eq false
end
end end
end end
end end
......
...@@ -290,6 +290,20 @@ describe Projects::MergeRequestsController do ...@@ -290,6 +290,20 @@ describe Projects::MergeRequestsController do
it_behaves_like 'update invalid issuable', MergeRequest it_behaves_like 'update invalid issuable', MergeRequest
end end
context 'two merge requests with the same source branch' do
it 'does not allow a closed merge request to be reopened if another one is open' do
merge_request.close!
create(:merge_request, source_project: merge_request.source_project, source_branch: merge_request.source_branch)
update_merge_request(state_event: 'reopen')
errors = assigns[:merge_request].errors
expect(errors[:validate_branches]).to include(/Another open merge request already exists for this source branch/)
expect(merge_request.reload).to be_closed
end
end
end end
describe 'POST merge' do describe 'POST merge' do
......
...@@ -70,6 +70,44 @@ describe 'Clusters Applications', :js do ...@@ -70,6 +70,44 @@ describe 'Clusters Applications', :js do
end end
end end
context 'when user installs Cert Manager' do
before do
allow(ClusterInstallAppWorker).to receive(:perform_async)
allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_in)
allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_async)
create(:clusters_applications_helm, :installed, cluster: cluster)
page.within('.js-cluster-application-row-cert_manager') do
click_button 'Install'
end
end
it 'shows status transition' do
def email_form_value
page.find('.js-email').value
end
page.within('.js-cluster-application-row-cert_manager') do
expect(email_form_value).to eq(cluster.user.email)
expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Install')
page.find('.js-email').set("new_email@example.org")
Clusters::Cluster.last.application_cert_manager.make_installing!
expect(email_form_value).to eq('new_email@example.org')
expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing')
Clusters::Cluster.last.application_cert_manager.make_installed!
expect(email_form_value).to eq('new_email@example.org')
expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installed')
end
expect(page).to have_content('Cert-Manager was successfully installed on your Kubernetes cluster')
end
end
context 'when user installs Ingress' do context 'when user installs Ingress' do
context 'when user installs application: Ingress' do context 'when user installs application: Ingress' do
before do before do
......
...@@ -346,44 +346,85 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do ...@@ -346,44 +346,85 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
describe 'Variables' do describe 'Variables' do
let(:trigger_request) { create(:ci_trigger_request) } let(:trigger_request) { create(:ci_trigger_request) }
let(:job) { create(:ci_build, pipeline: pipeline, trigger_request: trigger_request) }
let(:job) do context 'when user is a maintainer' do
create :ci_build, pipeline: pipeline, trigger_request: trigger_request shared_examples 'no reveal button variables behavior' do
end it 'renders a hidden value with no reveal values button', :js do
expect(page).to have_content('Token')
expect(page).to have_content('Variables')
expect(page).not_to have_css('.js-reveal-variables')
expect(page).to have_selector('.js-build-variable', text: 'TRIGGER_KEY_1')
expect(page).to have_selector('.js-build-value', text: '••••••')
end
end
context 'when variables are stored in trigger_request' do
before do
trigger_request.update_attribute(:variables, { 'TRIGGER_KEY_1' => 'TRIGGER_VALUE_1' } )
visit project_job_path(project, job)
end
it_behaves_like 'no reveal button variables behavior'
end
shared_examples 'expected variables behavior' do context 'when variables are stored in pipeline_variables' do
it 'shows variable key and value after click', :js do before do
expect(page).to have_content('Token') create(:ci_pipeline_variable, pipeline: pipeline, key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1')
expect(page).to have_css('.js-reveal-variables')
expect(page).not_to have_css('.js-build-variable')
expect(page).not_to have_css('.js-build-value')
click_button 'Reveal Variables' visit project_job_path(project, job)
end
expect(page).not_to have_css('.js-reveal-variables') it_behaves_like 'no reveal button variables behavior'
expect(page).to have_selector('.js-build-variable', text: 'TRIGGER_KEY_1')
expect(page).to have_selector('.js-build-value', text: 'TRIGGER_VALUE_1')
end end
end end
context 'when variables are stored in trigger_request' do context 'when user is a maintainer' do
before do before do
trigger_request.update_attribute(:variables, { 'TRIGGER_KEY_1' => 'TRIGGER_VALUE_1' } ) project.add_maintainer(user)
end
visit project_job_path(project, job) shared_examples 'reveal button variables behavior' do
it 'renders a hidden value with a reveal values button', :js do
expect(page).to have_content('Token')
expect(page).to have_content('Variables')
expect(page).to have_css('.js-reveal-variables')
expect(page).to have_selector('.js-build-variable', text: 'TRIGGER_KEY_1')
expect(page).to have_selector('.js-build-value', text: '••••••')
end
it 'reveals values on button click', :js do
click_button 'Reveal values'
expect(page).to have_selector('.js-build-variable', text: 'TRIGGER_KEY_1')
expect(page).to have_selector('.js-build-value', text: 'TRIGGER_VALUE_1')
end
end end
it_behaves_like 'expected variables behavior' context 'when variables are stored in trigger_request' do
end before do
trigger_request.update_attribute(:variables, { 'TRIGGER_KEY_1' => 'TRIGGER_VALUE_1' } )
context 'when variables are stored in pipeline_variables' do visit project_job_path(project, job)
before do end
create(:ci_pipeline_variable, pipeline: pipeline, key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1')
visit project_job_path(project, job) it_behaves_like 'reveal button variables behavior'
end end
it_behaves_like 'expected variables behavior' context 'when variables are stored in pipeline_variables' do
before do
create(:ci_pipeline_variable, pipeline: pipeline, key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1')
visit project_job_path(project, job)
end
it_behaves_like 'reveal button variables behavior'
end
end end
end end
......
...@@ -115,6 +115,21 @@ describe 'Prioritize labels' do ...@@ -115,6 +115,21 @@ describe 'Prioritize labels' do
end end
end end
it 'user can see a primary button when there are only prioritized labels', :js do
visit project_labels_path(project)
page.within('.other-labels') do
all('.js-toggle-priority').each do |el|
el.click
end
wait_for_requests
end
page.within('.breadcrumbs-container') do
expect(page).to have_link('New label')
end
end
it 'shows a help message about prioritized labels' do it 'shows a help message about prioritized labels' do
visit project_labels_path(project) visit project_labels_path(project)
......
...@@ -738,4 +738,131 @@ describe IssuesFinder do ...@@ -738,4 +738,131 @@ describe IssuesFinder do
end end
end end
end end
describe '#use_subquery_for_search?' do
let(:finder) { described_class.new(nil, params) }
before do
allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
stub_feature_flags(use_subquery_for_group_issues_search: true)
end
context 'when there is no search param' do
let(:params) { { attempt_group_search_optimizations: true } }
it 'returns false' do
expect(finder.use_subquery_for_search?).to be_falsey
end
end
context 'when the database is not Postgres' do
let(:params) { { search: 'foo', attempt_group_search_optimizations: true } }
before do
allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
end
it 'returns false' do
expect(finder.use_subquery_for_search?).to be_falsey
end
end
context 'when the attempt_group_search_optimizations param is falsey' do
let(:params) { { search: 'foo' } }
it 'returns false' do
expect(finder.use_subquery_for_search?).to be_falsey
end
end
context 'when the use_subquery_for_group_issues_search flag is disabled' do
let(:params) { { search: 'foo', attempt_group_search_optimizations: true } }
before do
stub_feature_flags(use_subquery_for_group_issues_search: false)
end
it 'returns false' do
expect(finder.use_subquery_for_search?).to be_falsey
end
end
context 'when all conditions are met' do
let(:params) { { search: 'foo', attempt_group_search_optimizations: true } }
it 'returns true' do
expect(finder.use_subquery_for_search?).to be_truthy
end
end
end
describe '#use_cte_for_search?' do
let(:finder) { described_class.new(nil, params) }
before do
allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
stub_feature_flags(use_cte_for_group_issues_search: true)
stub_feature_flags(use_subquery_for_group_issues_search: false)
end
context 'when there is no search param' do
let(:params) { { attempt_group_search_optimizations: true } }
it 'returns false' do
expect(finder.use_cte_for_search?).to be_falsey
end
end
context 'when the database is not Postgres' do
let(:params) { { search: 'foo', attempt_group_search_optimizations: true } }
before do
allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
end
it 'returns false' do
expect(finder.use_cte_for_search?).to be_falsey
end
end
context 'when the attempt_group_search_optimizations param is falsey' do
let(:params) { { search: 'foo' } }
it 'returns false' do
expect(finder.use_cte_for_search?).to be_falsey
end
end
context 'when the use_cte_for_group_issues_search flag is disabled' do
let(:params) { { search: 'foo', attempt_group_search_optimizations: true } }
before do
stub_feature_flags(use_cte_for_group_issues_search: false)
end
it 'returns false' do
expect(finder.use_cte_for_search?).to be_falsey
end
end
context 'when use_subquery_for_search? is true' do
let(:params) { { search: 'foo', attempt_group_search_optimizations: true } }
before do
stub_feature_flags(use_subquery_for_group_issues_search: true)
end
it 'returns false' do
expect(finder.use_cte_for_search?).to be_falsey
end
end
context 'when all conditions are met' do
let(:params) { { search: 'foo', attempt_group_search_optimizations: true } }
it 'returns true' do
expect(finder.use_cte_for_search?).to be_truthy
end
end
end
end end
...@@ -32,7 +32,8 @@ ...@@ -32,7 +32,8 @@
}, },
"status_reason": { "type": ["string", "null"] }, "status_reason": { "type": ["string", "null"] },
"external_ip": { "type": ["string", "null"] }, "external_ip": { "type": ["string", "null"] },
"hostname": { "type": ["string", "null"] } "hostname": { "type": ["string", "null"] },
"email": { "type": ["string", "null"] }
}, },
"required" : [ "name", "status" ] "required" : [ "name", "status" ]
} }
......
...@@ -12,12 +12,11 @@ ...@@ -12,12 +12,11 @@
"type": "object", "type": "object",
"required": [ "required": [
"key", "key",
"value",
"public" "public"
], ],
"properties": { "properties": {
"key": { "type": "string" }, "key": { "type": "string" },
"value": { "type": "string" }, "value": { "type": "string", "optional": true },
"public": { "type": "boolean" } "public": { "type": "boolean" }
}, },
"additionalProperties": false "additionalProperties": false
......
# frozen_string_literal: true
require 'spec_helper'
describe 'lograge', type: :request do
let(:headers) { { 'X-Request-ID' => 'new-correlation-id' } }
context 'for API requests' do
subject { get("/api/v4/endpoint", {}, headers) }
it 'logs to api_json log' do
# we assert receiving parameters by grape logger
expect_any_instance_of(Gitlab::GrapeLogging::Formatters::LogrageWithTimestamp).to receive(:call)
.with(anything, anything, anything, a_hash_including("correlation_id" => "new-correlation-id"))
.and_call_original
subject
end
end
context 'for Controller requests' do
subject { get("/", {}, headers) }
it 'logs to production_json log' do
# formatter receives a hash with correlation id
expect(Lograge.formatter).to receive(:call)
.with(a_hash_including("correlation_id" => "new-correlation-id"))
.and_call_original
# a log file receives a line with correlation id
expect(Lograge.logger).to receive(:send)
.with(anything, include('"correlation_id":"new-correlation-id"'))
.and_call_original
subject
end
end
end
...@@ -176,6 +176,54 @@ describe('Applications', () => { ...@@ -176,6 +176,54 @@ describe('Applications', () => {
}); });
}); });
describe('Cert-Manager application', () => {
describe('when not installed', () => {
it('renders email & allows editing', () => {
vm = mountComponent(Applications, {
applications: {
helm: { title: 'Helm Tiller', status: 'installed' },
ingress: { title: 'Ingress', status: 'installed', externalIp: '1.1.1.1' },
cert_manager: {
title: 'Cert-Manager',
email: 'before@example.com',
status: 'installable',
},
runner: { title: 'GitLab Runner' },
prometheus: { title: 'Prometheus' },
jupyter: { title: 'JupyterHub', hostname: '', status: 'installable' },
knative: { title: 'Knative', hostname: '', status: 'installable' },
},
});
expect(vm.$el.querySelector('.js-email').value).toEqual('before@example.com');
expect(vm.$el.querySelector('.js-email').getAttribute('readonly')).toBe(null);
});
});
describe('when installed', () => {
it('renders email in readonly', () => {
vm = mountComponent(Applications, {
applications: {
helm: { title: 'Helm Tiller', status: 'installed' },
ingress: { title: 'Ingress', status: 'installed', externalIp: '1.1.1.1' },
cert_manager: {
title: 'Cert-Manager',
email: 'after@example.com',
status: 'installed',
},
runner: { title: 'GitLab Runner' },
prometheus: { title: 'Prometheus' },
jupyter: { title: 'JupyterHub', hostname: '', status: 'installable' },
knative: { title: 'Knative', hostname: '', status: 'installable' },
},
});
expect(vm.$el.querySelector('.js-email').value).toEqual('after@example.com');
expect(vm.$el.querySelector('.js-email').getAttribute('readonly')).toEqual('readonly');
});
});
});
describe('Jupyter application', () => { describe('Jupyter application', () => {
describe('with ingress installed with ip & jupyter installable', () => { describe('with ingress installed with ip & jupyter installable', () => {
it('renders hostname active input', () => { it('renders hostname active input', () => {
......
...@@ -42,6 +42,7 @@ const CLUSTERS_MOCK_DATA = { ...@@ -42,6 +42,7 @@ const CLUSTERS_MOCK_DATA = {
name: 'cert_manager', name: 'cert_manager',
status: APPLICATION_STATUS.ERROR, status: APPLICATION_STATUS.ERROR,
status_reason: 'Cannot connect', status_reason: 'Cannot connect',
email: 'test@example.com',
}, },
], ],
}, },
...@@ -86,6 +87,7 @@ const CLUSTERS_MOCK_DATA = { ...@@ -86,6 +87,7 @@ const CLUSTERS_MOCK_DATA = {
name: 'cert_manager', name: 'cert_manager',
status: APPLICATION_STATUS.ERROR, status: APPLICATION_STATUS.ERROR,
status_reason: 'Cannot connect', status_reason: 'Cannot connect',
email: 'test@example.com',
}, },
], ],
}, },
......
...@@ -115,6 +115,7 @@ describe('Clusters Store', () => { ...@@ -115,6 +115,7 @@ describe('Clusters Store', () => {
statusReason: mockResponseData.applications[6].status_reason, statusReason: mockResponseData.applications[6].status_reason,
requestStatus: null, requestStatus: null,
requestReason: null, requestReason: null,
email: mockResponseData.applications[6].email,
}, },
}, },
}); });
......
...@@ -489,8 +489,6 @@ export default { ...@@ -489,8 +489,6 @@ export default {
diff_discussion: true, diff_discussion: true,
truncated_diff_lines: truncated_diff_lines:
'<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="1">\n1\n</td>\n<td class="line_content new noteable_line"><span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n</td>\n</tr>\n<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="2">\n2\n</td>\n<td class="line_content new noteable_line"><span id="LC2" class="line" lang="plaintext"></span>\n</td>\n</tr>\n', '<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="1">\n1\n</td>\n<td class="line_content new noteable_line"><span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n</td>\n</tr>\n<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="2">\n2\n</td>\n<td class="line_content new noteable_line"><span id="LC2" class="line" lang="plaintext"></span>\n</td>\n</tr>\n',
image_diff_html:
'<div class="image js-replaced-image" data="">\n<div class="two-up view">\n<div class="wrap">\n<div class="frame deleted">\n<img alt="CHANGELOG" src="http://localhost:3000/gitlab-org/gitlab-test/raw/e63f41fe459e62e1228fcef60d7189127aeba95a/CHANGELOG" />\n</div>\n<p class="image-info hide">\n<span class="meta-filesize">22.3 KB</span>\n|\n<strong>W:</strong>\n<span class="meta-width"></span>\n|\n<strong>H:</strong>\n<span class="meta-height"></span>\n</p>\n</div>\n<div class="wrap">\n<div class="added frame js-image-frame" data-note-type="DiffNote" data-position="{&quot;base_sha&quot;:&quot;e63f41fe459e62e1228fcef60d7189127aeba95a&quot;,&quot;start_sha&quot;:&quot;d9eaefe5a676b820c57ff18cf5b68316025f7962&quot;,&quot;head_sha&quot;:&quot;c48ee0d1bf3b30453f5b32250ce03134beaa6d13&quot;,&quot;old_path&quot;:&quot;CHANGELOG&quot;,&quot;new_path&quot;:&quot;CHANGELOG&quot;,&quot;position_type&quot;:&quot;text&quot;,&quot;old_line&quot;:null,&quot;new_line&quot;:2}">\n<img alt="CHANGELOG" draggable="false" src="http://localhost:3000/gitlab-org/gitlab-test/raw/c48ee0d1bf3b30453f5b32250ce03134beaa6d13/CHANGELOG" />\n</div>\n\n<p class="image-info hide">\n<span class="meta-filesize">22.3 KB</span>\n|\n<strong>W:</strong>\n<span class="meta-width"></span>\n|\n<strong>H:</strong>\n<span class="meta-height"></span>\n</p>\n</div>\n</div>\n<div class="swipe view hide">\n<div class="swipe-frame">\n<div class="frame deleted">\n<img alt="CHANGELOG" src="http://localhost:3000/gitlab-org/gitlab-test/raw/e63f41fe459e62e1228fcef60d7189127aeba95a/CHANGELOG" />\n</div>\n<div class="swipe-wrap">\n<div class="added frame js-image-frame" data-note-type="DiffNote" data-position="{&quot;base_sha&quot;:&quot;e63f41fe459e62e1228fcef60d7189127aeba95a&quot;,&quot;start_sha&quot;:&quot;d9eaefe5a676b820c57ff18cf5b68316025f7962&quot;,&quot;head_sha&quot;:&quot;c48ee0d1bf3b30453f5b32250ce03134beaa6d13&quot;,&quot;old_path&quot;:&quot;CHANGELOG&quot;,&quot;new_path&quot;:&quot;CHANGELOG&quot;,&quot;position_type&quot;:&quot;text&quot;,&quot;old_line&quot;:null,&quot;new_line&quot;:2}">\n<img alt="CHANGELOG" draggable="false" src="http://localhost:3000/gitlab-org/gitlab-test/raw/c48ee0d1bf3b30453f5b32250ce03134beaa6d13/CHANGELOG" />\n</div>\n\n</div>\n<span class="swipe-bar">\n<span class="top-handle"></span>\n<span class="bottom-handle"></span>\n</span>\n</div>\n</div>\n<div class="onion-skin view hide">\n<div class="onion-skin-frame">\n<div class="frame deleted">\n<img alt="CHANGELOG" src="http://localhost:3000/gitlab-org/gitlab-test/raw/e63f41fe459e62e1228fcef60d7189127aeba95a/CHANGELOG" />\n</div>\n<div class="added frame js-image-frame" data-note-type="DiffNote" data-position="{&quot;base_sha&quot;:&quot;e63f41fe459e62e1228fcef60d7189127aeba95a&quot;,&quot;start_sha&quot;:&quot;d9eaefe5a676b820c57ff18cf5b68316025f7962&quot;,&quot;head_sha&quot;:&quot;c48ee0d1bf3b30453f5b32250ce03134beaa6d13&quot;,&quot;old_path&quot;:&quot;CHANGELOG&quot;,&quot;new_path&quot;:&quot;CHANGELOG&quot;,&quot;position_type&quot;:&quot;text&quot;,&quot;old_line&quot;:null,&quot;new_line&quot;:2}">\n<img alt="CHANGELOG" draggable="false" src="http://localhost:3000/gitlab-org/gitlab-test/raw/c48ee0d1bf3b30453f5b32250ce03134beaa6d13/CHANGELOG" />\n</div>\n\n<div class="controls">\n<div class="transparent"></div>\n<div class="drag-track">\n<div class="dragger" style="left: 0px;"></div>\n</div>\n<div class="opaque"></div>\n</div>\n</div>\n</div>\n</div>\n<div class="view-modes hide">\n<ul class="view-modes-menu">\n<li class="two-up" data-mode="two-up">2-up</li>\n<li class="swipe" data-mode="swipe">Swipe</li>\n<li class="onion-skin" data-mode="onion-skin">Onion skin</li>\n</ul>\n</div>\n',
}; };
export const imageDiffDiscussions = [ export const imageDiffDiscussions = [
......
...@@ -31,6 +31,7 @@ import actions, { ...@@ -31,6 +31,7 @@ import actions, {
import eventHub from '~/notes/event_hub'; import eventHub from '~/notes/event_hub';
import * as types from '~/diffs/store/mutation_types'; import * as types from '~/diffs/store/mutation_types';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import mockDiffFile from 'spec/diffs/mock_data/diff_file';
import testAction from '../../helpers/vuex_action_helper'; import testAction from '../../helpers/vuex_action_helper';
describe('DiffsStoreActions', () => { describe('DiffsStoreActions', () => {
...@@ -609,11 +610,18 @@ describe('DiffsStoreActions', () => { ...@@ -609,11 +610,18 @@ describe('DiffsStoreActions', () => {
}); });
describe('saveDiffDiscussion', () => { describe('saveDiffDiscussion', () => {
beforeEach(() => {
spyOnDependency(actions, 'getNoteFormData').and.returnValue('testData');
});
it('dispatches actions', done => { it('dispatches actions', done => {
const commitId = 'something';
const formData = {
diffFile: { ...mockDiffFile },
noteableData: {},
};
const note = {};
const state = {
commit: {
id: commitId,
},
};
const dispatch = jasmine.createSpy('dispatch').and.callFake(name => { const dispatch = jasmine.createSpy('dispatch').and.callFake(name => {
switch (name) { switch (name) {
case 'saveNote': case 'saveNote':
...@@ -627,11 +635,19 @@ describe('DiffsStoreActions', () => { ...@@ -627,11 +635,19 @@ describe('DiffsStoreActions', () => {
} }
}); });
saveDiffDiscussion({ dispatch }, { note: {}, formData: {} }) saveDiffDiscussion({ state, dispatch }, { note, formData })
.then(() => { .then(() => {
expect(dispatch.calls.argsFor(0)).toEqual(['saveNote', 'testData', { root: true }]); const { calls } = dispatch;
expect(dispatch.calls.argsFor(1)).toEqual(['updateDiscussion', 'test', { root: true }]);
expect(dispatch.calls.argsFor(2)).toEqual(['assignDiscussionsToDiff', ['discussion']]); expect(calls.count()).toBe(5);
expect(calls.argsFor(0)).toEqual(['saveNote', jasmine.any(Object), { root: true }]);
const postData = calls.argsFor(0)[1];
expect(postData.data.note.commit_id).toBe(commitId);
expect(calls.argsFor(1)).toEqual(['updateDiscussion', 'test', { root: true }]);
expect(calls.argsFor(2)).toEqual(['assignDiscussionsToDiff', ['discussion']]);
}) })
.then(done) .then(done)
.catch(done.fail); .catch(done.fail);
......
...@@ -150,7 +150,7 @@ describe('DiffsStoreUtils', () => { ...@@ -150,7 +150,7 @@ describe('DiffsStoreUtils', () => {
note: { note: {
noteable_type: options.noteableType, noteable_type: options.noteableType,
noteable_id: options.noteableData.id, noteable_id: options.noteableData.id,
commit_id: '', commit_id: undefined,
type: DIFF_NOTE_TYPE, type: DIFF_NOTE_TYPE,
line_code: options.noteTargetLine.line_code, line_code: options.noteTargetLine.line_code,
note: options.note, note: options.note,
...@@ -209,7 +209,7 @@ describe('DiffsStoreUtils', () => { ...@@ -209,7 +209,7 @@ describe('DiffsStoreUtils', () => {
note: { note: {
noteable_type: options.noteableType, noteable_type: options.noteableType,
noteable_id: options.noteableData.id, noteable_id: options.noteableData.id,
commit_id: '', commit_id: undefined,
type: LEGACY_DIFF_NOTE_TYPE, type: LEGACY_DIFF_NOTE_TYPE,
line_code: options.noteTargetLine.line_code, line_code: options.noteTargetLine.line_code,
note: options.note, note: options.note,
......
...@@ -31,8 +31,8 @@ describe('Trigger block', () => { ...@@ -31,8 +31,8 @@ describe('Trigger block', () => {
}); });
describe('with variables', () => { describe('with variables', () => {
describe('reveal variables', () => { describe('hide/reveal variables', () => {
it('reveals variables on click', done => { it('should toggle variables on click', done => {
vm = mountComponent(Component, { vm = mountComponent(Component, {
trigger: { trigger: {
short_token: 'bd7e', short_token: 'bd7e',
...@@ -48,6 +48,10 @@ describe('Trigger block', () => { ...@@ -48,6 +48,10 @@ describe('Trigger block', () => {
vm.$nextTick() vm.$nextTick()
.then(() => { .then(() => {
expect(vm.$el.querySelector('.js-build-variables')).not.toBeNull(); expect(vm.$el.querySelector('.js-build-variables')).not.toBeNull();
expect(vm.$el.querySelector('.js-reveal-variables').textContent.trim()).toEqual(
'Hide values',
);
expect(vm.$el.querySelector('.js-build-variables').textContent).toContain( expect(vm.$el.querySelector('.js-build-variables').textContent).toContain(
'UPLOAD_TO_GCS', 'UPLOAD_TO_GCS',
); );
...@@ -58,6 +62,26 @@ describe('Trigger block', () => { ...@@ -58,6 +62,26 @@ describe('Trigger block', () => {
); );
expect(vm.$el.querySelector('.js-build-variables').textContent).toContain('true'); expect(vm.$el.querySelector('.js-build-variables').textContent).toContain('true');
vm.$el.querySelector('.js-reveal-variables').click();
})
.then(vm.$nextTick)
.then(() => {
expect(vm.$el.querySelector('.js-reveal-variables').textContent.trim()).toEqual(
'Reveal values',
);
expect(vm.$el.querySelector('.js-build-variables').textContent).toContain(
'UPLOAD_TO_GCS',
);
expect(vm.$el.querySelector('.js-build-value').textContent).toContain('••••••');
expect(vm.$el.querySelector('.js-build-variables').textContent).toContain(
'UPLOAD_TO_S3',
);
expect(vm.$el.querySelector('.js-build-value').textContent).toContain('••••••');
}) })
.then(done) .then(done)
.catch(done.fail); .catch(done.fail);
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::BranchPushMergeCommitAnalyzer do
let(:project) { create(:project, :repository) }
let(:oldrev) { 'merge-commit-analyze-before' }
let(:newrev) { 'merge-commit-analyze-after' }
let(:commits) { project.repository.commits_between(oldrev, newrev).reverse }
subject { described_class.new(commits) }
describe '#get_merge_commit' do
let(:expected_merge_commits) do
{
'646ece5cfed840eca0a4feb21bcd6a81bb19bda3' => '646ece5cfed840eca0a4feb21bcd6a81bb19bda3',
'29284d9bcc350bcae005872d0be6edd016e2efb5' => '29284d9bcc350bcae005872d0be6edd016e2efb5',
'5f82584f0a907f3b30cfce5bb8df371454a90051' => '29284d9bcc350bcae005872d0be6edd016e2efb5',
'8a994512e8c8f0dfcf22bb16df6e876be7a61036' => '29284d9bcc350bcae005872d0be6edd016e2efb5',
'689600b91aabec706e657e38ea706ece1ee8268f' => '29284d9bcc350bcae005872d0be6edd016e2efb5',
'db46a1c5a5e474aa169b6cdb7a522d891bc4c5f9' => 'db46a1c5a5e474aa169b6cdb7a522d891bc4c5f9'
}
end
it 'returns correct merge commit SHA for each commit' do
expected_merge_commits.each do |commit, merge_commit|
expect(subject.get_merge_commit(commit)).to eq(merge_commit)
end
end
context 'when one parent has two children' do
let(:oldrev) { '1adbdefe31288f3bbe4b614853de4908a0b6f792' }
let(:newrev) { '5f82584f0a907f3b30cfce5bb8df371454a90051' }
let(:expected_merge_commits) do
{
'5f82584f0a907f3b30cfce5bb8df371454a90051' => '5f82584f0a907f3b30cfce5bb8df371454a90051',
'8a994512e8c8f0dfcf22bb16df6e876be7a61036' => '5f82584f0a907f3b30cfce5bb8df371454a90051',
'689600b91aabec706e657e38ea706ece1ee8268f' => '689600b91aabec706e657e38ea706ece1ee8268f'
}
end
it 'returns correct merge commit SHA for each commit' do
expected_merge_commits.each do |commit, merge_commit|
expect(subject.get_merge_commit(commit)).to eq(merge_commit)
end
end
end
context 'when relevant_commit_ids is provided' do
let(:relevant_commit_id) { '8a994512e8c8f0dfcf22bb16df6e876be7a61036' }
subject { described_class.new(commits, relevant_commit_ids: [relevant_commit_id]) }
it 'returns correct merge commit' do
expected_merge_commits.each do |commit, merge_commit|
subject = described_class.new(commits, relevant_commit_ids: [commit])
expect(subject.get_merge_commit(commit)).to eq(merge_commit)
end
end
end
end
end
# frozen_string_literal: true
require 'fast_spec_helper'
describe Gitlab::CorrelationId do
describe '.use_id' do
it 'yields when executed' do
expect { |blk| described_class.use_id('id', &blk) }.to yield_control
end
it 'stacks correlation ids' do
described_class.use_id('id1') do
described_class.use_id('id2') do |current_id|
expect(current_id).to eq('id2')
end
end
end
it 'for missing correlation id it generates random one' do
described_class.use_id('id1') do
described_class.use_id(nil) do |current_id|
expect(current_id).not_to be_empty
expect(current_id).not_to eq('id1')
end
end
end
end
describe '.current_id' do
subject { described_class.current_id }
it 'returns last correlation id' do
described_class.use_id('id1') do
described_class.use_id('id2') do
is_expected.to eq('id2')
end
end
end
end
describe '.current_or_new_id' do
subject { described_class.current_or_new_id }
context 'when correlation id is set' do
it 'returns last correlation id' do
described_class.use_id('id1') do
is_expected.to eq('id1')
end
end
end
context 'when correlation id is missing' do
it 'returns a new correlation id' do
expect(described_class).to receive(:new_id)
.and_call_original
is_expected.not_to be_empty
end
end
end
describe '.ids' do
subject { described_class.send(:ids) }
it 'returns empty list if not correlation is used' do
is_expected.to be_empty
end
it 'returns list if correlation ids are used' do
described_class.use_id('id1') do
described_class.use_id('id2') do
is_expected.to eq(%w(id1 id2))
end
end
end
end
end
...@@ -7,6 +7,10 @@ describe Gitlab::JsonLogger do ...@@ -7,6 +7,10 @@ describe Gitlab::JsonLogger do
let(:now) { Time.now } let(:now) { Time.now }
describe '#format_message' do describe '#format_message' do
before do
allow(Gitlab::CorrelationId).to receive(:current_id).and_return('new-correlation-id')
end
it 'formats strings' do it 'formats strings' do
output = subject.format_message('INFO', now, 'test', 'Hello world') output = subject.format_message('INFO', now, 'test', 'Hello world')
data = JSON.parse(output) data = JSON.parse(output)
...@@ -14,6 +18,7 @@ describe Gitlab::JsonLogger do ...@@ -14,6 +18,7 @@ describe Gitlab::JsonLogger do
expect(data['severity']).to eq('INFO') expect(data['severity']).to eq('INFO')
expect(data['time']).to eq(now.utc.iso8601(3)) expect(data['time']).to eq(now.utc.iso8601(3))
expect(data['message']).to eq('Hello world') expect(data['message']).to eq('Hello world')
expect(data['correlation_id']).to eq('new-correlation-id')
end end
it 'formats hashes' do it 'formats hashes' do
...@@ -24,6 +29,7 @@ describe Gitlab::JsonLogger do ...@@ -24,6 +29,7 @@ describe Gitlab::JsonLogger do
expect(data['time']).to eq(now.utc.iso8601(3)) expect(data['time']).to eq(now.utc.iso8601(3))
expect(data['hello']).to eq(1) expect(data['hello']).to eq(1)
expect(data['message']).to be_nil expect(data['message']).to be_nil
expect(data['correlation_id']).to eq('new-correlation-id')
end end
end end
end end
...@@ -19,14 +19,15 @@ describe Gitlab::Sentry do ...@@ -19,14 +19,15 @@ describe Gitlab::Sentry do
end end
it 'raises the exception if it should' do it 'raises the exception if it should' do
expect(described_class).to receive(:should_raise?).and_return(true) expect(described_class).to receive(:should_raise_for_dev?).and_return(true)
expect { described_class.track_exception(exception) } expect { described_class.track_exception(exception) }
.to raise_error(RuntimeError) .to raise_error(RuntimeError)
end end
context 'when exceptions should not be raised' do context 'when exceptions should not be raised' do
before do before do
allow(described_class).to receive(:should_raise?).and_return(false) allow(described_class).to receive(:should_raise_for_dev?).and_return(false)
allow(Gitlab::CorrelationId).to receive(:current_id).and_return('cid')
end end
it 'logs the exception with all attributes passed' do it 'logs the exception with all attributes passed' do
...@@ -35,8 +36,14 @@ describe Gitlab::Sentry do ...@@ -35,8 +36,14 @@ describe Gitlab::Sentry do
issue_url: 'http://gitlab.com/gitlab-org/gitlab-ce/issues/1' issue_url: 'http://gitlab.com/gitlab-org/gitlab-ce/issues/1'
} }
expected_tags = {
correlation_id: 'cid'
}
expect(Raven).to receive(:capture_exception) expect(Raven).to receive(:capture_exception)
.with(exception, extra: a_hash_including(expected_extras)) .with(exception,
tags: a_hash_including(expected_tags),
extra: a_hash_including(expected_extras))
described_class.track_exception( described_class.track_exception(
exception, exception,
...@@ -58,6 +65,7 @@ describe Gitlab::Sentry do ...@@ -58,6 +65,7 @@ describe Gitlab::Sentry do
before do before do
allow(described_class).to receive(:enabled?).and_return(true) allow(described_class).to receive(:enabled?).and_return(true)
allow(Gitlab::CorrelationId).to receive(:current_id).and_return('cid')
end end
it 'calls Raven.capture_exception' do it 'calls Raven.capture_exception' do
...@@ -66,8 +74,14 @@ describe Gitlab::Sentry do ...@@ -66,8 +74,14 @@ describe Gitlab::Sentry do
issue_url: 'http://gitlab.com/gitlab-org/gitlab-ce/issues/1' issue_url: 'http://gitlab.com/gitlab-org/gitlab-ce/issues/1'
} }
expected_tags = {
correlation_id: 'cid'
}
expect(Raven).to receive(:capture_exception) expect(Raven).to receive(:capture_exception)
.with(exception, extra: a_hash_including(expected_extras)) .with(exception,
tags: a_hash_including(expected_tags),
extra: a_hash_including(expected_extras))
described_class.track_acceptable_exception( described_class.track_acceptable_exception(
exception, exception,
......
...@@ -12,7 +12,8 @@ describe Gitlab::SidekiqLogging::StructuredLogger do ...@@ -12,7 +12,8 @@ describe Gitlab::SidekiqLogging::StructuredLogger do
"queue_namespace" => "cronjob", "queue_namespace" => "cronjob",
"jid" => "da883554ee4fe414012f5f42", "jid" => "da883554ee4fe414012f5f42",
"created_at" => timestamp.to_f, "created_at" => timestamp.to_f,
"enqueued_at" => timestamp.to_f "enqueued_at" => timestamp.to_f,
"correlation_id" => 'cid'
} }
end end
let(:logger) { double() } let(:logger) { double() }
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::SidekiqMiddleware::CorrelationInjector do
class TestWorker
include ApplicationWorker
end
before do |example|
Sidekiq.client_middleware do |chain|
chain.add described_class
end
end
after do |example|
Sidekiq.client_middleware do |chain|
chain.remove described_class
end
Sidekiq::Queues.clear_all
end
around do |example|
Sidekiq::Testing.fake! do
example.run
end
end
it 'injects into payload the correlation id' do
expect_any_instance_of(described_class).to receive(:call).and_call_original
Gitlab::CorrelationId.use_id('new-correlation-id') do
TestWorker.perform_async(1234)
end
expected_job_params = {
"class" => "TestWorker",
"args" => [1234],
"correlation_id" => "new-correlation-id"
}
expect(Sidekiq::Queues.jobs_by_worker).to a_hash_including(
"TestWorker" => a_collection_containing_exactly(
a_hash_including(expected_job_params)))
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::SidekiqMiddleware::CorrelationLogger do
class TestWorker
include ApplicationWorker
end
before do |example|
Sidekiq::Testing.server_middleware do |chain|
chain.add described_class
end
end
after do |example|
Sidekiq::Testing.server_middleware do |chain|
chain.remove described_class
end
end
it 'injects into payload the correlation id' do
expect_any_instance_of(described_class).to receive(:call).and_call_original
expect_any_instance_of(TestWorker).to receive(:perform).with(1234) do
expect(Gitlab::CorrelationId.current_id).to eq('new-correlation-id')
end
Sidekiq::Client.push(
'queue' => 'test',
'class' => TestWorker,
'args' => [1234],
'correlation_id' => 'new-correlation-id')
end
end
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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