Merge branch 'master' into diff-line-comment-vuejs
This commit is contained in:
commit
b5d0346a99
52 changed files with 846 additions and 178 deletions
|
@ -5,6 +5,7 @@ v 8.11.0 (unreleased)
|
||||||
- Remove the http_parser.rb dependency by removing the tinder gem. !5758 (tbalthazar)
|
- Remove the http_parser.rb dependency by removing the tinder gem. !5758 (tbalthazar)
|
||||||
- Ability to specify branches for Pivotal Tracker integration (Egor Lynko)
|
- Ability to specify branches for Pivotal Tracker integration (Egor Lynko)
|
||||||
- Fix don't pass a local variable called `i` to a partial. !20510 (herminiotorres)
|
- Fix don't pass a local variable called `i` to a partial. !20510 (herminiotorres)
|
||||||
|
- Add delimiter to project stars and forks count (ClemMakesApps)
|
||||||
- Fix rename `add_users_into_project` and `projects_ids`. !20512 (herminiotorres)
|
- Fix rename `add_users_into_project` and `projects_ids`. !20512 (herminiotorres)
|
||||||
- Fix the title of the toggle dropdown button. !5515 (herminiotorres)
|
- Fix the title of the toggle dropdown button. !5515 (herminiotorres)
|
||||||
- Rename `markdown_preview` routes to `preview_markdown`. (Christopher Bartz)
|
- Rename `markdown_preview` routes to `preview_markdown`. (Christopher Bartz)
|
||||||
|
@ -34,6 +35,7 @@ v 8.11.0 (unreleased)
|
||||||
- Fix awardable button mutuality loading spinners (ClemMakesApps)
|
- Fix awardable button mutuality loading spinners (ClemMakesApps)
|
||||||
- Add support for using RequestStore within Sidekiq tasks via SIDEKIQ_REQUEST_STORE env variable
|
- Add support for using RequestStore within Sidekiq tasks via SIDEKIQ_REQUEST_STORE env variable
|
||||||
- Optimize maximum user access level lookup in loading of notes
|
- Optimize maximum user access level lookup in loading of notes
|
||||||
|
- Send notification emails to users newly mentioned in issue and MR edits !5800
|
||||||
- Add "No one can push" as an option for protected branches. !5081
|
- Add "No one can push" as an option for protected branches. !5081
|
||||||
- Improve performance of AutolinkFilter#text_parse by using XPath
|
- Improve performance of AutolinkFilter#text_parse by using XPath
|
||||||
- Add experimental Redis Sentinel support !1877
|
- Add experimental Redis Sentinel support !1877
|
||||||
|
@ -45,6 +47,7 @@ v 8.11.0 (unreleased)
|
||||||
- Remove unused images (ClemMakesApps)
|
- Remove unused images (ClemMakesApps)
|
||||||
- Get issue and merge request description templates from repositories
|
- Get issue and merge request description templates from repositories
|
||||||
- Add hover state to todos !5361 (winniehell)
|
- Add hover state to todos !5361 (winniehell)
|
||||||
|
- Fix icon alignment of star and fork buttons !5451 (winniehell)
|
||||||
- Limit git rev-list output count to one in forced push check
|
- Limit git rev-list output count to one in forced push check
|
||||||
- Show deployment status on merge requests with external URLs
|
- Show deployment status on merge requests with external URLs
|
||||||
- Clean up unused routes (Josef Strzibny)
|
- Clean up unused routes (Josef Strzibny)
|
||||||
|
@ -100,9 +103,11 @@ v 8.11.0 (unreleased)
|
||||||
- Speedup DiffNote#active? on discussions, preloading noteables and avoid touching git repository to return diff_refs when possible
|
- Speedup DiffNote#active? on discussions, preloading noteables and avoid touching git repository to return diff_refs when possible
|
||||||
- Add commit stats in commit api. !5517 (dixpac)
|
- Add commit stats in commit api. !5517 (dixpac)
|
||||||
- Add CI configuration button on project page
|
- Add CI configuration button on project page
|
||||||
|
- Fix merge request new view not changing code view rendering style
|
||||||
- Make error pages responsive (Takuya Noguchi)
|
- Make error pages responsive (Takuya Noguchi)
|
||||||
- The performance of the project dropdown used for moving issues has been improved
|
- The performance of the project dropdown used for moving issues has been improved
|
||||||
- Fix skip_repo parameter being ignored when destroying a namespace
|
- Fix skip_repo parameter being ignored when destroying a namespace
|
||||||
|
- Add all builds into stage/job dropdowns on builds page
|
||||||
- Change requests_profiles resource constraint to catch virtually any file
|
- Change requests_profiles resource constraint to catch virtually any file
|
||||||
- Bump gitlab_git to lazy load compare commits
|
- Bump gitlab_git to lazy load compare commits
|
||||||
- Reduce number of queries made for merge_requests/:id/diffs
|
- Reduce number of queries made for merge_requests/:id/diffs
|
||||||
|
@ -115,6 +120,7 @@ v 8.11.0 (unreleased)
|
||||||
- Speed up and reduce memory usage of Commit#repo_changes, Repository#expire_avatar_cache and IrkerWorker
|
- Speed up and reduce memory usage of Commit#repo_changes, Repository#expire_avatar_cache and IrkerWorker
|
||||||
- Add unfold links for Side-by-Side view. !5415 (Tim Masliuchenko)
|
- Add unfold links for Side-by-Side view. !5415 (Tim Masliuchenko)
|
||||||
- Adds support for pending invitation project members importing projects
|
- Adds support for pending invitation project members importing projects
|
||||||
|
- Add pipeline visualization/graph on pipeline page
|
||||||
- Update devise initializer to turn on changed password notification emails. !5648 (tombell)
|
- Update devise initializer to turn on changed password notification emails. !5648 (tombell)
|
||||||
- Avoid to show the original password field when password is automatically set. !5712 (duduribeiro)
|
- Avoid to show the original password field when password is automatically set. !5712 (duduribeiro)
|
||||||
- Fix importing GitLab projects with an invalid MR source project
|
- Fix importing GitLab projects with an invalid MR source project
|
||||||
|
|
|
@ -6,19 +6,26 @@
|
||||||
|
|
||||||
Build.state = null;
|
Build.state = null;
|
||||||
|
|
||||||
function Build(page_url, build_url, build_status, state1) {
|
function Build(options) {
|
||||||
this.page_url = page_url;
|
this.page_url = options.page_url;
|
||||||
this.build_url = build_url;
|
this.build_url = options.build_url;
|
||||||
this.build_status = build_status;
|
this.build_status = options.build_status;
|
||||||
this.state = state1;
|
this.state = options.state1;
|
||||||
|
this.build_stage = options.build_stage;
|
||||||
this.hideSidebar = bind(this.hideSidebar, this);
|
this.hideSidebar = bind(this.hideSidebar, this);
|
||||||
this.toggleSidebar = bind(this.toggleSidebar, this);
|
this.toggleSidebar = bind(this.toggleSidebar, this);
|
||||||
|
this.updateDropdown = bind(this.updateDropdown, this);
|
||||||
clearInterval(Build.interval);
|
clearInterval(Build.interval);
|
||||||
this.bp = Breakpoints.get();
|
this.bp = Breakpoints.get();
|
||||||
this.hideSidebar();
|
|
||||||
$('.js-build-sidebar').niceScroll();
|
$('.js-build-sidebar').niceScroll();
|
||||||
|
|
||||||
|
this.populateJobs(this.build_stage);
|
||||||
|
this.updateStageDropdownText(this.build_stage);
|
||||||
|
this.hideSidebar();
|
||||||
|
|
||||||
$(document).off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar);
|
$(document).off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar);
|
||||||
$(window).off('resize.build').on('resize.build', this.hideSidebar);
|
$(window).off('resize.build').on('resize.build', this.hideSidebar);
|
||||||
|
$(document).off('click', '.stage-item').on('click', '.stage-item', this.updateDropdown);
|
||||||
this.updateArtifactRemoveDate();
|
this.updateArtifactRemoveDate();
|
||||||
if ($('#build-trace').length) {
|
if ($('#build-trace').length) {
|
||||||
this.getInitialBuildTrace();
|
this.getInitialBuildTrace();
|
||||||
|
@ -132,6 +139,22 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Build.prototype.populateJobs = function(stage) {
|
||||||
|
$('.build-job').hide();
|
||||||
|
$('.build-job[data-stage="' + stage + '"]').show();
|
||||||
|
};
|
||||||
|
|
||||||
|
Build.prototype.updateStageDropdownText = function(stage) {
|
||||||
|
$('.stage-selection').text(stage);
|
||||||
|
};
|
||||||
|
|
||||||
|
Build.prototype.updateDropdown = function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var stage = e.currentTarget.text;
|
||||||
|
this.updateStageDropdownText(stage);
|
||||||
|
this.populateJobs(stage);
|
||||||
|
};
|
||||||
|
|
||||||
return Build;
|
return Build;
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
function MergeRequestTabs(opts) {
|
function MergeRequestTabs(opts) {
|
||||||
this.opts = opts != null ? opts : {};
|
this.opts = opts != null ? opts : {};
|
||||||
|
this.opts.setUrl = this.opts.setUrl !== undefined ? this.opts.setUrl : true;
|
||||||
this.setCurrentAction = bind(this.setCurrentAction, this);
|
this.setCurrentAction = bind(this.setCurrentAction, this);
|
||||||
this.tabShown = bind(this.tabShown, this);
|
this.tabShown = bind(this.tabShown, this);
|
||||||
this.showTab = bind(this.showTab, this);
|
this.showTab = bind(this.showTab, this);
|
||||||
|
@ -58,7 +59,9 @@
|
||||||
} else {
|
} else {
|
||||||
this.expandView();
|
this.expandView();
|
||||||
}
|
}
|
||||||
return this.setCurrentAction(action);
|
if (this.opts.setUrl) {
|
||||||
|
this.setCurrentAction(action);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
MergeRequestTabs.prototype.scrollToElement = function(container) {
|
MergeRequestTabs.prototype.scrollToElement = function(container) {
|
||||||
|
|
15
app/assets/javascripts/pipeline.js.es6
Normal file
15
app/assets/javascripts/pipeline.js.es6
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
(function() {
|
||||||
|
function toggleGraph() {
|
||||||
|
const $pipelineBtn = $(this).closest('.toggle-pipeline-btn');
|
||||||
|
const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph');
|
||||||
|
const $btnText = $(this).find('.toggle-btn-text');
|
||||||
|
|
||||||
|
$($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed');
|
||||||
|
|
||||||
|
const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed');
|
||||||
|
|
||||||
|
graphCollapsed ? $btnText.text('Expand') : $btnText.text('Hide')
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).on('click', '.toggle-pipeline-btn', toggleGraph);
|
||||||
|
})();
|
|
@ -204,6 +204,10 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 2px;
|
top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
svg, .fa {
|
||||||
|
margin-right: 3px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-lg {
|
.btn-lg {
|
||||||
|
|
|
@ -222,3 +222,7 @@ header.header-pinned-nav {
|
||||||
padding-right: $sidebar_collapsed_width;
|
padding-right: $sidebar_collapsed_width;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.right-sidebar {
|
||||||
|
border-left: 1px solid $border-color;
|
||||||
|
}
|
||||||
|
|
|
@ -53,14 +53,6 @@
|
||||||
left: 70px;
|
left: 70px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-links {
|
|
||||||
svg {
|
|
||||||
position: relative;
|
|
||||||
top: 2px;
|
|
||||||
margin-right: 3px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.build-header {
|
.build-header {
|
||||||
|
@ -108,24 +100,98 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.right-sidebar.build-sidebar {
|
.right-sidebar.build-sidebar {
|
||||||
padding-top: $gl-padding;
|
padding: $gl-padding 0;
|
||||||
padding-bottom: $gl-padding;
|
|
||||||
|
|
||||||
&.right-sidebar-collapsed {
|
&.right-sidebar-collapsed {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.blocks-container {
|
||||||
|
padding: $gl-padding;
|
||||||
|
}
|
||||||
|
|
||||||
.block {
|
.block {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.build-sidebar-header {
|
.build-sidebar-header {
|
||||||
padding-top: 0;
|
padding: 0 $gl-padding $gl-padding;
|
||||||
|
|
||||||
.gutter-toggle {
|
.gutter-toggle {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stage-item {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $gl-text-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.build-dropdown {
|
||||||
|
padding: 0 $gl-padding;
|
||||||
|
|
||||||
|
.dropdown-menu-toggle {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu {
|
||||||
|
right: $gl-padding;
|
||||||
|
left: $gl-padding;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.builds-container {
|
||||||
|
margin-top: $gl-padding;
|
||||||
|
background-color: $white-light;
|
||||||
|
border-top: 1px solid $border-color;
|
||||||
|
border-bottom: 1px solid $border-color;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow: scroll;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
position: relative;
|
||||||
|
top: 2px;
|
||||||
|
margin-right: 3px;
|
||||||
|
height: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: block;
|
||||||
|
padding: $gl-padding 10px $gl-padding 40px;
|
||||||
|
width: 270px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $row-hover;
|
||||||
|
color: $gl-text-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.build-job {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.fa {
|
||||||
|
position: absolute;
|
||||||
|
left: 15px;
|
||||||
|
top: 20px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
.fa {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.build-detail-row {
|
.build-detail-row {
|
||||||
|
|
|
@ -230,6 +230,187 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pipeline visualization
|
||||||
|
|
||||||
|
.toggle-pipeline-btn {
|
||||||
|
background-color: $gray-dark;
|
||||||
|
|
||||||
|
.caret {
|
||||||
|
border-top: none;
|
||||||
|
border-bottom: 4px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.graph-collapsed {
|
||||||
|
background-color: $white-light;
|
||||||
|
|
||||||
|
.caret {
|
||||||
|
border-bottom: none;
|
||||||
|
border-top: 4px solid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pipeline-graph {
|
||||||
|
width: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
white-space: nowrap;
|
||||||
|
max-height: 500px;
|
||||||
|
transition: max-height 0.3s, padding 0.3s;
|
||||||
|
|
||||||
|
&.graph-collapsed {
|
||||||
|
max-height: 0;
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pipeline-visualization {
|
||||||
|
position: relative;
|
||||||
|
min-width: 1220px;
|
||||||
|
|
||||||
|
ul {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stage-column {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
margin-right: 50px;
|
||||||
|
|
||||||
|
li {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stage-name {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
font-weight: bold;
|
||||||
|
width: 150px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.build {
|
||||||
|
border: 1px solid $border-color;
|
||||||
|
position: relative;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 30px;
|
||||||
|
width: 150px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
&.playable {
|
||||||
|
background-color: $gray-light;
|
||||||
|
}
|
||||||
|
|
||||||
|
.build-content {
|
||||||
|
width: 130px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: $layout-link-gray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
position: relative;
|
||||||
|
top: 2px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect first build in each stage with right horizontal line
|
||||||
|
&:first-child {
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: -54px;
|
||||||
|
border-top: 2px solid $border-color;
|
||||||
|
width: 54px;
|
||||||
|
height: 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect each build (except for first) with curved lines
|
||||||
|
&:not(:first-child) {
|
||||||
|
&::after, &::before {
|
||||||
|
content: '';
|
||||||
|
top: -47px;
|
||||||
|
position: absolute;
|
||||||
|
border-bottom: 2px solid $border-color;
|
||||||
|
width: 20px;
|
||||||
|
height: 65px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Right connecting curves
|
||||||
|
&::after {
|
||||||
|
right: -20px;
|
||||||
|
border-right: 2px solid $border-color;
|
||||||
|
border-radius: 0 0 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Left connecting curves
|
||||||
|
&::before {
|
||||||
|
left: -20px;
|
||||||
|
border-left: 2px solid $border-color;
|
||||||
|
border-radius: 0 0 0 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect second build to first build with smaller curved line
|
||||||
|
&:nth-child(2) {
|
||||||
|
&::after, &::before {
|
||||||
|
height: 45px;
|
||||||
|
top: -26px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
.build {
|
||||||
|
// Remove right connecting horizontal line from first build in last stage
|
||||||
|
&:first-child {
|
||||||
|
&::after, &::before {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Remove right curved connectors from all builds in last stage
|
||||||
|
&:not(:first-child) {
|
||||||
|
&::after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
.build {
|
||||||
|
// Remove left curved connectors from all builds in first stage
|
||||||
|
&:not(:first-child) {
|
||||||
|
&::before {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pipeline-actions {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-pipeline-btn {
|
||||||
|
|
||||||
|
.fa {
|
||||||
|
color: $dropdown-header-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.pipelines.tab-pane {
|
.pipelines.tab-pane {
|
||||||
|
|
||||||
.content-list.pipelines {
|
.content-list.pipelines {
|
||||||
|
|
|
@ -83,6 +83,7 @@ class Projects::ApplicationController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def apply_diff_view_cookie!
|
def apply_diff_view_cookie!
|
||||||
|
@show_changes_tab = params[:view].present?
|
||||||
cookies.permanent[:diff_view] = params.delete(:view) if params[:view].present?
|
cookies.permanent[:diff_view] = params.delete(:view) if params[:view].present?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -198,6 +198,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def new
|
def new
|
||||||
|
apply_diff_view_cookie!
|
||||||
|
|
||||||
build_merge_request
|
build_merge_request
|
||||||
@noteable = @merge_request
|
@noteable = @merge_request
|
||||||
|
|
||||||
|
@ -214,7 +216,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
||||||
@base_commit = @merge_request.diff_base_commit
|
@base_commit = @merge_request.diff_base_commit
|
||||||
@diffs = @merge_request.diffs(diff_options) if @merge_request.compare
|
@diffs = @merge_request.diffs(diff_options) if @merge_request.compare
|
||||||
@diff_notes_disabled = true
|
@diff_notes_disabled = true
|
||||||
|
|
||||||
@pipeline = @merge_request.pipeline
|
@pipeline = @merge_request.pipeline
|
||||||
@statuses = @pipeline.statuses.relevant if @pipeline
|
@statuses = @pipeline.statuses.relevant if @pipeline
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,10 @@ module CiStatusHelper
|
||||||
'icon_status_pending'
|
'icon_status_pending'
|
||||||
when 'running'
|
when 'running'
|
||||||
'icon_status_running'
|
'icon_status_running'
|
||||||
|
when 'play'
|
||||||
|
return icon('play fw')
|
||||||
|
when 'created'
|
||||||
|
'icon_status_pending'
|
||||||
else
|
else
|
||||||
'icon_status_cancel'
|
'icon_status_cancel'
|
||||||
end
|
end
|
||||||
|
@ -48,13 +52,13 @@ module CiStatusHelper
|
||||||
def render_commit_status(commit, tooltip_placement: 'auto left')
|
def render_commit_status(commit, tooltip_placement: 'auto left')
|
||||||
project = commit.project
|
project = commit.project
|
||||||
path = builds_namespace_project_commit_path(project.namespace, project, commit)
|
path = builds_namespace_project_commit_path(project.namespace, project, commit)
|
||||||
render_status_with_link('commit', commit.status, path, tooltip_placement)
|
render_status_with_link('commit', commit.status, path, tooltip_placement: tooltip_placement)
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_pipeline_status(pipeline, tooltip_placement: 'auto left')
|
def render_pipeline_status(pipeline, tooltip_placement: 'auto left')
|
||||||
project = pipeline.project
|
project = pipeline.project
|
||||||
path = namespace_project_pipeline_path(project.namespace, project, pipeline)
|
path = namespace_project_pipeline_path(project.namespace, project, pipeline)
|
||||||
render_status_with_link('pipeline', pipeline.status, path, tooltip_placement)
|
render_status_with_link('pipeline', pipeline.status, path, tooltip_placement: tooltip_placement)
|
||||||
end
|
end
|
||||||
|
|
||||||
def no_runners_for_project?(project)
|
def no_runners_for_project?(project)
|
||||||
|
@ -62,13 +66,17 @@ module CiStatusHelper
|
||||||
Ci::Runner.shared.blank?
|
Ci::Runner.shared.blank?
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
def render_status_with_link(type, status, path = nil, tooltip_placement: 'auto left', cssclass: '')
|
||||||
|
klass = "ci-status-link ci-status-icon-#{status.dasherize} #{cssclass}"
|
||||||
|
title = "#{type.titleize}: #{ci_label_for_status(status)}"
|
||||||
|
data = { toggle: 'tooltip', placement: tooltip_placement }
|
||||||
|
|
||||||
def render_status_with_link(type, status, path, tooltip_placement, cssclass: '')
|
if path
|
||||||
link_to ci_icon_for_status(status),
|
link_to ci_icon_for_status(status), path,
|
||||||
path,
|
class: klass, title: title, data: data
|
||||||
class: "ci-status-link ci-status-icon-#{status.dasherize} #{cssclass}",
|
else
|
||||||
title: "#{type.titleize}: #{ci_label_for_status(status)}",
|
content_tag :span, ci_icon_for_status(status),
|
||||||
data: { toggle: 'tooltip', placement: tooltip_placement }
|
class: klass, title: title, data: data
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,6 +6,11 @@ module Emails
|
||||||
mail_new_thread(@issue, issue_thread_options(@issue.author_id, recipient_id))
|
mail_new_thread(@issue, issue_thread_options(@issue.author_id, recipient_id))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def new_mention_in_issue_email(recipient_id, issue_id, updated_by_user_id)
|
||||||
|
setup_issue_mail(issue_id, recipient_id)
|
||||||
|
mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id))
|
||||||
|
end
|
||||||
|
|
||||||
def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id, updated_by_user_id)
|
def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id, updated_by_user_id)
|
||||||
setup_issue_mail(issue_id, recipient_id)
|
setup_issue_mail(issue_id, recipient_id)
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,11 @@ module Emails
|
||||||
mail_new_thread(@merge_request, merge_request_thread_options(@merge_request.author_id, recipient_id))
|
mail_new_thread(@merge_request, merge_request_thread_options(@merge_request.author_id, recipient_id))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def new_mention_in_merge_request_email(recipient_id, merge_request_id, updated_by_user_id)
|
||||||
|
setup_merge_request_mail(merge_request_id, recipient_id)
|
||||||
|
mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id))
|
||||||
|
end
|
||||||
|
|
||||||
def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id, updated_by_user_id)
|
def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id, updated_by_user_id)
|
||||||
setup_merge_request_mail(merge_request_id, recipient_id)
|
setup_merge_request_mail(merge_request_id, recipient_id)
|
||||||
|
|
||||||
|
|
|
@ -97,7 +97,7 @@ module Ci
|
||||||
end
|
end
|
||||||
|
|
||||||
def playable?
|
def playable?
|
||||||
project.builds_enabled? && commands.present? && manual?
|
project.builds_enabled? && commands.present? && manual? && skipped?
|
||||||
end
|
end
|
||||||
|
|
||||||
def play(current_user = nil)
|
def play(current_user = nil)
|
||||||
|
|
|
@ -78,6 +78,10 @@ module Ci
|
||||||
CommitStatus.where(pipeline: pluck(:id)).stages
|
CommitStatus.where(pipeline: pluck(:id)).stages
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def stages_with_latest_statuses
|
||||||
|
statuses.latest.order(:stage_idx).group_by(&:stage)
|
||||||
|
end
|
||||||
|
|
||||||
def project_id
|
def project_id
|
||||||
project.id
|
project.id
|
||||||
end
|
end
|
||||||
|
|
|
@ -104,11 +104,12 @@ class IssuableBaseService < BaseService
|
||||||
change_subscription(issuable)
|
change_subscription(issuable)
|
||||||
filter_params
|
filter_params
|
||||||
old_labels = issuable.labels.to_a
|
old_labels = issuable.labels.to_a
|
||||||
|
old_mentioned_users = issuable.mentioned_users.to_a
|
||||||
|
|
||||||
if params.present? && update_issuable(issuable, params)
|
if params.present? && update_issuable(issuable, params)
|
||||||
issuable.reset_events_cache
|
issuable.reset_events_cache
|
||||||
handle_common_system_notes(issuable, old_labels: old_labels)
|
handle_common_system_notes(issuable, old_labels: old_labels)
|
||||||
handle_changes(issuable, old_labels: old_labels)
|
handle_changes(issuable, old_labels: old_labels, old_mentioned_users: old_mentioned_users)
|
||||||
issuable.create_new_cross_references!(current_user)
|
issuable.create_new_cross_references!(current_user)
|
||||||
execute_hooks(issuable, 'update')
|
execute_hooks(issuable, 'update')
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,7 +4,7 @@ module Issues
|
||||||
update(issue)
|
update(issue)
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_changes(issue, old_labels: [])
|
def handle_changes(issue, old_labels: [], old_mentioned_users: [])
|
||||||
if has_changes?(issue, old_labels: old_labels)
|
if has_changes?(issue, old_labels: old_labels)
|
||||||
todo_service.mark_pending_todos_as_done(issue, current_user)
|
todo_service.mark_pending_todos_as_done(issue, current_user)
|
||||||
end
|
end
|
||||||
|
@ -32,6 +32,11 @@ module Issues
|
||||||
if added_labels.present?
|
if added_labels.present?
|
||||||
notification_service.relabeled_issue(issue, added_labels, current_user)
|
notification_service.relabeled_issue(issue, added_labels, current_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
added_mentions = issue.mentioned_users - old_mentioned_users
|
||||||
|
if added_mentions.present?
|
||||||
|
notification_service.new_mentions_in_issue(issue, added_mentions, current_user)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def reopen_service
|
def reopen_service
|
||||||
|
|
|
@ -16,7 +16,7 @@ module MergeRequests
|
||||||
update(merge_request)
|
update(merge_request)
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_changes(merge_request, old_labels: [])
|
def handle_changes(merge_request, old_labels: [], old_mentioned_users: [])
|
||||||
if has_changes?(merge_request, old_labels: old_labels)
|
if has_changes?(merge_request, old_labels: old_labels)
|
||||||
todo_service.mark_pending_todos_as_done(merge_request, current_user)
|
todo_service.mark_pending_todos_as_done(merge_request, current_user)
|
||||||
end
|
end
|
||||||
|
@ -55,6 +55,15 @@ module MergeRequests
|
||||||
current_user
|
current_user
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
added_mentions = merge_request.mentioned_users - old_mentioned_users
|
||||||
|
if added_mentions.present?
|
||||||
|
notification_service.new_mentions_in_merge_request(
|
||||||
|
merge_request,
|
||||||
|
added_mentions,
|
||||||
|
current_user
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def reopen_service
|
def reopen_service
|
||||||
|
|
|
@ -35,6 +35,20 @@ class NotificationService
|
||||||
new_resource_email(issue, issue.project, :new_issue_email)
|
new_resource_email(issue, issue.project, :new_issue_email)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# When issue text is updated, we should send an email to:
|
||||||
|
#
|
||||||
|
# * newly mentioned project team members with notification level higher than Participating
|
||||||
|
#
|
||||||
|
def new_mentions_in_issue(issue, new_mentioned_users, current_user)
|
||||||
|
new_mentions_in_resource_email(
|
||||||
|
issue,
|
||||||
|
issue.project,
|
||||||
|
new_mentioned_users,
|
||||||
|
current_user,
|
||||||
|
:new_mention_in_issue_email
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
# When we close an issue we should send an email to:
|
# When we close an issue we should send an email to:
|
||||||
#
|
#
|
||||||
# * issue author if their notification level is not Disabled
|
# * issue author if their notification level is not Disabled
|
||||||
|
@ -75,6 +89,20 @@ class NotificationService
|
||||||
new_resource_email(merge_request, merge_request.target_project, :new_merge_request_email)
|
new_resource_email(merge_request, merge_request.target_project, :new_merge_request_email)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# When merge request text is updated, we should send an email to:
|
||||||
|
#
|
||||||
|
# * newly mentioned project team members with notification level higher than Participating
|
||||||
|
#
|
||||||
|
def new_mentions_in_merge_request(merge_request, new_mentioned_users, current_user)
|
||||||
|
new_mentions_in_resource_email(
|
||||||
|
merge_request,
|
||||||
|
merge_request.target_project,
|
||||||
|
new_mentioned_users,
|
||||||
|
current_user,
|
||||||
|
:new_mention_in_merge_request_email
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
# When we reassign a merge_request we should send an email to:
|
# When we reassign a merge_request we should send an email to:
|
||||||
#
|
#
|
||||||
# * merge_request old assignee if their notification level is not Disabled
|
# * merge_request old assignee if their notification level is not Disabled
|
||||||
|
@ -479,6 +507,15 @@ class NotificationService
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def new_mentions_in_resource_email(target, project, new_mentioned_users, current_user, method)
|
||||||
|
recipients = build_recipients(target, project, current_user, action: "new")
|
||||||
|
recipients = recipients & new_mentioned_users
|
||||||
|
|
||||||
|
recipients.each do |recipient|
|
||||||
|
mailer.send(method, recipient.id, target.id, current_user.id).deliver_later
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def close_resource_email(target, project, current_user, method)
|
def close_resource_email(target, project, current_user, method)
|
||||||
action = method == :merged_merge_request_email ? "merge" : "close"
|
action = method == :merged_merge_request_email ? "merge" : "close"
|
||||||
recipients = build_recipients(target, project, current_user, action: action)
|
recipients = build_recipients(target, project, current_user, action: action)
|
||||||
|
|
12
app/views/notify/new_mention_in_issue_email.html.haml
Normal file
12
app/views/notify/new_mention_in_issue_email.html.haml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
%p
|
||||||
|
You have been mentioned in an issue.
|
||||||
|
|
||||||
|
- if current_application_settings.email_author_in_body
|
||||||
|
%div
|
||||||
|
#{link_to @issue.author_name, user_url(@issue.author)} wrote:
|
||||||
|
-if @issue.description
|
||||||
|
= markdown(@issue.description, pipeline: :email, author: @issue.author)
|
||||||
|
|
||||||
|
- if @issue.assignee_id.present?
|
||||||
|
%p
|
||||||
|
Assignee: #{@issue.assignee_name}
|
7
app/views/notify/new_mention_in_issue_email.text.erb
Normal file
7
app/views/notify/new_mention_in_issue_email.text.erb
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
You have been mentioned in an issue.
|
||||||
|
|
||||||
|
Issue <%= @issue.iid %>: <%= url_for(namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)) %>
|
||||||
|
Author: <%= @issue.author_name %>
|
||||||
|
Assignee: <%= @issue.assignee_name %>
|
||||||
|
|
||||||
|
<%= @issue.description %>
|
|
@ -0,0 +1,15 @@
|
||||||
|
%p
|
||||||
|
You have been mentioned in Merge Request #{@merge_request.to_reference}
|
||||||
|
|
||||||
|
- if current_application_settings.email_author_in_body
|
||||||
|
%div
|
||||||
|
#{link_to @merge_request.author_name, user_url(@merge_request.author)} wrote:
|
||||||
|
%p.details
|
||||||
|
!= merge_path_description(@merge_request, '→')
|
||||||
|
|
||||||
|
- if @merge_request.assignee_id.present?
|
||||||
|
%p
|
||||||
|
Assignee: #{@merge_request.author_name} → #{@merge_request.assignee_name}
|
||||||
|
|
||||||
|
-if @merge_request.description
|
||||||
|
= markdown(@merge_request.description, pipeline: :email, author: @merge_request.author)
|
|
@ -0,0 +1,9 @@
|
||||||
|
You have been mentioned in Merge Request <%= @merge_request.to_reference %>
|
||||||
|
|
||||||
|
<%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)) %>
|
||||||
|
|
||||||
|
<%= merge_path_description(@merge_request, 'to') %>
|
||||||
|
Author: <%= @merge_request.author_name %>
|
||||||
|
Assignee: <%= @merge_request.assignee_name %>
|
||||||
|
|
||||||
|
<%= @merge_request.description %>
|
|
@ -11,98 +11,133 @@
|
||||||
%p.build-detail-row
|
%p.build-detail-row
|
||||||
#{@build.coverage}%
|
#{@build.coverage}%
|
||||||
|
|
||||||
- if can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?)
|
- builds = @build.pipeline.builds.latest.to_a
|
||||||
.block{ class: ("block-first" if !@build.coverage) }
|
- statuses = ["failed", "pending", "running", "canceled", "success", "skipped"]
|
||||||
|
- if builds.size > 1
|
||||||
|
.dropdown.build-dropdown
|
||||||
|
.build-light-text Stage
|
||||||
|
%button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'}
|
||||||
|
%span.stage-selection More
|
||||||
|
= icon('caret-down')
|
||||||
|
%ul.dropdown-menu
|
||||||
|
- builds.map(&:stage).uniq.each do |stage|
|
||||||
|
%li
|
||||||
|
%a.stage-item= stage
|
||||||
|
|
||||||
|
.builds-container
|
||||||
|
- statuses.each do |build_status|
|
||||||
|
- builds.select{|build| build.status == build_status}.each do |build|
|
||||||
|
.build-job{class: ('active' if build == @build), data: {stage: build.stage}}
|
||||||
|
= link_to namespace_project_build_path(@project.namespace, @project, build) do
|
||||||
|
= icon('check')
|
||||||
|
= ci_icon_for_status(build.status)
|
||||||
|
%span
|
||||||
|
- if build.name
|
||||||
|
= build.name
|
||||||
|
- else
|
||||||
|
= build.id
|
||||||
|
|
||||||
|
- if @build.retried?
|
||||||
|
%li.active
|
||||||
|
%a
|
||||||
|
Build ##{@build.id}
|
||||||
|
·
|
||||||
|
%i.fa.fa-warning
|
||||||
|
This build was retried.
|
||||||
|
|
||||||
|
.blocks-container
|
||||||
|
- if can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?)
|
||||||
|
.block{ class: ("block-first" if !@build.coverage) }
|
||||||
|
.title
|
||||||
|
Build artifacts
|
||||||
|
- if @build.artifacts_expired?
|
||||||
|
%p.build-detail-row
|
||||||
|
The artifacts were removed
|
||||||
|
#{time_ago_with_tooltip(@build.artifacts_expire_at)}
|
||||||
|
- elsif @build.artifacts_expire_at
|
||||||
|
%p.build-detail-row
|
||||||
|
The artifacts will be removed in
|
||||||
|
%span.js-artifacts-remove= @build.artifacts_expire_at
|
||||||
|
|
||||||
|
- if @build.artifacts?
|
||||||
|
.btn-group.btn-group-justified{ role: :group }
|
||||||
|
- if @build.artifacts_expire_at
|
||||||
|
= link_to keep_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default', method: :post do
|
||||||
|
Keep
|
||||||
|
|
||||||
|
= link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default' do
|
||||||
|
Download
|
||||||
|
|
||||||
|
- if @build.artifacts_metadata?
|
||||||
|
= link_to browse_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default' do
|
||||||
|
Browse
|
||||||
|
|
||||||
|
.block{ class: ("block-first" if !@build.coverage && !(can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?))) }
|
||||||
.title
|
.title
|
||||||
Build artifacts
|
Build details
|
||||||
- if @build.artifacts_expired?
|
- if can?(current_user, :update_build, @build) && @build.retryable?
|
||||||
|
= link_to "Retry", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'pull-right', method: :post
|
||||||
|
- if @build.merge_request
|
||||||
%p.build-detail-row
|
%p.build-detail-row
|
||||||
The artifacts were removed
|
%span.build-light-text Merge Request:
|
||||||
#{time_ago_with_tooltip(@build.artifacts_expire_at)}
|
= link_to "#{@build.merge_request.to_reference}", merge_request_path(@build.merge_request)
|
||||||
- elsif @build.artifacts_expire_at
|
- if @build.duration
|
||||||
%p.build-detail-row
|
%p.build-detail-row
|
||||||
The artifacts will be removed in
|
%span.build-light-text Duration:
|
||||||
%span.js-artifacts-remove= @build.artifacts_expire_at
|
= time_interval_in_words(@build.duration)
|
||||||
|
- if @build.finished_at
|
||||||
- if @build.artifacts?
|
%p.build-detail-row
|
||||||
.btn-group.btn-group-justified{ role: :group }
|
%span.build-light-text Finished:
|
||||||
- if @build.artifacts_expire_at
|
#{time_ago_with_tooltip(@build.finished_at)}
|
||||||
= link_to keep_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default', method: :post do
|
- if @build.erased_at
|
||||||
Keep
|
%p.build-detail-row
|
||||||
|
%span.build-light-text Erased:
|
||||||
= link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default' do
|
#{time_ago_with_tooltip(@build.erased_at)}
|
||||||
Download
|
|
||||||
|
|
||||||
- if @build.artifacts_metadata?
|
|
||||||
= link_to browse_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default' do
|
|
||||||
Browse
|
|
||||||
|
|
||||||
.block{ class: ("block-first" if !@build.coverage && !(can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?))) }
|
|
||||||
.title
|
|
||||||
Build details
|
|
||||||
- if can?(current_user, :update_build, @build) && @build.retryable?
|
|
||||||
= link_to "Retry", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'pull-right', method: :post
|
|
||||||
- if @build.merge_request
|
|
||||||
%p.build-detail-row
|
%p.build-detail-row
|
||||||
%span.build-light-text Merge Request:
|
%span.build-light-text Runner:
|
||||||
= link_to "#{@build.merge_request.to_reference}", merge_request_path(@build.merge_request)
|
- if @build.runner && current_user && current_user.admin
|
||||||
- if @build.duration
|
= link_to "##{@build.runner.id}", admin_runner_path(@build.runner.id)
|
||||||
%p.build-detail-row
|
- elsif @build.runner
|
||||||
%span.build-light-text Duration:
|
\##{@build.runner.id}
|
||||||
= time_interval_in_words(@build.duration)
|
.btn-group.btn-group-justified{ role: :group }
|
||||||
- if @build.finished_at
|
- if @build.has_trace?
|
||||||
%p.build-detail-row
|
= link_to 'Raw', raw_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default'
|
||||||
%span.build-light-text Finished:
|
- if @build.active?
|
||||||
#{time_ago_with_tooltip(@build.finished_at)}
|
= link_to "Cancel", cancel_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default', method: :post
|
||||||
- if @build.erased_at
|
- if can?(current_user, :update_build, @project) && @build.erasable?
|
||||||
%p.build-detail-row
|
= link_to erase_namespace_project_build_path(@project.namespace, @project, @build),
|
||||||
%span.build-light-text Erased:
|
class: "btn btn-sm btn-default", method: :post,
|
||||||
#{time_ago_with_tooltip(@build.erased_at)}
|
data: { confirm: "Are you sure you want to erase this build?" } do
|
||||||
%p.build-detail-row
|
Erase
|
||||||
%span.build-light-text Runner:
|
|
||||||
- if @build.runner && current_user && current_user.admin
|
|
||||||
= link_to "##{@build.runner.id}", admin_runner_path(@build.runner.id)
|
|
||||||
- elsif @build.runner
|
|
||||||
\##{@build.runner.id}
|
|
||||||
.btn-group.btn-group-justified{ role: :group }
|
|
||||||
- if @build.has_trace?
|
|
||||||
= link_to 'Raw', raw_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default'
|
|
||||||
- if @build.active?
|
|
||||||
= link_to "Cancel", cancel_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default', method: :post
|
|
||||||
- if can?(current_user, :update_build, @project) && @build.erasable?
|
|
||||||
= link_to erase_namespace_project_build_path(@project.namespace, @project, @build),
|
|
||||||
class: "btn btn-sm btn-default", method: :post,
|
|
||||||
data: { confirm: "Are you sure you want to erase this build?" } do
|
|
||||||
Erase
|
|
||||||
|
|
||||||
- if @build.trigger_request
|
- if @build.trigger_request
|
||||||
.build-widget
|
.build-widget
|
||||||
%h4.title
|
%h4.title
|
||||||
Trigger
|
Trigger
|
||||||
|
|
||||||
%p
|
|
||||||
%span.build-light-text Token:
|
|
||||||
#{@build.trigger_request.trigger.short_token}
|
|
||||||
|
|
||||||
- if @build.trigger_request.variables
|
|
||||||
%p
|
%p
|
||||||
%span.build-light-text Variables:
|
%span.build-light-text Token:
|
||||||
|
#{@build.trigger_request.trigger.short_token}
|
||||||
|
|
||||||
|
- if @build.trigger_request.variables
|
||||||
|
%p
|
||||||
|
%span.build-light-text Variables:
|
||||||
|
|
||||||
|
|
||||||
- @build.trigger_request.variables.each do |key, value|
|
- @build.trigger_request.variables.each do |key, value|
|
||||||
%code
|
%code
|
||||||
#{key}=#{value}
|
#{key}=#{value}
|
||||||
|
|
||||||
.block
|
|
||||||
.title
|
|
||||||
Commit title
|
|
||||||
%p.build-light-text.append-bottom-0
|
|
||||||
#{@build.pipeline.git_commit_title}
|
|
||||||
|
|
||||||
- if @build.tags.any?
|
|
||||||
.block
|
.block
|
||||||
.title
|
.title
|
||||||
Tags
|
Commit title
|
||||||
- @build.tag_list.each do |tag|
|
%p.build-light-text.append-bottom-0
|
||||||
%span.label.label-primary
|
#{@build.pipeline.git_commit_title}
|
||||||
= tag
|
|
||||||
|
- if @build.tags.any?
|
||||||
|
.block
|
||||||
|
.title
|
||||||
|
Tags
|
||||||
|
- @build.tag_list.each do |tag|
|
||||||
|
%span.label.label-primary
|
||||||
|
= tag
|
||||||
|
|
|
@ -5,26 +5,6 @@
|
||||||
.build-page
|
.build-page
|
||||||
= render "header"
|
= render "header"
|
||||||
|
|
||||||
- builds = @build.pipeline.builds.latest.to_a
|
|
||||||
- if builds.size > 1
|
|
||||||
%ul.nav-links.no-top.no-bottom
|
|
||||||
- builds.each do |build|
|
|
||||||
%li{class: ('active' if build == @build) }
|
|
||||||
= link_to namespace_project_build_path(@project.namespace, @project, build) do
|
|
||||||
= ci_icon_for_status(build.status)
|
|
||||||
%span
|
|
||||||
- if build.name
|
|
||||||
= build.name
|
|
||||||
- else
|
|
||||||
= build.id
|
|
||||||
|
|
||||||
- if @build.retried?
|
|
||||||
%li.active
|
|
||||||
%a
|
|
||||||
Build ##{@build.id}
|
|
||||||
·
|
|
||||||
%i.fa.fa-warning
|
|
||||||
This build was retried.
|
|
||||||
- if @build.stuck?
|
- if @build.stuck?
|
||||||
- unless @build.any_runners_online?
|
- unless @build.any_runners_online?
|
||||||
.bs-callout.bs-callout-warning
|
.bs-callout.bs-callout-warning
|
||||||
|
@ -67,4 +47,10 @@
|
||||||
= render "sidebar"
|
= render "sidebar"
|
||||||
|
|
||||||
:javascript
|
:javascript
|
||||||
new Build("#{namespace_project_build_url(@project.namespace, @project, @build)}", "#{namespace_project_build_url(@project.namespace, @project, @build, :json)}", "#{@build.status}", "#{trace_with_state[:state]}")
|
new Build({
|
||||||
|
page_url: "#{namespace_project_build_url(@project.namespace, @project, @build)}",
|
||||||
|
build_url: "#{namespace_project_build_url(@project.namespace, @project, @build, :json)}",
|
||||||
|
build_status: "#{@build.status}",
|
||||||
|
build_stage: "#{@build.stage}",
|
||||||
|
state1: "#{trace_with_state[:state]}"
|
||||||
|
})
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
- if current_user
|
- if current_user
|
||||||
= link_to toggle_star_namespace_project_path(@project.namespace, @project), { class: 'btn star-btn toggle-star has-tooltip', method: :post, remote: true, title: current_user.starred?(@project) ? 'Unstar project' : 'Star project' } do
|
= link_to toggle_star_namespace_project_path(@project.namespace, @project), { class: 'btn star-btn toggle-star has-tooltip', method: :post, remote: true, title: current_user.starred?(@project) ? 'Unstar project' : 'Star project' } do
|
||||||
- if current_user.starred?(@project)
|
- if current_user.starred?(@project)
|
||||||
= icon('star fw')
|
= icon('star')
|
||||||
%span.starred Unstar
|
%span.starred Unstar
|
||||||
- else
|
- else
|
||||||
= icon('star-o fw')
|
= icon('star-o')
|
||||||
%span Star
|
%span Star
|
||||||
%div.count-with-arrow
|
%div.count-with-arrow
|
||||||
%span.arrow
|
%span.arrow
|
||||||
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
- else
|
- else
|
||||||
= link_to new_user_session_path, class: 'btn has-tooltip star-btn', title: 'You must sign in to star a project' do
|
= link_to new_user_session_path, class: 'btn has-tooltip star-btn', title: 'You must sign in to star a project' do
|
||||||
= icon('star fw')
|
= icon('star')
|
||||||
Star
|
Star
|
||||||
%div.count-with-arrow
|
%div.count-with-arrow
|
||||||
%span.arrow
|
%span.arrow
|
||||||
|
|
14
app/views/projects/ci/builds/_build_pipeline.html.haml
Normal file
14
app/views/projects/ci/builds/_build_pipeline.html.haml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
- is_playable = subject.playable? && can?(current_user, :update_build, @project)
|
||||||
|
%li.build{class: ("playable" if is_playable)}
|
||||||
|
.build-content
|
||||||
|
- if is_playable
|
||||||
|
= link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, title: 'Play' do
|
||||||
|
= render_status_with_link('build', 'play')
|
||||||
|
= subject.name
|
||||||
|
- elsif can?(current_user, :read_build, @project)
|
||||||
|
= link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do
|
||||||
|
= render_status_with_link('build', subject.status)
|
||||||
|
= subject.name
|
||||||
|
- else
|
||||||
|
= render_status_with_link('build', subject.status)
|
||||||
|
= ci_icon_for_status(subject.status)
|
|
@ -1,5 +1,9 @@
|
||||||
.row-content-block.build-content.middle-block
|
.row-content-block.build-content.middle-block.pipeline-actions
|
||||||
.pull-right
|
.pull-right
|
||||||
|
.btn.btn-grouped.btn-white.toggle-pipeline-btn
|
||||||
|
%span.toggle-btn-text Hide
|
||||||
|
%span pipeline graph
|
||||||
|
%span.caret
|
||||||
- if can?(current_user, :update_pipeline, pipeline.project)
|
- if can?(current_user, :update_pipeline, pipeline.project)
|
||||||
- if pipeline.builds.latest.failed.any?(&:retryable?)
|
- if pipeline.builds.latest.failed.any?(&:retryable?)
|
||||||
= link_to "Retry failed", retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn btn-grouped btn-primary', method: :post
|
= link_to "Retry failed", retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn btn-grouped btn-primary', method: :post
|
||||||
|
@ -23,6 +27,22 @@
|
||||||
in
|
in
|
||||||
= time_interval_in_words pipeline.duration
|
= time_interval_in_words pipeline.duration
|
||||||
|
|
||||||
|
.row-content-block.build-content.middle-block.pipeline-graph
|
||||||
|
.pipeline-visualization
|
||||||
|
%ul.stage-column-list
|
||||||
|
- stages = pipeline.stages_with_latest_statuses
|
||||||
|
- stages.each do |stage, statuses|
|
||||||
|
%li.stage-column
|
||||||
|
.stage-name
|
||||||
|
%a{name: stage}
|
||||||
|
- if stage
|
||||||
|
= stage.titleize
|
||||||
|
.builds-container
|
||||||
|
%ul
|
||||||
|
- statuses.each do |status|
|
||||||
|
= render "projects/#{status.to_partial_path}_pipeline", subject: status
|
||||||
|
|
||||||
|
|
||||||
- if pipeline.yaml_errors.present?
|
- if pipeline.yaml_errors.present?
|
||||||
.bs-callout.bs-callout-danger
|
.bs-callout.bs-callout-danger
|
||||||
%h4 Found errors in your .gitlab-ci.yml:
|
%h4 Found errors in your .gitlab-ci.yml:
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
%li.build
|
||||||
|
.build-content
|
||||||
|
- if subject.target_url
|
||||||
|
- link_to subject.target_url do
|
||||||
|
= render_status_with_link('commit status', subject.status)
|
||||||
|
= subject.name
|
||||||
|
- else
|
||||||
|
= render_status_with_link('commit status', subject.status)
|
||||||
|
= subject.name
|
|
@ -20,7 +20,7 @@
|
||||||
.mr-compare.merge-request
|
.mr-compare.merge-request
|
||||||
%ul.merge-request-tabs.nav-links.no-top.no-bottom
|
%ul.merge-request-tabs.nav-links.no-top.no-bottom
|
||||||
%li.commits-tab
|
%li.commits-tab
|
||||||
= link_to url_for(params), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do
|
= link_to url_for(params), data: {target: 'div#commits', action: 'new', toggle: 'tab'} do
|
||||||
Commits
|
Commits
|
||||||
%span.badge= @commits.size
|
%span.badge= @commits.size
|
||||||
- if @pipeline
|
- if @pipeline
|
||||||
|
@ -52,11 +52,8 @@
|
||||||
$('#merge_request_assignee_id').val("#{current_user.id}").trigger("change");
|
$('#merge_request_assignee_id').val("#{current_user.id}").trigger("change");
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
:javascript
|
:javascript
|
||||||
var merge_request
|
var merge_request = new MergeRequest({
|
||||||
merge_request = new MergeRequest({
|
action: "#{(@show_changes_tab ? 'diffs' : 'new')}",
|
||||||
action: 'new',
|
setUrl: false
|
||||||
diffs_loaded: true,
|
|
||||||
commits_loaded: true
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40">
|
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="40" viewBox="5 0 30 40">
|
||||||
<path fill="#7E7E7E" fill-rule="evenodd" d="M22,29.5351288 L22,22.7193602 C26.1888699,21.5098039 29.3985457,16.802989 29.3985457,16.802989 C29.740988,16.3567547 30,15.5559546 30,15.0081969 L30,10.4648712 C31.1956027,9.77325238 32,8.48056471 32,7 C32,4.790861 30.209139,3 28,3 C25.790861,3 24,4.790861 24,7 C24,8.48056471 24.8043973,9.77325238 26,10.4648712 L26,14.7083871 C26,14.8784435 25.9055559,15.0987329 25.7890533,15.2104147 C25.7890533,15.2104147 24.5373893,16.4126202 23.9488702,16.9515733 C22.5015398,18.2770075 21.1191354,19 20.090554,19 C19.0477772,19 17.6172728,18.2608988 16.1128852,16.9142923 C15.5030182,16.3683886 14.3672121,15.3403307 14.3672121,15.3403307 C14.1659605,15.1583364 14.0000086,14.7846305 14.0000192,14.5088473 C14.0000192,14.5088473 14.0000932,12.7539451 14.0001308,10.4647956 C15.1956614,9.77315812 16,8.48051074 16,7 C16,4.790861 14.209139,3 12,3 C9.790861,3 8,4.790861 8,7 C8,8.48056471 8.80439726,9.77325238 10,10.4648712 L10,15.0081969 C10,15.5446944 10.2736352,16.3534183 10.6111812,16.7893819 C10.6111812,16.7893819 13.8599776,21.3779363 18,22.6668724 L18,29.5351288 C16.8043973,30.2267476 16,31.5194353 16,33 C16,35.209139 17.790861,37 20,37 C22.209139,37 24,35.209139 24,33 C24,31.5194353 23.1956027,30.2267476 22,29.5351288 Z M14,7 C14,5.8954305 13.1045695,5 12,5 C10.8954305,5 10,5.8954305 10,7 C10,8.1045695 10.8954305,9 12,9 C13.1045695,9 14,8.1045695 14,7 Z M30,7 C30,5.8954305 29.1045695,5 28,5 C26.8954305,5 26,5.8954305 26,7 C26,8.1045695 26.8954305,9 28,9 C29.1045695,9 30,8.1045695 30,7 Z M22,33 C22,31.8954305 21.1045695,31 20,31 C18.8954305,31 18,31.8954305 18,33 C18,34.1045695 18.8954305,35 20,35 C21.1045695,35 22,34.1045695 22,33 Z"/>
|
<path fill="#7E7E7E" fill-rule="evenodd" d="M22,29.5351288 L22,22.7193602 C26.1888699,21.5098039 29.3985457,16.802989 29.3985457,16.802989 C29.740988,16.3567547 30,15.5559546 30,15.0081969 L30,10.4648712 C31.1956027,9.77325238 32,8.48056471 32,7 C32,4.790861 30.209139,3 28,3 C25.790861,3 24,4.790861 24,7 C24,8.48056471 24.8043973,9.77325238 26,10.4648712 L26,14.7083871 C26,14.8784435 25.9055559,15.0987329 25.7890533,15.2104147 C25.7890533,15.2104147 24.5373893,16.4126202 23.9488702,16.9515733 C22.5015398,18.2770075 21.1191354,19 20.090554,19 C19.0477772,19 17.6172728,18.2608988 16.1128852,16.9142923 C15.5030182,16.3683886 14.3672121,15.3403307 14.3672121,15.3403307 C14.1659605,15.1583364 14.0000086,14.7846305 14.0000192,14.5088473 C14.0000192,14.5088473 14.0000932,12.7539451 14.0001308,10.4647956 C15.1956614,9.77315812 16,8.48051074 16,7 C16,4.790861 14.209139,3 12,3 C9.790861,3 8,4.790861 8,7 C8,8.48056471 8.80439726,9.77325238 10,10.4648712 L10,15.0081969 C10,15.5446944 10.2736352,16.3534183 10.6111812,16.7893819 C10.6111812,16.7893819 13.8599776,21.3779363 18,22.6668724 L18,29.5351288 C16.8043973,30.2267476 16,31.5194353 16,33 C16,35.209139 17.790861,37 20,37 C22.209139,37 24,35.209139 24,33 C24,31.5194353 23.1956027,30.2267476 22,29.5351288 Z M14,7 C14,5.8954305 13.1045695,5 12,5 C10.8954305,5 10,5.8954305 10,7 C10,8.1045695 10.8954305,9 12,9 C13.1045695,9 14,8.1045695 14,7 Z M30,7 C30,5.8954305 29.1045695,5 28,5 C26.8954305,5 26,5.8954305 26,7 C26,8.1045695 26.8954305,9 28,9 C29.1045695,9 30,8.1045695 30,7 Z M22,33 C22,31.8954305 21.1045695,31 20,31 C18.8954305,31 18,31.8954305 18,33 C18,34.1045695 18.8954305,35 20,35 C21.1045695,35 22,34.1045695 22,33 Z"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
@ -27,9 +27,7 @@
|
||||||
= render "shared/issuable/label_dropdown"
|
= render "shared/issuable/label_dropdown"
|
||||||
|
|
||||||
.pull-right
|
.pull-right
|
||||||
- if controller.controller_name != 'boards'
|
- if controller.controller_name == 'boards' && can?(current_user, :admin_list, @project)
|
||||||
= render 'shared/sort_dropdown'
|
|
||||||
- if can?(current_user, :admin_list, @project)
|
|
||||||
.dropdown
|
.dropdown
|
||||||
%button.btn.btn-create.js-new-board-list{ type: "button", data: { toggle: "dropdown", labels: labels_filter_path, project_id: @project.try(:id) } }
|
%button.btn.btn-create.js-new-board-list{ type: "button", data: { toggle: "dropdown", labels: labels_filter_path, project_id: @project.try(:id) } }
|
||||||
Create new list
|
Create new list
|
||||||
|
@ -38,6 +36,8 @@
|
||||||
- if can?(current_user, :admin_label, @project)
|
- if can?(current_user, :admin_label, @project)
|
||||||
= render partial: "shared/issuable/label_page_create"
|
= render partial: "shared/issuable/label_page_create"
|
||||||
= dropdown_loading
|
= dropdown_loading
|
||||||
|
- else
|
||||||
|
= render 'shared/sort_dropdown'
|
||||||
|
|
||||||
- if controller.controller_name == 'issues'
|
- if controller.controller_name == 'issues'
|
||||||
.issues_bulk_update.hide
|
.issues_bulk_update.hide
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
- if can_add_template?(issuable)
|
- if can_add_template?(issuable)
|
||||||
%p.help-block
|
%p.help-block
|
||||||
Add
|
Add
|
||||||
= link_to "issuable templates", help_page_path('workflow/description_templates')
|
= link_to "description templates", help_page_path('user/project/description_templates')
|
||||||
to help your contributors communicate effectively!
|
to help your contributors communicate effectively!
|
||||||
|
|
||||||
.form-group.detail-page-description
|
.form-group.detail-page-description
|
||||||
|
|
|
@ -20,11 +20,11 @@
|
||||||
- if forks
|
- if forks
|
||||||
%span
|
%span
|
||||||
= icon('code-fork')
|
= icon('code-fork')
|
||||||
= project.forks_count
|
= number_with_delimiter(project.forks_count)
|
||||||
- if stars
|
- if stars
|
||||||
%span
|
%span
|
||||||
= icon('star')
|
= icon('star')
|
||||||
= project.star_count
|
= number_with_delimiter(project.star_count)
|
||||||
%span.visibility-icon.has-tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(project)}
|
%span.visibility-icon.has-tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(project)}
|
||||||
= visibility_level_icon(project.visibility_level, fw: true)
|
= visibility_level_icon(project.visibility_level, fw: true)
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
class Gitlab::Seeder::Builds
|
class Gitlab::Seeder::Builds
|
||||||
STAGES = %w[build notify_build test notify_test deploy notify_deploy]
|
STAGES = %w[build test deploy notify]
|
||||||
BUILDS = [
|
BUILDS = [
|
||||||
{ name: 'build:linux', stage: 'build', status: :success },
|
{ name: 'build:linux', stage: 'build', status: :success },
|
||||||
{ name: 'build:osx', stage: 'build', status: :success },
|
{ name: 'build:osx', stage: 'build', status: :success },
|
||||||
{ name: 'slack post build', stage: 'notify_build', status: :success },
|
|
||||||
{ name: 'rspec:linux', stage: 'test', status: :success },
|
{ name: 'rspec:linux', stage: 'test', status: :success },
|
||||||
{ name: 'rspec:windows', stage: 'test', status: :success },
|
{ name: 'rspec:windows', stage: 'test', status: :success },
|
||||||
{ name: 'rspec:windows', stage: 'test', status: :success },
|
{ name: 'rspec:windows', stage: 'test', status: :success },
|
||||||
|
@ -12,9 +11,9 @@ class Gitlab::Seeder::Builds
|
||||||
{ name: 'spinach:osx', stage: 'test', status: :canceled },
|
{ name: 'spinach:osx', stage: 'test', status: :canceled },
|
||||||
{ name: 'cucumber:linux', stage: 'test', status: :running },
|
{ name: 'cucumber:linux', stage: 'test', status: :running },
|
||||||
{ name: 'cucumber:osx', stage: 'test', status: :failed },
|
{ name: 'cucumber:osx', stage: 'test', status: :failed },
|
||||||
{ name: 'slack post test', stage: 'notify_test', status: :success },
|
|
||||||
{ name: 'staging', stage: 'deploy', environment: 'staging', status: :success },
|
{ name: 'staging', stage: 'deploy', environment: 'staging', status: :success },
|
||||||
{ name: 'production', stage: 'deploy', environment: 'production', when: 'manual', status: :success },
|
{ name: 'production', stage: 'deploy', environment: 'production', when: 'manual', status: :skipped },
|
||||||
|
{ name: 'slack', stage: 'notify', when: 'manual', status: :created },
|
||||||
]
|
]
|
||||||
|
|
||||||
def initialize(project)
|
def initialize(project)
|
||||||
|
@ -25,7 +24,7 @@ class Gitlab::Seeder::Builds
|
||||||
pipelines.each do |pipeline|
|
pipelines.each do |pipeline|
|
||||||
begin
|
begin
|
||||||
BUILDS.each { |opts| build_create!(pipeline, opts) }
|
BUILDS.each { |opts| build_create!(pipeline, opts) }
|
||||||
commit_status_create!(pipeline, name: 'jenkins', status: :success)
|
commit_status_create!(pipeline, name: 'jenkins', stage: 'test', status: :success)
|
||||||
print '.'
|
print '.'
|
||||||
rescue ActiveRecord::RecordInvalid
|
rescue ActiveRecord::RecordInvalid
|
||||||
print 'F'
|
print 'F'
|
||||||
|
|
42
doc/user/project/description_templates.md
Normal file
42
doc/user/project/description_templates.md
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
# Description templates
|
||||||
|
|
||||||
|
>[Introduced][ce-4981] in GitLab 8.11.
|
||||||
|
|
||||||
|
Description templates allow you to define context-specific templates for issue
|
||||||
|
and merge request description fields for your project.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
By using the description templates, users that create a new issue or merge
|
||||||
|
request can select a description template to help them communicate with other
|
||||||
|
contributors effectively.
|
||||||
|
|
||||||
|
Every GitLab project can define its own set of description templates as they
|
||||||
|
are added to the root directory of a GitLab project's repository.
|
||||||
|
|
||||||
|
Description templates must be written in [Markdown](../markdown.md) and stored
|
||||||
|
in your project's repository under a directory named `.gitlab`. Only the
|
||||||
|
templates of the default branch will be taken into account.
|
||||||
|
|
||||||
|
## Creating issue templates
|
||||||
|
|
||||||
|
Create a new Markdown (`.md`) file inside the `.gitlab/issue_templates/`
|
||||||
|
directory in your repository. Commit and push to your default branch.
|
||||||
|
|
||||||
|
## Creating merge request templates
|
||||||
|
|
||||||
|
Similarly to issue templates, create a new Markdown (`.md`) file inside the
|
||||||
|
`.gitlab/merge_request_templates/` directory in your repository. Commit and
|
||||||
|
push to your default branch.
|
||||||
|
|
||||||
|
## Using the templates
|
||||||
|
|
||||||
|
Let's take for example that you've created the file `.gitlab/issue_templates/Bug.md`.
|
||||||
|
This will enable the `Bug` dropdown option when creating or editing issues. When
|
||||||
|
`Bug` is selected, the content from the `Bug.md` template file will be copied
|
||||||
|
to the issue description field. The 'Reset template' button will discard any
|
||||||
|
changes you made after picking the template and return it to its initial status.
|
||||||
|
|
||||||
|
![Description templates](img/description_templates.png)
|
||||||
|
|
||||||
|
[ce-4981]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4981
|
BIN
doc/user/project/img/description_templates.png
Normal file
BIN
doc/user/project/img/description_templates.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
- [Authorization for merge requests](authorization_for_merge_requests.md)
|
- [Authorization for merge requests](authorization_for_merge_requests.md)
|
||||||
- [Change your time zone](timezone.md)
|
- [Change your time zone](timezone.md)
|
||||||
|
- [Description templates](../user/project/description_templates.md)
|
||||||
- [Feature branch workflow](workflow.md)
|
- [Feature branch workflow](workflow.md)
|
||||||
- [GitLab Flow](gitlab_flow.md)
|
- [GitLab Flow](gitlab_flow.md)
|
||||||
- [Groups](groups.md)
|
- [Groups](groups.md)
|
||||||
|
@ -17,7 +18,6 @@
|
||||||
- [Share projects with other groups](share_projects_with_other_groups.md)
|
- [Share projects with other groups](share_projects_with_other_groups.md)
|
||||||
- [Web Editor](web_editor.md)
|
- [Web Editor](web_editor.md)
|
||||||
- [Releases](releases.md)
|
- [Releases](releases.md)
|
||||||
- [Issuable Templates](issuable_templates.md)
|
|
||||||
- [Milestones](milestones.md)
|
- [Milestones](milestones.md)
|
||||||
- [Merge Requests](merge_requests.md)
|
- [Merge Requests](merge_requests.md)
|
||||||
- [Revert changes](revert_changes.md)
|
- [Revert changes](revert_changes.md)
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
# Description templates
|
|
||||||
|
|
||||||
Description templates allow you to define context-specific templates for issue and merge request description fields for your project. When in use, users that create a new issue or merge request can select a description template to help them communicate with other contributors effectively.
|
|
||||||
|
|
||||||
Every GitLab project can define its own set of description templates as they are added to the root directory of a GitLab project's repository.
|
|
||||||
|
|
||||||
Description templates are written in markdown _(`.md`)_ and stored in your projects repository under the `/.gitlab/issue_templates/` and `/.gitlab/merge_request_templates/` directories.
|
|
||||||
|
|
||||||
![Description templates](img/description_templates.png)
|
|
||||||
|
|
||||||
_Example:_
|
|
||||||
`/.gitlab/issue_templates/bug.md` will enable the `bug` dropdown option for new issues. When `bug` is selected, the content from the `bug.md` template file will be copied to the issue description field.
|
|
Binary file not shown.
Before Width: | Height: | Size: 56 KiB |
|
@ -67,7 +67,7 @@ In all of the below cases, the notification will be sent to:
|
||||||
- Participants:
|
- Participants:
|
||||||
- the author and assignee of the issue/merge request
|
- the author and assignee of the issue/merge request
|
||||||
- authors of comments on the issue/merge request
|
- authors of comments on the issue/merge request
|
||||||
- anyone mentioned by `@username` in the issue/merge request description
|
- anyone mentioned by `@username` in the issue/merge request title or description
|
||||||
- anyone mentioned by `@username` in any of the comments on the issue/merge request
|
- anyone mentioned by `@username` in any of the comments on the issue/merge request
|
||||||
|
|
||||||
...with notification level "Participating" or higher
|
...with notification level "Participating" or higher
|
||||||
|
@ -89,6 +89,11 @@ In all of the below cases, the notification will be sent to:
|
||||||
| Merge merge request | |
|
| Merge merge request | |
|
||||||
| New comment | The above, plus anyone mentioned by `@username` in the comment, with notification level "Mention" or higher |
|
| New comment | The above, plus anyone mentioned by `@username` in the comment, with notification level "Mention" or higher |
|
||||||
|
|
||||||
|
|
||||||
|
In addition, if the title or description of an Issue or Merge Request is
|
||||||
|
changed, notifications will be sent to any **new** mentions by `@username` as
|
||||||
|
if they had been mentioned in the original text.
|
||||||
|
|
||||||
You won't receive notifications for Issues, Merge Requests or Milestones
|
You won't receive notifications for Issues, Merge Requests or Milestones
|
||||||
created by yourself. You will only receive automatic notifications when
|
created by yourself. You will only receive automatic notifications when
|
||||||
somebody else comments or adds changes to the ones that you've created or
|
somebody else comments or adds changes to the ones that you've created or
|
||||||
|
|
|
@ -45,6 +45,8 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
|
||||||
|
|
||||||
step 'I click link "All"' do
|
step 'I click link "All"' do
|
||||||
click_link "All"
|
click_link "All"
|
||||||
|
# Waits for load
|
||||||
|
expect(find('.issues-state-filters > .active')).to have_content 'All'
|
||||||
end
|
end
|
||||||
|
|
||||||
step 'I click link "Release 0.4"' do
|
step 'I click link "Release 0.4"' do
|
||||||
|
@ -354,8 +356,6 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter_issue(text)
|
def filter_issue(text)
|
||||||
sleep 1
|
|
||||||
fill_in 'issue_search', with: text
|
fill_in 'issue_search', with: text
|
||||||
sleep 1
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,6 +22,8 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
|
||||||
|
|
||||||
step 'I click link "All"' do
|
step 'I click link "All"' do
|
||||||
click_link "All"
|
click_link "All"
|
||||||
|
# Waits for load
|
||||||
|
expect(find('.issues-state-filters > .active')).to have_content 'All'
|
||||||
end
|
end
|
||||||
|
|
||||||
step 'I click link "Merged"' do
|
step 'I click link "Merged"' do
|
||||||
|
@ -489,7 +491,6 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
|
||||||
end
|
end
|
||||||
|
|
||||||
step 'I fill in merge request search with "Fe"' do
|
step 'I fill in merge request search with "Fe"' do
|
||||||
sleep 1
|
|
||||||
fill_in 'issue_search', with: "Fe"
|
fill_in 'issue_search', with: "Fe"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,11 @@ feature 'Create New Merge Request', feature: true, js: true do
|
||||||
project.team << [user, :master]
|
project.team << [user, :master]
|
||||||
|
|
||||||
login_as user
|
login_as user
|
||||||
visit namespace_project_merge_requests_path(project.namespace, project)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'generates a diff for an orphaned branch' do
|
it 'generates a diff for an orphaned branch' do
|
||||||
|
visit namespace_project_merge_requests_path(project.namespace, project)
|
||||||
|
|
||||||
click_link 'New Merge Request'
|
click_link 'New Merge Request'
|
||||||
expect(page).to have_content('Source branch')
|
expect(page).to have_content('Source branch')
|
||||||
expect(page).to have_content('Target branch')
|
expect(page).to have_content('Target branch')
|
||||||
|
@ -42,4 +43,20 @@ feature 'Create New Merge Request', feature: true, js: true do
|
||||||
expect(page).not_to have_content private_project.to_reference
|
expect(page).not_to have_content private_project.to_reference
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'allows to change the diff view' do
|
||||||
|
visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_branch: 'master', source_branch: 'fix' })
|
||||||
|
|
||||||
|
click_link 'Changes'
|
||||||
|
|
||||||
|
expect(page).to have_css('a.btn.active', text: 'Inline')
|
||||||
|
expect(page).not_to have_css('a.btn.active', text: 'Side-by-side')
|
||||||
|
|
||||||
|
click_link 'Side-by-side'
|
||||||
|
|
||||||
|
within '.merge-request' do
|
||||||
|
expect(page).not_to have_css('a.btn.active', text: 'Inline')
|
||||||
|
expect(page).to have_css('a.btn.active', text: 'Side-by-side')
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
20
spec/features/projects/issues/list_spec.rb
Normal file
20
spec/features/projects/issues/list_spec.rb
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
feature 'Issues List' do
|
||||||
|
let(:user) { create(:user) }
|
||||||
|
let(:project) { create(:empty_project) }
|
||||||
|
|
||||||
|
background do
|
||||||
|
project.team << [user, :developer]
|
||||||
|
|
||||||
|
login_as(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario 'user does not see create new list button' do
|
||||||
|
create(:issue, project: project)
|
||||||
|
|
||||||
|
visit namespace_project_issues_path(project.namespace, project)
|
||||||
|
|
||||||
|
expect(page).not_to have_selector('.js-new-board-list')
|
||||||
|
end
|
||||||
|
end
|
20
spec/features/projects/merge_requests/list_spec.rb
Normal file
20
spec/features/projects/merge_requests/list_spec.rb
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
feature 'Merge Requests List' do
|
||||||
|
let(:user) { create(:user) }
|
||||||
|
let(:project) { create(:project) }
|
||||||
|
|
||||||
|
background do
|
||||||
|
project.team << [user, :developer]
|
||||||
|
|
||||||
|
login_as(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario 'user does not see create new list button' do
|
||||||
|
create(:merge_request, source_project: project)
|
||||||
|
|
||||||
|
visit namespace_project_merge_requests_path(project.namespace, project)
|
||||||
|
|
||||||
|
expect(page).not_to have_selector('.js-new-board-list')
|
||||||
|
end
|
||||||
|
end
|
|
@ -193,7 +193,11 @@ describe "Pipelines" do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'playing manual build' do
|
context 'playing manual build' do
|
||||||
before { click_link('Play') }
|
before do
|
||||||
|
within '.pipeline-holder' do
|
||||||
|
click_link('Play')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
it { expect(@manual.reload).to be_pending }
|
it { expect(@manual.reload).to be_pending }
|
||||||
end
|
end
|
||||||
|
|
|
@ -319,5 +319,10 @@ describe Issues::UpdateService, services: true do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'updating mentions' do
|
||||||
|
let(:mentionable) { issue }
|
||||||
|
include_examples 'updating mentions', Issues::UpdateService
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -226,6 +226,11 @@ describe MergeRequests::UpdateService, services: true do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'updating mentions' do
|
||||||
|
let(:mentionable) { merge_request }
|
||||||
|
include_examples 'updating mentions', MergeRequests::UpdateService
|
||||||
|
end
|
||||||
|
|
||||||
context 'when MergeRequest has tasks' do
|
context 'when MergeRequest has tasks' do
|
||||||
before { update_merge_request({ description: "- [ ] Task 1\n- [ ] Task 2" }) }
|
before { update_merge_request({ description: "- [ ] Task 1\n- [ ] Task 2" }) }
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,28 @@ describe NotificationService, services: true do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
shared_examples 'notifications for new mentions' do
|
||||||
|
def send_notifications(*new_mentions)
|
||||||
|
reset_delivered_emails!
|
||||||
|
notification.send(notification_method, mentionable, new_mentions, @u_disabled)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends no emails when no new mentions are present' do
|
||||||
|
send_notifications
|
||||||
|
expect(ActionMailer::Base.deliveries).to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'emails new mentions with a watch level higher than participant' do
|
||||||
|
send_notifications(@u_watcher, @u_participant_mentioned, @u_custom_global)
|
||||||
|
should_only_email(@u_watcher, @u_participant_mentioned, @u_custom_global)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not email new mentions with a watch level equal to or less than participant' do
|
||||||
|
send_notifications(@u_participating, @u_mentioned)
|
||||||
|
expect(ActionMailer::Base.deliveries).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'Keys' do
|
describe 'Keys' do
|
||||||
describe '#new_key' do
|
describe '#new_key' do
|
||||||
let!(:key) { create(:personal_key) }
|
let!(:key) { create(:personal_key) }
|
||||||
|
@ -399,6 +421,13 @@ describe NotificationService, services: true do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#new_mentions_in_issue' do
|
||||||
|
let(:notification_method) { :new_mentions_in_issue }
|
||||||
|
let(:mentionable) { issue }
|
||||||
|
|
||||||
|
include_examples 'notifications for new mentions'
|
||||||
|
end
|
||||||
|
|
||||||
describe '#reassigned_issue' do
|
describe '#reassigned_issue' do
|
||||||
before do
|
before do
|
||||||
update_custom_notification(:reassign_issue, @u_guest_custom, project)
|
update_custom_notification(:reassign_issue, @u_guest_custom, project)
|
||||||
|
@ -700,6 +729,8 @@ describe NotificationService, services: true do
|
||||||
before do
|
before do
|
||||||
build_team(merge_request.target_project)
|
build_team(merge_request.target_project)
|
||||||
add_users_with_subscription(merge_request.target_project, merge_request)
|
add_users_with_subscription(merge_request.target_project, merge_request)
|
||||||
|
update_custom_notification(:new_merge_request, @u_guest_custom, project)
|
||||||
|
update_custom_notification(:new_merge_request, @u_custom_global)
|
||||||
ActionMailer::Base.deliveries.clear
|
ActionMailer::Base.deliveries.clear
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -763,6 +794,13 @@ describe NotificationService, services: true do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#new_mentions_in_merge_request' do
|
||||||
|
let(:notification_method) { :new_mentions_in_merge_request }
|
||||||
|
let(:mentionable) { merge_request }
|
||||||
|
|
||||||
|
include_examples 'notifications for new mentions'
|
||||||
|
end
|
||||||
|
|
||||||
describe '#reassigned_merge_request' do
|
describe '#reassigned_merge_request' do
|
||||||
before do
|
before do
|
||||||
update_custom_notification(:reassign_merge_request, @u_guest_custom, project)
|
update_custom_notification(:reassign_merge_request, @u_guest_custom, project)
|
||||||
|
|
|
@ -3,6 +3,16 @@ module EmailHelpers
|
||||||
ActionMailer::Base.deliveries.map(&:to).flatten.count(user.email) == 1
|
ActionMailer::Base.deliveries.map(&:to).flatten.count(user.email) == 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def reset_delivered_emails!
|
||||||
|
ActionMailer::Base.deliveries.clear
|
||||||
|
end
|
||||||
|
|
||||||
|
def should_only_email(*users)
|
||||||
|
users.each {|user| should_email(user) }
|
||||||
|
recipients = ActionMailer::Base.deliveries.flat_map(&:to)
|
||||||
|
expect(recipients.count).to eq(users.count)
|
||||||
|
end
|
||||||
|
|
||||||
def should_email(user)
|
def should_email(user)
|
||||||
expect(sent_to_user?(user)).to be_truthy
|
expect(sent_to_user?(user)).to be_truthy
|
||||||
end
|
end
|
||||||
|
|
32
spec/support/updating_mentions_shared_examples.rb
Normal file
32
spec/support/updating_mentions_shared_examples.rb
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
RSpec.shared_examples 'updating mentions' do |service_class|
|
||||||
|
let(:mentioned_user) { create(:user) }
|
||||||
|
let(:service_class) { service_class }
|
||||||
|
|
||||||
|
before { project.team << [mentioned_user, :developer] }
|
||||||
|
|
||||||
|
def update_mentionable(opts)
|
||||||
|
reset_delivered_emails!
|
||||||
|
|
||||||
|
perform_enqueued_jobs do
|
||||||
|
service_class.new(project, user, opts).execute(mentionable)
|
||||||
|
end
|
||||||
|
|
||||||
|
mentionable.reload
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'in title' do
|
||||||
|
before { update_mentionable(title: mentioned_user.to_reference) }
|
||||||
|
|
||||||
|
it 'emails only the newly-mentioned user' do
|
||||||
|
should_only_email(mentioned_user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'in description' do
|
||||||
|
before { update_mentionable(description: mentioned_user.to_reference) }
|
||||||
|
|
||||||
|
it 'emails only the newly-mentioned user' do
|
||||||
|
should_only_email(mentioned_user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue