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 { ...@@ -30,6 +30,7 @@ export default class Clusters {
installHelmPath, installHelmPath,
installIngressPath, installIngressPath,
installRunnerPath, installRunnerPath,
installPrometheusPath,
clusterStatus, clusterStatus,
clusterStatusReason, clusterStatusReason,
helpPath, helpPath,
...@@ -44,6 +45,7 @@ export default class Clusters { ...@@ -44,6 +45,7 @@ export default class Clusters {
installHelmEndpoint: installHelmPath, installHelmEndpoint: installHelmPath,
installIngressEndpoint: installIngressPath, installIngressEndpoint: installIngressPath,
installRunnerEndpoint: installRunnerPath, installRunnerEndpoint: installRunnerPath,
installPrometheusEndpoint: installPrometheusPath,
}); });
this.toggle = this.toggle.bind(this); this.toggle = this.toggle.bind(this);
......
...@@ -67,6 +67,16 @@ export default { ...@@ -67,6 +67,16 @@ export default {
and send the results back to GitLab.`, 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> </script>
...@@ -105,6 +115,16 @@ export default { ...@@ -105,6 +115,16 @@ export default {
:status-reason="applications.ingress.statusReason" :status-reason="applications.ingress.statusReason"
:request-status="applications.ingress.requestStatus" :request-status="applications.ingress.requestStatus"
:request-reason="applications.ingress.requestReason" :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 --> <!-- 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 --> <!-- Add GitLab Runner row, all other plumbing is complete -->
......
...@@ -7,6 +7,7 @@ export default class ClusterService { ...@@ -7,6 +7,7 @@ export default class ClusterService {
helm: this.options.installHelmEndpoint, helm: this.options.installHelmEndpoint,
ingress: this.options.installIngressEndpoint, ingress: this.options.installIngressEndpoint,
runner: this.options.installRunnerEndpoint, runner: this.options.installRunnerEndpoint,
prometheus: this.options.installPrometheusEndpoint,
}; };
} }
......
...@@ -28,6 +28,13 @@ export default class ClusterStore { ...@@ -28,6 +28,13 @@ export default class ClusterStore {
requestStatus: null, requestStatus: null,
requestReason: null, requestReason: null,
}, },
prometheus: {
title: s__('ClusterIntegration|Prometheus'),
status: null,
statusReason: null,
requestStatus: null,
requestReason: null,
},
}, },
}; };
} }
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue'; import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import limitWarning from './limit_warning_component.vue'; import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue'; import totalTime from './total_time_component.vue';
import icon from '../../vue_shared/components/icon.vue';
export default { export default {
props: { props: {
...@@ -12,6 +13,7 @@ ...@@ -12,6 +13,7 @@
userAvatarImage, userAvatarImage,
totalTime, totalTime,
limitWarning, limitWarning,
icon,
}, },
}; };
</script> </script>
...@@ -52,7 +54,10 @@ ...@@ -52,7 +54,10 @@
</template> </template>
<template v-else> <template v-else>
<span class="merge-request-branch" v-if="mergeRequest.branch"> <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> <a :href="mergeRequest.branch.url">{{ mergeRequest.branch.name }}</a>
</span> </span>
</template> </template>
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
import iconBranch from '../svg/icon_branch.svg'; import iconBranch from '../svg/icon_branch.svg';
import limitWarning from './limit_warning_component.vue'; import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue'; import totalTime from './total_time_component.vue';
import icon from '../../vue_shared/components/icon.vue';
export default { export default {
props: { props: {
...@@ -13,6 +14,7 @@ ...@@ -13,6 +14,7 @@
userAvatarImage, userAvatarImage,
totalTime, totalTime,
limitWarning, limitWarning,
icon,
}, },
computed: { computed: {
iconBranch() { iconBranch() {
...@@ -37,7 +39,10 @@ ...@@ -37,7 +39,10 @@
<user-avatar-image :img-src="build.author.avatarUrl"/> <user-avatar-image :img-src="build.author.avatarUrl"/>
<h5 class="item-title"> <h5 class="item-title">
<a :href="build.url" class="pipeline-id">#{{ build.id }}</a> <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> <a :href="build.branch.url" class="ref-name">{{ build.branch.name }}</a>
<span class="icon-branch" v-html="iconBranch"></span> <span class="icon-branch" v-html="iconBranch"></span>
<a :href="build.commitUrl" class="commit-sha">{{ build.shortSha }}</a> <a :href="build.commitUrl" class="commit-sha">{{ build.shortSha }}</a>
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
import iconBranch from '../svg/icon_branch.svg'; import iconBranch from '../svg/icon_branch.svg';
import limitWarning from './limit_warning_component.vue'; import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue'; import totalTime from './total_time_component.vue';
import icon from '../../vue_shared/components/icon.vue';
export default { export default {
props: { props: {
...@@ -12,6 +13,7 @@ ...@@ -12,6 +13,7 @@
components: { components: {
totalTime, totalTime,
limitWarning, limitWarning,
icon,
}, },
computed: { computed: {
iconBuildStatus() { iconBuildStatus() {
...@@ -40,7 +42,10 @@ ...@@ -40,7 +42,10 @@
<a :href="build.url" class="item-build-name">{{ build.name }}</a> <a :href="build.url" class="item-build-name">{{ build.name }}</a>
&middot; &middot;
<a :href="build.url" class="pipeline-id">#{{ build.id }}</a> <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> <a :href="build.branch.url" class="ref-name">{{ build.branch.name }}</a>
<span class="icon-branch" v-html="iconBranch"></span> <span class="icon-branch" v-html="iconBranch"></span>
<a :href="build.commitUrl" class="commit-sha">{{ build.shortSha }}</a> <a :href="build.commitUrl" class="commit-sha">{{ build.shortSha }}</a>
......
...@@ -123,6 +123,8 @@ import initLDAPGroupsSelect from 'ee/ldap_groups_select'; // eslint-disable-line ...@@ -123,6 +123,8 @@ import initLDAPGroupsSelect from 'ee/ldap_groups_select'; // eslint-disable-line
return false; return false;
} }
const fail = () => Flash('Error loading dynamic module');
path = page.split(':'); path = page.split(':');
shortcut_handler = null; shortcut_handler = null;
...@@ -606,7 +608,7 @@ import initLDAPGroupsSelect from 'ee/ldap_groups_select'; // eslint-disable-line ...@@ -606,7 +608,7 @@ import initLDAPGroupsSelect from 'ee/ldap_groups_select'; // eslint-disable-line
new CILintEditor(); new CILintEditor();
break; break;
case 'users:show': case 'users:show':
new UserCallout(); import('./pages/users/show').then(m => m.default()).catch(fail);
break; break;
case 'admin:conversational_development_index:show': case 'admin:conversational_development_index:show':
new UserCallout(); new UserCallout();
......
...@@ -300,7 +300,7 @@ GitLabDropdown = (function() { ...@@ -300,7 +300,7 @@ GitLabDropdown = (function() {
return function(data) { return function(data) {
_this.fullData = data; _this.fullData = data;
_this.parseData(_this.fullData); _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() !== '') { if (_this.options.filterable && _this.filter && _this.filter.input && _this.filter.input.val() && _this.filter.input.val().trim() !== '') {
return _this.filter.input.trigger('input'); return _this.filter.input.trigger('input');
} }
...@@ -806,24 +806,16 @@ GitLabDropdown = (function() { ...@@ -806,24 +806,16 @@ GitLabDropdown = (function() {
return [selectedObject, isMarking]; return [selectedObject, isMarking];
}; };
GitLabDropdown.prototype.focusTextInput = function(triggerFocus = false) { GitLabDropdown.prototype.focusTextInput = function() {
if (this.options.filterable) { if (this.options.filterable) {
this.dropdown.one('transitionend', () => { const initialScrollTop = $(window).scrollTop();
const initialScrollTop = $(window).scrollTop();
if (this.dropdown.is('.open')) { if (this.dropdown.is('.open')) {
this.filterInput.focus(); this.filterInput.focus();
} }
if ($(window).scrollTop() < initialScrollTop) {
$(window).scrollTop(initialScrollTop);
}
});
if (triggerFocus) { if ($(window).scrollTop() < initialScrollTop) {
// This triggers after a ajax request $(window).scrollTop(initialScrollTop);
// in case of slow requests, the dropdown transition could already be finished
this.dropdown.trigger('transitionend');
} }
} }
}; };
......
...@@ -2,7 +2,7 @@ import timeago from 'timeago.js'; ...@@ -2,7 +2,7 @@ import timeago from 'timeago.js';
import dateFormat from 'vendor/date.format'; import dateFormat from 'vendor/date.format';
import { pluralize } from './text_utility'; import { pluralize } from './text_utility';
import { import {
lang, languageCode,
s__, s__,
} from '../../locale'; } from '../../locale';
...@@ -24,7 +24,15 @@ export const getDayName = date => ['Sunday', 'Monday', 'Tuesday', 'Wednesday', ' ...@@ -24,7 +24,15 @@ export const getDayName = date => ['Sunday', 'Monday', 'Tuesday', 'Wednesday', '
*/ */
export const formatDate = datetime => dateFormat(datetime, 'mmm d, yyyy h:MMtt Z'); 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; let timeagoInstance;
/** /**
* Sets a timeago Instance * Sets a timeago Instance
*/ */
...@@ -67,8 +75,8 @@ export function getTimeago() { ...@@ -67,8 +75,8 @@ export function getTimeago() {
][index]; ][index];
}; };
timeago.register(lang, locale); timeago.register(timeagoLanguageCode, locale);
timeago.register(`${lang}-remaining`, localeRemaining); timeago.register(`${timeagoLanguageCode}-remaining`, localeRemaining);
timeagoInstance = timeago(); timeagoInstance = timeago();
} }
...@@ -83,7 +91,7 @@ export const renderTimeago = ($els) => { ...@@ -83,7 +91,7 @@ export const renderTimeago = ($els) => {
const timeagoEls = $els || document.querySelectorAll('.js-timeago-render'); const timeagoEls = $els || document.querySelectorAll('.js-timeago-render');
// timeago.js sets timeouts internally for each timeago value to be updated in real time // 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) => { ...@@ -118,7 +126,7 @@ export const timeFor = (time, expiredLabel) => {
if (new Date(time) < new Date()) { if (new Date(time) < new Date()) {
return expiredLabel || s__('Timeago|Past due'); 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) => { export const getDayDifference = (a, b) => {
......
...@@ -4,9 +4,9 @@ import { bisector } from 'd3-array'; ...@@ -4,9 +4,9 @@ import { bisector } from 'd3-array';
const d3 = { time, bisector, timeSecond, timeMinute, timeHour, timeDay, timeMonth, timeYear }; 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 dateFormat = d3.time('%b %-d, %Y');
export const timeFormat = d3.time('%-I:%M%p'); 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 const bisectDate = d3.bisector(d => d.time).left;
export function timeScaleFormat(date) { export function timeScaleFormat(date) {
......
import UserCallout from '~/user_callout';
export default () => new UserCallout();
<script> <script>
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
import icon from '../../vue_shared/components/icon.vue';
export default { export default {
props: { props: {
...@@ -11,6 +12,9 @@ ...@@ -11,6 +12,9 @@
directives: { directives: {
tooltip, tooltip,
}, },
components: {
icon,
},
}; };
</script> </script>
<template> <template>
...@@ -24,10 +28,9 @@ ...@@ -24,10 +28,9 @@
data-placement="top" data-placement="top"
data-toggle="dropdown" data-toggle="dropdown"
aria-label="Artifacts"> aria-label="Artifacts">
<i <icon
class="fa fa-download" name="download">
aria-hidden="true"> </icon>
</i>
<i <i
class="fa fa-caret-down" class="fa fa-caret-down"
aria-hidden="true"> aria-hidden="true">
......
...@@ -117,12 +117,10 @@ ...@@ -117,12 +117,10 @@
}()); }());
markdownPreview = new window.MarkdownPreview(); markdownPreview = new window.MarkdownPreview();
previewButtonSelector = '.js-md-preview-button'; previewButtonSelector = '.js-md-preview-button';
writeButtonSelector = '.js-md-write-button'; writeButtonSelector = '.js-md-write-button';
lastTextareaPreviewed = null; lastTextareaPreviewed = null;
const markdownToolbar = $('.md-header-toolbar');
$.fn.setupMarkdownPreview = function () { $.fn.setupMarkdownPreview = function () {
var $form = $(this); var $form = $(this);
...@@ -146,6 +144,7 @@ ...@@ -146,6 +144,7 @@
// toggle content // toggle content
$form.find('.md-write-holder').hide(); $form.find('.md-write-holder').hide();
$form.find('.md-preview-holder').show(); $form.find('.md-preview-holder').show();
markdownToolbar.removeClass('active');
markdownPreview.showPreview($form); markdownPreview.showPreview($form);
}); });
...@@ -167,6 +166,7 @@ ...@@ -167,6 +166,7 @@
$form.find('.md-write-holder').show(); $form.find('.md-write-holder').show();
$form.find('textarea.markdown-area').focus(); $form.find('textarea.markdown-area').focus();
$form.find('.md-preview-holder').hide(); $form.find('.md-preview-holder').hide();
markdownToolbar.addClass('active');
markdownPreview.hideReferencedCommands($form); markdownPreview.hideReferencedCommands($form);
}); });
......
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
import { pluralize } from '../../lib/utils/text_utility'; import { pluralize } from '../../lib/utils/text_utility';
import icon from '../../vue_shared/components/icon.vue';
export default { export default {
name: 'MRWidgetHeader', name: 'MRWidgetHeader',
...@@ -9,6 +10,9 @@ export default { ...@@ -9,6 +10,9 @@ export default {
directives: { directives: {
tooltip, tooltip,
}, },
components: {
icon,
},
computed: { computed: {
shouldShowCommitsBehindText() { shouldShowCommitsBehindText() {
return this.mr.divergedCommitsCount > 0; return this.mr.divergedCommitsCount > 0;
...@@ -81,10 +85,9 @@ export default { ...@@ -81,10 +85,9 @@ export default {
data-toggle="dropdown" data-toggle="dropdown"
aria-label="Download as" aria-label="Download as"
role="button"> role="button">
<i <icon
class="fa fa-download" name="download">
aria-hidden="true"> </icon>
</i>
<i <i
class="fa fa-caret-down" class="fa fa-caret-down"
aria-hidden="true"> aria-hidden="true">
......
...@@ -2,13 +2,14 @@ ...@@ -2,13 +2,14 @@
import commitIconSvg from 'icons/_icon_commit.svg'; import commitIconSvg from 'icons/_icon_commit.svg';
import userAvatarLink from './user_avatar/user_avatar_link.vue'; import userAvatarLink from './user_avatar/user_avatar_link.vue';
import tooltip from '../directives/tooltip'; import tooltip from '../directives/tooltip';
import Icon from '../../vue_shared/components/icon.vue';
export default { export default {
props: { props: {
/** /**
* Indicates the existance of a tag. * Indicates the existance of a tag.
* Used to render the correct icon, if true will render `fa-tag` icon, * 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: { tag: {
type: Boolean, type: Boolean,
...@@ -107,6 +108,7 @@ ...@@ -107,6 +108,7 @@
}, },
components: { components: {
userAvatarLink, userAvatarLink,
Icon,
}, },
created() { created() {
this.commitIconSvg = commitIconSvg; this.commitIconSvg = commitIconSvg;
...@@ -123,11 +125,10 @@ ...@@ -123,11 +125,10 @@
class="fa fa-tag" class="fa fa-tag"
aria-hidden="true"> aria-hidden="true">
</i> </i>
<i <icon
v-if="!tag" v-if="!tag"
class="fa fa-code-fork" name="fork">
aria-hidden="true"> </icon>
</i>
</div> </div>
<a <a
......
...@@ -72,7 +72,9 @@ ...@@ -72,7 +72,9 @@
Preview Preview
</a> </a>
</li> </li>
<li class="md-header-toolbar"> <li
class="md-header-toolbar"
:class="{ active: !previewMarkdown }">
<toolbar-button <toolbar-button
tag="**" tag="**"
button-title="Add bold text" button-title="Add bold text"
......
...@@ -122,11 +122,17 @@ export default { ...@@ -122,11 +122,17 @@ export default {
return items; return items;
}, },
showPagination() {
return this.pageInfo.totalPages > 1;
},
}, },
}; };
</script> </script>
<template> <template>
<div class="gl-pagination"> <div
v-if="showPagination"
class="gl-pagination"
>
<ul class="pagination clearfix"> <ul class="pagination clearfix">
<li <li
v-for="item in getItems" v-for="item in getItems"
......
...@@ -9,12 +9,6 @@ ...@@ -9,12 +9,6 @@
padding-left: $contextual-sidebar-width; 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 { .issues-bulk-update.right-sidebar.right-sidebar-expanded .issuable-sidebar-header {
padding: 10px 0 15px; padding: 10px 0 15px;
} }
......
...@@ -16,27 +16,18 @@ ...@@ -16,27 +16,18 @@
@mixin set-visible { @mixin set-visible {
transform: translateY(0); transform: translateY(0);
visibility: visible; display: block;
opacity: 1;
transition-duration: 100ms, 150ms, 25ms;
transition-delay: 35ms, 50ms, 25ms;
} }
@mixin set-invisible { @mixin set-invisible {
transform: translateY(-10px); transform: translateY(-10px);
visibility: hidden; display: none;
opacity: 0;
transition-property: opacity, transform, visibility;
transition-duration: 70ms, 250ms, 250ms;
transition-timing-function: linear, $dropdown-animation-timing;
transition-delay: 25ms, 50ms, 0ms;
} }
.open { .open {
.dropdown-menu, .dropdown-menu,
.dropdown-menu-nav { .dropdown-menu-nav {
@include set-visible; @include set-visible;
display: block;
min-height: 40px; min-height: 40px;
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
...@@ -55,6 +46,11 @@ ...@@ -55,6 +46,11 @@
} }
} }
// Get search dropdown to line up with other nav dropdowns
.search-input-container .dropdown-menu {
margin-top: 11px;
}
.dropdown-toggle { .dropdown-toggle {
padding: 6px 8px 6px 10px; padding: 6px 8px 6px 10px;
background-color: $white-light; background-color: $white-light;
...@@ -214,7 +210,6 @@ ...@@ -214,7 +210,6 @@
.dropdown-menu, .dropdown-menu,
.dropdown-menu-nav { .dropdown-menu-nav {
@include set-invisible; @include set-invisible;
display: block;
position: absolute; position: absolute;
width: auto; width: auto;
top: 100%; top: 100%;
......
...@@ -74,7 +74,7 @@ ...@@ -74,7 +74,7 @@
} }
.md-header-tab { .md-header-tab {
@media(max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
flex: 1; flex: 1;
width: 100%; width: 100%;
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
...@@ -82,16 +82,23 @@ ...@@ -82,16 +82,23 @@
} }
} }
.md-header-toolbar { .nav-links {
margin-left: auto; li.md-header-toolbar {
margin-left: auto;
display: none;
@media(max-width: $screen-xs-max) { &.active {
flex: none; display: block;
display: flex;
justify-content: center; @media (max-width: $screen-xs-max) {
width: 100%; flex: none;
padding-top: $gl-padding-top; display: flex;
padding-bottom: $gl-padding-top; justify-content: center;
width: 100%;
padding-top: $gl-padding-top;
padding-bottom: $gl-padding-top;
}
}
} }
} }
...@@ -175,7 +182,7 @@ ...@@ -175,7 +182,7 @@
margin-left: $gl-padding; margin-left: $gl-padding;
margin-right: -5px; margin-right: -5px;
@media(max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
margin-left: 0; margin-left: 0;
margin-right: 0; margin-right: 0;
} }
...@@ -239,7 +246,7 @@ ...@@ -239,7 +246,7 @@
} }
} }
@media(max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
.atwho-view-ul { .atwho-view-ul {
width: 350px; width: 350px;
} }
......
...@@ -90,11 +90,6 @@ ...@@ -90,11 +90,6 @@
.right-sidebar { .right-sidebar {
border-left: 1px solid $border-color; border-left: 1px solid $border-color;
height: calc(100% - #{$header-height}); height: calc(100% - #{$header-height});
&.affix {
position: fixed;
top: $header-height;
}
} }
.with-performance-bar .right-sidebar.affix { .with-performance-bar .right-sidebar.affix {
......
...@@ -16,6 +16,10 @@ ...@@ -16,6 +16,10 @@
.commit-sha, .commit-sha,
.commit-info { .commit-info {
margin-left: 4px; margin-left: 4px;
.fork-svg {
margin-right: 4px;
}
} }
.ref-name { .ref-name {
...@@ -79,7 +83,7 @@ ...@@ -79,7 +83,7 @@
} }
.limit-icon { .limit-icon {
margin: 0 8px; margin: 0 4px;
} }
.limit-message { .limit-message {
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
.cluster-applications-table { .cluster-applications-table {
// Wait for the Vue to kick-in and render the applications block // Wait for the Vue to kick-in and render the applications block
min-height: 302px; min-height: 400px;
} }
.clusters-dropdown-menu { .clusters-dropdown-menu {
......
...@@ -122,7 +122,7 @@ ...@@ -122,7 +122,7 @@
} }
.right-sidebar { .right-sidebar {
position: absolute; position: fixed;
top: $header-height; top: $header-height;
bottom: 0; bottom: 0;
right: 0; right: 0;
...@@ -503,7 +503,7 @@ ...@@ -503,7 +503,7 @@
top: $header-height + $performance-bar-height; top: $header-height + $performance-bar-height;
.issuable-sidebar { .issuable-sidebar {
height: calc(100% - #{$header-height} - #{$performance-bar-height}); height: calc(100% - #{$performance-bar-height});
} }
} }
......
...@@ -774,6 +774,11 @@ ...@@ -774,6 +774,11 @@
} }
} }
.fork-sprite {
margin-right: -5px;
}
.mr-widget-icon { .mr-widget-icon {
font-size: 22px; font-size: 22px;
margin: 0 10px 0 0; margin: 0 10px 0 0;
......
...@@ -39,6 +39,10 @@ ...@@ -39,6 +39,10 @@
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
svg {
vertical-align: middle;
}
} }
.next-run-cell { .next-run-cell {
......
...@@ -170,6 +170,12 @@ ...@@ -170,6 +170,12 @@
fill: $gl-text-color-secondary; fill: $gl-text-color-secondary;
} }
.sprite {
width: 12px;
height: 12px;
fill: $gl-text-color;
}
.fa { .fa {
font-size: 12px; font-size: 12px;
color: $gl-text-color; color: $gl-text-color;
......
...@@ -108,13 +108,6 @@ input[type="checkbox"]:hover { ...@@ -108,13 +108,6 @@ input[type="checkbox"]:hover {
// Custom dropdown positioning // Custom dropdown positioning
.dropdown-menu { .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; left: -5px;
} }
...@@ -152,13 +145,6 @@ input[type="checkbox"]:hover { ...@@ -152,13 +145,6 @@ input[type="checkbox"]:hover {
background-color: $nav-badge-bg; background-color: $nav-badge-bg;
border-color: $border-color; border-color: $border-color;
} }
.dropdown-menu {
transition-duration: 100ms, 75ms;
transition-delay: 75ms, 100ms;
transform: translateY(7px);
opacity: 1;
}
} }
&.has-value { &.has-value {
......
...@@ -5,9 +5,6 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -5,9 +5,6 @@ class Projects::BlobController < Projects::ApplicationController
include RendersBlob include RendersBlob
include ActionView::Helpers::SanitizeHelper include ActionView::Helpers::SanitizeHelper
# Raised when given an invalid file path
InvalidPathError = Class.new(StandardError)
prepend_before_action :authenticate_user!, only: [:edit] prepend_before_action :authenticate_user!, only: [:edit]
before_action :require_non_empty_project, except: [:new, :create] before_action :require_non_empty_project, except: [:new, :create]
...@@ -61,7 +58,6 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -61,7 +58,6 @@ class Projects::BlobController < Projects::ApplicationController
create_commit(Files::UpdateService, success_path: -> { after_edit_path }, create_commit(Files::UpdateService, success_path: -> { after_edit_path },
failure_view: :edit, failure_view: :edit,
failure_path: project_blob_path(@project, @id)) failure_path: project_blob_path(@project, @id))
rescue Files::UpdateService::FileChangedError rescue Files::UpdateService::FileChangedError
@conflict = true @conflict = true
render :edit render :edit
...@@ -132,7 +128,6 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -132,7 +128,6 @@ class Projects::BlobController < Projects::ApplicationController
def assign_blob_vars def assign_blob_vars
@id = params[:id] @id = params[:id]
@ref, @path = extract_ref(@id) @ref, @path = extract_ref(@id)
rescue InvalidPathError rescue InvalidPathError
render_404 render_404
end end
......
...@@ -269,7 +269,7 @@ module BlobHelper ...@@ -269,7 +269,7 @@ module BlobHelper
return if blob.empty? return if blob.empty?
if blob.raw_binary? || blob.stored_externally? if blob.raw_binary? || blob.stored_externally?
icon = icon('download') icon = sprite_icon('download')
title = 'Download' title = 'Download'
else else
icon = icon('file-code-o') icon = icon('file-code-o')
......
...@@ -63,7 +63,7 @@ module CommitsHelper ...@@ -63,7 +63,7 @@ module CommitsHelper
# Returns a link formatted as a commit branch link # Returns a link formatted as a commit branch link
def commit_branch_link(url, text) def commit_branch_link(url, text)
link_to(url, class: 'label label-gray ref-name branch-link') do 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
end end
......
...@@ -46,14 +46,20 @@ module SortingHelper ...@@ -46,14 +46,20 @@ module SortingHelper
end end
def groups_sort_options_hash 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_recently_created => sort_title_recently_created,
sort_value_oldest_created => sort_title_oldest_created, sort_value_oldest_created => sort_title_oldest_created,
sort_value_recently_updated => sort_title_recently_updated, sort_value_recently_updated => sort_title_recently_updated,
sort_value_oldest_updated => sort_title_oldest_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 end
def member_sort_options_hash def member_sort_options_hash
......
...@@ -3,32 +3,19 @@ module Clusters ...@@ -3,32 +3,19 @@ module Clusters
class Helm < ActiveRecord::Base class Helm < ActiveRecord::Base
self.table_name = 'clusters_applications_helm' self.table_name = 'clusters_applications_helm'
include ::Clusters::Concerns::ApplicationCore
include ::Clusters::Concerns::ApplicationStatus include ::Clusters::Concerns::ApplicationStatus
belongs_to :cluster, class_name: 'Clusters::Cluster', foreign_key: :cluster_id
default_value_for :version, Gitlab::Kubernetes::Helm::HELM_VERSION 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 def set_initial_status
return unless not_installable? return unless not_installable?
self.status = 'installable' if cluster&.platform_kubernetes_active? self.status = 'installable' if cluster&.platform_kubernetes_active?
end end
def name
self.class.application_name
end
def install_command def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(name, true) Gitlab::Kubernetes::Helm::InstallCommand.new(name, install_helm: true)
end end
end end
end end
......
...@@ -3,41 +3,22 @@ module Clusters ...@@ -3,41 +3,22 @@ module Clusters
class Ingress < ActiveRecord::Base class Ingress < ActiveRecord::Base
self.table_name = 'clusters_applications_ingress' self.table_name = 'clusters_applications_ingress'
include ::Clusters::Concerns::ApplicationCore
include ::Clusters::Concerns::ApplicationStatus 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 :ingress_type, :nginx
default_value_for :version, :nginx default_value_for :version, :nginx
after_initialize :set_initial_status
enum ingress_type: { enum ingress_type: {
nginx: 1 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 def chart
'stable/nginx-ingress' 'stable/nginx-ingress'
end end
def install_command def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(name, false, chart) Gitlab::Kubernetes::Helm::InstallCommand.new(name, chart: chart)
end end
end 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 ...@@ -7,7 +7,8 @@ module Clusters
APPLICATIONS = { APPLICATIONS = {
Applications::Helm.application_name => Applications::Helm, 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 }.freeze
belongs_to :user belongs_to :user
...@@ -22,6 +23,7 @@ module Clusters ...@@ -22,6 +23,7 @@ module Clusters
has_one :application_helm, class_name: 'Clusters::Applications::Helm' has_one :application_helm, class_name: 'Clusters::Applications::Helm'
has_one :application_ingress, class_name: 'Clusters::Applications::Ingress' 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 :provider_gcp, update_only: true
accepts_nested_attributes_for :platform_kubernetes, update_only: true accepts_nested_attributes_for :platform_kubernetes, update_only: true
...@@ -64,7 +66,8 @@ module Clusters ...@@ -64,7 +66,8 @@ module Clusters
def applications def applications
[ [
application_helm || build_application_helm, application_helm || build_application_helm,
application_ingress || build_application_ingress application_ingress || build_application_ingress,
application_prometheus || build_application_prometheus
] ]
end 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 ...@@ -9,7 +9,6 @@ module DiscussionOnDiff
:original_line_code, :original_line_code,
:diff_file, :diff_file,
:diff_line, :diff_line,
:for_line?,
:active?, :active?,
:created_at_diff?, :created_at_diff?,
...@@ -39,17 +38,16 @@ module DiscussionOnDiff ...@@ -39,17 +38,16 @@ module DiscussionOnDiff
# Returns an array of at most 16 highlighted lines above a diff note # Returns an array of at most 16 highlighted lines above a diff note
def truncated_diff_lines(highlight: true) def truncated_diff_lines(highlight: true)
lines = highlight ? highlighted_diff_lines : diff_lines lines = highlight ? highlighted_diff_lines : diff_lines
initial_line_index = [diff_line.index - NUMBER_OF_TRUNCATED_DIFF_LINES + 1, 0].max
prev_lines = [] prev_lines = []
lines.each do |line| lines[initial_line_index..diff_line.index].each do |line|
if line.meta? if line.meta?
prev_lines.clear prev_lines.clear
else else
prev_lines << line prev_lines << line
break if for_line?(line)
prev_lines.shift if prev_lines.length >= NUMBER_OF_TRUNCATED_DIFF_LINES
end end
end end
......
...@@ -14,10 +14,6 @@ module NoteOnDiff ...@@ -14,10 +14,6 @@ module NoteOnDiff
raise NotImplementedError raise NotImplementedError
end end
def for_line?(line)
raise NotImplementedError
end
def original_line_code def original_line_code
raise NotImplementedError raise NotImplementedError
end end
......
...@@ -26,7 +26,7 @@ class DiffNote < Note ...@@ -26,7 +26,7 @@ class DiffNote < Note
before_validation :set_original_position, on: :create before_validation :set_original_position, on: :create
before_validation :update_position, on: :create, if: :on_text? 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 after_save :keep_around_commits
def discussion_class(*) def discussion_class(*)
...@@ -66,10 +66,6 @@ class DiffNote < Note ...@@ -66,10 +66,6 @@ class DiffNote < Note
@diff_line ||= diff_file&.line_for_position(self.original_position) @diff_line ||= diff_file&.line_for_position(self.original_position)
end end
def for_line?(line)
diff_file.position(line) == self.original_position
end
def original_line_code def original_line_code
return unless on_text? return unless on_text?
......
...@@ -72,7 +72,7 @@ class Event < ActiveRecord::Base ...@@ -72,7 +72,7 @@ class Event < ActiveRecord::Base
# We're using preload for "push_event_payload" as otherwise the association # We're using preload for "push_event_payload" as otherwise the association
# is not always available (depending on the query being built). # is not always available (depending on the query being built).
includes(:author, :project, project: :namespace) includes(:author, :project, project: :namespace)
.preload(:push_event_payload, target: :author) .preload(:target, :push_event_payload)
end end
scope :for_milestone_id, ->(milestone_id) { where(target_type: "Milestone", target_id: milestone_id) } scope :for_milestone_id, ->(milestone_id) { where(target_type: "Milestone", target_id: milestone_id) }
......
...@@ -10,6 +10,8 @@ class Identity < ActiveRecord::Base ...@@ -10,6 +10,8 @@ class Identity < ActiveRecord::Base
validates :extern_uid, allow_blank: true, uniqueness: { scope: :provider, case_sensitive: false } validates :extern_uid, allow_blank: true, uniqueness: { scope: :provider, case_sensitive: false }
validates :user_id, uniqueness: { scope: :provider } 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_provider, ->(provider) { where(provider: provider) }
scope :with_extern_uid, ->(provider, extern_uid) do scope :with_extern_uid, ->(provider, extern_uid) do
iwhere(extern_uid: normalize_uid(provider, extern_uid)).with_provider(provider) iwhere(extern_uid: normalize_uid(provider, extern_uid)).with_provider(provider)
...@@ -26,4 +28,12 @@ class Identity < ActiveRecord::Base ...@@ -26,4 +28,12 @@ class Identity < ActiveRecord::Base
uid.to_s uid.to_s
end end
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 end
...@@ -43,11 +43,7 @@ class LegacyDiffNote < Note ...@@ -43,11 +43,7 @@ class LegacyDiffNote < Note
end end
def diff_line def diff_line
@diff_line ||= diff_file.line_for_line_code(self.line_code) if diff_file @diff_line ||= diff_file&.line_for_line_code(self.line_code)
end
def for_line?(line)
line.discussable? && diff_file.line_code(line) == self.line_code
end end
def original_line_code def original_line_code
......
...@@ -960,7 +960,9 @@ class Project < ActiveRecord::Base ...@@ -960,7 +960,9 @@ class Project < ActiveRecord::Base
def send_move_instructions(old_path_with_namespace) def send_move_instructions(old_path_with_namespace)
# New project path needs to be committed to the DB or notification will # New project path needs to be committed to the DB or notification will
# retrieve stale information # 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 end
def owner def owner
...@@ -972,15 +974,19 @@ class Project < ActiveRecord::Base ...@@ -972,15 +974,19 @@ class Project < ActiveRecord::Base
end end
def execute_hooks(data, hooks_scope = :push_hooks) def execute_hooks(data, hooks_scope = :push_hooks)
hooks.public_send(hooks_scope).each do |hook| # rubocop:disable GitlabSecurity/PublicSend run_after_commit_or_now do
hook.async_execute(data, hooks_scope.to_s) hooks.public_send(hooks_scope).each do |hook| # rubocop:disable GitlabSecurity/PublicSend
hook.async_execute(data, hooks_scope.to_s)
end
end end
end end
def execute_services(data, hooks_scope = :push_hooks) def execute_services(data, hooks_scope = :push_hooks)
# Call only service hooks that are active for this scope # Call only service hooks that are active for this scope
services.public_send(hooks_scope).each do |service| # rubocop:disable GitlabSecurity/PublicSend run_after_commit_or_now do
service.async_execute(data) services.public_send(hooks_scope).each do |service| # rubocop:disable GitlabSecurity/PublicSend
service.async_execute(data)
end
end end
end end
...@@ -1158,7 +1164,7 @@ class Project < ActiveRecord::Base ...@@ -1158,7 +1164,7 @@ class Project < ActiveRecord::Base
def change_head(branch) def change_head(branch)
if repository.branch_exists?(branch) if repository.branch_exists?(branch)
repository.before_change_head 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.copy_gitattributes(branch)
repository.after_change_head repository.after_change_head
reload_default_branch reload_default_branch
......
...@@ -24,7 +24,6 @@ class Repository ...@@ -24,7 +24,6 @@ class Repository
attr_accessor :full_path, :disk_path, :project, :is_wiki attr_accessor :full_path, :disk_path, :project, :is_wiki
delegate :ref_name_for_sha, to: :raw_repository delegate :ref_name_for_sha, to: :raw_repository
delegate :write_ref, to: :raw_repository
CreateTreeError = Class.new(StandardError) CreateTreeError = Class.new(StandardError)
...@@ -263,10 +262,11 @@ class Repository ...@@ -263,10 +262,11 @@ class Repository
# This will still fail if the file is corrupted (e.g. 0 bytes) # This will still fail if the file is corrupted (e.g. 0 bytes)
begin begin
write_ref(keep_around_ref_name(sha), sha, force: true) write_ref(keep_around_ref_name(sha), sha)
rescue Gitlab::Git::Repository::GitError => ex rescue Rugged::ReferenceError => ex
# Necessary because https://gitlab.com/gitlab-org/gitlab-ce/issues/20156 Rails.logger.error "Unable to create #{REF_KEEP_AROUND} reference for repository #{path}: #{ex}"
return true if ex.message =~ /Failed to create locked file/ && ex.message =~ /File exists/ 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}" Rails.logger.error "Unable to create #{REF_KEEP_AROUND} reference for repository #{path}: #{ex}"
end end
...@@ -276,6 +276,10 @@ class Repository ...@@ -276,6 +276,10 @@ class Repository
ref_exists?(keep_around_ref_name(sha)) ref_exists?(keep_around_ref_name(sha))
end end
def write_ref(ref_path, sha)
rugged.references.create(ref_path, sha, force: true)
end
def diverging_commit_counts(branch) def diverging_commit_counts(branch)
root_ref_hash = raw_repository.commit(root_ref).id root_ref_hash = raw_repository.commit(root_ref).id
cache.fetch(:"diverging_commit_counts_#{branch.name}") do cache.fetch(:"diverging_commit_counts_#{branch.name}") do
...@@ -1064,7 +1068,7 @@ class Repository ...@@ -1064,7 +1068,7 @@ class Repository
end end
def create_ref(ref, ref_path) def create_ref(ref, ref_path)
write_ref(ref_path, ref) raw_repository.write_ref(ref_path, ref)
end end
def ls_files(ref) def ls_files(ref)
......
...@@ -7,7 +7,7 @@ class MergeRequestSerializer < BaseSerializer ...@@ -7,7 +7,7 @@ class MergeRequestSerializer < BaseSerializer
case opts[:serializer] case opts[:serializer]
when 'basic', 'sidebar' when 'basic', 'sidebar'
MergeRequestBasicEntity MergeRequestBasicEntity
when 'widget' else # It's 'widget'
MergeRequestWidgetEntity MergeRequestWidgetEntity
end end
......
...@@ -18,7 +18,7 @@ module Clusters ...@@ -18,7 +18,7 @@ module Clusters
end end
def helm_api def helm_api
@helm_api ||= Gitlab::Kubernetes::Helm.new(kubeclient) @helm_api ||= Gitlab::Kubernetes::Helm::Api.new(kubeclient)
end end
def install_command def install_command
......
module Files module Files
class BaseService < Commits::CreateService class BaseService < Commits::CreateService
FileChangedError = Class.new(StandardError)
def initialize(*args) def initialize(*args)
super super
@author_email = params[:author_email] @author_email = params[:author_email]
@author_name = params[:author_name] @author_name = params[:author_name]
@commit_message = params[:commit_message] @commit_message = params[:commit_message]
@last_commit_sha = params[:last_commit_sha]
@file_path = params[:file_path] @file_path = params[:file_path]
@previous_path = params[:previous_path] @previous_path = params[:previous_path]
...@@ -13,5 +16,16 @@ module Files ...@@ -13,5 +16,16 @@ module Files
@file_content = params[:file_content] @file_content = params[:file_content]
@file_content = Base64.decode64(@file_content) if params[:file_content_encoding] == 'base64' @file_content = Base64.decode64(@file_content) if params[:file_content_encoding] == 'base64'
end 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
end end
...@@ -11,5 +11,15 @@ module Files ...@@ -11,5 +11,15 @@ module Files
start_project: @start_project, start_project: @start_project,
start_branch_name: @start_branch) start_branch_name: @start_branch)
end 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
end end
module Files module Files
class MultiService < Files::BaseService class MultiService < Files::BaseService
UPDATE_FILE_ACTIONS = %w(update move delete).freeze
def create_commit! def create_commit!
repository.multi_action( repository.multi_action(
user: current_user, user: current_user,
...@@ -20,6 +22,7 @@ module Files ...@@ -20,6 +22,7 @@ module Files
params[:actions].each do |action| params[:actions].each do |action|
validate_action!(action) validate_action!(action)
validate_file_status!(action)
end end
end end
...@@ -28,5 +31,15 @@ module Files ...@@ -28,5 +31,15 @@ module Files
raise_error("Unknown action '#{action[:action]}'") raise_error("Unknown action '#{action[:action]}'")
end end
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
end end
module Files module Files
class UpdateService < Files::BaseService class UpdateService < Files::BaseService
FileChangedError = Class.new(StandardError)
def initialize(*args)
super
@last_commit_sha = params[:last_commit_sha]
end
def create_commit! def create_commit!
repository.update_file(current_user, @file_path, @file_content, repository.update_file(current_user, @file_path, @file_content,
message: @commit_message, message: @commit_message,
...@@ -21,21 +13,10 @@ module Files ...@@ -21,21 +13,10 @@ module Files
private 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! def validate!
super 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." raise FileChangedError, "You are attempting to update a file that has changed since you started editing it."
end end
end end
......
...@@ -5,7 +5,7 @@ module Projects ...@@ -5,7 +5,7 @@ module Projects
if fork_source = @project.fork_source if fork_source = @project.fork_source
fork_source.lfs_objects.find_each do |lfs_object| fork_source.lfs_objects.find_each do |lfs_object|
lfs_object.projects << @project lfs_object.projects << @project unless lfs_object.projects.include?(@project)
end end
refresh_forks_count(fork_source) refresh_forks_count(fork_source)
......
...@@ -3,16 +3,19 @@ module Search ...@@ -3,16 +3,19 @@ module Search
include Gitlab::CurrentSettings include Gitlab::CurrentSettings
attr_accessor :current_user, :params attr_accessor :current_user, :params
attr_reader :default_project_filter
def initialize(user, params) def initialize(user, params)
@current_user, @params = user, params.dup @current_user, @params = user, params.dup
@default_project_filter = true
end end
def execute def execute
if current_application_settings.elasticsearch_search? if current_application_settings.elasticsearch_search?
Gitlab::Elastic::SearchResults.new(current_user, params[:search], elastic_projects, elastic_global) Gitlab::Elastic::SearchResults.new(current_user, params[:search], elastic_projects, elastic_global)
else 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
end end
......
...@@ -5,6 +5,7 @@ module Search ...@@ -5,6 +5,7 @@ module Search
def initialize(user, group, params) def initialize(user, group, params)
super(user, params) super(user, params)
@default_project_filter = false
@group = group @group = group
end end
......
...@@ -11,23 +11,7 @@ ...@@ -11,23 +11,7 @@
.search-field-holder .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' = 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") = icon("search", class: "search-icon")
.dropdown = render "shared/groups/dropdown", options_hash: admin_groups_sort_options_hash
- 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
= link_to new_admin_group_path, class: "btn btn-new" do = link_to new_admin_group_path, class: "btn btn-new" do
New group New group
%ul.content-list %ul.content-list
......
- event = last_push_event - event = last_push_event
- if event && show_last_push_widget?(event) - if event && show_last_push_widget?(event)
.row-content-block.top-block.hidden-xs.white %div{ class: container_class }
.event-last-push .row-content-block.top-block.hidden-xs.white
.event-last-push-text .event-last-push
%span= s_("LastPushEvent|You pushed to") .event-last-push-text
%strong %span= s_("LastPushEvent|You pushed to")
= link_to event.ref_name, project_commits_path(event.project, event.ref_name), class: 'ref-name' %strong
= link_to event.ref_name, project_commits_path(event.project, event.ref_name), class: 'ref-name'
- if event.project != @project - if event.project != @project
%span= s_("LastPushEvent|at") %span= s_("LastPushEvent|at")
%strong= link_to_project event.project %strong= link_to_project event.project
#{time_ago_with_tooltip(event.created_at)} #{time_ago_with_tooltip(event.created_at)}
.pull-right .pull-right
= link_to new_mr_path_from_push_event(event), title: _("New merge request"), class: "btn btn-info btn-sm" do = link_to new_mr_path_from_push_event(event), title: _("New merge request"), class: "btn btn-info btn-sm" do
#{ _('Create merge request') } #{ _('Create merge request') }
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
%a.js-md-preview-button{ href: "#md-preview-holder", tabindex: -1 } %a.js-md-preview-button{ href: "#md-preview-holder", tabindex: -1 }
Preview 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: "bold", data: { "md-tag" => "**" }, title: "Add bold text" })
= markdown_toolbar_button({ icon: "italic", data: { "md-tag" => "*" }, title: "Add italic 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" }) = markdown_toolbar_button({ icon: "quote", data: { "md-tag" => "> ", "md-prepend" => true }, title: "Insert a quote" })
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
.tree-controls .tree-controls
= link_to download_project_job_artifacts_path(@project, @build), = link_to download_project_job_artifacts_path(@project, @build),
rel: 'nofollow', download: '', class: 'btn btn-default download' do rel: 'nofollow', download: '', class: 'btn btn-default download' do
= icon('download') = sprite_icon('download')
Download artifacts archive Download artifacts archive
.tree-content-holder .tree-content-holder
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
.file-holder-bottom-radius.file-holder.file.append-bottom-default .file-holder-bottom-radius.file-holder.file.append-bottom-default
.js-file-title.file-title.clearfix{ data: { current_action: action } } .js-file-title.file-title.clearfix{ data: { current_action: action } }
.editor-ref .editor-ref
= icon('code-fork') = sprite_icon('fork', size: 12)
= ref = ref
%span.editor-file-name %span.editor-file-name
- if current_action?(:edit) || current_action?(:update) - if current_action?(:edit) || current_action?(:update)
......
...@@ -2,6 +2,6 @@ ...@@ -2,6 +2,6 @@
.center.render-error.vertical-center .center.render-error.vertical-center
= link_to blob_raw_path do = link_to blob_raw_path do
%h1.light %h1.light
= icon('download') = sprite_icon('download')
%h4 %h4
Download (#{number_to_human_size(viewer.blob.raw_size)}) Download (#{number_to_human_size(viewer.blob.raw_size)})
...@@ -8,7 +8,8 @@ ...@@ -8,7 +8,8 @@
%li{ class: "js-branch-#{branch.name}" } %li{ class: "js-branch-#{branch.name}" }
%div %div
= link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated ref-name' do = 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; &nbsp;
- if branch.name == @repository.root_ref - if branch.name == @repository.root_ref
%span.label.label-primary default %span.label.label-primary default
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
- if !project.empty_repo? && can?(current_user, :download_code, project) - if !project.empty_repo? && can?(current_user, :download_code, project)
.project-action-button.dropdown.inline> .project-action-button.dropdown.inline>
%button.btn.has-tooltip{ title: s_('DownloadSource|Download'), 'data-toggle' => 'dropdown', 'aria-label' => s_('DownloadSource|Download') } %button.btn.has-tooltip{ title: s_('DownloadSource|Download'), 'data-toggle' => 'dropdown', 'aria-label' => s_('DownloadSource|Download') }
= icon('download') = sprite_icon('download')
= icon("caret-down") = icon("caret-down")
%span.sr-only= _('Select Archive Format') %span.sr-only= _('Select Archive Format')
%ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' } %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' }
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
- if ref - if ref
- if job.ref - if job.ref
.icon-container .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" = link_to job.ref, project_ref_path(job.project, job.ref), class: "ref-name"
- else - else
.light none .light none
...@@ -96,7 +96,7 @@ ...@@ -96,7 +96,7 @@
.pull-right .pull-right
- if can?(current_user, :read_build, job) && job.artifacts? - 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 = 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 can?(current_user, :update_build, job)
- if job.active? - 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 = 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 @@ ...@@ -9,6 +9,7 @@
.edit-cluster-form.js-edit-cluster-form{ data: { status_path: status_path, .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_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_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', toggle_status: @cluster.enabled? ? 'true': 'false',
cluster_status: @cluster.status_name, cluster_status: @cluster.status_name,
cluster_status_reason: @cluster.status_reason, 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"} } .has-tooltip{ class: "limit-box limit-box-#{objects} prepend-left-5", data: { title: "Project has too many #{label_for_message} to search"} }
.limit-icon .limit-icon
- if objects == :branch - if objects == :branch
= icon('code-fork') = sprite_icon('fork', size: 12)
- else - else
= icon('tag') = icon('tag')
.limit-message .limit-message
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
.branch-commit .branch-commit
- if deployment.ref - if deployment.ref
%span.icon-container %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" = link_to deployment.ref, project_ref_path(@project, deployment.ref), class: "ref-name"
.icon-container.commit-icon .icon-container.commit-icon
= custom_icon("icon_commit") = custom_icon("icon_commit")
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
- if @forked_project && !@forked_project.saved? - if @forked_project && !@forked_project.saved?
.alert.alert-danger.alert-block .alert.alert-danger.alert-block
%h4 %h4
%i.fa.fa-code-fork = sprite_icon('fork', size: 16)
Fork Error! Fork Error!
%p %p
You tried to fork You tried to fork
...@@ -21,5 +21,4 @@ ...@@ -21,5 +21,4 @@
%p %p
= link_to new_project_fork_path(@project), title: "Fork", class: "btn" do = link_to new_project_fork_path(@project), title: "Fork", class: "btn" do
%i.fa.fa-code-fork
Try to fork again Try to fork again
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
- if ref - if ref
- if generic_commit_status.ref - if generic_commit_status.ref
.icon-container .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) = link_to generic_commit_status.ref, project_commits_path(generic_commit_status.project, generic_commit_status.ref)
- else - else
.light none .light none
......
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
%span.project-ref-path %span.project-ref-path
&nbsp; &nbsp;
= link_to project_ref_path(merge_request.project, merge_request.target_branch), class: 'ref-name' do = 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 = merge_request.target_branch
- if merge_request.labels.any? - if merge_request.labels.any?
&nbsp; &nbsp;
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
%td %td
= pipeline_schedule.description = pipeline_schedule.description
%td.branch-name-cell %td.branch-name-cell
= icon('code-fork') = sprite_icon('fork', size: 12)
- if pipeline_schedule.ref.present? - if pipeline_schedule.ref.present?
= link_to pipeline_schedule.ref, project_ref_path(@project, pipeline_schedule.ref), class: "ref-name" = link_to pipeline_schedule.ref, project_ref_path(@project, pipeline_schedule.ref), class: "ref-name"
%td %td
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
= content_for :meta_tags do = content_for :meta_tags do
= auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits") = 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)] } %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) = 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) - show_archive_options = local_assigns.fetch(:show_archive_options, false)
- if @sort.present? - if @sort.present?
- default_sort_by = @sort - default_sort_by = @sort
...@@ -10,12 +11,12 @@ ...@@ -10,12 +11,12 @@
.dropdown.inline.js-group-filter-dropdown-wrap.append-right-10 .dropdown.inline.js-group-filter-dropdown-wrap.append-right-10
%button.dropdown-toggle{ type: 'button', 'data-toggle' => 'dropdown' } %button.dropdown-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
%span.dropdown-label %span.dropdown-label
= sort_options_hash[default_sort_by] = options_hash[default_sort_by]
= icon('chevron-down') = icon('chevron-down')
%ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable
%li.dropdown-header %li.dropdown-header
= _("Sort by") = _("Sort by")
- groups_sort_options_hash.each do |value, title| - options_hash.each do |value, title|
%li.js-filter-sort-order %li.js-filter-sort-order
= link_to filter_groups_path(sort: value), class: ("is-active" if default_sort_by == value) do = link_to filter_groups_path(sort: value), class: ("is-active" if default_sort_by == value) do
= title = title
......
...@@ -52,7 +52,7 @@ ...@@ -52,7 +52,7 @@
= render_project_pipeline_status(project.pipeline_status) = render_project_pipeline_status(project.pipeline_status)
- if forks - if forks
%span.prepend-left-10 %span.prepend-left-10
= icon('code-fork') = sprite_icon('fork')
= number_with_delimiter(project.forks_count) = number_with_delimiter(project.forks_count)
- if stars - if stars
%span.prepend-left-10 %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: merge_request:
author: author:
type: performance 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 Sidekiq
module Worker module Worker
EnqueueFromTransactionError = Class.new(StandardError)
mattr_accessor :skip_transaction_check mattr_accessor :skip_transaction_check
self.skip_transaction_check = false self.skip_transaction_check = false
...@@ -12,11 +14,11 @@ module Sidekiq ...@@ -12,11 +14,11 @@ module Sidekiq
end end
module ClassMethods module ClassMethods
module NoSchedulingFromTransactions module NoEnqueueingFromTransactions
%i(perform_async perform_at perform_in).each do |name| %i(perform_async perform_at perform_in).each do |name|
define_method(name) do |*args| define_method(name) do |*args|
if !Sidekiq::Worker.skip_transaction_check && AfterCommitQueue.inside_transaction? 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 `#{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 race conditions when the worker runs before the transaction is committed and
tries to access a model that has not been saved yet. tries to access a model that has not been saved yet.
...@@ -30,7 +32,7 @@ module Sidekiq ...@@ -30,7 +32,7 @@ module Sidekiq
end end
end end
prepend NoSchedulingFromTransactions prepend NoEnqueueingFromTransactions
end end
end end
end end
......
...@@ -131,7 +131,10 @@ var config = { ...@@ -131,7 +131,10 @@ var config = {
}, },
{ {
test: /\_worker\.js$/, test: /\_worker\.js$/,
loader: 'worker-loader', use: [
{ loader: 'worker-loader' },
{ loader: 'babel-loader' },
],
}, },
{ {
test: /\.(worker(\.min)?\.js|pdf|bmpr)$/, test: /\.(worker(\.min)?\.js|pdf|bmpr)$/,
...@@ -151,6 +154,7 @@ var config = { ...@@ -151,6 +154,7 @@ var config = {
], ],
noParse: [/monaco-editor\/\w+\/vs\//], noParse: [/monaco-editor\/\w+\/vs\//],
strictExportPresence: true,
}, },
plugins: [ plugins: [
...@@ -189,8 +193,13 @@ var config = { ...@@ -189,8 +193,13 @@ var config = {
return chunk.name; return chunk.name;
} }
return chunk.mapModules((m) => { return chunk.mapModules((m) => {
var chunkPath = m.request.split('!').pop(); const pagesBase = path.join(ROOT_PATH, 'app/assets/javascripts/pages');
return path.relative(m.context, chunkPath); 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('_'); }).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 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # 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 # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -660,6 +660,15 @@ ActiveRecord::Schema.define(version: 20171213160445) do ...@@ -660,6 +660,15 @@ ActiveRecord::Schema.define(version: 20171213160445) do
t.text "status_reason" t.text "status_reason"
end 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| create_table "container_repositories", force: :cascade do |t|
t.integer "project_id", null: false t.integer "project_id", null: false
t.string "name", null: false t.string "name", null: false
......
...@@ -84,6 +84,7 @@ POST /projects/:id/repository/commits ...@@ -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` | | `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` | | `content` | string | no | File content, required for all except `delete`. Optional for `move` |
| `encoding` | string | no | `text` or `base64`. `text` is default. | | `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 ```bash
PAYLOAD=$(cat << 'JSON' PAYLOAD=$(cat << 'JSON'
......
...@@ -47,6 +47,7 @@ Parameters: ...@@ -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` | | `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` | | `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)_ | | `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 ```json
[ [
...@@ -161,6 +162,7 @@ Parameters: ...@@ -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)_ | | `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)_ | | `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)_ | | `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 ```json
[ [
......
...@@ -151,3 +151,4 @@ Parameters: ...@@ -151,3 +151,4 @@ Parameters:
- `author_email` (optional) - Specify the commit author's email address - `author_email` (optional) - Specify the commit author's email address
- `author_name` (optional) - Specify the commit author's name - `author_name` (optional) - Specify the commit author's name
- `commit_message` (required) - Commit message - `commit_message` (required) - Commit message
- `last_commit_id` (optional) - Last known file commit id
...@@ -164,6 +164,21 @@ not without its own challenges: ...@@ -164,6 +164,21 @@ not without its own challenges:
- By default, `docker:dind` uses `--storage-driver vfs` which is the slowest - By default, `docker:dind` uses `--storage-driver vfs` which is the slowest
form offered. To use a different driver, see form offered. To use a different driver, see
[Using the overlayfs driver](#using-the-overlayfs-driver). [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. 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 ...@@ -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 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). 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