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 {
ingressExternalIp() {
return this.applications.ingress.externalIp;
},
certManagerInstalled() {
return this.applications.cert_manager.status === APPLICATION_STATUS.INSTALLED;
},
ingressDescription() {
const extraCostParagraph = sprintf(
_.escape(
......@@ -130,9 +133,9 @@ export default {
return sprintf(
_.escape(
s__(
`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.`,
`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.`,
),
),
{
......@@ -259,6 +262,16 @@ export default {
</span>
</div>
<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>
<p v-if="!ingressExternalIp" class="settings-message js-no-ip-message">
......@@ -272,17 +285,6 @@ export default {
{{ __('More information') }}
</a>
</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>
<div v-html="ingressDescription"></div>
</div>
......@@ -295,10 +297,41 @@ export default {
:status-reason="applications.cert_manager.statusReason"
:request-status="applications.cert_manager.requestStatus"
:request-reason="applications.cert_manager.requestReason"
:install-application-request-params="{ email: applications.cert_manager.email }"
:disabled="!helmInstalled"
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
v-if="isProjectCluster"
......@@ -381,16 +414,17 @@ export default {
/>
</span>
</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>
<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>
</div>
</application-row>
......
......@@ -24,3 +24,4 @@ export const REQUEST_FAILURE = 'request-failure';
export const INGRESS = 'ingress';
export const JUPYTER = 'jupyter';
export const KNATIVE = 'knative';
export const CERT_MANAGER = 'cert_manager';
import { s__ } from '../../locale';
import { INGRESS, JUPYTER, KNATIVE } from '../constants';
import { INGRESS, JUPYTER, KNATIVE, CERT_MANAGER } from '../constants';
export default class ClusterStore {
constructor() {
......@@ -30,6 +30,7 @@ export default class ClusterStore {
statusReason: null,
requestStatus: null,
requestReason: null,
email: null,
},
runner: {
title: s__('ClusterIntegration|GitLab Runner'),
......@@ -103,6 +104,9 @@ export default class ClusterStore {
if (appId === INGRESS) {
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) {
this.state.applications.jupyter.hostname =
serverAppEntry.hostname ||
......
......@@ -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({
commit: state.commit,
note,
...formData,
});
......
......@@ -27,6 +27,7 @@ export const getReversePosition = linePosition => {
export function getFormData(params) {
const {
commit,
note,
noteableType,
noteableData,
......@@ -66,7 +67,7 @@ export function getFormData(params) {
position,
noteable_type: noteableType,
noteable_id: noteableData.id,
commit_id: '',
commit_id: commit && commit.id,
type:
diffFile.diff_refs.start_sha && diffFile.diff_refs.head_sha
? DIFF_NOTE_TYPE
......
<script>
import { __ } from '~/locale';
import { GlButton } from '@gitlab/ui';
const HIDDEN_VALUE = '••••••';
export default {
components: {
GlButton,
......@@ -13,17 +16,26 @@ export default {
},
data() {
return {
areVariablesVisible: false,
showVariableValues: false,
};
},
computed: {
hasVariables() {
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: {
revealVariables() {
this.areVariablesVisible = true;
toggleValues() {
this.showVariableValues = !this.showVariableValues;
},
getDisplayValue(value) {
return this.showVariableValues ? value : HIDDEN_VALUE;
},
},
};
......@@ -33,31 +45,33 @@ export default {
<div class="build-widget block">
<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 }}
</p>
<p v-if="hasVariables">
<gl-button
v-if="!areVariablesVisible"
type="button"
class="btn btn-default group js-reveal-variables"
@click="revealVariables"
>
{{ __('Reveal Variables') }}
</gl-button>
</p>
<template v-if="hasVariables">
<p class="trigger-variables-btn-container">
<span class="build-light-text"> {{ __('Variables:') }} </span>
<dl v-if="areVariablesVisible" class="js-build-variables trigger-build-variables">
<template v-for="variable in trigger.variables">
<dt :key="`${variable.key}-variable`" class="js-build-variable trigger-build-variable">
{{ variable.key }}
</dt>
<gl-button v-if="hasValues" class="group js-reveal-variables" @click="toggleValues">
{{ getToggleButtonText }}
</gl-button>
</p>
<dd :key="`${variable.key}-value`" class="js-build-value trigger-build-value">
{{ variable.value }}
</dd>
</template>
</dl>
<table class="js-build-variables trigger-build-variables">
<tr v-for="(variable, index) in trigger.variables" :key="`${variable.key}-${index}`">
<td class="js-build-variable trigger-build-variable trigger-variables-table-cell">
{{ variable.key }}
</td>
<td class="js-build-value trigger-build-value trigger-variables-table-cell">
{{ getDisplayValue(variable.value) }}
</td>
</tr>
</table>
</template>
</div>
</template>
......@@ -155,7 +155,7 @@ export default class MilestoneSelect {
const { $el, e } = clickEvent;
let selected = clickEvent.selectedObj;
let data, boardsStore;
let data, modalStoreFilter;
if (!selected) return;
if (options.handleClick) {
......@@ -179,11 +179,11 @@ export default class MilestoneSelect {
}
if ($dropdown.closest('.add-issues-modal').length) {
boardsStore = ModalStore.store.filter;
modalStoreFilter = ModalStore.store.filter;
}
if (boardsStore) {
boardsStore[$dropdown.data('fieldName')] = selected.name;
if (modalStoreFilter) {
modalStoreFilter[$dropdown.data('fieldName')] = selected.name;
e.preventDefault();
} else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
return Issuable.filterResults($dropdown.closest('form'));
......
......@@ -247,15 +247,19 @@ Please check your network connection and try again.`;
} else {
this.reopenIssue()
.then(() => this.enableButton())
.catch(() => {
.catch(({ data }) => {
this.enableButton();
this.toggleStateButtonLoading(false);
Flash(
sprintf(
__('Something went wrong while reopening the %{issuable}. Please try again later'),
{ issuable: this.noteableDisplayName },
),
let errorMessage = sprintf(
__('Something went wrong while reopening the %{issuable}. Please try again later'),
{ issuable: this.noteableDisplayName },
);
if (data) {
errorMessage = Object.values(data).join('\n');
}
Flash(errorMessage);
});
}
},
......
......@@ -46,6 +46,7 @@ export default {
class="sidebar-collapsed-icon"
data-placement="left"
data-container="body"
data-boundary="viewport"
@click="handleClick"
>
<i aria-hidden="true" data-hidden="true" class="fa fa-tags"> </i>
......
......@@ -383,6 +383,16 @@
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 {
......
......@@ -221,9 +221,16 @@
padding: 16px 0;
}
.trigger-variables-btn-container {
@extend .d-flex;
justify-content: space-between;
align-items: center;
}
.trigger-build-variables {
margin: 0;
overflow-x: auto;
width: 100%;
-ms-overflow-style: scrollbar;
-webkit-overflow-scrolling: touch;
}
......@@ -236,7 +243,15 @@
.trigger-build-value {
padding: 2px 4px;
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 {
......
......@@ -8,7 +8,6 @@ class ApplicationController < ActionController::Base
include GitlabRoutingHelper
include PageLayoutHelper
include SafeParamsHelper
include SentryHelper
include WorkhorseHelper
include EnforcesTwoFactorAuthentication
include WithPerformanceBar
......@@ -129,6 +128,7 @@ class ApplicationController < ActionController::Base
payload[:ua] = request.env["HTTP_USER_AGENT"]
payload[:remote_ip] = request.remote_ip
payload[Gitlab::CorrelationId::LOG_KEY] = Gitlab::CorrelationId.current_id
logged_user = auth_user
......@@ -159,7 +159,7 @@ class ApplicationController < ActionController::Base
end
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
application_trace = ActionDispatch::ExceptionWrapper.new(backtrace_cleaner, exception).application_trace
......@@ -495,4 +495,8 @@ class ApplicationController < ActionController::Base
def impersonator
@impersonator ||= User.find(session[:impersonator_id]) if session[:impersonator_id]
end
def sentry_context
Gitlab::Sentry.context(current_user)
end
end
......@@ -23,6 +23,6 @@ class Clusters::ApplicationsController < Clusters::BaseController
end
def create_cluster_application_params
params.permit(:application, :hostname)
params.permit(:application, :hostname, :email)
end
end
......@@ -102,7 +102,7 @@ module IssuableCollections
elsif @group
options[:group_id] = @group.id
options[:include_subgroups] = true
options[:use_cte_for_search] = true
options[:attempt_group_search_optimizations] = true
end
params.permit(finder_type.valid_params).merge(options)
......
......@@ -124,17 +124,21 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
respond_to do |format|
format.html do
if @merge_request.valid?
redirect_to([@merge_request.target_project.namespace.becomes(Namespace), @merge_request.target_project, @merge_request])
else
if @merge_request.errors.present?
define_edit_vars
render :edit
else
redirect_to project_merge_request_path(@merge_request.target_project, @merge_request)
end
end
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
rescue ActiveRecord::StaleObjectError
......
......@@ -27,12 +27,13 @@
# created_before: datetime
# updated_after: datetime
# updated_before: datetime
# use_cte_for_search: boolean
# attempt_group_search_optimizations: boolean
#
class IssuableFinder
prepend FinderWithCrossProjectAccess
include FinderMethods
include CreatedAtFilter
include Gitlab::Utils::StrongMemoize
requires_cross_project_access unless: -> { project? }
......@@ -75,8 +76,9 @@ class IssuableFinder
items = init_collection
items = filter_items(items)
# This has to be last as we may use a CTE as an optimization fence by
# passing the use_cte_for_search param
# This has to be last as we may use a CTE as an optimization fence
# 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
items = by_search(items)
......@@ -85,6 +87,8 @@ class IssuableFinder
def filter_items(items)
items = by_project(items)
items = by_group(items)
items = by_subquery(items)
items = by_scope(items)
items = by_created_at(items)
items = by_updated_at(items)
......@@ -282,12 +286,31 @@ class IssuableFinder
end
# 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
def init_collection
klass.all
end
def attempt_group_search_optimizations?
search && Gitlab::Database.postgresql? && params[:attempt_group_search_optimizations]
end
def count_key(value)
Array(value).last.to_sym
end
......@@ -351,12 +374,13 @@ class IssuableFinder
end
# rubocop: enable CodeReuse/ActiveRecord
def use_cte_for_search?
return false unless search
return false unless Gitlab::Database.postgresql?
return false unless Feature.enabled?(:use_cte_for_group_issues_search, default_enabled: true)
params[:use_cte_for_search]
# Wrap projects and groups in a subquery if the conditions are met.
def by_subquery(items)
if use_subquery_for_search?
klass.where(id: items.select(:id)) # rubocop: disable CodeReuse/ActiveRecord
else
items
end
end
# rubocop: disable CodeReuse/ActiveRecord
......
......@@ -42,7 +42,7 @@ module IconsHelper
end
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)
exception = ArgumentError.new("#{icon_name} is not a known icon in @gitlab-org/gitlab-svg")
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
default_value_for :version, VERSION
default_value_for :email do |cert_manager|
cert_manager.cluster&.user&.email
end
validates :email, presence: true
def chart
......
......@@ -3,7 +3,7 @@
module Clusters
module Applications
class Runner < ActiveRecord::Base
VERSION = '0.1.38'.freeze
VERSION = '0.1.39'.freeze
self.table_name = 'clusters_applications_runners'
......
......@@ -177,7 +177,9 @@ class Commit
def title
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
# Returns the full commits title
......
......@@ -38,12 +38,13 @@ module DiscussionOnDiff
end
# 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)
diff_limit = [diff_limit, NUMBER_OF_TRUNCATED_DIFF_LINES].compact.min
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 = []
......
......@@ -541,15 +541,26 @@ class MergeRequest < ActiveRecord::Base
def validate_branches
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
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 = similar_mrs.where('id not in (?)', self.id) if self.id
if similar_mrs.any?
errors.add :validate_branches,
"Cannot Create: This merge request already exists: #{similar_mrs.pluck(:title)}"
similar_mrs = target_project
.merge_requests
.where(source_branch: source_branch, target_branch: target_branch)
.where(source_project_id: source_project&.id)
.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
......
......@@ -6,4 +6,5 @@ class ClusterApplicationEntity < Grape::Entity
expose :status_reason
expose :external_ip, if: -> (e, _) { e.respond_to?(:external_ip) }
expose :hostname, if: -> (e, _) { e.respond_to?(:hostname) }
expose :email, if: -> (e, _) { e.respond_to?(:email) }
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
class DiffFileEntity < Grape::Entity
include RequestAwareEntity
class DiffFileEntity < DiffFileBaseEntity
include CommitsHelper
include DiffHelper
include SubmoduleHelper
include BlobHelper
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 :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 :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|
merge_request = options[:merge_request]
......@@ -76,36 +24,6 @@ class DiffFileEntity < Grape::Entity
)
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|
merge_request = options[:merge_request]
......@@ -146,18 +64,4 @@ class DiffFileEntity < Grape::Entity
# Used for parallel diffs
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
# frozen_string_literal: true
class DiscussionDiffFileEntity < DiffFileBaseEntity
end
......@@ -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)
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
......@@ -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 :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 :commit_id
......
......@@ -3,5 +3,6 @@
class TriggerVariableEntity < Grape::Entity
include RequestAwareEntity
expose :key, :value, :public
expose :key, :public
expose :value, if: ->(_, _) { can?(request.current_user, :admin_build, request.project) }
end
......@@ -20,7 +20,7 @@ module Clusters
end
if application.has_attribute?(:email)
application.email = current_user.email
application.email = params[:email]
end
if application.respond_to?(:oauth_application)
......
......@@ -58,13 +58,27 @@ module MergeRequests
.preload(:latest_merge_request_diff)
.where(target_branch: @push.branch_name).to_a
.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_ids.include?(merge_request.diff_head_sha) &&
merge_request.merge_request_diff.state != 'empty'
commit_analyze_enabled = Feature.enabled?(:branch_push_merge_commit_analyze, @project, default_enabled: true)
if commit_analyze_enabled
analyzer = Gitlab::BranchPushMergeCommitAnalyzer.new(
@commits.reverse,
relevant_commit_ids: merge_requests.map(&:diff_head_sha)
)
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
.new(merge_request.target_project, @current_user)
.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?
- on_image = discussion.on_image? if diff_discussion
- if discussion
- phrase_end_char = on_image ? "." : ":"
%p.details
%p{ style: "color: #777777;" }
= 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 discussion.new_discussion?
......@@ -15,16 +20,16 @@
- else
commented on a discussion
on #{link_to discussion.file_path, @target_url}
on #{link_to discussion.file_path, target_url}
- else
- if discussion.new_discussion?
started a new discussion
- else
commented on a #{link_to 'discussion', @target_url}
commented on a #{link_to 'discussion', target_url}
- elsif Gitlab::CurrentSettings.email_author_in_body
%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
= content_for :head do
......@@ -32,11 +37,11 @@
%table
= render partial: "projects/diffs/line",
collection: discussion.truncated_diff_lines,
collection: discussion.truncated_diff_lines(diff_limit: diff_limit),
as: :line,
locals: { diff_file: discussion.diff_file,
plain: true,
email: true }
%div
= markdown(@note.note, pipeline: :email, author: @note.author)
%div{ style: note_style }
= 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? -%>
<%= @note.author_name -%>
<%= note.author_name -%>
<% if discussion.new_discussion? -%>
<%= " started a new discussion" -%>
<% else -%>
......@@ -13,14 +16,14 @@
<% elsif Gitlab::CurrentSettings.email_author_in_body -%>
<%= "#{@note.author_name} commented:" -%>
<%= "#{note.author_name} commented:" -%>
<% end -%>
<% 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" -%>
<% end -%>
<% end -%>
<%= @note.note -%>
<%= note.note -%>
......@@ -5,7 +5,7 @@
- subscribed = params[:subscribed]
- 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
.nav-controls
= link_to _('New label'), new_project_label_path(@project), class: "btn btn-success qa-label-create-new"
......
......@@ -8,11 +8,18 @@ class NewNoteWorker
# rubocop: disable CodeReuse/ActiveRecord
def perform(note_id, _params = {})
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
else
Rails.logger.error("NewNoteWorker: couldn't find note with ID=#{note_id}, skipping job")
end
end
private
# EE-only method
def skip_notification?(note)
false
end
# rubocop: enable CodeReuse/ActiveRecord
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?
gitaly_calls = Gitlab::GitalyClient.get_request_count
payload[:gitaly_calls] = gitaly_calls if gitaly_calls > 0
payload[:response] = event.payload[:response] if event.payload[:response]
payload[Gitlab::CorrelationId::LOG_KEY] = Gitlab::CorrelationId.current_id
payload
end
......
......@@ -24,4 +24,4 @@ def configure_sentry
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|
chain.add Gitlab::SidekiqMiddleware::Shutdown
chain.add Gitlab::SidekiqMiddleware::RequestStoreMiddleware unless ENV['SIDEKIQ_REQUEST_STORE'] == '0'
chain.add Gitlab::SidekiqMiddleware::BatchLoader
chain.add Gitlab::SidekiqMiddleware::CorrelationLogger
chain.add Gitlab::SidekiqStatus::ServerMiddleware
end
......@@ -31,6 +32,7 @@ Sidekiq.configure_server do |config|
config.client_middleware do |chain|
chain.add Gitlab::SidekiqStatus::ClientMiddleware
chain.add Gitlab::SidekiqMiddleware::CorrelationInjector
end
config.on :startup do
......@@ -87,6 +89,7 @@ Sidekiq.configure_client do |config|
config.redis = queues_config_hash
config.client_middleware do |chain|
chain.add Gitlab::SidekiqMiddleware::CorrelationInjector
chain.add Gitlab::SidekiqStatus::ClientMiddleware
end
end
......@@ -24,23 +24,24 @@ The following files require a review from the Documentation team:
* #{docs_paths_to_review.map { |path| "`#{path}`" }.join("\n* ")}
When your content is ready for review, mention a technical writer in a separate
comment and explain what needs to be reviewed.
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.
Who to ping [based on DevOps stages](https://about.gitlab.com/handbook/product/categories/#devops-stages):
When your content is ready for review, assign the MR to a technical writer
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
to be reviewed.
| Tech writer | Stage(s) |
| ------------ | ------------------------------------------------------------ |
| `@marcia` | ~Create ~Release |
| `@marcia` | ~Create ~Release + ~"development guidelines" |
| `@axil` | ~Distribution ~Gitaly ~Gitter ~Monitoring ~Packaging ~Secure |
| `@eread` | ~Manage ~Configure ~Geo ~Verify |
| `@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
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
unless gitlab.mr_labels.include?('Documentation')
......
......@@ -71,13 +71,17 @@ Sidekiq::Testing.inline! do
params[:storage_version] = Project::LATEST_STORAGE_VERSION
end
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 = nil
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.import_state.send(:_run_after_commit_queue)
end
if project.valid? && project.valid_repo?
......
......@@ -25,7 +25,9 @@ Gitlab::Seeder.quiet do
developer = project.team.developers.sample
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 '.'
end
end
......@@ -39,7 +41,9 @@ Gitlab::Seeder.quiet do
target_branch: 'master',
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 '.'
params = {
......@@ -47,6 +51,8 @@ Gitlab::Seeder.quiet do
target_branch: 'feature',
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 '.'
end
......@@ -989,10 +989,9 @@ curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://git
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 merge request is already merged or closed - you'll get a `406` and the error message 'Method Not Allowed'
If it has some conflicts and can not be merged - you'll get a `406` and the error message 'Branch cannot be merged'
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:
### 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
results:
- Found in filenames are displayed before results found in contents.
- 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
types are displayed separately.
- 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.
might be found in both the filename and content, or might appear multiple
times in the content.
```bash
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:
### Scope: blobs
Filters are available for this scope:
- filename
- path
- 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:
- Found in filenames are displayed before results found in contents.
- 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
types are displayed separately.
- 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.
might be found in both the filename and content, or might appear multiple
times in the content.
```bash
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:
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)
......
......@@ -368,6 +368,16 @@ You can combine one or more of the following:
= 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
......@@ -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.
Submitting an EE-equivalent merge request cherry-picking all commits from CE to EE is
essential to avoid them.
1. In a full pipeline, tests for [`/help`](#gitlab-help-tests).
### Linting
......
......@@ -113,7 +113,15 @@ feature flag. You can stub a feature flag as follows:
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).
......@@ -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 |
| [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) |
| [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/) |
......
......@@ -20,7 +20,8 @@ module API
Gitlab::GrapeLogging::Loggers::RouteLogger.new,
Gitlab::GrapeLogging::Loggers::UserLogger.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
......@@ -91,7 +92,6 @@ module API
content_type :txt, "text/plain"
# Ensure the namespace is right, otherwise we might load Grape::API::Helpers
helpers ::SentryHelper
helpers ::API::Helpers
helpers ::API::Helpers::CommonHelpers
......
......@@ -381,10 +381,10 @@ module API
end
def handle_api_exception(exception)
if sentry_enabled? && report_exception?(exception)
if report_exception?(exception)
define_params_for_grape_middleware
sentry_context
Raven.capture_exception(exception, extra: params)
Gitlab::Sentry.context(current_user)
Gitlab::Sentry.track_acceptable_exception(exception, extra: params)
end
# 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
feature = feature_stack && feature_stack[0]
metadata['call_site'] = feature.to_s if feature
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)
......
# 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
data = {}
data[:severity] = severity
data[:time] = timestamp.utc.iso8601(3)
data[Gitlab::CorrelationId::LOG_KEY] = Gitlab::CorrelationId.current_id
case message
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 @@
module Gitlab
module Sentry
def self.enabled?
Rails.env.production? && Gitlab::CurrentSettings.sentry_enabled?
(Rails.env.production? || Rails.env.development?) &&
Gitlab::CurrentSettings.sentry_enabled?
end
def self.context(current_user = nil)
......@@ -31,7 +32,7 @@ module Gitlab
def self.track_exception(exception, issue_url: nil, extra: {})
track_acceptable_exception(exception, issue_url: issue_url, extra: extra)
raise exception if should_raise?
raise exception if should_raise_for_dev?
end
# This should be used when you do not want to raise an exception in
......@@ -43,7 +44,11 @@ module Gitlab
extra[:issue_url] = issue_url if issue_url
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
......@@ -55,7 +60,7 @@ module Gitlab
end
end
def self.should_raise?
def self.should_raise_for_dev?
Rails.env.development? || Rails.env.test?
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 ""
msgid "ClusterIntegration|Cert-Manager"
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)"
msgstr ""
......@@ -1961,6 +1964,12 @@ msgstr ""
msgid "ClusterIntegration|Integration status"
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"
msgstr ""
......@@ -2183,9 +2192,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
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"
msgstr ""
......@@ -4487,6 +4493,9 @@ msgid_plural "Hide values"
msgstr[0] ""
msgstr[1] ""
msgid "Hide values"
msgstr ""
msgid "Hide whitespace changes"
msgstr ""
......@@ -7316,14 +7325,14 @@ msgstr ""
msgid "Retry verification"
msgstr ""
msgid "Reveal Variables"
msgstr ""
msgid "Reveal value"
msgid_plural "Reveal values"
msgstr[0] ""
msgstr[1] ""
msgid "Reveal values"
msgstr ""
msgid "Revert this commit"
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."
msgstr ""
msgid "Variables:"
msgstr ""
msgid "Various container registry settings."
msgstr ""
......
......@@ -460,6 +460,14 @@ describe ApplicationController do
expect(controller.last_payload.has_key?(:response)).to be_falsey
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
it 'logs a response with a string' do
response = spy(ActionDispatch::Response, status: 422, body: 'Hello world', content_type: 'application/json', cookies: {})
......
......@@ -258,9 +258,10 @@ describe GroupsController do
end
context 'searching' do
# Remove as part of https://gitlab.com/gitlab-org/gitlab-ce/issues/52271
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_subquery_for_group_issues_search: true)
end
it 'works with popularity sort' do
......
......@@ -401,18 +401,56 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
context 'with variables' do
before do
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
it 'exposes trigger information and variables' do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('job/job_details')
expect(json_response['trigger']['short_token']).to eq 'toke'
expect(json_response['trigger']['variables'].length).to eq 1
expect(json_response['trigger']['variables'].first['key']).to eq "TRIGGER_KEY_1"
expect(json_response['trigger']['variables'].first['value']).to eq "TRIGGER_VALUE_1"
expect(json_response['trigger']['variables'].first['public']).to eq false
context 'user is not a mantainer' do
before do
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 be_nil
expect(first_variable['public']).to eq false
end
end
end
end
......
......@@ -290,6 +290,20 @@ describe Projects::MergeRequestsController do
it_behaves_like 'update invalid issuable', MergeRequest
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
describe 'POST merge' do
......
......@@ -70,6 +70,44 @@ describe 'Clusters Applications', :js do
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 application: Ingress' do
before do
......
......@@ -346,44 +346,85 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
describe 'Variables' do
let(:trigger_request) { create(:ci_trigger_request) }
let(:job) { create(:ci_build, pipeline: pipeline, trigger_request: trigger_request) }
let(:job) do
create :ci_build, pipeline: pipeline, trigger_request: trigger_request
end
context 'when user is a maintainer' do
shared_examples 'no reveal button variables behavior' do
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
it 'shows variable key and value after click', :js do
expect(page).to have_content('Token')
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')
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')
click_button 'Reveal Variables'
visit project_job_path(project, job)
end
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: 'TRIGGER_VALUE_1')
it_behaves_like 'no reveal button variables behavior'
end
end
context 'when variables are stored in trigger_request' do
context 'when user is a maintainer' 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
it_behaves_like 'expected variables behavior'
end
context 'when variables are stored in trigger_request' do
before do
trigger_request.update_attribute(:variables, { 'TRIGGER_KEY_1' => 'TRIGGER_VALUE_1' } )
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
visit project_job_path(project, job)
it_behaves_like 'reveal button variables behavior'
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
......
......@@ -115,6 +115,21 @@ describe 'Prioritize labels' do
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
visit project_labels_path(project)
......
......@@ -738,4 +738,131 @@ describe IssuesFinder do
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
......@@ -32,7 +32,8 @@
},
"status_reason": { "type": ["string", "null"] },
"external_ip": { "type": ["string", "null"] },
"hostname": { "type": ["string", "null"] }
"hostname": { "type": ["string", "null"] },
"email": { "type": ["string", "null"] }
},
"required" : [ "name", "status" ]
}
......
......@@ -12,12 +12,11 @@
"type": "object",
"required": [
"key",
"value",
"public"
],
"properties": {
"key": { "type": "string" },
"value": { "type": "string" },
"value": { "type": "string", "optional": true },
"public": { "type": "boolean" }
},
"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', () => {
});
});
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('with ingress installed with ip & jupyter installable', () => {
it('renders hostname active input', () => {
......
......@@ -42,6 +42,7 @@ const CLUSTERS_MOCK_DATA = {
name: 'cert_manager',
status: APPLICATION_STATUS.ERROR,
status_reason: 'Cannot connect',
email: 'test@example.com',
},
],
},
......@@ -86,6 +87,7 @@ const CLUSTERS_MOCK_DATA = {
name: 'cert_manager',
status: APPLICATION_STATUS.ERROR,
status_reason: 'Cannot connect',
email: 'test@example.com',
},
],
},
......
......@@ -115,6 +115,7 @@ describe('Clusters Store', () => {
statusReason: mockResponseData.applications[6].status_reason,
requestStatus: null,
requestReason: null,
email: mockResponseData.applications[6].email,
},
},
});
......
......@@ -489,8 +489,6 @@ export default {
diff_discussion: true,
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',
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 = [
......
......@@ -31,6 +31,7 @@ import actions, {
import eventHub from '~/notes/event_hub';
import * as types from '~/diffs/store/mutation_types';
import axios from '~/lib/utils/axios_utils';
import mockDiffFile from 'spec/diffs/mock_data/diff_file';
import testAction from '../../helpers/vuex_action_helper';
describe('DiffsStoreActions', () => {
......@@ -609,11 +610,18 @@ describe('DiffsStoreActions', () => {
});
describe('saveDiffDiscussion', () => {
beforeEach(() => {
spyOnDependency(actions, 'getNoteFormData').and.returnValue('testData');
});
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 => {
switch (name) {
case 'saveNote':
......@@ -627,11 +635,19 @@ describe('DiffsStoreActions', () => {
}
});
saveDiffDiscussion({ dispatch }, { note: {}, formData: {} })
saveDiffDiscussion({ state, dispatch }, { note, formData })
.then(() => {
expect(dispatch.calls.argsFor(0)).toEqual(['saveNote', 'testData', { root: true }]);
expect(dispatch.calls.argsFor(1)).toEqual(['updateDiscussion', 'test', { root: true }]);
expect(dispatch.calls.argsFor(2)).toEqual(['assignDiscussionsToDiff', ['discussion']]);
const { calls } = dispatch;
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)
.catch(done.fail);
......
......@@ -150,7 +150,7 @@ describe('DiffsStoreUtils', () => {
note: {
noteable_type: options.noteableType,
noteable_id: options.noteableData.id,
commit_id: '',
commit_id: undefined,
type: DIFF_NOTE_TYPE,
line_code: options.noteTargetLine.line_code,
note: options.note,
......@@ -209,7 +209,7 @@ describe('DiffsStoreUtils', () => {
note: {
noteable_type: options.noteableType,
noteable_id: options.noteableData.id,
commit_id: '',
commit_id: undefined,
type: LEGACY_DIFF_NOTE_TYPE,
line_code: options.noteTargetLine.line_code,
note: options.note,
......
......@@ -31,8 +31,8 @@ describe('Trigger block', () => {
});
describe('with variables', () => {
describe('reveal variables', () => {
it('reveals variables on click', done => {
describe('hide/reveal variables', () => {
it('should toggle variables on click', done => {
vm = mountComponent(Component, {
trigger: {
short_token: 'bd7e',
......@@ -48,6 +48,10 @@ describe('Trigger block', () => {
vm.$nextTick()
.then(() => {
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(
'UPLOAD_TO_GCS',
);
......@@ -58,6 +62,26 @@ describe('Trigger block', () => {
);
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)
.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
let(:now) { Time.now }
describe '#format_message' do
before do
allow(Gitlab::CorrelationId).to receive(:current_id).and_return('new-correlation-id')
end
it 'formats strings' do
output = subject.format_message('INFO', now, 'test', 'Hello world')
data = JSON.parse(output)
......@@ -14,6 +18,7 @@ describe Gitlab::JsonLogger do
expect(data['severity']).to eq('INFO')
expect(data['time']).to eq(now.utc.iso8601(3))
expect(data['message']).to eq('Hello world')
expect(data['correlation_id']).to eq('new-correlation-id')
end
it 'formats hashes' do
......@@ -24,6 +29,7 @@ describe Gitlab::JsonLogger do
expect(data['time']).to eq(now.utc.iso8601(3))
expect(data['hello']).to eq(1)
expect(data['message']).to be_nil
expect(data['correlation_id']).to eq('new-correlation-id')
end
end
end
......@@ -19,14 +19,15 @@ describe Gitlab::Sentry do
end
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) }
.to raise_error(RuntimeError)
end
context 'when exceptions should not be raised' 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
it 'logs the exception with all attributes passed' do
......@@ -35,8 +36,14 @@ describe Gitlab::Sentry do
issue_url: 'http://gitlab.com/gitlab-org/gitlab-ce/issues/1'
}
expected_tags = {
correlation_id: 'cid'
}
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(
exception,
......@@ -58,6 +65,7 @@ describe Gitlab::Sentry do
before do
allow(described_class).to receive(:enabled?).and_return(true)
allow(Gitlab::CorrelationId).to receive(:current_id).and_return('cid')
end
it 'calls Raven.capture_exception' do
......@@ -66,8 +74,14 @@ describe Gitlab::Sentry do
issue_url: 'http://gitlab.com/gitlab-org/gitlab-ce/issues/1'
}
expected_tags = {
correlation_id: 'cid'
}
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(
exception,
......
......@@ -12,7 +12,8 @@ describe Gitlab::SidekiqLogging::StructuredLogger do
"queue_namespace" => "cronjob",
"jid" => "da883554ee4fe414012f5f42",
"created_at" => timestamp.to_f,
"enqueued_at" => timestamp.to_f
"enqueued_at" => timestamp.to_f,
"correlation_id" => 'cid'
}
end
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