Commit 0ac012f0 authored by Luke "Jared" Bennett's avatar Luke "Jared" Bennett

Merge remote-tracking branch 'origin/master' into ide

parents 933cfe1e 25190274
...@@ -323,7 +323,7 @@ GEM ...@@ -323,7 +323,7 @@ GEM
multi_json (~> 1.10) multi_json (~> 1.10)
retriable (~> 1.4) retriable (~> 1.4)
signet (~> 0.6) signet (~> 0.6)
google-protobuf (3.2.0.2) google-protobuf (3.3.0)
googleauth (0.5.1) googleauth (0.5.1)
faraday (~> 0.9) faraday (~> 0.9)
jwt (~> 1.4) jwt (~> 1.4)
......
...@@ -128,7 +128,7 @@ information, see ...@@ -128,7 +128,7 @@ information, see
### After the 7th ### After the 7th
Once the stable branch is frozen, only fixes for regressions (bugs introduced in that same release) Once the stable branch is frozen, only fixes for [regressions](#regressions)
and security issues will be cherry-picked into the stable branch. and security issues will be cherry-picked into the stable branch.
Any merge requests cherry-picked into the stable branch for a previous release will also be picked into the latest stable branch. Any merge requests cherry-picked into the stable branch for a previous release will also be picked into the latest stable branch.
These fixes will be shipped in the next RC for that release if it is before the 22nd. These fixes will be shipped in the next RC for that release if it is before the 22nd.
...@@ -158,6 +158,24 @@ release should have the correct milestone assigned _and_ have the label ...@@ -158,6 +158,24 @@ release should have the correct milestone assigned _and_ have the label
Merge requests without a milestone and this label will Merge requests without a milestone and this label will
not be merged into any stable branches. not be merged into any stable branches.
### Regressions
A regression for a particular monthly release is a bug that exists in that
release, but wasn't present in the release before. This includes bugs in
features that were only added in that monthly release. Every regression **must**
have the milestone of the release it was introduced in - if a regression doesn't
have a milestone, it might be 'just' a bug!
For instance, if 10.5.0 adds a feature, and that feature doesn't work correctly,
then this is a regression in 10.5. If 10.5.1 then fixes that, but 10.5.3 somehow
reintroduces the bug, then this bug is still a regression in 10.5.
Because GitLab.com runs release candidates of new releases, a regression can be
reported in a release before its 'official' release date on the 22nd of the
month. When we say 'the most recent monthly release', this can refer to either
the version currently running on GitLab.com, or the most recent version
available in the package repositories.
## Release retrospective and kickoff ## Release retrospective and kickoff
### Retrospective ### Retrospective
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
/* global LabelsSelect */ /* global LabelsSelect */
/* global MilestoneSelect */ /* global MilestoneSelect */
/* global Commit */ /* global Commit */
/* global NewBranchForm */
/* global NotificationsForm */ /* global NotificationsForm */
/* global NotificationsDropdown */ /* global NotificationsDropdown */
/* global GroupAvatar */ /* global GroupAvatar */
...@@ -263,7 +264,7 @@ import FeatureHelper from './helpers/feature_helper'; ...@@ -263,7 +264,7 @@ import FeatureHelper from './helpers/feature_helper';
case 'projects:tags:new': case 'projects:tags:new':
new ZenMode(); new ZenMode();
new gl.GLForm($('.tag-form'), true); new gl.GLForm($('.tag-form'), true);
new RefSelectDropdown($('.js-branch-select'), window.gl.availableRefs); new RefSelectDropdown($('.js-branch-select'));
break; break;
case 'projects:snippets:show': case 'projects:snippets:show':
initNotes(); initNotes();
...@@ -334,6 +335,9 @@ import FeatureHelper from './helpers/feature_helper'; ...@@ -334,6 +335,9 @@ import FeatureHelper from './helpers/feature_helper';
case 'projects:edit': case 'projects:edit':
setupProjectEdit(); setupProjectEdit();
break; break;
case 'projects:pipelines:new':
new NewBranchForm($('.js-new-pipeline-form'));
break;
case 'projects:pipelines:builds': case 'projects:pipelines:builds':
case 'projects:pipelines:failures': case 'projects:pipelines:failures':
case 'projects:pipelines:show': case 'projects:pipelines:show':
...@@ -390,6 +394,9 @@ import FeatureHelper from './helpers/feature_helper'; ...@@ -390,6 +394,9 @@ import FeatureHelper from './helpers/feature_helper';
new TreeView(); new TreeView();
new BlobViewer(); new BlobViewer();
$('#tree-slider').waitForImages(function() {
gl.utils.ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath);
});
break; break;
case 'projects:find_file:show': case 'projects:find_file:show':
shortcut_handler = true; shortcut_handler = true;
...@@ -548,6 +555,7 @@ import FeatureHelper from './helpers/feature_helper'; ...@@ -548,6 +555,7 @@ import FeatureHelper from './helpers/feature_helper';
shortcut_handler = new ShortcutsWiki(); shortcut_handler = new ShortcutsWiki();
new ZenMode(); new ZenMode();
new gl.GLForm($('.wiki-form'), true); new gl.GLForm($('.wiki-form'), true);
new Sidebar();
break; break;
case 'snippets': case 'snippets':
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
......
...@@ -529,6 +529,7 @@ export default class Notes { ...@@ -529,6 +529,7 @@ export default class Notes {
form.find('#note_line_code').remove(); form.find('#note_line_code').remove();
form.find('#note_position').remove(); form.find('#note_position').remove();
form.find('#note_type').val(''); form.find('#note_type').val('');
form.find('#note_project_id').remove();
form.find('#in_reply_to_discussion_id').remove(); form.find('#in_reply_to_discussion_id').remove();
form.find('.js-comment-resolve-button').closest('comment-and-resolve-btn').remove(); form.find('.js-comment-resolve-button').closest('comment-and-resolve-btn').remove();
this.parentTimeline = form.parents('.timeline'); this.parentTimeline = form.parents('.timeline');
...@@ -556,6 +557,7 @@ export default class Notes { ...@@ -556,6 +557,7 @@ export default class Notes {
form.find('#note_noteable_id').val(), form.find('#note_noteable_id').val(),
form.find('#note_commit_id').val(), form.find('#note_commit_id').val(),
form.find('#note_type').val(), form.find('#note_type').val(),
form.find('#note_project_id').val(),
form.find('#in_reply_to_discussion_id').val(), form.find('#in_reply_to_discussion_id').val(),
// LegacyDiffNote // LegacyDiffNote
...@@ -848,6 +850,8 @@ export default class Notes { ...@@ -848,6 +850,8 @@ export default class Notes {
form.find('#in_reply_to_discussion_id').val(discussionID); form.find('#in_reply_to_discussion_id').val(discussionID);
} }
form.find('#note_project_id').val(dataHolder.data('discussionProjectId'));
form.attr('data-line-code', dataHolder.data('lineCode')); form.attr('data-line-code', dataHolder.data('lineCode'));
form.find('#line_type').val(dataHolder.data('lineType')); form.find('#line_type').val(dataHolder.data('lineType'));
......
import Chart from 'vendor/Chart';
document.addEventListener('DOMContentLoaded', () => {
const chartData = JSON.parse(document.getElementById('pipelinesChartsData').innerHTML);
const buildChart = (chartScope) => {
const data = {
labels: chartScope.labels,
datasets: [{
fillColor: '#7f8fa4',
strokeColor: '#7f8fa4',
pointColor: '#7f8fa4',
pointStrokeColor: '#EEE',
data: chartScope.totalValues,
},
{
fillColor: '#44aa22',
strokeColor: '#44aa22',
pointColor: '#44aa22',
pointStrokeColor: '#fff',
data: chartScope.successValues,
},
],
};
const ctx = $(`#${chartScope.scope}Chart`).get(0).getContext('2d');
const options = {
scaleOverlay: true,
responsive: true,
maintainAspectRatio: false,
};
if (window.innerWidth < 768) {
// Scale fonts if window width lower than 768px (iPad portrait)
options.scaleFontSize = 8;
}
new Chart(ctx).Line(data, options);
};
chartData.forEach(scope => buildChart(scope));
});
import Chart from 'vendor/Chart';
document.addEventListener('DOMContentLoaded', () => {
const chartData = JSON.parse(document.getElementById('pipelinesTimesChartsData').innerHTML);
const data = {
labels: chartData.labels,
datasets: [{
fillColor: 'rgba(220,220,220,0.5)',
strokeColor: 'rgba(220,220,220,1)',
barStrokeWidth: 1,
barValueSpacing: 1,
barDatasetSpacing: 1,
data: chartData.values,
}],
};
const ctx = $('#build_timesChart').get(0).getContext('2d');
const options = {
scaleOverlay: true,
responsive: true,
maintainAspectRatio: false,
};
if (window.innerWidth < 768) {
// Scale fonts if window width lower than 768px (iPad portrait)
options.scaleFontSize = 8;
}
new Chart(ctx).Bar(data, options);
});
document.addEventListener('DOMContentLoaded', () => {
const importBtnTooltip = 'Please enter a valid project name.';
const $importBtnWrapper = $('.import_gitlab_project');
$('.how_to_import_link').on('click', (e) => {
e.preventDefault();
$('.how_to_import_link').next('.modal').show();
});
$('.modal-header .close').on('click', () => {
$('.modal').hide();
});
$('.btn_import_gitlab_project').on('click', () => {
const importHref = $('a.btn_import_gitlab_project').attr('href');
$('.btn_import_gitlab_project').attr('href', `${importHref}?namespace_id=${$('#project_namespace_id').val()}&path=${$('#project_path').val()}`);
});
$('.btn_import_gitlab_project').attr('disabled', !$('#project_path').val().trim().length);
$importBtnWrapper.attr('title', importBtnTooltip);
$('#new_project').on('submit', () => {
const $path = $('#project_path');
$path.val($path.val().trim());
});
$('#project_path').on('keyup', () => {
if ($('#project_path').val().trim().length) {
$('.btn_import_gitlab_project').attr('disabled', false);
$importBtnWrapper.attr('title', '');
$importBtnWrapper.removeClass('has-tooltip');
} else {
$('.btn_import_gitlab_project').attr('disabled', true);
$importBtnWrapper.addClass('has-tooltip');
}
});
$('#project_import_url').disable();
$('.import_git').on('click', () => {
const $projectImportUrl = $('#project_import_url');
$projectImportUrl.attr('disabled', !$projectImportUrl.attr('disabled'));
});
});
class RefSelectDropdown { class RefSelectDropdown {
constructor($dropdownButton, availableRefs) { constructor($dropdownButton, availableRefs) {
const availableRefsValue = availableRefs || JSON.parse(document.getElementById('availableRefs').innerHTML);
$dropdownButton.glDropdown({ $dropdownButton.glDropdown({
data: availableRefs, data: availableRefsValue,
filterable: true, filterable: true,
filterByText: true, filterByText: true,
remote: false, remote: false,
......
...@@ -65,7 +65,7 @@ export default class MergeRequestStore { ...@@ -65,7 +65,7 @@ export default class MergeRequestStore {
this.mergeCheckPath = data.merge_check_path; this.mergeCheckPath = data.merge_check_path;
this.mergeActionsContentPath = data.commit_change_content_path; this.mergeActionsContentPath = data.commit_change_content_path;
this.isRemovingSourceBranch = this.isRemovingSourceBranch || false; this.isRemovingSourceBranch = this.isRemovingSourceBranch || false;
this.isOpen = data.state === 'opened' || data.state === 'reopened' || false; this.isOpen = data.state === 'opened';
this.hasMergeableDiscussionsState = data.mergeable_discussions_state === false; this.hasMergeableDiscussionsState = data.mergeable_discussions_state === false;
this.canRemoveSourceBranch = currentUser.can_remove_source_branch || false; this.canRemoveSourceBranch = currentUser.can_remove_source_branch || false;
this.canMerge = !!data.merge_path; this.canMerge = !!data.merge_path;
......
...@@ -88,6 +88,10 @@ ...@@ -88,6 +88,10 @@
overflow: hidden; overflow: hidden;
display: flex; display: flex;
a {
display: flex;
}
.avatar { .avatar {
border-radius: 0; border-radius: 0;
border: none; border: none;
......
...@@ -414,13 +414,16 @@ ...@@ -414,13 +414,16 @@
background-color: $dropdown-hover-color; background-color: $dropdown-hover-color;
color: $white-light; color: $white-light;
text-decoration: none; text-decoration: none;
outline: 0;
.avatar { .avatar {
border-color: $white-light; border-color: $white-light;
} }
} }
.filter-dropdown-item { .droplab-dropdown .dropdown-menu .filter-dropdown-item {
padding: 0;
.btn { .btn {
border: none; border: none;
width: 100%; width: 100%;
...@@ -455,14 +458,11 @@ ...@@ -455,14 +458,11 @@
} }
.dropdown-user { .dropdown-user {
display: -webkit-flex;
display: flex; display: flex;
} }
.dropdown-user-details { .dropdown-user-details {
display: -webkit-flex;
display: flex; display: flex;
-webkit-flex-direction: column;
flex-direction: column; flex-direction: column;
> span { > span {
......
...@@ -315,6 +315,10 @@ header { ...@@ -315,6 +315,10 @@ header {
} }
} }
.with-performance-bar header.navbar-gitlab {
top: $performance-bar-height;
}
.navbar-nav { .navbar-nav {
li { li {
.badge { .badge {
......
...@@ -127,3 +127,7 @@ of the body element here, we negate cascading side effects but allow momentum sc ...@@ -127,3 +127,7 @@ of the body element here, we negate cascading side effects but allow momentum sc
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.with-performance-bar .page-with-sidebar {
margin-top: $header-height + $performance-bar-height;
}
...@@ -347,6 +347,10 @@ ...@@ -347,6 +347,10 @@
} }
} }
.with-performance-bar .layout-nav {
margin-top: $header-height + $performance-bar-height;
}
.scrolling-tabs-container { .scrolling-tabs-container {
position: relative; position: relative;
...@@ -441,6 +445,22 @@ ...@@ -441,6 +445,22 @@
} }
} }
.with-performance-bar .page-with-layout-nav {
.right-sidebar {
top: ($header-height + 1) * 2 + $performance-bar-height;
}
&.page-with-sub-nav {
.right-sidebar {
top: ($header-height + 1) * 3 + $performance-bar-height;
&.affix {
top: $header-height + $performance-bar-height;
}
}
}
}
.nav-block { .nav-block {
&.activities { &.activities {
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
......
...@@ -89,6 +89,10 @@ ...@@ -89,6 +89,10 @@
} }
} }
.with-performance-bar .right-sidebar.affix {
top: $header-height + $performance-bar-height;
}
@mixin maintain-sidebar-dimensions { @mixin maintain-sidebar-dimensions {
display: block; display: block;
width: $gutter-width; width: $gutter-width;
......
...@@ -205,6 +205,7 @@ $divergence-graph-separator-bg: #ccc; ...@@ -205,6 +205,7 @@ $divergence-graph-separator-bg: #ccc;
$general-hover-transition-duration: 100ms; $general-hover-transition-duration: 100ms;
$general-hover-transition-curve: linear; $general-hover-transition-curve: linear;
$highlight-changes-color: rgb(235, 255, 232); $highlight-changes-color: rgb(235, 255, 232);
$performance-bar-height: 35px;
/* /*
......
...@@ -325,6 +325,7 @@ header.navbar-gitlab-new { ...@@ -325,6 +325,7 @@ header.navbar-gitlab-new {
.breadcrumbs-links { .breadcrumbs-links {
flex: 1; flex: 1;
min-width: 0;
align-self: center; align-self: center;
color: $gl-text-color-quaternary; color: $gl-text-color-quaternary;
...@@ -343,7 +344,7 @@ header.navbar-gitlab-new { ...@@ -343,7 +344,7 @@ header.navbar-gitlab-new {
} }
.title { .title {
white-space: nowrap; display: inline-block;
> a { > a {
&:last-of-type:not(:first-child) { &:last-of-type:not(:first-child) {
......
...@@ -118,7 +118,7 @@ $new-sidebar-width: 220px; ...@@ -118,7 +118,7 @@ $new-sidebar-width: 220px;
z-index: 400; z-index: 400;
width: $new-sidebar-width; width: $new-sidebar-width;
transition: left $sidebar-transition-duration; transition: left $sidebar-transition-duration;
top: 50px; top: $header-height;
bottom: 0; bottom: 0;
left: 0; left: 0;
overflow: auto; overflow: auto;
...@@ -163,6 +163,10 @@ $new-sidebar-width: 220px; ...@@ -163,6 +163,10 @@ $new-sidebar-width: 220px;
} }
} }
.with-performance-bar .nav-sidebar {
top: $header-height + $performance-bar-height;
}
.sidebar-sub-level-items { .sidebar-sub-level-items {
display: none; display: none;
padding-bottom: 8px; padding-bottom: 8px;
...@@ -260,7 +264,7 @@ $new-sidebar-width: 220px; ...@@ -260,7 +264,7 @@ $new-sidebar-width: 220px;
// Make issue boards full-height now that sub-nav is gone // Make issue boards full-height now that sub-nav is gone
.boards-list { .boards-list {
height: calc(100vh - 50px); height: calc(100vh - #{$header-height});
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
height: 475px; // Needed for PhantomJS height: 475px; // Needed for PhantomJS
...@@ -270,6 +274,10 @@ $new-sidebar-width: 220px; ...@@ -270,6 +274,10 @@ $new-sidebar-width: 220px;
} }
} }
.with-performance-bar .boards-list {
height: calc(100vh - #{$header-height} - #{$performance-bar-height});
}
// Change color of all horizontal tabs to match the new indigo color // Change color of all horizontal tabs to match the new indigo color
.nav-links li.active a { .nav-links li.active a {
......
...@@ -64,10 +64,10 @@ ...@@ -64,10 +64,10 @@
color: $gl-text-color; color: $gl-text-color;
position: sticky; position: sticky;
position: -webkit-sticky; position: -webkit-sticky;
top: 50px; top: $header-height;
&.affix { &.affix {
top: 50px; top: $header-height;
} }
// with sidebar // with sidebar
...@@ -86,6 +86,7 @@ ...@@ -86,6 +86,7 @@
position: absolute; position: absolute;
right: 0; right: 0;
left: 0; left: 0;
top: 0;
} }
.truncated-info { .truncated-info {
...@@ -171,6 +172,16 @@ ...@@ -171,6 +172,16 @@
} }
} }
.with-performance-bar .build-page {
.top-bar {
top: $header-height + $performance-bar-height;
&.affix {
top: $header-height + $performance-bar-height;
}
}
}
.build-header { .build-header {
.ci-header-container, .ci-header-container,
.header-action-buttons { .header-action-buttons {
......
...@@ -445,6 +445,14 @@ ...@@ -445,6 +445,14 @@
} }
} }
.with-performance-bar .right-sidebar {
top: $header-height + $performance-bar-height;
.issuable-sidebar {
height: calc(100% - #{$header-height} - #{$performance-bar-height});
}
}
.detail-page-description { .detail-page-description {
padding: 16px 0; padding: 16px 0;
......
...@@ -759,6 +759,10 @@ ...@@ -759,6 +759,10 @@
} }
} }
.with-performance-bar .merge-request-tabs-holder {
top: $header-height + $performance-bar-height;
}
.merge-request-tabs { .merge-request-tabs {
display: flex; display: flex;
margin-bottom: 0; margin-bottom: 0;
......
...@@ -3,9 +3,16 @@ ...@@ -3,9 +3,16 @@
@import "peek/views/rblineprof"; @import "peek/views/rblineprof";
#peek { #peek {
height: 35px; position: fixed;
left: 0;
top: 0;
width: 100%;
z-index: 2000;
overflow-x: hidden;
height: $performance-bar-height;
background: $black; background: $black;
line-height: 35px; line-height: $performance-bar-height;
color: $perf-bar-text; color: $perf-bar-text;
&.disabled { &.disabled {
...@@ -25,7 +32,8 @@ ...@@ -25,7 +32,8 @@
} }
.wrapper { .wrapper {
width: 1000px; width: 80%;
height: $performance-bar-height;
margin: 0 auto; margin: 0 auto;
} }
......
...@@ -4,6 +4,7 @@ module NotesActions ...@@ -4,6 +4,7 @@ module NotesActions
included do included do
before_action :authorize_admin_note!, only: [:update, :destroy] before_action :authorize_admin_note!, only: [:update, :destroy]
before_action :note_project, only: [:create]
end end
def index def index
...@@ -28,7 +29,8 @@ module NotesActions ...@@ -28,7 +29,8 @@ module NotesActions
merge_request_diff_head_sha: params[:merge_request_diff_head_sha], merge_request_diff_head_sha: params[:merge_request_diff_head_sha],
in_reply_to_discussion_id: params[:in_reply_to_discussion_id] in_reply_to_discussion_id: params[:in_reply_to_discussion_id]
) )
@note = Notes::CreateService.new(project, current_user, create_params).execute
@note = Notes::CreateService.new(note_project, current_user, create_params).execute
if @note.is_a?(Note) if @note.is_a?(Note)
Banzai::NoteRenderer.render([@note], @project, current_user) Banzai::NoteRenderer.render([@note], @project, current_user)
...@@ -177,4 +179,22 @@ module NotesActions ...@@ -177,4 +179,22 @@ module NotesActions
def notes_finder def notes_finder
@notes_finder ||= NotesFinder.new(project, current_user, finder_params) @notes_finder ||= NotesFinder.new(project, current_user, finder_params)
end end
def note_project
return @note_project if defined?(@note_project)
return nil unless project
note_project_id = params[:note_project_id]
@note_project =
if note_project_id.present?
Project.find(note_project_id)
else
project
end
return access_denied! unless can?(current_user, :create_note, @note_project)
@note_project
end
end end
...@@ -8,7 +8,7 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -8,7 +8,7 @@ class Projects::BranchesController < Projects::ApplicationController
before_action :authorize_push_code!, only: [:new, :create, :destroy, :destroy_all_merged] before_action :authorize_push_code!, only: [:new, :create, :destroy, :destroy_all_merged]
def index def index
@sort = params[:sort].presence || sort_value_name @sort = params[:sort].presence || sort_value_recently_updated
@branches = BranchesFinder.new(@repository, params).execute @branches = BranchesFinder.new(@repository, params).execute
@branches = Kaminari.paginate_array(@branches).page(params[:page]) @branches = Kaminari.paginate_array(@branches).page(params[:page])
......
...@@ -81,7 +81,6 @@ class IssuableFinder ...@@ -81,7 +81,6 @@ class IssuableFinder
end end
counts[:all] = counts.values.sum counts[:all] = counts.values.sum
counts[:opened] += counts[:reopened]
counts counts
end end
......
...@@ -264,7 +264,11 @@ module ApplicationHelper ...@@ -264,7 +264,11 @@ module ApplicationHelper
end end
def page_class def page_class
"issue-boards-page" if current_controller?(:boards) class_names = []
class_names << 'issue-boards-page' if current_controller?(:boards)
class_names << 'with-performance-bar' if performance_bar_enabled?
class_names
end end
# Returns active css class when condition returns true # Returns active css class when condition returns true
......
...@@ -15,7 +15,7 @@ module DiffHelper ...@@ -15,7 +15,7 @@ module DiffHelper
def diff_view def diff_view
@diff_view ||= begin @diff_view ||= begin
diff_views = %w(inline parallel) diff_views = %w(inline parallel)
diff_view = cookies[:diff_view] diff_view = params[:view] || cookies[:diff_view]
diff_view = diff_views.first unless diff_views.include?(diff_view) diff_view = diff_views.first unless diff_views.include?(diff_view)
diff_view.to_sym diff_view.to_sym
end end
......
module NavHelper module NavHelper
def page_with_sidebar_class
class_name = page_gutter_class
class_name << 'page-with-new-sidebar' if defined?(@new_sidebar) && @new_sidebar
class_name
end
def page_gutter_class def page_gutter_class
if current_path?('merge_requests#show') || if current_path?('merge_requests#show') ||
current_path?('projects/merge_requests/conflicts#show') || current_path?('projects/merge_requests/conflicts#show') ||
current_path?('issues#show') || current_path?('issues#show') ||
current_path?('milestones#show') current_path?('milestones#show')
if cookies[:collapsed_gutter] == 'true' if cookies[:collapsed_gutter] == 'true'
"page-gutter right-sidebar-collapsed" %w[page-gutter right-sidebar-collapsed]
else else
"page-gutter right-sidebar-expanded" %w[page-gutter right-sidebar-expanded]
end end
elsif current_path?('jobs#show') elsif current_path?('jobs#show')
"page-gutter build-sidebar right-sidebar-expanded" %w[page-gutter build-sidebar right-sidebar-expanded]
elsif current_path?('wikis#show') || elsif current_path?('wikis#show') ||
current_path?('wikis#edit') || current_path?('wikis#edit') ||
current_path?('wikis#update') || current_path?('wikis#update') ||
current_path?('wikis#history') || current_path?('wikis#history') ||
current_path?('wikis#git_access') current_path?('wikis#git_access')
"page-gutter wiki-sidebar right-sidebar-expanded" %w[page-gutter wiki-sidebar right-sidebar-expanded]
else
[]
end end
end end
def nav_header_class def nav_header_class
class_name = '' class_names = []
class_name << " with-horizontal-nav" if defined?(nav) && nav class_names << 'with-horizontal-nav' if defined?(nav) && nav
class_name class_names
end end
def layout_nav_class def layout_nav_class
class_name = '' return [] if show_new_nav?
class_name << " page-with-layout-nav" if defined?(nav) && nav
class_name << " page-with-sub-nav" if content_for?(:sub_nav)
class_name class_names = []
class_names << 'page-with-layout-nav' if defined?(nav) && nav
class_names << 'page-with-sub-nav' if content_for?(:sub_nav)
class_names
end end
def nav_control_class def nav_control_class
......
...@@ -62,7 +62,11 @@ module NotesHelper ...@@ -62,7 +62,11 @@ module NotesHelper
def link_to_reply_discussion(discussion, line_type = nil) def link_to_reply_discussion(discussion, line_type = nil)
return unless current_user return unless current_user
data = { discussion_id: discussion.reply_id, line_type: line_type } data = {
discussion_id: discussion.reply_id,
discussion_project_id: discussion.project&.id,
line_type: line_type
}
button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button', button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button',
data: data, title: 'Add a reply' data: data, title: 'Add a reply'
......
...@@ -34,6 +34,8 @@ module WebpackHelper ...@@ -34,6 +34,8 @@ module WebpackHelper
end end
def webpack_public_path def webpack_public_path
"#{webpack_public_host}/#{Rails.application.config.webpack.public_path}/" relative_path = Rails.application.config.relative_url_root
webpack_path = Rails.application.config.webpack.public_path
File.join(webpack_public_host.to_s, relative_path.to_s, webpack_path.to_s, '')
end end
end end
...@@ -71,9 +71,8 @@ module Issuable ...@@ -71,9 +71,8 @@ module Issuable
scope :of_projects, ->(ids) { where(project_id: ids) } scope :of_projects, ->(ids) { where(project_id: ids) }
scope :of_milestones, ->(ids) { where(milestone_id: ids) } scope :of_milestones, ->(ids) { where(milestone_id: ids) }
scope :with_milestone, ->(title) { left_joins_milestones.where(milestones: { title: title }) } scope :with_milestone, ->(title) { left_joins_milestones.where(milestones: { title: title }) }
scope :opened, -> { with_state(:opened, :reopened) } scope :opened, -> { with_state(:opened) }
scope :only_opened, -> { with_state(:opened) } scope :only_opened, -> { with_state(:opened) }
scope :only_reopened, -> { with_state(:reopened) }
scope :closed, -> { with_state(:closed) } scope :closed, -> { with_state(:closed) }
scope :left_joins_milestones, -> { joins("LEFT OUTER JOIN milestones ON #{table_name}.milestone_id = milestones.id") } scope :left_joins_milestones, -> { joins("LEFT OUTER JOIN milestones ON #{table_name}.milestone_id = milestones.id") }
...@@ -234,7 +233,7 @@ module Issuable ...@@ -234,7 +233,7 @@ module Issuable
end end
def open? def open?
opened? || reopened? opened?
end end
def user_notes_count def user_notes_count
......
...@@ -62,15 +62,14 @@ class Issue < ActiveRecord::Base ...@@ -62,15 +62,14 @@ class Issue < ActiveRecord::Base
state_machine :state, initial: :opened do state_machine :state, initial: :opened do
event :close do event :close do
transition [:reopened, :opened] => :closed transition [:opened] => :closed
end end
event :reopen do event :reopen do
transition closed: :reopened transition closed: :opened
end end
state :opened state :opened
state :reopened
state :closed state :closed
before_transition any => :closed do |issue| before_transition any => :closed do |issue|
......
...@@ -42,23 +42,23 @@ class MergeRequest < ActiveRecord::Base ...@@ -42,23 +42,23 @@ class MergeRequest < ActiveRecord::Base
state_machine :state, initial: :opened do state_machine :state, initial: :opened do
event :close do event :close do
transition [:reopened, :opened] => :closed transition [:opened] => :closed
end end
event :mark_as_merged do event :mark_as_merged do
transition [:reopened, :opened, :locked] => :merged transition [:opened, :locked] => :merged
end end
event :reopen do event :reopen do
transition closed: :reopened transition closed: :opened
end end
event :lock_mr do event :lock_mr do
transition [:reopened, :opened] => :locked transition [:opened] => :locked
end end
event :unlock_mr do event :unlock_mr do
transition locked: :reopened transition locked: :opened
end end
after_transition any => :locked do |merge_request, transition| after_transition any => :locked do |merge_request, transition|
...@@ -72,7 +72,6 @@ class MergeRequest < ActiveRecord::Base ...@@ -72,7 +72,6 @@ class MergeRequest < ActiveRecord::Base
end end
state :opened state :opened
state :reopened
state :closed state :closed
state :merged state :merged
state :locked state :locked
...@@ -368,7 +367,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -368,7 +367,7 @@ class MergeRequest < ActiveRecord::Base
errors.add :branch_conflict, "You can not use same project/branch for source and target" errors.add :branch_conflict, "You can not use same project/branch for source and target"
end end
if opened? || reopened? if opened?
similar_mrs = self.target_project.merge_requests.where(source_branch: source_branch, target_branch: target_branch, source_project_id: source_project.try(:id)).opened similar_mrs = self.target_project.merge_requests.where(source_branch: source_branch, target_branch: target_branch, source_project_id: source_project.try(:id)).opened
similar_mrs = similar_mrs.where('id not in (?)', self.id) if self.id similar_mrs = similar_mrs.where('id not in (?)', self.id) if self.id
if similar_mrs.any? if similar_mrs.any?
......
...@@ -114,7 +114,7 @@ class DroneCiService < CiService ...@@ -114,7 +114,7 @@ class DroneCiService < CiService
end end
def merge_request_valid?(data) def merge_request_valid?(data)
%w(opened reopened).include?(data[:object_attributes][:state]) && data[:object_attributes][:state] == 'opened' &&
data[:object_attributes][:merge_status] == 'unchecked' data[:object_attributes][:merge_status] == 'unchecked'
end end
end end
...@@ -45,6 +45,7 @@ class GitPushService < BaseService ...@@ -45,6 +45,7 @@ class GitPushService < BaseService
elsif push_to_existing_branch? elsif push_to_existing_branch?
# Collect data for this git push # Collect data for this git push
@push_commits = @project.repository.commits_between(params[:oldrev], params[:newrev]) @push_commits = @project.repository.commits_between(params[:oldrev], params[:newrev])
process_commit_messages process_commit_messages
# Update the bare repositories info/attributes file using the contents of the default branches # Update the bare repositories info/attributes file using the contents of the default branches
...@@ -66,15 +67,21 @@ class GitPushService < BaseService ...@@ -66,15 +67,21 @@ class GitPushService < BaseService
def update_caches def update_caches
if is_default_branch? if is_default_branch?
paths = Set.new if push_to_new_branch?
# If this is the initial push into the default branch, the file type caches
# will already be reset as a result of `Project#change_head`.
types = []
else
paths = Set.new
@push_commits.each do |commit| @push_commits.last(PROCESS_COMMIT_LIMIT).each do |commit|
commit.raw_deltas.each do |diff| commit.raw_deltas.each do |diff|
paths << diff.new_path paths << diff.new_path
end
end end
end
types = Gitlab::FileDetector.types_in_paths(paths.to_a) types = Gitlab::FileDetector.types_in_paths(paths.to_a)
end
else else
types = [] types = []
end end
...@@ -92,7 +99,7 @@ class GitPushService < BaseService ...@@ -92,7 +99,7 @@ class GitPushService < BaseService
def process_commit_messages def process_commit_messages
default = is_default_branch? default = is_default_branch?
push_commits.last(PROCESS_COMMIT_LIMIT).each do |commit| @push_commits.last(PROCESS_COMMIT_LIMIT).each do |commit|
if commit.matches_cross_reference_regex? if commit.matches_cross_reference_regex?
ProcessCommitWorker ProcessCommitWorker
.perform_async(project.id, current_user.id, commit.to_hash, default) .perform_async(project.id, current_user.id, commit.to_hash, default)
...@@ -111,7 +118,7 @@ class GitPushService < BaseService ...@@ -111,7 +118,7 @@ class GitPushService < BaseService
EventCreateService.new.push(@project, current_user, build_push_data) EventCreateService.new.push(@project, current_user, build_push_data)
Ci::CreatePipelineService.new(@project, current_user, build_push_data).execute(:push) Ci::CreatePipelineService.new(@project, current_user, build_push_data).execute(:push)
SystemHookPushWorker.perform_async(build_push_data.dup, :push_hooks) SystemHookPushWorker.perform_async(build_push_data.dup, :push_hooks)
@project.execute_hooks(build_push_data.dup, :push_hooks) @project.execute_hooks(build_push_data.dup, :push_hooks)
@project.execute_services(build_push_data.dup, :push_hooks) @project.execute_services(build_push_data.dup, :push_hooks)
...@@ -131,7 +138,10 @@ class GitPushService < BaseService ...@@ -131,7 +138,10 @@ class GitPushService < BaseService
end end
def process_default_branch def process_default_branch
@push_commits = project.repository.commits(params[:newrev]) @push_commits_count = project.repository.commit_count_for_ref(params[:ref])
offset = [@push_commits_count - PROCESS_COMMIT_LIMIT, 0].max
@push_commits = project.repository.commits(params[:newrev], offset: offset, limit: PROCESS_COMMIT_LIMIT)
# Ensure HEAD points to the default branch in case it is not master # Ensure HEAD points to the default branch in case it is not master
project.change_head(branch_name) project.change_head(branch_name)
...@@ -160,7 +170,8 @@ class GitPushService < BaseService ...@@ -160,7 +170,8 @@ class GitPushService < BaseService
params[:oldrev], params[:oldrev],
params[:newrev], params[:newrev],
params[:ref], params[:ref],
push_commits) @push_commits,
commits_count: @push_commits_count)
end end
def push_to_existing_branch? def push_to_existing_branch?
......
...@@ -5,7 +5,7 @@ module Issues ...@@ -5,7 +5,7 @@ module Issues
if issue.reopen if issue.reopen
event_service.reopen_issue(issue, current_user) event_service.reopen_issue(issue, current_user)
create_note(issue) create_note(issue, 'reopened')
notification_service.reopen_issue(issue, current_user) notification_service.reopen_issue(issue, current_user)
execute_hooks(issue, 'reopen') execute_hooks(issue, 'reopen')
invalidate_cache_counts(issue, users: issue.assignees) invalidate_cache_counts(issue, users: issue.assignees)
...@@ -16,8 +16,8 @@ module Issues ...@@ -16,8 +16,8 @@ module Issues
private private
def create_note(issue) def create_note(issue, state = issue.state)
SystemNoteService.change_status(issue, issue.project, current_user, issue.state, nil) SystemNoteService.change_status(issue, issue.project, current_user, state, nil)
end end
end end
end end
module MergeRequests module MergeRequests
class BaseService < ::IssuableBaseService class BaseService < ::IssuableBaseService
def create_note(merge_request) def create_note(merge_request, state = merge_request.state)
SystemNoteService.change_status(merge_request, merge_request.target_project, current_user, merge_request.state, nil) SystemNoteService.change_status(merge_request, merge_request.target_project, current_user, state, nil)
end end
def create_title_change_note(issuable, old_title) def create_title_change_note(issuable, old_title)
...@@ -44,7 +44,7 @@ module MergeRequests ...@@ -44,7 +44,7 @@ module MergeRequests
end end
# Returns all origin and fork merge requests from `@project` satisfying passed arguments. # Returns all origin and fork merge requests from `@project` satisfying passed arguments.
def merge_requests_for(source_branch, mr_states: [:opened, :reopened]) def merge_requests_for(source_branch, mr_states: [:opened])
MergeRequest MergeRequest
.with_state(mr_states) .with_state(mr_states)
.where(source_branch: source_branch, source_project_id: @project.id) .where(source_branch: source_branch, source_project_id: @project.id)
......
...@@ -5,7 +5,7 @@ module MergeRequests ...@@ -5,7 +5,7 @@ module MergeRequests
if merge_request.reopen if merge_request.reopen
event_service.reopen_mr(merge_request, current_user) event_service.reopen_mr(merge_request, current_user)
create_note(merge_request) create_note(merge_request, 'reopened')
notification_service.reopen_mr(merge_request, current_user) notification_service.reopen_mr(merge_request, current_user)
execute_hooks(merge_request, 'reopen') execute_hooks(merge_request, 'reopen')
merge_request.reload_diff(current_user) merge_request.reload_diff(current_user)
......
...@@ -4,6 +4,9 @@ module QuickActions ...@@ -4,6 +4,9 @@ module QuickActions
attr_reader :issuable attr_reader :issuable
SHRUG = '¯\\_(ツ)_/¯'.freeze
TABLEFLIP = '(╯°□°)╯︵ ┻━┻'.freeze
# Takes a text and interprets the commands that are extracted from it. # Takes a text and interprets the commands that are extracted from it.
# Returns the content without commands, and hash of changes to be applied to a record. # Returns the content without commands, and hash of changes to be applied to a record.
def execute(content, issuable) def execute(content, issuable)
...@@ -14,6 +17,7 @@ module QuickActions ...@@ -14,6 +17,7 @@ module QuickActions
content, commands = extractor.extract_commands(content, context) content, commands = extractor.extract_commands(content, context)
extract_updates(commands, context) extract_updates(commands, context)
[content, @updates] [content, @updates]
end end
...@@ -423,6 +427,18 @@ module QuickActions ...@@ -423,6 +427,18 @@ module QuickActions
@updates[:spend_time] = { duration: :reset, user: current_user } @updates[:spend_time] = { duration: :reset, user: current_user }
end end
desc "Append the comment with #{SHRUG}"
params '<Comment>'
substitution :shrug do |comment|
"#{comment} #{SHRUG}"
end
desc "Append the comment with #{TABLEFLIP}"
params '<Comment>'
substitution :tableflip do |comment|
"#{comment} #{TABLEFLIP}"
end
# This is a dummy command, so that it appears in the autocomplete commands # This is a dummy command, so that it appears in the autocomplete commands
desc 'CC' desc 'CC'
params '@user' params '@user'
......
...@@ -44,7 +44,7 @@ class WebHookService ...@@ -44,7 +44,7 @@ class WebHookService
http_status: response.code, http_status: response.code,
message: response.to_s message: response.to_s
} }
rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Net::OpenTimeout, Net::ReadTimeout => e
log_execution( log_execution(
trigger: hook_name, trigger: hook_name,
url: hook.url, url: hook.url,
......
.page-with-sidebar{ class: "#{('page-with-new-sidebar' if defined?(@new_sidebar) && @new_sidebar)} #{page_gutter_class}" } .page-with-sidebar{ class: page_with_sidebar_class }
- if show_new_nav? - if show_new_nav?
- if defined?(nav) && nav - if defined?(nav) && nav
= render "layouts/nav/#{nav}" = render "layouts/nav/#{nav}"
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
= render "layouts/nav/#{nav}" = render "layouts/nav/#{nav}"
- if content_for?(:sub_nav) - if content_for?(:sub_nav)
= yield :sub_nav = yield :sub_nav
.content-wrapper{ class: "#{(layout_nav_class unless show_new_nav?)}" } .content-wrapper{ class: layout_nav_class }
- if show_new_nav? - if show_new_nav?
.mobile-overlay .mobile-overlay
.alert-wrapper .alert-wrapper
......
!!! 5 !!! 5
%html{ lang: I18n.locale, class: "#{page_class}" } %html{ lang: I18n.locale, class: page_class }
= render "layouts/head" = render "layouts/head"
%body{ class: @body_class, data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}", find_file: find_file_path } } %body{ class: @body_class, data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}", find_file: find_file_path } }
= render "layouts/init_auto_complete" if @gfm_form = render "layouts/init_auto_complete" if @gfm_form
= render 'peek/bar'
- if show_new_nav? - if show_new_nav?
= render "layouts/header/new" = render "layouts/header/new"
- else - else
...@@ -10,5 +11,3 @@ ...@@ -10,5 +11,3 @@
= render 'layouts/page', sidebar: sidebar, nav: nav = render 'layouts/page', sidebar: sidebar, nav: nav
= yield :scripts_body = yield :scripts_body
= render 'peek/bar'
...@@ -91,8 +91,8 @@ ...@@ -91,8 +91,8 @@
= nav_link(controller: :abuse_reports) do = nav_link(controller: :abuse_reports) do
= link_to admin_abuse_reports_path, title: "Abuse Reports" do = link_to admin_abuse_reports_path, title: "Abuse Reports" do
%span %span
Abuse Reports
%span.badge.count= number_with_delimiter(AbuseReport.count(:all)) %span.badge.count= number_with_delimiter(AbuseReport.count(:all))
Abuse Reports
- if akismet_enabled? - if akismet_enabled?
= nav_link(controller: :spam_logs) do = nav_link(controller: :spam_logs) do
......
...@@ -28,9 +28,9 @@ ...@@ -28,9 +28,9 @@
= nav_link(path: ['groups#issues', 'labels#index', 'milestones#index']) do = nav_link(path: ['groups#issues', 'labels#index', 'milestones#index']) do
= link_to issues_group_path(@group), title: 'Issues' do = link_to issues_group_path(@group), title: 'Issues' do
%span %span
Issues
- issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute - issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute
%span.badge.count= number_with_delimiter(issues.count) %span.badge.count= number_with_delimiter(issues.count)
Issues
%ul.sidebar-sub-level-items %ul.sidebar-sub-level-items
= nav_link(path: 'groups#issues', html_options: { class: 'home' }) do = nav_link(path: 'groups#issues', html_options: { class: 'home' }) do
...@@ -51,9 +51,9 @@ ...@@ -51,9 +51,9 @@
= nav_link(path: 'groups#merge_requests') do = nav_link(path: 'groups#merge_requests') do
= link_to merge_requests_group_path(@group), title: 'Merge Requests' do = link_to merge_requests_group_path(@group), title: 'Merge Requests' do
%span %span
Merge Requests
- merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute - merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute
%span.badge.count= number_with_delimiter(merge_requests.count) %span.badge.count= number_with_delimiter(merge_requests.count)
Merge Requests
= nav_link(path: 'group_members#index') do = nav_link(path: 'group_members#index') do
= link_to group_group_members_path(@group), title: 'Members' do = link_to group_group_members_path(@group), title: 'Members' do
%span %span
......
%span.current-host
= truncate(view.hostname)
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
- page_title 'New Project' - page_title 'New Project'
- header_title "Projects", dashboard_projects_path - header_title "Projects", dashboard_projects_path
- visibility_level = params.dig(:project, :visibility_level) || default_project_visibility - visibility_level = params.dig(:project, :visibility_level) || default_project_visibility
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'project_new'
.project-edit-container .project-edit-container
.project-edit-errors .project-edit-errors
...@@ -111,46 +113,3 @@ ...@@ -111,46 +113,3 @@
%i.fa.fa-spinner.fa-spin %i.fa.fa-spinner.fa-spin
Creating project &amp; repository. Creating project &amp; repository.
%p Please wait a moment, this page will automatically refresh when ready. %p Please wait a moment, this page will automatically refresh when ready.
:javascript
var importBtnTooltip = "Please enter a valid project name.";
var $importBtnWrapper = $('.import_gitlab_project');
$('.how_to_import_link').bind('click', function (e) {
e.preventDefault();
var import_modal = $(this).next(".modal").show();
});
$('.modal-header .close').bind('click', function() {
$(".modal").hide();
});
$('.btn_import_gitlab_project').bind('click', function() {
var _href = $("a.btn_import_gitlab_project").attr("href");
$(".btn_import_gitlab_project").attr("href", _href + '?namespace_id=' + $("#project_namespace_id").val() + '&path=' + $("#project_path").val());
});
$('.btn_import_gitlab_project').attr('disabled', $('#project_path').val().trim().length === 0);
$importBtnWrapper.attr('title', importBtnTooltip);
$('#new_project').submit(function(){
var $path = $('#project_path');
$path.val($path.val().trim());
});
$('#project_path').keyup(function(){
if($(this).val().trim().length !== 0) {
$('.btn_import_gitlab_project').attr('disabled', false);
$importBtnWrapper.attr('title','');
$importBtnWrapper.removeClass('has-tooltip');
} else {
$('.btn_import_gitlab_project').attr('disabled',true);
$importBtnWrapper.addClass('has-tooltip');
}
});
$('#project_import_url').disable();
$('.import_git').click(function( event ) {
$projectImportUrl = $('#project_import_url');
$projectImportUrl.attr('disabled', !$projectImportUrl.attr('disabled'));
});
- content_for :page_specific_javascripts do
= webpack_bundle_tag('pipelines_times')
%div %div
%p.light %p.light
= _("Commit duration in minutes for last 30 commits") = _("Commit duration in minutes for last 30 commits")
%canvas#build_timesChart{ height: 200 } %canvas#build_timesChart{ height: 200 }
:javascript %script#pipelinesTimesChartsData{ type: "application/json" }= { :labels => @charts[:pipeline_times].labels, :values => @charts[:pipeline_times].pipeline_times }.to_json.html_safe
var data = {
labels : #{@charts[:pipeline_times].labels.to_json},
datasets : [
{
fillColor : "rgba(220,220,220,0.5)",
strokeColor : "rgba(220,220,220,1)",
barStrokeWidth: 1,
barValueSpacing: 1,
barDatasetSpacing: 1,
data : #{@charts[:pipeline_times].pipeline_times.to_json}
}
]
}
var ctx = $("#build_timesChart").get(0).getContext("2d");
var options = { scaleOverlay: true, responsive: true, maintainAspectRatio: false };
if (window.innerWidth < 768) {
// Scale fonts if window width lower than 768px (iPad portrait)
options.scaleFontSize = 8
}
new Chart(ctx).Bar(data, options);
- content_for :page_specific_javascripts do
= webpack_bundle_tag('pipelines_charts')
%h4= _("Pipelines charts") %h4= _("Pipelines charts")
%p %p
&nbsp; &nbsp;
...@@ -26,31 +29,8 @@ ...@@ -26,31 +29,8 @@
= _("Jobs for last year") = _("Jobs for last year")
%canvas#yearChart.padded{ height: 250 } %canvas#yearChart.padded{ height: 250 }
- [:week, :month, :year].each do |scope| %script#pipelinesChartsData{ type: "application/json" }
:javascript - chartData = []
var data = { - [:week, :month, :year].each do |scope|
labels : #{@charts[scope].labels.to_json}, - chartData.push({ 'scope' => scope, 'labels' => @charts[scope].labels, 'totalValues' => @charts[scope].total, 'successValues' => @charts[scope].success })
datasets : [ = chartData.to_json.html_safe
{
fillColor : "#7f8fa4",
strokeColor : "#7f8fa4",
pointColor : "#7f8fa4",
pointStrokeColor : "#EEE",
data : #{@charts[scope].total.to_json}
},
{
fillColor : "#44aa22",
strokeColor : "#44aa22",
pointColor : "#44aa22",
pointStrokeColor : "#fff",
data : #{@charts[scope].success.to_json}
}
]
}
var ctx = $("##{scope}Chart").get(0).getContext("2d");
var options = { scaleOverlay: true, responsive: true, maintainAspectRatio: false };
if (window.innerWidth < 768) {
// Scale fonts if window width lower than 768px (iPad portrait)
options.scaleFontSize = 8
}
new Chart(ctx).Line(data, options);
...@@ -20,7 +20,4 @@ ...@@ -20,7 +20,4 @@
= f.submit 'Create pipeline', class: 'btn btn-create', tabindex: 3 = f.submit 'Create pipeline', class: 'btn btn-create', tabindex: 3
= link_to 'Cancel', project_pipelines_path(@project), class: 'btn btn-cancel' = link_to 'Cancel', project_pipelines_path(@project), class: 'btn btn-cancel'
:javascript %script#availableRefs{ type: "application/json" }= @project.repository.ref_names.to_json.html_safe
var availableRefs = #{@project.repository.ref_names.to_json};
new NewBranchForm($('.js-new-pipeline-form'), availableRefs)
...@@ -40,7 +40,4 @@ ...@@ -40,7 +40,4 @@
.form-actions .form-actions
= button_tag 'Create tag', class: 'btn btn-create', tabindex: 3 = button_tag 'Create tag', class: 'btn btn-create', tabindex: 3
= link_to 'Cancel', project_tags_path(@project), class: 'btn btn-cancel' = link_to 'Cancel', project_tags_path(@project), class: 'btn btn-cancel'
%script#availableRefs{ type: "application/json" }= @project.repository.ref_names.to_json.html_safe
:javascript
window.gl = window.gl || { };
window.gl.availableRefs = #{@project.repository.ref_names.to_json};
.tree-content-holder .tree-content-holder.js-tree-content{ 'data-logs-path': @logs_path }
.table-holder .table-holder
%table.table#tree-slider{ class: "table_#{@hex_path} tree-table" } %table.table#tree-slider{ class: "table_#{@hex_path} tree-table" }
%thead %thead
......
...@@ -19,6 +19,3 @@ ...@@ -19,6 +19,3 @@
More Pages More Pages
= render 'projects/wikis/new' = render 'projects/wikis/new'
:javascript
new Sidebar();
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
= hidden_field_tag :line_type = hidden_field_tag :line_type
= hidden_field_tag :merge_request_diff_head_sha, @note.noteable.try(:diff_head_sha) = hidden_field_tag :merge_request_diff_head_sha, @note.noteable.try(:diff_head_sha)
= hidden_field_tag :in_reply_to_discussion_id = hidden_field_tag :in_reply_to_discussion_id
= hidden_field_tag :note_project_id
= note_target_fields(@note) = note_target_fields(@note)
= f.hidden_field :noteable_type = f.hidden_field :noteable_type
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
- if avatar - if avatar
.avatar-container.s40 .avatar-container.s40
= link_to project_path(project), class: dom_class(project) do = link_to project_path(project), class: dom_class(project) do
- if use_creator_avatar - if project.creator && use_creator_avatar
= image_tag avatar_icon(project.creator.email, 40), class: "avatar s40", alt:'' = image_tag avatar_icon(project.creator.email, 40), class: "avatar s40", alt:''
- else - else
= project_icon(project, alt: '', class: 'avatar project-avatar s40') = project_icon(project, alt: '', class: 'avatar project-avatar s40')
......
...@@ -5,6 +5,12 @@ class GitGarbageCollectWorker ...@@ -5,6 +5,12 @@ class GitGarbageCollectWorker
sidekiq_options retry: false sidekiq_options retry: false
GITALY_MIGRATED_TASKS = {
gc: :garbage_collect,
full_repack: :repack_full,
incremental_repack: :repack_incremental
}.freeze
def perform(project_id, task = :gc, lease_key = nil, lease_uuid = nil) def perform(project_id, task = :gc, lease_key = nil, lease_uuid = nil)
project = Project.find(project_id) project = Project.find(project_id)
task = task.to_sym task = task.to_sym
...@@ -15,8 +21,14 @@ class GitGarbageCollectWorker ...@@ -15,8 +21,14 @@ class GitGarbageCollectWorker
Gitlab::GitLogger.info(description) Gitlab::GitLogger.info(description)
output, status = Gitlab::Popen.popen(cmd, repo_path) gitaly_migrate(GITALY_MIGRATED_TASKS[task]) do |is_enabled|
Gitlab::GitLogger.error("#{description} failed:\n#{output}") unless status.zero? if is_enabled
gitaly_call(task, project.repository.raw_repository)
else
output, status = Gitlab::Popen.popen(cmd, repo_path)
Gitlab::GitLogger.error("#{description} failed:\n#{output}") unless status.zero?
end
end
# Refresh the branch cache in case garbage collection caused a ref lookup to fail # Refresh the branch cache in case garbage collection caused a ref lookup to fail
flush_ref_caches(project) if task == :gc flush_ref_caches(project) if task == :gc
...@@ -26,6 +38,19 @@ class GitGarbageCollectWorker ...@@ -26,6 +38,19 @@ class GitGarbageCollectWorker
private private
## `repository` has to be a Gitlab::Git::Repository
def gitaly_call(task, repository)
client = Gitlab::GitalyClient::RepositoryService.new(repository)
case task
when :gc
client.garbage_collect(bitmaps_enabled?)
when :full_repack
client.repack_full(bitmaps_enabled?)
when :incremental_repack
client.repack_incremental
end
end
def command(task) def command(task)
case task case task
when :gc when :gc
...@@ -55,4 +80,14 @@ class GitGarbageCollectWorker ...@@ -55,4 +80,14 @@ class GitGarbageCollectWorker
config_value = write_bitmaps ? 'true' : 'false' config_value = write_bitmaps ? 'true' : 'false'
%W[git -c repack.writeBitmaps=#{config_value}] %W[git -c repack.writeBitmaps=#{config_value}]
end end
def gitaly_migrate(method, &block)
Gitlab::GitalyClient.migrate(method, &block)
rescue GRPC::NotFound => e
Gitlab::GitLogger.error("#{method} failed:\nRepository not found")
raise Gitlab::Git::Repository::NoRepository.new(e)
rescue GRPC::BadStatus => e
Gitlab::GitLogger.error("#{method} failed:\n#{e}")
raise Gitlab::Git::CommandError.new(e)
end
end end
---
title: "Fix v3 api project_hooks POST and PUT operations for build_events"
merge_request: 12673
author: Richard Clamp
---
title: Add /shrug and /tableflip commands
merge_request: 10068
author: Alex Ives
---
title: Fix project logos that are not centered vertically on list pages
merge_request: 13124
author: Florian Lemaitre
---
title: Fix asynchronous javascript paths when GitLab is installed under a relative
URL
merge_request: 13165
author:
---
title: Properly affixes nav bar in job view in microsoft edge
merge_request:
author:
---
title: Fix display of new diff comments after changing b between diff views
merge_request:
author:
---
title: Fixed breadcrumbs title aggressively collapsing
merge_request:
author:
---
title: Improve performance of large (initial) push into default branch
merge_request:
author:
---
title: Fix LDAP authentication to Git repository or container registry
merge_request:
author:
---
title: Modify if condition to be more readable
merge_request:
author:
---
title: Improve deploy environment chatops slash command
merge_request: 13150
author:
---
title: Fix replying to commit comments on merge requests created from forks
merge_request:
author:
---
title: Merge issuable "reopened" state into "opened"
merge_request:
author:
...@@ -223,7 +223,7 @@ Settings.gitlab['default_can_create_group'] = true if Settings.gitlab['default_c ...@@ -223,7 +223,7 @@ Settings.gitlab['default_can_create_group'] = true if Settings.gitlab['default_c
Settings.gitlab['host'] ||= ENV['GITLAB_HOST'] || 'localhost' Settings.gitlab['host'] ||= ENV['GITLAB_HOST'] || 'localhost'
Settings.gitlab['ssh_host'] ||= Settings.gitlab.host Settings.gitlab['ssh_host'] ||= Settings.gitlab.host
Settings.gitlab['https'] = false if Settings.gitlab['https'].nil? Settings.gitlab['https'] = false if Settings.gitlab['https'].nil?
Settings.gitlab['port'] ||= Settings.gitlab.https ? 443 : 80 Settings.gitlab['port'] ||= ENV['GITLAB_PORT'] || (Settings.gitlab.https ? 443 : 80)
Settings.gitlab['relative_url_root'] ||= ENV['RAILS_RELATIVE_URL_ROOT'] || '' Settings.gitlab['relative_url_root'] ||= ENV['RAILS_RELATIVE_URL_ROOT'] || ''
Settings.gitlab['protocol'] ||= Settings.gitlab.https ? "https" : "http" Settings.gitlab['protocol'] ||= Settings.gitlab.https ? "https" : "http"
Settings.gitlab['email_enabled'] ||= true if Settings.gitlab['email_enabled'].nil? Settings.gitlab['email_enabled'] ||= true if Settings.gitlab['email_enabled'].nil?
......
...@@ -55,8 +55,11 @@ var config = { ...@@ -55,8 +55,11 @@ var config = {
notebook_viewer: './blob/notebook_viewer.js', notebook_viewer: './blob/notebook_viewer.js',
pdf_viewer: './blob/pdf_viewer.js', pdf_viewer: './blob/pdf_viewer.js',
pipelines: './pipelines/pipelines_bundle.js', pipelines: './pipelines/pipelines_bundle.js',
pipelines_details: './pipelines/pipeline_details_bundle.js', pipelines_charts: './pipelines/pipelines_charts.js',
pipelines_details: './pipelines/pipeline_details_bundle.js',
pipelines_times: './pipelines/pipelines_times.js',
profile: './profile/profile_bundle.js', profile: './profile/profile_bundle.js',
project_new: './projects/project_new.js',
prometheus_metrics: './prometheus_metrics', prometheus_metrics: './prometheus_metrics',
protected_branches: './protected_branches', protected_branches: './protected_branches',
protected_tags: './protected_tags', protected_tags: './protected_tags',
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class MergeIssuableReopenedIntoOpenedState < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
class Issue < ActiveRecord::Base
self.table_name = 'issues'
include EachBatch
end
class MergeRequest < ActiveRecord::Base
self.table_name = 'merge_requests'
include EachBatch
end
def up
[Issue, MergeRequest].each do |model|
say "Changing #{model.table_name}.state from 'reopened' to 'opened'"
model.where(state: 'reopened').each_batch do |batch|
batch.update_all(state: 'opened')
end
end
end
end
...@@ -254,32 +254,32 @@ ActiveRecord::Schema.define(version: 20170725145659) do ...@@ -254,32 +254,32 @@ ActiveRecord::Schema.define(version: 20170725145659) do
add_index "ci_builds", ["updated_at"], name: "index_ci_builds_on_updated_at", using: :btree add_index "ci_builds", ["updated_at"], name: "index_ci_builds_on_updated_at", using: :btree
add_index "ci_builds", ["user_id"], name: "index_ci_builds_on_user_id", using: :btree add_index "ci_builds", ["user_id"], name: "index_ci_builds_on_user_id", using: :btree
create_table "ci_pipeline_schedule_variables", force: :cascade do |t| create_table "ci_group_variables", force: :cascade do |t|
t.string "key", null: false t.string "key", null: false
t.text "value" t.text "value"
t.text "encrypted_value" t.text "encrypted_value"
t.string "encrypted_value_salt" t.string "encrypted_value_salt"
t.string "encrypted_value_iv" t.string "encrypted_value_iv"
t.integer "pipeline_schedule_id", null: false t.integer "group_id", null: false
t.boolean "protected", default: false, null: false
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
end end
add_index "ci_pipeline_schedule_variables", ["pipeline_schedule_id", "key"], name: "index_ci_pipeline_schedule_variables_on_schedule_id_and_key", unique: true, using: :btree add_index "ci_group_variables", ["group_id", "key"], name: "index_ci_group_variables_on_group_id_and_key", unique: true, using: :btree
create_table "ci_group_variables", force: :cascade do |t| create_table "ci_pipeline_schedule_variables", force: :cascade do |t|
t.string "key", null: false t.string "key", null: false
t.text "value" t.text "value"
t.text "encrypted_value" t.text "encrypted_value"
t.string "encrypted_value_salt" t.string "encrypted_value_salt"
t.string "encrypted_value_iv" t.string "encrypted_value_iv"
t.integer "group_id", null: false t.integer "pipeline_schedule_id", null: false
t.boolean "protected", default: false, null: false t.datetime "created_at"
t.datetime "created_at", null: false t.datetime "updated_at"
t.datetime "updated_at", null: false
end end
add_index "ci_group_variables", ["group_id", "key"], name: "index_ci_group_variables_on_group_id_and_key", unique: true, using: :btree add_index "ci_pipeline_schedule_variables", ["pipeline_schedule_id", "key"], name: "index_ci_pipeline_schedule_variables_on_schedule_id_and_key", unique: true, using: :btree
create_table "ci_pipeline_schedules", force: :cascade do |t| create_table "ci_pipeline_schedules", force: :cascade do |t|
t.string "description" t.string "description"
...@@ -1624,8 +1624,8 @@ ActiveRecord::Schema.define(version: 20170725145659) do ...@@ -1624,8 +1624,8 @@ ActiveRecord::Schema.define(version: 20170725145659) do
add_foreign_key "ci_builds", "ci_pipelines", column: "auto_canceled_by_id", name: "fk_a2141b1522", on_delete: :nullify add_foreign_key "ci_builds", "ci_pipelines", column: "auto_canceled_by_id", name: "fk_a2141b1522", on_delete: :nullify
add_foreign_key "ci_builds", "ci_stages", column: "stage_id", name: "fk_3a9eaa254d", on_delete: :cascade add_foreign_key "ci_builds", "ci_stages", column: "stage_id", name: "fk_3a9eaa254d", on_delete: :cascade
add_foreign_key "ci_builds", "projects", name: "fk_befce0568a", on_delete: :cascade add_foreign_key "ci_builds", "projects", name: "fk_befce0568a", on_delete: :cascade
add_foreign_key "ci_pipeline_schedule_variables", "ci_pipeline_schedules", column: "pipeline_schedule_id", name: "fk_41c35fda51", on_delete: :cascade
add_foreign_key "ci_group_variables", "namespaces", column: "group_id", name: "fk_33ae4d58d8", on_delete: :cascade add_foreign_key "ci_group_variables", "namespaces", column: "group_id", name: "fk_33ae4d58d8", on_delete: :cascade
add_foreign_key "ci_pipeline_schedule_variables", "ci_pipeline_schedules", column: "pipeline_schedule_id", name: "fk_41c35fda51", on_delete: :cascade
add_foreign_key "ci_pipeline_schedules", "projects", name: "fk_8ead60fcc4", on_delete: :cascade add_foreign_key "ci_pipeline_schedules", "projects", name: "fk_8ead60fcc4", on_delete: :cascade
add_foreign_key "ci_pipeline_schedules", "users", column: "owner_id", name: "fk_9ea99f58d2", on_delete: :nullify add_foreign_key "ci_pipeline_schedules", "users", column: "owner_id", name: "fk_9ea99f58d2", on_delete: :nullify
add_foreign_key "ci_pipeline_variables", "ci_pipelines", column: "pipeline_id", name: "fk_f29c5f4380", on_delete: :cascade add_foreign_key "ci_pipeline_variables", "ci_pipelines", column: "pipeline_id", name: "fk_f29c5f4380", on_delete: :cascade
......
...@@ -44,16 +44,17 @@ Shortcuts to GitLab's most visited docs: ...@@ -44,16 +44,17 @@ Shortcuts to GitLab's most visited docs:
### Projects and groups ### Projects and groups
- [Create a project](gitlab-basics/create-project.md) - [Projects](user/project/index.md):
- [Fork a project](gitlab-basics/fork-project.md) - [Create a project](gitlab-basics/create-project.md)
- [Importing and exporting projects between instances](user/project/settings/import_export.md). - [Fork a project](gitlab-basics/fork-project.md)
- [Project access](public_access/public_access.md): Setting up your project's visibility to public, internal, or private. - [Importing and exporting projects between instances](user/project/settings/import_export.md).
- [Project access](public_access/public_access.md): Setting up your project's visibility to public, internal, or private.
- [GitLab Pages](user/project/pages/index.md): Build, test, and deploy your static website with GitLab Pages.
- [Groups](user/group/index.md): Organize your projects in groups. - [Groups](user/group/index.md): Organize your projects in groups.
- [GitLab Subgroups](user/group/subgroups/index.md) - [Subgroups](user/group/subgroups/index.md)
- [Search through GitLab](user/search/index.md): Search for issues, merge requests, projects, groups, todos, and issues in Issue Boards. - [Search through GitLab](user/search/index.md): Search for issues, merge requests, projects, groups, todos, and issues in Issue Boards.
- [Snippets](user/snippets.md): Snippets allow you to create little bits of code. - [Snippets](user/snippets.md): Snippets allow you to create little bits of code.
- [Wikis](user/project/wiki/index.md): Enhance your repository documentation with built-in wikis. - [Wikis](user/project/wiki/index.md): Enhance your repository documentation with built-in wikis.
- [GitLab Pages](user/project/pages/index.md): Build, test, and deploy your static website with GitLab Pages.
### Repository ### Repository
......
...@@ -46,6 +46,10 @@ GitLab does not recommend using EFS with GitLab. ...@@ -46,6 +46,10 @@ GitLab does not recommend using EFS with GitLab.
many small files are written in a serialized manner are not well-suited for EFS. many small files are written in a serialized manner are not well-suited for EFS.
EBS with an NFS server on top will perform much better. EBS with an NFS server on top will perform much better.
In addition, avoid storing GitLab log files (e.g. those in `/var/log/gitlab`)
because this will also affect performance. We recommend that the log files be
stored on a local volume.
For more details on another person's experience with EFS, see For more details on another person's experience with EFS, see
[Amazon's Elastic File System: Burst Credits](https://www.rawkode.io/2017/04/amazons-elastic-file-system-burst-credits/) [Amazon's Elastic File System: Burst Credits](https://www.rawkode.io/2017/04/amazons-elastic-file-system-burst-credits/)
......
...@@ -241,26 +241,26 @@ POST /users ...@@ -241,26 +241,26 @@ POST /users
Parameters: Parameters:
- `email` (required) - Email - `email` (required) - Email
- `password` (optional) - Password - `password` (optional) - Password
- `reset_password` (optional) - Send user password reset link - true or false(default) - `reset_password` (optional) - Send user password reset link - true or false(default)
- `username` (required) - Username - `username` (required) - Username
- `name` (required) - Name - `name` (required) - Name
- `skype` (optional) - Skype ID - `skype` (optional) - Skype ID
- `linkedin` (optional) - LinkedIn - `linkedin` (optional) - LinkedIn
- `twitter` (optional) - Twitter account - `twitter` (optional) - Twitter account
- `website_url` (optional) - Website URL - `website_url` (optional) - Website URL
- `organization` (optional) - Organization name - `organization` (optional) - Organization name
- `projects_limit` (optional) - Number of projects user can create - `projects_limit` (optional) - Number of projects user can create
- `extern_uid` (optional) - External UID - `extern_uid` (optional) - External UID
- `provider` (optional) - External provider name - `provider` (optional) - External provider name
- `bio` (optional) - User's biography - `bio` (optional) - User's biography
- `location` (optional) - User's location - `location` (optional) - User's location
- `admin` (optional) - User is admin - true or false (default) - `admin` (optional) - User is admin - true or false (default)
- `can_create_group` (optional) - User can create groups - true or false - `can_create_group` (optional) - User can create groups - true or false
- `confirm` (optional) - Require confirmation - true (default) or false - `skip_confirmation` (optional) - Skip confirmation - true or false (default)
- `external` (optional) - Flags the user as external - true or false(default) - `external` (optional) - Flags the user as external - true or false(default)
- `avatar` (optional) - Image file for user's avatar - `avatar` (optional) - Image file for user's avatar
## User modification ## User modification
......
...@@ -441,13 +441,25 @@ There are a few rules that apply to the usage of refs policy: ...@@ -441,13 +441,25 @@ There are a few rules that apply to the usage of refs policy:
* `only` and `except` are inclusive. If both `only` and `except` are defined * `only` and `except` are inclusive. If both `only` and `except` are defined
in a job specification, the ref is filtered by `only` and `except`. in a job specification, the ref is filtered by `only` and `except`.
* `only` and `except` allow the use of regular expressions. * `only` and `except` allow the use of regular expressions.
* `only` and `except` allow the use of special keywords:
`api`, `branches`, `external`, `tags`, `pushes`, `schedules`, `triggers`, and `web`
* `only` and `except` allow to specify a repository path to filter jobs for * `only` and `except` allow to specify a repository path to filter jobs for
forks. forks.
In addition, `only` and `except` allow the use of special keywords:
| **Value** | **Description** |
| --------- | ---------------- |
| `branches` | When a branch is pushed. |
| `tags` | When a tag is pushed. |
| `api` | When pipeline has been triggered by a second pipelines API (not triggers API). |
| `external` | When using CI services other than GitLab. |
| `pipelines` | For multi-project triggers, created using the API with `CI_JOB_TOKEN`. |
| `pushes` | Pipeline is triggered by a `git push` by the user. |
| `schedules` | For [scheduled pipelines][schedules]. |
| `triggers` | For pipelines created using a trigger token. |
| `web` | For pipelines created using **Run pipeline** button in GitLab UI (under your project's **Pipelines**). |
In the example below, `job` will run only for refs that start with `issue-`, In the example below, `job` will run only for refs that start with `issue-`,
whereas all branches will be skipped. whereas all branches will be skipped:
```yaml ```yaml
job: job:
...@@ -460,7 +472,7 @@ job: ...@@ -460,7 +472,7 @@ job:
``` ```
In this example, `job` will run only for refs that are tagged, or if a build is In this example, `job` will run only for refs that are tagged, or if a build is
explicitly requested via an API trigger or a [Pipeline Schedule](../../user/project/pipelines/schedules.md). explicitly requested via an API trigger or a [Pipeline Schedule][schedules]:
```yaml ```yaml
job: job:
...@@ -1532,3 +1544,4 @@ CI with various languages. ...@@ -1532,3 +1544,4 @@ CI with various languages.
[ce-7983]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7983 [ce-7983]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7983
[ce-7447]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7447 [ce-7447]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7447
[ce-3442]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3442 [ce-3442]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3442
[schedules]: ../../user/project/pipelines/schedules.md
...@@ -106,6 +106,14 @@ When using verbs or adjectives: ...@@ -106,6 +106,14 @@ When using verbs or adjectives:
* If the context clearly refers to the object, use them alone. Example: `Edit` or `Closed` * If the context clearly refers to the object, use them alone. Example: `Edit` or `Closed`
* If the context isn’t clear enough, use them with the object. Example: `Edit issue` or `Closed issues` * If the context isn’t clear enough, use them with the object. Example: `Edit issue` or `Closed issues`
### Search
| Term | Use |
| ---- | --- |
| Search | When using all metadata to add criteria that match/don't match. Search can also affect ordering, by ranking best results. |
| Filter | When taking a single criteria that removes items within a list that match/don't match. Filters do not affect ordering. |
| Sort | Orders a list based on a single or grouped criteria |
### Projects and Groups ### Projects and Groups
| Term | Use | :no_entry_sign: Don't | | Term | Use | :no_entry_sign: Don't |
......
...@@ -2,7 +2,11 @@ ...@@ -2,7 +2,11 @@
Slash commands in Mattermost and Slack allow you to control GitLab and view GitLab content right inside your chat client, without having to leave it. For Slack, this requires a [project service configuration](../user/project/integrations/slack_slash_commands.md). Simply type the command as a message in your chat client to activate it. Slash commands in Mattermost and Slack allow you to control GitLab and view GitLab content right inside your chat client, without having to leave it. For Slack, this requires a [project service configuration](../user/project/integrations/slack_slash_commands.md). Simply type the command as a message in your chat client to activate it.
Commands are scoped to a project, with a trigger term that is specified during configuration. (We suggest you use the project name as the trigger term for simplicty and clarity.) Taking the trigger term as `project-name`, the commands are: Commands are scoped to a project, with a trigger term that is specified during configuration.
We suggest you use the project name as the trigger term for simplicity and clarity.
Taking the trigger term as `project-name`, the commands are:
| Command | Effect | | Command | Effect |
...@@ -12,3 +16,18 @@ Commands are scoped to a project, with a trigger term that is specified during c ...@@ -12,3 +16,18 @@ Commands are scoped to a project, with a trigger term that is specified during c
| `/project-name issue show <id>` | Shows the issue with id `<id>` | | `/project-name issue show <id>` | Shows the issue with id `<id>` |
| `/project-name issue search <query>` | Shows up to 5 issues matching `<query>` | | `/project-name issue search <query>` | Shows up to 5 issues matching `<query>` |
| `/project-name deploy <from> to <to>` | Deploy from the `<from>` environment to the `<to>` environment | | `/project-name deploy <from> to <to>` | Deploy from the `<from>` environment to the `<to>` environment |
## Issue commands
It is possible to create new issue, display issue details and search up to 5 issues.
## Deploy command
In order to deploy to an environment, GitLab will try to find a deployment
manual action in the pipeline.
If there is only one action for a given environment, it is going to be triggered.
If there is more than one action defined, GitLab will try to find an action
which name equals the environment name we want to deploy to.
Command will return an error when no matching action has been found.
...@@ -122,6 +122,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project ...@@ -122,6 +122,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
1. [Setting up GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/) 1. [Setting up GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/)
1. [IBM: Continuous Delivery vs Continuous Deployment - Video](https://www.youtube.com/watch?v=igwFj8PPSnw) 1. [IBM: Continuous Delivery vs Continuous Deployment - Video](https://www.youtube.com/watch?v=igwFj8PPSnw)
1. [Amazon: Transition to Continuous Delivery - Video](https://www.youtube.com/watch?v=esEFaY0FDKc) 1. [Amazon: Transition to Continuous Delivery - Video](https://www.youtube.com/watch?v=esEFaY0FDKc)
2. [TechBeacon: Doing continuous delivery? Focus first on reducing release cycle times](https://techbeacon.com/doing-continuous-delivery-focus-first-reducing-release-cycle-times)
1. See **[Integrations](#integrations)** for integrations with other CI services. 1. See **[Integrations](#integrations)** for integrations with other CI services.
#### 2.4. Workflow #### 2.4. Workflow
......
...@@ -66,7 +66,7 @@ For more use cases please check our [Technical Articles](../articles/index.md). ...@@ -66,7 +66,7 @@ For more use cases please check our [Technical Articles](../articles/index.md).
## Projects ## Projects
In GitLab, you can create projects for numerous reasons, such as, host In GitLab, you can create [projects](project/index.md) for numerous reasons, such as, host
your code, use it as an issue tracker, collaborate on code, and continuously your code, use it as an issue tracker, collaborate on code, and continuously
build, test, and deploy your app with built-in GitLab CI/CD. Or, you can do build, test, and deploy your app with built-in GitLab CI/CD. Or, you can do
it all at once, from one single project. it all at once, from one single project.
......
...@@ -23,7 +23,7 @@ On your profile page, you will see the following information: ...@@ -23,7 +23,7 @@ On your profile page, you will see the following information:
- Personal information - Personal information
- Activity stream: see your activity streamline and the history of your contributions - Activity stream: see your activity streamline and the history of your contributions
- Groups: [groups](../group/index.md) you're a member of - Groups: [groups](../group/index.md) you're a member of
- Contributed projects: projects you contributed to - Contributed projects: [projects](../project/index.md) you contributed to
- Personal projects: your personal projects (respecting the project's visibility level) - Personal projects: your personal projects (respecting the project's visibility level)
- Snippets: your personal code [snippets](../snippets.md#personal-snippets) - Snippets: your personal code [snippets](../snippets.md#personal-snippets)
......
# Projects
In GitLab, you can create projects for hosting
your codebase, use it as an issue tracker, collaborate on code, and continuously
build, test, and deploy your app with built-in GitLab CI/CD.
Your projects can be [available](../../public_access/public_access.md)
publicly, internally, or privately, at your choice. GitLab does not limit
the number of private projects you create.
## Project's features
When you create a project in GitLab, you'll have access to a large number of
[features](https://about.gitlab.com/features/):
**Issues and merge requests:**
- [Issue tracker](issues/index.md): Discuss implementations with your team within issues
- [Issue Boards](issue_board.md): Organize and prioritize your workflow
- [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards) (**EES/EEP**): Allow your teams to create their own workflows (Issue Boards) for the same project
- [Repositories](repository/index.md): Host your code in a fully
integrated platform
- [Protected branches](protected_branches.md): Prevent collaborators
from messing with history or pushing code without review
- [Protected tags](protected_tags.md): Control over who has
permission to create tags, and prevent accidental update or deletion
- [Merge Requests](merge_requests/index.md): Apply your branching
strategy and get reviewed by your team
- [Merge Request Approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) (**EES/EEP**): Ask for approval before
implementing a change
- [Fix merge conflicts from the UI](merge_requests/resolve_conflicts.md):
Your Git diff tool right from GitLab's UI
- [Review Apps](../../ci/review_apps/index.md): Live preview the results
of the changes proposed in a merge request in a per-branch basis
- [Labels](labels.md): Organize issues and merge requests by labels
- [Time Tracking](../../workflow/time_tracking.md): Track estimate time
and time spent on
the conclusion of an issue or merge request
- [Milestones](milestones/index.md): Work towards a target date
- [Description templates](description_templates.md): Define context-specific
templates for issue and merge request description fields for your project
- [Slash commands (quick actions)](quick_actions.md): Textual shortcuts for
common actions on issues or merge requests
**GitLab CI/CD:**
- [GitLab CI/CD](../../ci/README.md): GitLab's built-in [Continuous Integration, Delivery, and Deployment](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/) tool
- [Container Registry](container_registry.md): Build and push Docker
images out-of-the-box
- [Auto Deploy](../../ci/autodeploy/index.md): Configure GitLab CI/CD
to automatically set up your app's deployment
- [Enable and disable GitLab CI](../../ci/enable_or_disable_ci.md)
- [Pipelines](../../ci/pipelines.md#pipelines): Configure and visualize
your GitLab CI/CD pipelines from the UI
- [Scheduled Pipelines](pipelines/schedules.md): Schedule a pipeline
to start at a chosen time
- [Pipeline Graphs](../../ci/pipelines.md#pipeline-graphs): View your
entire pipeline from the UI
- [Job artifacts](pipelines/job_artifacts.md): Define,
browse, and download job artifacts
- [Pipeline settings](pipelines/settings.md): Set up Git strategy (choose the default way your repository is fetched from GitLab in a job),
timeout (defines the maximum amount of time in minutes that a job is able run), custom path for `.gitlab-ci.yml`, test coverage parsing, pipeline's visibility, and much more
- [GitLab Pages](pages/index.md): Build, test, and deploy your static
website with GitLab Pages
**Other features:**
- [Cycle Analytics](cycle_analytics.md): Review your development lifecycle
- [Koding integration](koding.md) (not available on GitLab.com): Integrate
with Koding to have access to a web terminal right from the GitLab UI
- [Syntax highlighting](highlighting.md): An alternative to customize
your code blocks, overriding GitLab's default choice of language
### Project's integrations
[Integrate your project](integrations/index.md) with Jira, Mattermost,
Kubernetes, Slack, and a lot more.
## New project
Learn how to [create a new project](../../gitlab-basics/create-project.md) in GitLab.
### Fork a project
You can [fork a project](../../gitlab-basics/fork-project.md) in order to:
- Collaborate on code by forking a project and creating a merge request
from your fork to the upstream project
- Fork a sample project to work on the top of that
## Import or export a project
- Import a project from:
- [GitHub to GitLab](../../workflow/importing/import_projects_from_github.md)
- [BitBucket to GitLab](../../workflow/importing/import_projects_from_bitbucket.md)
- [Gitea to GitLab](../../workflow/importing/import_projects_from_gitea.md)
- [FogBugz to GitLab](../../workflow/importing/import_projects_from_fogbugz.md)
- [Export a project from GitLab](settings/import_export.md#exporting-a-project-and-its-data)
- [Importing and exporting projects between GitLab instances](settings/import_export.md)
## Leave a project
**Leave project** will only display on the project's dashboard
when a project is part of a group (under a
[group namespace](../group/index.md#namespaces)).
If you choose to leave a project you will no longer be a project
member, therefore, unable to contribute.
# GitLab JIRA integration # GitLab JIRA integration
GitLab can be configured to interact with JIRA. Configuration happens via GitLab can be configured to interact with [JIRA], a project management platform.
user name and password. Connecting to a JIRA server via CAS is not possible.
Each project can be configured to connect to a different JIRA instance, see the Once your GitLab project is connected to JIRA, you can reference and close the
[configuration](#configuration) section. If you have one JIRA instance you can issues in JIRA directly from GitLab.
pre-fill the settings page with a default template. To configure the template
see the [Services Templates][services-templates] document.
Once the project is connected to JIRA, you can reference and close the issues For a use case, check out this article of [How and why to integrate GitLab with
in JIRA directly from GitLab. JIRA](https://www.programmableweb.com/news/how-and-why-to-integrate-gitlab-jira/how-to/2017/04/25).
## Configuration ## Configuration
Each GitLab project can be configured to connect to a different JIRA instance.
If you have one JIRA instance you can pre-fill the settings page with a default
template, see the [Services Templates][services-templates] docs.
Configuration happens via user name and password. Connecting to a JIRA server
via CAS is not possible.
In order to enable the JIRA service in GitLab, you need to first configure the In order to enable the JIRA service in GitLab, you need to first configure the
project in JIRA and then enter the correct values in GitLab. project in JIRA and then enter the correct values in GitLab.
...@@ -213,3 +217,4 @@ your project needs to close a ticket. ...@@ -213,3 +217,4 @@ your project needs to close a ticket.
[services-templates]: services_templates.md [services-templates]: services_templates.md
[jira-repo-old-docs]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-13-stable/doc/project_services/jira.md [jira-repo-old-docs]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-13-stable/doc/project_services/jira.md
[jira]: https://www.atlassian.com/software/jira
# Monitoring HA Proxy # Monitoring HAProxy
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12621) in GitLab 9.4 > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12621) in GitLab 9.4
GitLab has support for automatically detecting and monitoring HA Proxy. This is provided by leveraging the [HA Proxy Exporter](https://github.com/hnlq715/nginx-vts-exporter), which translates HA Proxy statistics into a Prometheus readable form. GitLab has support for automatically detecting and monitoring HAProxy. This is provided by leveraging the [HAProxy Exporter](https://github.com/prometheus/haproxy_exporter), which translates HAProxy statistics into a Prometheus readable form.
## Metrics supported ## Metrics supported
...@@ -10,9 +10,9 @@ GitLab has support for automatically detecting and monitoring HA Proxy. This is ...@@ -10,9 +10,9 @@ GitLab has support for automatically detecting and monitoring HA Proxy. This is
| Throughput (req/sec) | sum(rate(haproxy_frontend_http_requests_total{%{environment_filter}}[2m])) | | Throughput (req/sec) | sum(rate(haproxy_frontend_http_requests_total{%{environment_filter}}[2m])) |
| HTTP Error Rate (%) | sum(rate(haproxy_frontend_http_requests_total{code="5xx",%{environment_filter}}[2m])) / sum(rate(haproxy_frontend_http_requests_total{%{environment_filter}}[2m])) | | HTTP Error Rate (%) | sum(rate(haproxy_frontend_http_requests_total{code="5xx",%{environment_filter}}[2m])) / sum(rate(haproxy_frontend_http_requests_total{%{environment_filter}}[2m])) |
## Configuring Prometheus to monitor for HA Proxy metrics ## Configuring Prometheus to monitor for HAProxy metrics
To get started with NGINX monitoring, you should install and configure the [HA Proxy exporter](https://github.com/prometheus/haproxy_exporter) which parses these statistics and translates them into a Prometheus monitoring endpoint. To get started with NGINX monitoring, you should install and configure the [HAProxy exporter](https://github.com/prometheus/haproxy_exporter) which parses these statistics and translates them into a Prometheus monitoring endpoint.
## Specifying the Environment label ## Specifying the Environment label
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
GitLab offers automatic detection of select [Prometheus exporters](https://prometheus.io/docs/instrumenting/exporters/). Currently supported exporters are: GitLab offers automatic detection of select [Prometheus exporters](https://prometheus.io/docs/instrumenting/exporters/). Currently supported exporters are:
* [Kubernetes](kubernetes.md) * [Kubernetes](kubernetes.md)
* [NGINX](nginx.md) * [NGINX](nginx.md)
* [HA Proxy](haproxy.md) * [HAProxy](haproxy.md)
* [Amazon Cloud Watch](cloudwatch.md) * [Amazon Cloud Watch](cloudwatch.md)
We have tried to surface the most important metrics for each exporter, and will be continuing to add support for additional exporters in future releases. If you would like to add support for other official exporters, [contributions](#adding-to-the-library) are welcome. We have tried to surface the most important metrics for each exporter, and will be continuing to add support for additional exporters in future releases. If you would like to add support for other official exporters, [contributions](#adding-to-the-library) are welcome.
......
# Koding & GitLab # Koding integration
> [Introduced][ce-5909] in GitLab 8.11. > [Introduced][ce-5909] in GitLab 8.11.
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
A [repository](https://git-scm.com/book/en/v2/Git-Basics-Getting-a-Git-Repository) A [repository](https://git-scm.com/book/en/v2/Git-Basics-Getting-a-Git-Repository)
is what you use to store your codebase in GitLab and change it with version control. is what you use to store your codebase in GitLab and change it with version control.
A repository is part of a project, which has a lot of other features. A repository is part of a [project](../index.md), which has a lot of other features.
## Create a repository ## Create a repository
......
...@@ -91,7 +91,6 @@ This workflow where commits only flow downstream ensures that everything has bee ...@@ -91,7 +91,6 @@ This workflow where commits only flow downstream ensures that everything has bee
If you need to cherry-pick a commit with a hotfix it is common to develop it on a feature branch and merge it into master with a merge request, do not delete the feature branch. If you need to cherry-pick a commit with a hotfix it is common to develop it on a feature branch and merge it into master with a merge request, do not delete the feature branch.
If master is good to go (it should be if you are practicing [continuous delivery](http://martinfowler.com/bliki/ContinuousDelivery.html)) you then merge it to the other branches. If master is good to go (it should be if you are practicing [continuous delivery](http://martinfowler.com/bliki/ContinuousDelivery.html)) you then merge it to the other branches.
If this is not possible because more manual testing is required you can send merge requests from the feature branch to the downstream branches. If this is not possible because more manual testing is required you can send merge requests from the feature branch to the downstream branches.
An 'extreme' version of environment branches are setting up an environment for each feature branch as done by [Teatro](https://teatro.io/).
## Release branches with GitLab flow ## Release branches with GitLab flow
......
...@@ -12,7 +12,7 @@ module API ...@@ -12,7 +12,7 @@ module API
end end
def expose_url(path) def expose_url(path)
url_options = Rails.application.routes.default_url_options url_options = Gitlab::Application.routes.default_url_options
protocol, host, port = url_options.slice(:protocol, :host, :port).values protocol, host, port = url_options.slice(:protocol, :host, :port).values
URI::HTTP.build(scheme: protocol, host: host, port: port, path: path).to_s URI::HTTP.build(scheme: protocol, host: host, port: port, path: path).to_s
......
...@@ -56,7 +56,9 @@ module API ...@@ -56,7 +56,9 @@ module API
use :project_hook_properties use :project_hook_properties
end end
post ":id/hooks" do post ":id/hooks" do
hook = user_project.hooks.new(declared_params(include_missing: false)) attrs = declared_params(include_missing: false)
attrs[:job_events] = attrs.delete(:build_events) if attrs.key?(:build_events)
hook = user_project.hooks.new(attrs)
if hook.save if hook.save
present hook, with: ::API::V3::Entities::ProjectHook present hook, with: ::API::V3::Entities::ProjectHook
...@@ -77,7 +79,9 @@ module API ...@@ -77,7 +79,9 @@ module API
put ":id/hooks/:hook_id" do put ":id/hooks/:hook_id" do
hook = user_project.hooks.find(params.delete(:hook_id)) hook = user_project.hooks.find(params.delete(:hook_id))
if hook.update_attributes(declared_params(include_missing: false)) attrs = declared_params(include_missing: false)
attrs[:job_events] = attrs.delete(:build_events) if attrs.key?(:build_events)
if hook.update_attributes(attrs)
present hook, with: ::API::V3::Entities::ProjectHook present hook, with: ::API::V3::Entities::ProjectHook
else else
error!("Invalid url given", 422) if hook.errors[:url].present? error!("Invalid url given", 422) if hook.errors[:url].present?
......
...@@ -24,11 +24,11 @@ module Gitlab ...@@ -24,11 +24,11 @@ module Gitlab
# total_commits_count: Fixnum # total_commits_count: Fixnum
# } # }
# #
def build(project, user, oldrev, newrev, ref, commits = [], message = nil) def build(project, user, oldrev, newrev, ref, commits = [], message = nil, commits_count: nil)
commits = Array(commits) commits = Array(commits)
# Total commits count # Total commits count
commits_count = commits.size commits_count ||= commits.size
# Get latest 20 commits ASC # Get latest 20 commits ASC
commits_limited = commits.last(20) commits_limited = commits.last(20)
......
...@@ -4,12 +4,28 @@ module Gitlab ...@@ -4,12 +4,28 @@ module Gitlab
def initialize(repository) def initialize(repository)
@repository = repository @repository = repository
@gitaly_repo = repository.gitaly_repository @gitaly_repo = repository.gitaly_repository
@storage = repository.storage
end end
def exists? def exists?
request = Gitaly::RepositoryExistsRequest.new(repository: @gitaly_repo) request = Gitaly::RepositoryExistsRequest.new(repository: @gitaly_repo)
GitalyClient.call(@repository.storage, :repository_service, :exists, request).exists GitalyClient.call(@storage, :repository_service, :exists, request).exists
end
def garbage_collect(create_bitmap)
request = Gitaly::GarbageCollectRequest.new(repository: @gitaly_repo, create_bitmap: create_bitmap)
GitalyClient.call(@storage, :repository_service, :garbage_collect, request)
end
def repack_full(create_bitmap)
request = Gitaly::RepackFullRequest.new(repository: @gitaly_repo, create_bitmap: create_bitmap)
GitalyClient.call(@storage, :repository_service, :repack_full, request)
end
def repack_incremental
request = Gitaly::RepackIncrementalRequest.new(repository: @gitaly_repo)
GitalyClient.call(@storage, :repository_service, :repack_incremental, request)
end end
end end
end end
......
...@@ -42,7 +42,7 @@ module Gitlab ...@@ -42,7 +42,7 @@ module Gitlab
end end
def adapter def adapter
OmniAuth::LDAP::Adaptor.new(config.options.symbolize_keys) OmniAuth::LDAP::Adaptor.new(config.omniauth_options)
end end
def config def config
......
...@@ -105,9 +105,32 @@ module Gitlab ...@@ -105,9 +105,32 @@ module Gitlab
# # Awesome code block # # Awesome code block
# end # end
def command(*command_names, &block) def command(*command_names, &block)
define_command(CommandDefinition, *command_names, &block)
end
# Registers a new substitution which is recognizable from body of email or
# comment.
# It accepts aliases and takes a block with the formatted content.
#
# Example:
#
# command :my_substitution, :alias_for_my_substitution do |text|
# "#{text} MY AWESOME SUBSTITUTION"
# end
def substitution(*substitution_names, &block)
define_command(SubstitutionDefinition, *substitution_names, &block)
end
def definition_by_name(name)
command_definitions_by_name[name.to_sym]
end
private
def define_command(klass, *command_names, &block)
name, *aliases = command_names name, *aliases = command_names
definition = CommandDefinition.new( definition = klass.new(
name, name,
aliases: aliases, aliases: aliases,
description: @description, description: @description,
...@@ -130,10 +153,6 @@ module Gitlab ...@@ -130,10 +153,6 @@ module Gitlab
@condition_block = nil @condition_block = nil
@parse_params_block = nil @parse_params_block = nil
end end
def definition_by_name(name)
command_definitions_by_name[name.to_sym]
end
end end
end end
end end
......
...@@ -46,6 +46,8 @@ module Gitlab ...@@ -46,6 +46,8 @@ module Gitlab
end end
end end
content, commands = perform_substitutions(content, commands)
[content.strip, commands] [content.strip, commands]
end end
...@@ -110,6 +112,26 @@ module Gitlab ...@@ -110,6 +112,26 @@ module Gitlab
}mx }mx
end end
def perform_substitutions(content, commands)
return unless content
substitution_definitions = self.command_definitions.select do |definition|
definition.is_a?(Gitlab::QuickActions::SubstitutionDefinition)
end
substitution_definitions.each do |substitution|
match_data = substitution.match(content)
if match_data
command = [substitution.name.to_s]
command << match_data[1] unless match_data[1].empty?
commands << command
end
content = substitution.perform_substitution(self, content)
end
[content, commands]
end
def command_names(opts) def command_names(opts)
command_definitions.flat_map do |command| command_definitions.flat_map do |command|
next if command.noop? next if command.noop?
......
module Gitlab
module QuickActions
class SubstitutionDefinition < CommandDefinition
# noop?=>true means these won't get extracted or removed by Gitlab::QuickActions::Extractor#extract_commands
# QuickActions::InterpretService#perform_substitutions handles them separately
def noop?
true
end
def match(content)
content.match %r{^/#{all_names.join('|')} ?(.*)$}
end
def perform_substitution(context, content)
return unless content
all_names.each do |a_name|
content.gsub!(%r{/#{a_name} ?(.*)$}, execute_block(action_block, context, '\1'))
end
content
end
end
end
end
...@@ -7,6 +7,14 @@ module Gitlab ...@@ -7,6 +7,14 @@ module Gitlab
class Controller < ActionController::Base class Controller < ActionController::Base
protect_from_forgery with: :exception protect_from_forgery with: :exception
rescue_from ActionController::InvalidAuthenticityToken do |e|
logger.warn "This CSRF token verification failure is handled internally by `GitLab::RequestForgeryProtection`"
logger.warn "Unlike the logs may suggest, this does not result in an actual 422 response to the user"
logger.warn "For API requests, the only effect is that `current_user` will be `nil` for the duration of the request"
raise e
end
def index def index
head :ok head :ok
end end
......
...@@ -21,29 +21,34 @@ module Gitlab ...@@ -21,29 +21,34 @@ module Gitlab
from = match[:from] from = match[:from]
to = match[:to] to = match[:to]
actions = find_actions(from, to) action = find_action(from, to)
if actions.none? if action.nil?
Gitlab::SlashCommands::Presenters::Deploy.new(nil).no_actions Gitlab::SlashCommands::Presenters::Deploy
elsif actions.one? .new(action).action_not_found
action = play!(from, to, actions.first)
Gitlab::SlashCommands::Presenters::Deploy.new(action).present(from, to)
else else
Gitlab::SlashCommands::Presenters::Deploy.new(actions).too_many_actions deployment = action.play(current_user)
Gitlab::SlashCommands::Presenters::Deploy
.new(deployment).present(from, to)
end end
end end
private private
def play!(from, to, action) def find_action(from, to)
action.play(current_user)
end
def find_actions(from, to)
environment = project.environments.find_by(name: from) environment = project.environments.find_by(name: from)
return [] unless environment return unless environment
environment.actions_for(to).select(&:starts_environment?) actions = environment.actions_for(to).select do |action|
action.starts_environment?
end
if actions.many?
actions.find { |action| action.name == to.to_s }
else
actions.first
end
end end
end end
end end
......
...@@ -3,17 +3,14 @@ module Gitlab ...@@ -3,17 +3,14 @@ module Gitlab
module Presenters module Presenters
class Deploy < Presenters::Base class Deploy < Presenters::Base
def present(from, to) def present(from, to)
message = "Deployment started from #{from} to #{to}. [Follow its progress](#{resource_url})." message = "Deployment started from #{from} to #{to}. " \
"[Follow its progress](#{resource_url})."
in_channel_response(text: message) in_channel_response(text: message)
end end
def no_actions def action_not_found
ephemeral_response(text: "No action found to be executed") ephemeral_response(text: "Couldn't find a deployment manual action.")
end
def too_many_actions
ephemeral_response(text: "Too many actions defined")
end end
end end
end end
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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