Commit 72a0a866 authored by Robert Speicher's avatar Robert Speicher

Merge branch 'ce-to-ee-2017-12-23' into 'master'

CE upstream - Saturday

Closes gitlab-ce#36970 and gitlab-qa#122

See merge request gitlab-org/gitlab-ee!3879
parents 7a2e8dcc 2834da69
10.3.0-pre
10.4.0-pre
......@@ -30,6 +30,7 @@ export default class Clusters {
installHelmPath,
installIngressPath,
installRunnerPath,
installPrometheusPath,
clusterStatus,
clusterStatusReason,
helpPath,
......@@ -44,6 +45,7 @@ export default class Clusters {
installHelmEndpoint: installHelmPath,
installIngressEndpoint: installIngressPath,
installRunnerEndpoint: installRunnerPath,
installPrometheusEndpoint: installPrometheusPath,
});
this.toggle = this.toggle.bind(this);
......
......@@ -67,6 +67,16 @@ export default {
and send the results back to GitLab.`,
));
},
prometheusDescription() {
return sprintf(
_.escape(s__('ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications.')), {
gitlabIntegrationLink: `<a href="https://docs.gitlab.com/ce/user/project/integrations/prometheus.html", target="_blank" rel="noopener noreferrer">
${_.escape(s__('ClusterIntegration|Gitlab Integration'))}
</a>`,
},
false,
);
},
},
};
</script>
......@@ -105,6 +115,16 @@ export default {
:status-reason="applications.ingress.statusReason"
:request-status="applications.ingress.requestStatus"
:request-reason="applications.ingress.requestReason"
/>
<application-row
id="prometheus"
:title="applications.prometheus.title"
title-link="https://prometheus.io/docs/introduction/overview/"
:description="prometheusDescription"
:status="applications.prometheus.status"
:status-reason="applications.prometheus.statusReason"
:request-status="applications.prometheus.requestStatus"
:request-reason="applications.prometheus.requestReason"
/>
<!-- NOTE: Don't forget to update `clusters.scss` min-height for this block and uncomment `application_spec` tests -->
<!-- Add GitLab Runner row, all other plumbing is complete -->
......
......@@ -7,6 +7,7 @@ export default class ClusterService {
helm: this.options.installHelmEndpoint,
ingress: this.options.installIngressEndpoint,
runner: this.options.installRunnerEndpoint,
prometheus: this.options.installPrometheusEndpoint,
};
}
......
......@@ -28,6 +28,13 @@ export default class ClusterStore {
requestStatus: null,
requestReason: null,
},
prometheus: {
title: s__('ClusterIntegration|Prometheus'),
status: null,
statusReason: null,
requestStatus: null,
requestReason: null,
},
},
};
}
......
......@@ -2,6 +2,7 @@
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
import icon from '../../vue_shared/components/icon.vue';
export default {
props: {
......@@ -12,6 +13,7 @@
userAvatarImage,
totalTime,
limitWarning,
icon,
},
};
</script>
......@@ -52,7 +54,10 @@
</template>
<template v-else>
<span class="merge-request-branch" v-if="mergeRequest.branch">
<i class= "fa fa-code-fork"></i>
<icon
name="fork"
:size="16">
</icon>
<a :href="mergeRequest.branch.url">{{ mergeRequest.branch.name }}</a>
</span>
</template>
......
......@@ -3,6 +3,7 @@
import iconBranch from '../svg/icon_branch.svg';
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
import icon from '../../vue_shared/components/icon.vue';
export default {
props: {
......@@ -13,6 +14,7 @@
userAvatarImage,
totalTime,
limitWarning,
icon,
},
computed: {
iconBranch() {
......@@ -37,7 +39,10 @@
<user-avatar-image :img-src="build.author.avatarUrl"/>
<h5 class="item-title">
<a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
<i class="fa fa-code-fork"></i>
<icon
name="fork"
:size="16">
</icon>
<a :href="build.branch.url" class="ref-name">{{ build.branch.name }}</a>
<span class="icon-branch" v-html="iconBranch"></span>
<a :href="build.commitUrl" class="commit-sha">{{ build.shortSha }}</a>
......
......@@ -3,6 +3,7 @@
import iconBranch from '../svg/icon_branch.svg';
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
import icon from '../../vue_shared/components/icon.vue';
export default {
props: {
......@@ -12,6 +13,7 @@
components: {
totalTime,
limitWarning,
icon,
},
computed: {
iconBuildStatus() {
......@@ -40,7 +42,10 @@
<a :href="build.url" class="item-build-name">{{ build.name }}</a>
&middot;
<a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
<i class="fa fa-code-fork"></i>
<icon
name="fork"
:size="16">
</icon>
<a :href="build.branch.url" class="ref-name">{{ build.branch.name }}</a>
<span class="icon-branch" v-html="iconBranch"></span>
<a :href="build.commitUrl" class="commit-sha">{{ build.shortSha }}</a>
......
......@@ -123,6 +123,8 @@ import initLDAPGroupsSelect from 'ee/ldap_groups_select'; // eslint-disable-line
return false;
}
const fail = () => Flash('Error loading dynamic module');
path = page.split(':');
shortcut_handler = null;
......@@ -606,7 +608,7 @@ import initLDAPGroupsSelect from 'ee/ldap_groups_select'; // eslint-disable-line
new CILintEditor();
break;
case 'users:show':
new UserCallout();
import('./pages/users/show').then(m => m.default()).catch(fail);
break;
case 'admin:conversational_development_index:show':
new UserCallout();
......
......@@ -300,7 +300,7 @@ GitLabDropdown = (function() {
return function(data) {
_this.fullData = data;
_this.parseData(_this.fullData);
_this.focusTextInput(true);
_this.focusTextInput();
if (_this.options.filterable && _this.filter && _this.filter.input && _this.filter.input.val() && _this.filter.input.val().trim() !== '') {
return _this.filter.input.trigger('input');
}
......@@ -806,24 +806,16 @@ GitLabDropdown = (function() {
return [selectedObject, isMarking];
};
GitLabDropdown.prototype.focusTextInput = function(triggerFocus = false) {
GitLabDropdown.prototype.focusTextInput = function() {
if (this.options.filterable) {
this.dropdown.one('transitionend', () => {
const initialScrollTop = $(window).scrollTop();
const initialScrollTop = $(window).scrollTop();
if (this.dropdown.is('.open')) {
this.filterInput.focus();
}
if ($(window).scrollTop() < initialScrollTop) {
$(window).scrollTop(initialScrollTop);
}
});
if (this.dropdown.is('.open')) {
this.filterInput.focus();
}
if (triggerFocus) {
// This triggers after a ajax request
// in case of slow requests, the dropdown transition could already be finished
this.dropdown.trigger('transitionend');
if ($(window).scrollTop() < initialScrollTop) {
$(window).scrollTop(initialScrollTop);
}
}
};
......
......@@ -2,7 +2,7 @@ import timeago from 'timeago.js';
import dateFormat from 'vendor/date.format';
import { pluralize } from './text_utility';
import {
lang,
languageCode,
s__,
} from '../../locale';
......@@ -24,7 +24,15 @@ export const getDayName = date => ['Sunday', 'Monday', 'Tuesday', 'Wednesday', '
*/
export const formatDate = datetime => dateFormat(datetime, 'mmm d, yyyy h:MMtt Z');
/**
* Timeago uses underscores instead of dashes to separate language from country code.
*
* see https://github.com/hustcc/timeago.js/tree/v3.0.0/locales
*/
const timeagoLanguageCode = languageCode().replace(/-/g, '_');
let timeagoInstance;
/**
* Sets a timeago Instance
*/
......@@ -67,8 +75,8 @@ export function getTimeago() {
][index];
};
timeago.register(lang, locale);
timeago.register(`${lang}-remaining`, localeRemaining);
timeago.register(timeagoLanguageCode, locale);
timeago.register(`${timeagoLanguageCode}-remaining`, localeRemaining);
timeagoInstance = timeago();
}
......@@ -83,7 +91,7 @@ export const renderTimeago = ($els) => {
const timeagoEls = $els || document.querySelectorAll('.js-timeago-render');
// timeago.js sets timeouts internally for each timeago value to be updated in real time
getTimeago().render(timeagoEls, lang);
getTimeago().render(timeagoEls, timeagoLanguageCode);
};
/**
......@@ -118,7 +126,7 @@ export const timeFor = (time, expiredLabel) => {
if (new Date(time) < new Date()) {
return expiredLabel || s__('Timeago|Past due');
}
return getTimeago().format(time, `${lang}-remaining`).trim();
return getTimeago().format(time, `${timeagoLanguageCode}-remaining`).trim();
};
export const getDayDifference = (a, b) => {
......
......@@ -4,9 +4,9 @@ import { bisector } from 'd3-array';
const d3 = { time, bisector, timeSecond, timeMinute, timeHour, timeDay, timeMonth, timeYear };
export const dateFormatWithName = d3.time('%a, %b %-d');
export const dateFormat = d3.time('%b %-d, %Y');
export const timeFormat = d3.time('%-I:%M%p');
export const dateFormatWithName = d3.time('%a, %b %-d');
export const bisectDate = d3.bisector(d => d.time).left;
export function timeScaleFormat(date) {
......
import UserCallout from '~/user_callout';
export default () => new UserCallout();
<script>
import tooltip from '../../vue_shared/directives/tooltip';
import icon from '../../vue_shared/components/icon.vue';
export default {
props: {
......@@ -11,6 +12,9 @@
directives: {
tooltip,
},
components: {
icon,
},
};
</script>
<template>
......@@ -24,10 +28,9 @@
data-placement="top"
data-toggle="dropdown"
aria-label="Artifacts">
<i
class="fa fa-download"
aria-hidden="true">
</i>
<icon
name="download">
</icon>
<i
class="fa fa-caret-down"
aria-hidden="true">
......
......@@ -117,12 +117,10 @@
}());
markdownPreview = new window.MarkdownPreview();
previewButtonSelector = '.js-md-preview-button';
writeButtonSelector = '.js-md-write-button';
lastTextareaPreviewed = null;
const markdownToolbar = $('.md-header-toolbar');
$.fn.setupMarkdownPreview = function () {
var $form = $(this);
......@@ -146,6 +144,7 @@
// toggle content
$form.find('.md-write-holder').hide();
$form.find('.md-preview-holder').show();
markdownToolbar.removeClass('active');
markdownPreview.showPreview($form);
});
......@@ -167,6 +166,7 @@
$form.find('.md-write-holder').show();
$form.find('textarea.markdown-area').focus();
$form.find('.md-preview-holder').hide();
markdownToolbar.addClass('active');
markdownPreview.hideReferencedCommands($form);
});
......
import tooltip from '../../vue_shared/directives/tooltip';
import { pluralize } from '../../lib/utils/text_utility';
import icon from '../../vue_shared/components/icon.vue';
export default {
name: 'MRWidgetHeader',
......@@ -9,6 +10,9 @@ export default {
directives: {
tooltip,
},
components: {
icon,
},
computed: {
shouldShowCommitsBehindText() {
return this.mr.divergedCommitsCount > 0;
......@@ -81,10 +85,9 @@ export default {
data-toggle="dropdown"
aria-label="Download as"
role="button">
<i
class="fa fa-download"
aria-hidden="true">
</i>
<icon
name="download">
</icon>
<i
class="fa fa-caret-down"
aria-hidden="true">
......
......@@ -2,13 +2,14 @@
import commitIconSvg from 'icons/_icon_commit.svg';
import userAvatarLink from './user_avatar/user_avatar_link.vue';
import tooltip from '../directives/tooltip';
import Icon from '../../vue_shared/components/icon.vue';
export default {
props: {
/**
* Indicates the existance of a tag.
* Used to render the correct icon, if true will render `fa-tag` icon,
* if false will render `fa-code-fork` icon.
* if false will render a svg sprite fork icon
*/
tag: {
type: Boolean,
......@@ -107,6 +108,7 @@
},
components: {
userAvatarLink,
Icon,
},
created() {
this.commitIconSvg = commitIconSvg;
......@@ -123,11 +125,10 @@
class="fa fa-tag"
aria-hidden="true">
</i>
<i
<icon
v-if="!tag"
class="fa fa-code-fork"
aria-hidden="true">
</i>
name="fork">
</icon>
</div>
<a
......
......@@ -72,7 +72,9 @@
Preview
</a>
</li>
<li class="md-header-toolbar">
<li
class="md-header-toolbar"
:class="{ active: !previewMarkdown }">
<toolbar-button
tag="**"
button-title="Add bold text"
......
......@@ -122,11 +122,17 @@ export default {
return items;
},
showPagination() {
return this.pageInfo.totalPages > 1;
},
},
};
</script>
<template>
<div class="gl-pagination">
<div
v-if="showPagination"
class="gl-pagination"
>
<ul class="pagination clearfix">
<li
v-for="item in getItems"
......
......@@ -9,12 +9,6 @@
padding-left: $contextual-sidebar-width;
}
// Override position: absolute
.right-sidebar {
position: fixed;
height: calc(100% - #{$header-height});
}
.issues-bulk-update.right-sidebar.right-sidebar-expanded .issuable-sidebar-header {
padding: 10px 0 15px;
}
......
......@@ -16,27 +16,18 @@
@mixin set-visible {
transform: translateY(0);
visibility: visible;
opacity: 1;
transition-duration: 100ms, 150ms, 25ms;
transition-delay: 35ms, 50ms, 25ms;
display: block;
}
@mixin set-invisible {
transform: translateY(-10px);
visibility: hidden;
opacity: 0;
transition-property: opacity, transform, visibility;
transition-duration: 70ms, 250ms, 250ms;
transition-timing-function: linear, $dropdown-animation-timing;
transition-delay: 25ms, 50ms, 0ms;
display: none;
}
.open {
.dropdown-menu,
.dropdown-menu-nav {
@include set-visible;
display: block;
min-height: 40px;
@media (max-width: $screen-xs-max) {
......@@ -55,6 +46,11 @@
}
}
// Get search dropdown to line up with other nav dropdowns
.search-input-container .dropdown-menu {
margin-top: 11px;
}
.dropdown-toggle {
padding: 6px 8px 6px 10px;
background-color: $white-light;
......@@ -214,7 +210,6 @@
.dropdown-menu,
.dropdown-menu-nav {
@include set-invisible;
display: block;
position: absolute;
width: auto;
top: 100%;
......
......@@ -74,7 +74,7 @@
}
.md-header-tab {
@media(max-width: $screen-xs-max) {
@media (max-width: $screen-xs-max) {
flex: 1;
width: 100%;
border-bottom: 1px solid $border-color;
......@@ -82,16 +82,23 @@
}
}
.md-header-toolbar {
margin-left: auto;
.nav-links {
li.md-header-toolbar {
margin-left: auto;
display: none;
@media(max-width: $screen-xs-max) {
flex: none;
display: flex;
justify-content: center;
width: 100%;
padding-top: $gl-padding-top;
padding-bottom: $gl-padding-top;
&.active {
display: block;
@media (max-width: $screen-xs-max) {
flex: none;
display: flex;
justify-content: center;
width: 100%;
padding-top: $gl-padding-top;
padding-bottom: $gl-padding-top;
}
}
}
}
......@@ -175,7 +182,7 @@
margin-left: $gl-padding;
margin-right: -5px;
@media(max-width: $screen-xs-max) {
@media (max-width: $screen-xs-max) {
margin-left: 0;
margin-right: 0;
}
......@@ -239,7 +246,7 @@
}
}
@media(max-width: $screen-xs-max) {
@media (max-width: $screen-xs-max) {
.atwho-view-ul {
width: 350px;
}
......
......@@ -90,11 +90,6 @@
.right-sidebar {
border-left: 1px solid $border-color;
height: calc(100% - #{$header-height});
&.affix {
position: fixed;
top: $header-height;
}
}
.with-performance-bar .right-sidebar.affix {
......
......@@ -16,6 +16,10 @@
.commit-sha,
.commit-info {
margin-left: 4px;
.fork-svg {
margin-right: 4px;
}
}
.ref-name {
......@@ -79,7 +83,7 @@
}
.limit-icon {
margin: 0 8px;
margin: 0 4px;
}
.limit-message {
......
......@@ -6,7 +6,7 @@
.cluster-applications-table {
// Wait for the Vue to kick-in and render the applications block
min-height: 302px;
min-height: 400px;
}
.clusters-dropdown-menu {
......
......@@ -122,7 +122,7 @@
}
.right-sidebar {
position: absolute;
position: fixed;
top: $header-height;
bottom: 0;
right: 0;
......@@ -503,7 +503,7 @@
top: $header-height + $performance-bar-height;
.issuable-sidebar {
height: calc(100% - #{$header-height} - #{$performance-bar-height});
height: calc(100% - #{$performance-bar-height});
}
}
......
......@@ -774,6 +774,11 @@
}
}
.fork-sprite {
margin-right: -5px;
}
.mr-widget-icon {
font-size: 22px;
margin: 0 10px 0 0;
......
......@@ -39,6 +39,10 @@
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
svg {
vertical-align: middle;
}
}
.next-run-cell {
......
......@@ -170,6 +170,12 @@
fill: $gl-text-color-secondary;
}
.sprite {
width: 12px;
height: 12px;
fill: $gl-text-color;
}
.fa {
font-size: 12px;
color: $gl-text-color;
......
......@@ -108,13 +108,6 @@ input[type="checkbox"]:hover {
// Custom dropdown positioning
.dropdown-menu {
transition-property: opacity, transform;
transition-duration: 250ms, 250ms;
transition-delay: 0ms, 25ms;
transition-timing-function: $dropdown-animation-timing;
transform: translateY(0);
opacity: 0;
display: block;
left: -5px;
}
......@@ -152,13 +145,6 @@ input[type="checkbox"]:hover {
background-color: $nav-badge-bg;
border-color: $border-color;
}
.dropdown-menu {
transition-duration: 100ms, 75ms;
transition-delay: 75ms, 100ms;
transform: translateY(7px);
opacity: 1;
}
}
&.has-value {
......
......@@ -5,9 +5,6 @@ class Projects::BlobController < Projects::ApplicationController
include RendersBlob
include ActionView::Helpers::SanitizeHelper
# Raised when given an invalid file path
InvalidPathError = Class.new(StandardError)
prepend_before_action :authenticate_user!, only: [:edit]
before_action :require_non_empty_project, except: [:new, :create]
......@@ -61,7 +58,6 @@ class Projects::BlobController < Projects::ApplicationController
create_commit(Files::UpdateService, success_path: -> { after_edit_path },
failure_view: :edit,
failure_path: project_blob_path(@project, @id))
rescue Files::UpdateService::FileChangedError
@conflict = true
render :edit
......@@ -132,7 +128,6 @@ class Projects::BlobController < Projects::ApplicationController
def assign_blob_vars
@id = params[:id]
@ref, @path = extract_ref(@id)
rescue InvalidPathError
render_404
end
......
......@@ -269,7 +269,7 @@ module BlobHelper
return if blob.empty?
if blob.raw_binary? || blob.stored_externally?
icon = icon('download')
icon = sprite_icon('download')
title = 'Download'
else
icon = icon('file-code-o')
......
......@@ -63,7 +63,7 @@ module CommitsHelper
# Returns a link formatted as a commit branch link
def commit_branch_link(url, text)
link_to(url, class: 'label label-gray ref-name branch-link') do
icon('code-fork', class: 'append-right-5') + "#{text}"
sprite_icon('fork', size: 16, css_class: 'fork-svg') + "#{text}"
end
end
......
......@@ -46,14 +46,20 @@ module SortingHelper
end
def groups_sort_options_hash
options = {
{
sort_value_name => sort_title_name,
sort_value_name_desc => sort_title_name_desc,
sort_value_recently_created => sort_title_recently_created,
sort_value_oldest_created => sort_title_oldest_created,
sort_value_recently_updated => sort_title_recently_updated,
sort_value_oldest_updated => sort_title_oldest_updated
}
end
options
def admin_groups_sort_options_hash
groups_sort_options_hash.merge(
sort_value_largest_group => sort_title_largest_group
)
end
def member_sort_options_hash
......
......@@ -3,32 +3,19 @@ module Clusters
class Helm < ActiveRecord::Base
self.table_name = 'clusters_applications_helm'
include ::Clusters::Concerns::ApplicationCore
include ::Clusters::Concerns::ApplicationStatus
belongs_to :cluster, class_name: 'Clusters::Cluster', foreign_key: :cluster_id
default_value_for :version, Gitlab::Kubernetes::Helm::HELM_VERSION
validates :cluster, presence: true
after_initialize :set_initial_status
def self.application_name
self.to_s.demodulize.underscore
end
def set_initial_status
return unless not_installable?
self.status = 'installable' if cluster&.platform_kubernetes_active?
end
def name
self.class.application_name
end
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(name, true)
Gitlab::Kubernetes::Helm::InstallCommand.new(name, install_helm: true)
end
end
end
......
......@@ -3,41 +3,22 @@ module Clusters
class Ingress < ActiveRecord::Base
self.table_name = 'clusters_applications_ingress'
include ::Clusters::Concerns::ApplicationCore
include ::Clusters::Concerns::ApplicationStatus
belongs_to :cluster, class_name: 'Clusters::Cluster', foreign_key: :cluster_id
validates :cluster, presence: true
default_value_for :ingress_type, :nginx
default_value_for :version, :nginx
after_initialize :set_initial_status
enum ingress_type: {
nginx: 1
}
def self.application_name
self.to_s.demodulize.underscore
end
def set_initial_status
return unless not_installable?
self.status = 'installable' if cluster&.application_helm_installed?
end
def name
self.class.application_name
end
def chart
'stable/nginx-ingress'
end
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(name, false, chart)
Gitlab::Kubernetes::Helm::InstallCommand.new(name, chart: chart)
end
end
end
......
module Clusters
module Applications
class Prometheus < ActiveRecord::Base
VERSION = "2.0.0".freeze
self.table_name = 'clusters_applications_prometheus'
include ::Clusters::Concerns::ApplicationCore
include ::Clusters::Concerns::ApplicationStatus
default_value_for :version, VERSION
def chart
'stable/prometheus'
end
def chart_values_file
"#{Rails.root}/vendor/#{name}/values.yaml"
end
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(name, chart: chart, chart_values_file: chart_values_file)
end
end
end
end
......@@ -7,7 +7,8 @@ module Clusters
APPLICATIONS = {
Applications::Helm.application_name => Applications::Helm,
Applications::Ingress.application_name => Applications::Ingress
Applications::Ingress.application_name => Applications::Ingress,
Applications::Prometheus.application_name => Applications::Prometheus
}.freeze
belongs_to :user
......@@ -22,6 +23,7 @@ module Clusters
has_one :application_helm, class_name: 'Clusters::Applications::Helm'
has_one :application_ingress, class_name: 'Clusters::Applications::Ingress'
has_one :application_prometheus, class_name: 'Clusters::Applications::Prometheus'
accepts_nested_attributes_for :provider_gcp, update_only: true
accepts_nested_attributes_for :platform_kubernetes, update_only: true
......@@ -64,7 +66,8 @@ module Clusters
def applications
[
application_helm || build_application_helm,
application_ingress || build_application_ingress
application_ingress || build_application_ingress,
application_prometheus || build_application_prometheus
]
end
......
module Clusters
module Concerns
module ApplicationCore
extend ActiveSupport::Concern
included do
belongs_to :cluster, class_name: 'Clusters::Cluster', foreign_key: :cluster_id
validates :cluster, presence: true
after_initialize :set_initial_status
def set_initial_status
return unless not_installable?
self.status = 'installable' if cluster&.application_helm_installed?
end
def self.application_name
self.to_s.demodulize.underscore
end
def name
self.class.application_name
end
end
end
end
end
......@@ -9,7 +9,6 @@ module DiscussionOnDiff
:original_line_code,
:diff_file,
:diff_line,
:for_line?,
:active?,
:created_at_diff?,
......@@ -39,17 +38,16 @@ module DiscussionOnDiff
# Returns an array of at most 16 highlighted lines above a diff note
def truncated_diff_lines(highlight: true)
lines = highlight ? highlighted_diff_lines : diff_lines
initial_line_index = [diff_line.index - NUMBER_OF_TRUNCATED_DIFF_LINES + 1, 0].max
prev_lines = []
lines.each do |line|
lines[initial_line_index..diff_line.index].each do |line|
if line.meta?
prev_lines.clear
else
prev_lines << line
break if for_line?(line)
prev_lines.shift if prev_lines.length >= NUMBER_OF_TRUNCATED_DIFF_LINES
end
end
......
......@@ -14,10 +14,6 @@ module NoteOnDiff
raise NotImplementedError
end
def for_line?(line)
raise NotImplementedError
end
def original_line_code
raise NotImplementedError
end
......
......@@ -26,7 +26,7 @@ class DiffNote < Note
before_validation :set_original_position, on: :create
before_validation :update_position, on: :create, if: :on_text?
before_validation :set_line_code
before_validation :set_line_code, if: :on_text?
after_save :keep_around_commits
def discussion_class(*)
......@@ -66,10 +66,6 @@ class DiffNote < Note
@diff_line ||= diff_file&.line_for_position(self.original_position)
end
def for_line?(line)
diff_file.position(line) == self.original_position
end
def original_line_code
return unless on_text?
......
......@@ -72,7 +72,7 @@ class Event < ActiveRecord::Base
# We're using preload for "push_event_payload" as otherwise the association
# is not always available (depending on the query being built).
includes(:author, :project, project: :namespace)
.preload(:push_event_payload, target: :author)
.preload(:target, :push_event_payload)
end
scope :for_milestone_id, ->(milestone_id) { where(target_type: "Milestone", target_id: milestone_id) }
......
......@@ -10,6 +10,8 @@ class Identity < ActiveRecord::Base
validates :extern_uid, allow_blank: true, uniqueness: { scope: :provider, case_sensitive: false }
validates :user_id, uniqueness: { scope: :provider }
before_save :ensure_normalized_extern_uid, if: :extern_uid_changed?
scope :with_provider, ->(provider) { where(provider: provider) }
scope :with_extern_uid, ->(provider, extern_uid) do
iwhere(extern_uid: normalize_uid(provider, extern_uid)).with_provider(provider)
......@@ -26,4 +28,12 @@ class Identity < ActiveRecord::Base
uid.to_s
end
end
private
def ensure_normalized_extern_uid
return if extern_uid.nil?
self.extern_uid = Identity.normalize_uid(self.provider, self.extern_uid)
end
end
......@@ -43,11 +43,7 @@ class LegacyDiffNote < Note
end
def diff_line
@diff_line ||= diff_file.line_for_line_code(self.line_code) if diff_file
end
def for_line?(line)
line.discussable? && diff_file.line_code(line) == self.line_code
@diff_line ||= diff_file&.line_for_line_code(self.line_code)
end
def original_line_code
......
......@@ -960,7 +960,9 @@ class Project < ActiveRecord::Base
def send_move_instructions(old_path_with_namespace)
# New project path needs to be committed to the DB or notification will
# retrieve stale information
run_after_commit { NotificationService.new.project_was_moved(self, old_path_with_namespace) }
run_after_commit do
NotificationService.new.project_was_moved(self, old_path_with_namespace)
end
end
def owner
......@@ -972,15 +974,19 @@ class Project < ActiveRecord::Base
end
def execute_hooks(data, hooks_scope = :push_hooks)
hooks.public_send(hooks_scope).each do |hook| # rubocop:disable GitlabSecurity/PublicSend
hook.async_execute(data, hooks_scope.to_s)
run_after_commit_or_now do
hooks.public_send(hooks_scope).each do |hook| # rubocop:disable GitlabSecurity/PublicSend
hook.async_execute(data, hooks_scope.to_s)
end
end
end
def execute_services(data, hooks_scope = :push_hooks)
# Call only service hooks that are active for this scope
services.public_send(hooks_scope).each do |service| # rubocop:disable GitlabSecurity/PublicSend
service.async_execute(data)
run_after_commit_or_now do
services.public_send(hooks_scope).each do |service| # rubocop:disable GitlabSecurity/PublicSend
service.async_execute(data)
end
end
end
......@@ -1158,7 +1164,7 @@ class Project < ActiveRecord::Base
def change_head(branch)
if repository.branch_exists?(branch)
repository.before_change_head
repository.write_ref('HEAD', "refs/heads/#{branch}", force: true)
repository.write_ref('HEAD', "refs/heads/#{branch}")
repository.copy_gitattributes(branch)
repository.after_change_head
reload_default_branch
......
......@@ -24,7 +24,6 @@ class Repository
attr_accessor :full_path, :disk_path, :project, :is_wiki
delegate :ref_name_for_sha, to: :raw_repository
delegate :write_ref, to: :raw_repository
CreateTreeError = Class.new(StandardError)
......@@ -263,10 +262,11 @@ class Repository
# This will still fail if the file is corrupted (e.g. 0 bytes)
begin
write_ref(keep_around_ref_name(sha), sha, force: true)
rescue Gitlab::Git::Repository::GitError => ex
# Necessary because https://gitlab.com/gitlab-org/gitlab-ce/issues/20156
return true if ex.message =~ /Failed to create locked file/ && ex.message =~ /File exists/
write_ref(keep_around_ref_name(sha), sha)
rescue Rugged::ReferenceError => ex
Rails.logger.error "Unable to create #{REF_KEEP_AROUND} reference for repository #{path}: #{ex}"
rescue Rugged::OSError => ex
raise unless ex.message =~ /Failed to create locked file/ && ex.message =~ /File exists/
Rails.logger.error "Unable to create #{REF_KEEP_AROUND} reference for repository #{path}: #{ex}"
end
......@@ -276,6 +276,10 @@ class Repository
ref_exists?(keep_around_ref_name(sha))
end
def write_ref(ref_path, sha)
rugged.references.create(ref_path, sha, force: true)
end
def diverging_commit_counts(branch)
root_ref_hash = raw_repository.commit(root_ref).id
cache.fetch(:"diverging_commit_counts_#{branch.name}") do
......@@ -1064,7 +1068,7 @@ class Repository
end
def create_ref(ref, ref_path)
write_ref(ref_path, ref)
raw_repository.write_ref(ref_path, ref)
end
def ls_files(ref)
......
......@@ -7,7 +7,7 @@ class MergeRequestSerializer < BaseSerializer
case opts[:serializer]
when 'basic', 'sidebar'
MergeRequestBasicEntity
when 'widget'
else # It's 'widget'
MergeRequestWidgetEntity
end
......
......@@ -18,7 +18,7 @@ module Clusters
end
def helm_api
@helm_api ||= Gitlab::Kubernetes::Helm.new(kubeclient)
@helm_api ||= Gitlab::Kubernetes::Helm::Api.new(kubeclient)
end
def install_command
......
module Files
class BaseService < Commits::CreateService
FileChangedError = Class.new(StandardError)
def initialize(*args)
super
@author_email = params[:author_email]
@author_name = params[:author_name]
@commit_message = params[:commit_message]
@last_commit_sha = params[:last_commit_sha]
@file_path = params[:file_path]
@previous_path = params[:previous_path]
......@@ -13,5 +16,16 @@ module Files
@file_content = params[:file_content]
@file_content = Base64.decode64(@file_content) if params[:file_content_encoding] == 'base64'
end
def file_has_changed?(path, commit_id)
return false unless commit_id
last_commit = Gitlab::Git::Commit
.last_for_path(@start_project.repository, @start_branch, path)
return false unless last_commit
last_commit.sha != commit_id
end
end
end
......@@ -11,5 +11,15 @@ module Files
start_project: @start_project,
start_branch_name: @start_branch)
end
private
def validate!
super
if file_has_changed?(@file_path, @last_commit_sha)
raise FileChangedError, "You are attempting to delete a file that has been previously updated."
end
end
end
end
module Files
class MultiService < Files::BaseService
UPDATE_FILE_ACTIONS = %w(update move delete).freeze
def create_commit!
repository.multi_action(
user: current_user,
......@@ -20,6 +22,7 @@ module Files
params[:actions].each do |action|
validate_action!(action)
validate_file_status!(action)
end
end
......@@ -28,5 +31,15 @@ module Files
raise_error("Unknown action '#{action[:action]}'")
end
end
def validate_file_status!(action)
return unless UPDATE_FILE_ACTIONS.include?(action[:action])
file_path = action[:previous_path] || action[:file_path]
if file_has_changed?(file_path, action[:last_commit_id])
raise_error("The file has changed since you started editing it: #{file_path}")
end
end
end
end
module Files
class UpdateService < Files::BaseService
FileChangedError = Class.new(StandardError)
def initialize(*args)
super
@last_commit_sha = params[:last_commit_sha]
end
def create_commit!
repository.update_file(current_user, @file_path, @file_content,
message: @commit_message,
......@@ -21,21 +13,10 @@ module Files
private
def file_has_changed?
return false unless @last_commit_sha && last_commit
@last_commit_sha != last_commit.sha
end
def last_commit
@last_commit ||= Gitlab::Git::Commit
.last_for_path(@start_project.repository, @start_branch, @file_path)
end
def validate!
super
if file_has_changed?
if file_has_changed?(@file_path, @last_commit_sha)
raise FileChangedError, "You are attempting to update a file that has changed since you started editing it."
end
end
......
......@@ -5,7 +5,7 @@ module Projects
if fork_source = @project.fork_source
fork_source.lfs_objects.find_each do |lfs_object|
lfs_object.projects << @project
lfs_object.projects << @project unless lfs_object.projects.include?(@project)
end
refresh_forks_count(fork_source)
......
......@@ -3,16 +3,19 @@ module Search
include Gitlab::CurrentSettings
attr_accessor :current_user, :params
attr_reader :default_project_filter
def initialize(user, params)
@current_user, @params = user, params.dup
@default_project_filter = true
end
def execute
if current_application_settings.elasticsearch_search?
Gitlab::Elastic::SearchResults.new(current_user, params[:search], elastic_projects, elastic_global)
else
Gitlab::SearchResults.new(current_user, projects, params[:search])
Gitlab::SearchResults.new(current_user, projects, params[:search],
default_project_filter: default_project_filter)
end
end
......
......@@ -5,6 +5,7 @@ module Search
def initialize(user, group, params)
super(user, params)
@default_project_filter = false
@group = group
end
......
......@@ -11,23 +11,7 @@
.search-field-holder
= search_field_tag :name, project_name, class: "form-control search-text-input js-search-input", autofocus: true, spellcheck: false, placeholder: 'Search by name'
= icon("search", class: "search-icon")
.dropdown
- toggle_text = @sort.present? ? sort_options_hash[@sort] : sort_title_recently_created
= dropdown_toggle(toggle_text, { toggle: 'dropdown' })
%ul.dropdown-menu.dropdown-menu-align-right
%li.dropdown-header
Sort by
%li
= link_to admin_groups_path(sort: sort_value_recently_created, name: project_name) do
= sort_title_recently_created
= link_to admin_groups_path(sort: sort_value_oldest_created, name: project_name) do
= sort_title_oldest_created
= link_to admin_groups_path(sort: sort_value_recently_updated, name: project_name) do
= sort_title_recently_updated
= link_to admin_groups_path(sort: sort_value_oldest_updated, name: project_name) do
= sort_title_oldest_updated
= link_to admin_groups_path(sort: sort_value_largest_group, name: project_name) do
= sort_title_largest_group
= render "shared/groups/dropdown", options_hash: admin_groups_sort_options_hash
= link_to new_admin_group_path, class: "btn btn-new" do
New group
%ul.content-list
......
- event = last_push_event
- if event && show_last_push_widget?(event)
.row-content-block.top-block.hidden-xs.white
.event-last-push
.event-last-push-text
%span= s_("LastPushEvent|You pushed to")
%strong
= link_to event.ref_name, project_commits_path(event.project, event.ref_name), class: 'ref-name'
%div{ class: container_class }
.row-content-block.top-block.hidden-xs.white
.event-last-push
.event-last-push-text
%span= s_("LastPushEvent|You pushed to")
%strong
= link_to event.ref_name, project_commits_path(event.project, event.ref_name), class: 'ref-name'
- if event.project != @project
%span= s_("LastPushEvent|at")
%strong= link_to_project event.project
- if event.project != @project
%span= s_("LastPushEvent|at")
%strong= link_to_project event.project
#{time_ago_with_tooltip(event.created_at)}
#{time_ago_with_tooltip(event.created_at)}
.pull-right
= link_to new_mr_path_from_push_event(event), title: _("New merge request"), class: "btn btn-info btn-sm" do
#{ _('Create merge request') }
.pull-right
= link_to new_mr_path_from_push_event(event), title: _("New merge request"), class: "btn btn-info btn-sm" do
#{ _('Create merge request') }
......@@ -17,7 +17,7 @@
%a.js-md-preview-button{ href: "#md-preview-holder", tabindex: -1 }
Preview
%li.md-header-toolbar
%li.md-header-toolbar.active
= markdown_toolbar_button({ icon: "bold", data: { "md-tag" => "**" }, title: "Add bold text" })
= markdown_toolbar_button({ icon: "italic", data: { "md-tag" => "*" }, title: "Add italic text" })
= markdown_toolbar_button({ icon: "quote", data: { "md-tag" => "> ", "md-prepend" => true }, title: "Insert a quote" })
......
......@@ -18,7 +18,7 @@
.tree-controls
= link_to download_project_job_artifacts_path(@project, @build),
rel: 'nofollow', download: '', class: 'btn btn-default download' do
= icon('download')
= sprite_icon('download')
Download artifacts archive
.tree-content-holder
......
......@@ -3,7 +3,7 @@
.file-holder-bottom-radius.file-holder.file.append-bottom-default
.js-file-title.file-title.clearfix{ data: { current_action: action } }
.editor-ref
= icon('code-fork')
= sprite_icon('fork', size: 12)
= ref
%span.editor-file-name
- if current_action?(:edit) || current_action?(:update)
......
......@@ -2,6 +2,6 @@
.center.render-error.vertical-center
= link_to blob_raw_path do
%h1.light
= icon('download')
= sprite_icon('download')
%h4
Download (#{number_to_human_size(viewer.blob.raw_size)})
......@@ -8,7 +8,8 @@
%li{ class: "js-branch-#{branch.name}" }
%div
= link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated ref-name' do
= icon('code-fork', class: 'append-right-5') + "#{branch.name}"
= sprite_icon('fork', size: 12)
= branch.name
&nbsp;
- if branch.name == @repository.root_ref
%span.label.label-primary default
......
......@@ -3,7 +3,7 @@
- if !project.empty_repo? && can?(current_user, :download_code, project)
.project-action-button.dropdown.inline>
%button.btn.has-tooltip{ title: s_('DownloadSource|Download'), 'data-toggle' => 'dropdown', 'aria-label' => s_('DownloadSource|Download') }
= icon('download')
= sprite_icon('download')
= icon("caret-down")
%span.sr-only= _('Select Archive Format')
%ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' }
......
......@@ -22,7 +22,7 @@
- if ref
- if job.ref
.icon-container
= job.tag? ? icon('tag') : icon('code-fork')
= job.tag? ? icon('tag') : sprite_icon('fork', css_class: 'sprite')
= link_to job.ref, project_ref_path(job.project, job.ref), class: "ref-name"
- else
.light none
......@@ -96,7 +96,7 @@
.pull-right
- if can?(current_user, :read_build, job) && job.artifacts?
= link_to download_project_job_artifacts_path(job.project, job), rel: 'nofollow', download: '', title: 'Download artifacts', class: 'btn btn-build' do
= icon('download')
= sprite_icon('download')
- if can?(current_user, :update_build, job)
- if job.active?
= link_to cancel_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do
......
......@@ -9,6 +9,7 @@
.edit-cluster-form.js-edit-cluster-form{ data: { status_path: status_path,
install_helm_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :helm),
install_ingress_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :ingress),
install_prometheus_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :prometheus),
toggle_status: @cluster.enabled? ? 'true': 'false',
cluster_status: @cluster.status_name,
cluster_status_reason: @cluster.status_reason,
......
.has-tooltip{ class: "limit-box limit-box-#{objects} prepend-left-5", data: { title: "Project has too many #{label_for_message} to search"} }
.limit-icon
- if objects == :branch
= icon('code-fork')
= sprite_icon('fork', size: 12)
- else
= icon('tag')
.limit-message
......
......@@ -2,7 +2,7 @@
.branch-commit
- if deployment.ref
%span.icon-container
= deployment.tag? ? icon('tag') : icon('code-fork')
= deployment.tag? ? icon('tag') : sprite_icon('fork', css_class: 'sprite')
= link_to deployment.ref, project_ref_path(@project, deployment.ref), class: "ref-name"
.icon-container.commit-icon
= custom_icon("icon_commit")
......
......@@ -2,7 +2,7 @@
- if @forked_project && !@forked_project.saved?
.alert.alert-danger.alert-block
%h4
%i.fa.fa-code-fork
= sprite_icon('fork', size: 16)
Fork Error!
%p
You tried to fork
......@@ -21,5 +21,4 @@
%p
= link_to new_project_fork_path(@project), title: "Fork", class: "btn" do
%i.fa.fa-code-fork
Try to fork again
......@@ -19,7 +19,7 @@
- if ref
- if generic_commit_status.ref
.icon-container
= generic_commit_status.tags.any? ? icon('tag') : icon('code-fork')
= generic_commit_status.tags.any? ? icon('tag') : sprite_icon('fork', size: 10)
= link_to generic_commit_status.ref, project_commits_path(generic_commit_status.project, generic_commit_status.ref)
- else
.light none
......
......@@ -30,7 +30,7 @@
%span.project-ref-path
&nbsp;
= link_to project_ref_path(merge_request.project, merge_request.target_branch), class: 'ref-name' do
= icon('code-fork')
= sprite_icon('fork', size: 12, css_class: 'fork-sprite')
= merge_request.target_branch
- if merge_request.labels.any?
&nbsp;
......
......@@ -3,7 +3,7 @@
%td
= pipeline_schedule.description
%td.branch-name-cell
= icon('code-fork')
= sprite_icon('fork', size: 12)
- if pipeline_schedule.ref.present?
= link_to pipeline_schedule.ref, project_ref_path(@project, pipeline_schedule.ref), class: "ref-name"
%td
......
......@@ -6,6 +6,7 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits")
= render 'projects/last_push'
%div{ class: [(container_class), ("limit-container-width" unless fluid_layout)] }
= render 'projects/last_push'
= render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_tree_path(@project, @id)
- options_hash = local_assigns.fetch(:options_hash, groups_sort_options_hash)
- show_archive_options = local_assigns.fetch(:show_archive_options, false)
- if @sort.present?
- default_sort_by = @sort
......@@ -10,12 +11,12 @@
.dropdown.inline.js-group-filter-dropdown-wrap.append-right-10
%button.dropdown-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
%span.dropdown-label
= sort_options_hash[default_sort_by]
= options_hash[default_sort_by]
= icon('chevron-down')
%ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable
%li.dropdown-header
= _("Sort by")
- groups_sort_options_hash.each do |value, title|
- options_hash.each do |value, title|
%li.js-filter-sort-order
= link_to filter_groups_path(sort: value), class: ("is-active" if default_sort_by == value) do
= title
......
......@@ -52,7 +52,7 @@
= render_project_pipeline_status(project.pipeline_status)
- if forks
%span.prepend-left-10
= icon('code-fork')
= sprite_icon('fork')
= number_with_delimiter(project.forks_count)
- if stars
%span.prepend-left-10
......
---
title: 'Validate file status when commiting multiple files'
merge_request: 15922
author:
type: added
---
title: Fix N+1 query when displaying events
title: Improve search query for merge requests.
merge_request:
author:
type: performance
---
title: Disable Vue pagination when only one page of content is available
merge_request: 15999
author: Mario de la Ossa
type: fixed
---
title: Enable ordering of groups and their children by name
merge_request:
author:
type: added
---
title: Add optional search param for Merge Requests API
merge_request:
author:
type: fixed
---
title: Hide markdown toolbar in preview mode
merge_request:
author:
type: changed
---
title: Add Prometheus to available Cluster applications
merge_request: 15895
author:
type: added
---
title: Don't link LFS objects to a project when unlinking forks when they were already
linked
merge_request: 16006
author:
type: fixed
---
title: Improve performance of MR discussions on large diffs
merge_request:
author:
type: performance
---
title: Execute project hooks and services after commit when moving an issue
merge_request:
author:
type: fixed
---
title: Last push event widget width for fixed layout
merge_request: 15862
author: George Tsiolis
type: fixed
---
title: Normalizing Identity extern_uid when saving the record
merge_request:
author:
type: fixed
---
title: Add index on namespaces lower(name) for UsersController#exists
merge_request:
author:
type: performance
---
title: Reduce the number of buckets in gitlab_cache_operation_duration_seconds metric
merge_request: 15881
author:
type: changed
module Sidekiq
module Worker
EnqueueFromTransactionError = Class.new(StandardError)
mattr_accessor :skip_transaction_check
self.skip_transaction_check = false
......@@ -12,11 +14,11 @@ module Sidekiq
end
module ClassMethods
module NoSchedulingFromTransactions
module NoEnqueueingFromTransactions
%i(perform_async perform_at perform_in).each do |name|
define_method(name) do |*args|
if !Sidekiq::Worker.skip_transaction_check && AfterCommitQueue.inside_transaction?
raise <<-MSG.strip_heredoc
raise Sidekiq::Worker::EnqueueFromTransactionError, <<~MSG
`#{self}.#{name}` cannot be called inside a transaction as this can lead to
race conditions when the worker runs before the transaction is committed and
tries to access a model that has not been saved yet.
......@@ -30,7 +32,7 @@ module Sidekiq
end
end
prepend NoSchedulingFromTransactions
prepend NoEnqueueingFromTransactions
end
end
end
......
......@@ -131,7 +131,10 @@ var config = {
},
{
test: /\_worker\.js$/,
loader: 'worker-loader',
use: [
{ loader: 'worker-loader' },
{ loader: 'babel-loader' },
],
},
{
test: /\.(worker(\.min)?\.js|pdf|bmpr)$/,
......@@ -151,6 +154,7 @@ var config = {
],
noParse: [/monaco-editor\/\w+\/vs\//],
strictExportPresence: true,
},
plugins: [
......@@ -189,8 +193,13 @@ var config = {
return chunk.name;
}
return chunk.mapModules((m) => {
var chunkPath = m.request.split('!').pop();
return path.relative(m.context, chunkPath);
const pagesBase = path.join(ROOT_PATH, 'app/assets/javascripts/pages');
if (m.resource.indexOf(pagesBase) === 0) {
return path.relative(pagesBase, m.resource)
.replace(/\/index\.[a-z]+$/, '')
.replace(/\//g, '__');
}
return path.relative(m.context, m.resource);
}).join('_');
}),
......
class CreateClustersApplicationsPrometheus < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
create_table :clusters_applications_prometheus do |t|
t.references :cluster, null: false, unique: true, foreign_key: { on_delete: :cascade }
t.integer :status, null: false
t.string :version, null: false
t.text :status_reason
t.timestamps_with_timezone null: false
end
end
end
class AddIndexOnNamespacesLowerName < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
INDEX_NAME = 'index_on_namespaces_lower_name'
disable_ddl_transaction!
def up
return unless Gitlab::Database.postgresql?
disable_statement_timeout
if Gitlab::Database.version.to_f >= 9.5
# Allow us to hot-patch the index manually ahead of the migration
execute "CREATE INDEX CONCURRENTLY IF NOT EXISTS #{INDEX_NAME} ON namespaces (lower(name));"
else
execute "CREATE INDEX CONCURRENTLY #{INDEX_NAME} ON namespaces (lower(name));"
end
end
def down
return unless Gitlab::Database.postgresql?
disable_statement_timeout
if Gitlab::Database.version.to_f >= 9.2
execute "DROP INDEX CONCURRENTLY IF EXISTS #{INDEX_NAME};"
else
execute "DROP INDEX IF EXISTS #{INDEX_NAME};"
end
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class NormalizeExternUidFromIdentities < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
MIGRATION = 'NormalizeLdapExternUidsRange'.freeze
DELAY_INTERVAL = 10.seconds
disable_ddl_transaction!
class Identity < ActiveRecord::Base
include EachBatch
self.table_name = 'identities'
end
def up
ldap_identities = Identity.where("provider like 'ldap%'")
if ldap_identities.any?
queue_background_migration_jobs_by_range_at_intervals(Identity, MIGRATION, DELAY_INTERVAL)
end
end
def down
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20171213160445) do
ActiveRecord::Schema.define(version: 20171220191323) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -660,6 +660,15 @@ ActiveRecord::Schema.define(version: 20171213160445) do
t.text "status_reason"
end
create_table "clusters_applications_prometheus", force: :cascade do |t|
t.integer "cluster_id", null: false
t.integer "status", null: false
t.string "version", null: false
t.text "status_reason"
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
end
create_table "container_repositories", force: :cascade do |t|
t.integer "project_id", null: false
t.string "name", null: false
......
......@@ -84,6 +84,7 @@ POST /projects/:id/repository/commits
| `previous_path` | string | no | Original full path to the file being moved. Ex. `lib/class1.rb` |
| `content` | string | no | File content, required for all except `delete`. Optional for `move` |
| `encoding` | string | no | `text` or `base64`. `text` is default. |
| `last_commit_id` | string | no | Last known file commit id. Will be only considered in update, move and delete actions. |
```bash
PAYLOAD=$(cat << 'JSON'
......
......@@ -47,6 +47,7 @@ Parameters:
| `author_id` | integer | no | Returns merge requests created by the given user `id`. Combine with `scope=all` or `scope=assigned-to-me` |
| `assignee_id` | integer | no | Returns merge requests assigned to the given user `id` |
| `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ |
| `search` | string | no | Search merge requests against their `title` and `description` |
```json
[
......@@ -161,6 +162,7 @@ Parameters:
| `author_id` | integer | no | Returns merge requests created by the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ |
| `assignee_id` | integer | no | Returns merge requests assigned to the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ |
| `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ |
| `search` | string | no | Search merge requests against their `title` and `description` |
```json
[
......
......@@ -151,3 +151,4 @@ Parameters:
- `author_email` (optional) - Specify the commit author's email address
- `author_name` (optional) - Specify the commit author's name
- `commit_message` (required) - Commit message
- `last_commit_id` (optional) - Last known file commit id
......@@ -164,6 +164,21 @@ not without its own challenges:
- By default, `docker:dind` uses `--storage-driver vfs` which is the slowest
form offered. To use a different driver, see
[Using the overlayfs driver](#using-the-overlayfs-driver).
- Since the `docker:dind` container and the runner container don't share their
root filesystem, the job's working directory can be used as a mount point for
children containers. For example, if you have files you want to share with a
child container, you may create a subdirectory under `/builds/$CI_PROJECT_PATH`
and use it as your mount point (for a more thorough explanation, check [issue
#41227](https://gitlab.com/gitlab-org/gitlab-ce/issues/41227)):
```yaml
variables:
MOUNT_POINT: /builds/$CI_PROJECT_PATH/mnt
script:
- mkdir -p "$MOUNT_POINT"
- docker run -v "$MOUNT_POINT:/mnt" my-docker-image
```
An example project using this approach can be found here: https://gitlab.com/gitlab-examples/docker.
......
......@@ -27,3 +27,27 @@ This will create a `performance` job in your CI/CD pipeline and will run Sitespe
For GitLab [Enterprise Edition Premium](https://about.gitlab.com/gitlab-ee/) users, this information can be automatically
extracted and shown right in the merge request widget. [Learn more on performance diffs in merge requests](../../user/project/merge_requests/browser_performance_testing.md).
## Performance testing on Review Apps
The above CI YML is great for testing against static environments, and it can be extended for dynamic environments. There are a few extra steps to take to set this up:
1. The `performance` job should run after the environment has started.
1. In the `deploy` job, persist the hostname so it is available to the `performance` job. The same can be done for static environments like staging and production to unify the code path. Saving it as an artifact is as simple as `echo $CI_ENVIRONMENT_URL > environment_url.txt`.
1. In the `performance` job read the artifact into an environment variable, like `$CI_ENVIRONMENT_URL`, and use it to parameterize the test URL's.
1. Now you can run the Sitespeed.io container against the desired hostname and paths.
A simple `performance` job would look like:
```yaml
stage: performance
image: docker:git
services:
- docker:dind
script:
- export CI_ENVIRONMENT_URL=$(cat environment_url.txt)
- mkdir sitespeed-results
- docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io --outputFolder sitespeed-results $CI_ENVIRONMENT_URL
artifacts:
paths:
- [performance.json]
```
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.
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.
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.
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.
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.
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