resolves conflicts with new buttons
@@ -1,13 +1,21 @@
|
||||
Please view this file on the master branch, on stable branches it's out of date.
|
||||
|
||||
v 8.3.0 (unreleased)
|
||||
v 8.4.0 (unreleased)
|
||||
- Implement new UI for group page
|
||||
- Add project permissions to all project API endpoints (Stan Hu)
|
||||
|
||||
v 8.3.0
|
||||
- Add CAS support (tduehr)
|
||||
- Bump rack-attack to 4.3.1 for security fix (Stan Hu)
|
||||
- API support for starred projects for authorized user (Zeger-Jan van de Weg)
|
||||
- Add link to merge request on build detail page.
|
||||
- Add open_issues_count to project API (Stan Hu)
|
||||
- Expand character set of usernames created by Omniauth (Corey Hinshaw)
|
||||
- Add button to automatically merge a merge request when the build succeeds (Zeger-Jan van de Weg)
|
||||
- Provide better diagnostic message upon project creation errors (Stan Hu)
|
||||
- Bump devise to 3.5.3 to fix reset token expiring after account creation (Stan Hu)
|
||||
- Remove api credentials from link to build_page
|
||||
- Deprecate GitLabCiService making it to always be inactive
|
||||
- Bump gollum-lib to 4.1.0 (Stan Hu)
|
||||
- Fix broken group avatar upload under "New group" (Stan Hu)
|
||||
- Update project repositorize size and commit count during import:repos task (Stan Hu)
|
||||
@@ -19,8 +27,10 @@ v 8.3.0 (unreleased)
|
||||
- Fix 500 error when update group member permission
|
||||
- Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera)
|
||||
- Recognize issue/MR/snippet/commit links as references
|
||||
- Backport JIRA features from EE to CE
|
||||
- Add ignore whitespace change option to commit view
|
||||
- Fire update hook from GitLab
|
||||
- Allow account unlock via email
|
||||
- Style warning about mentioning many people in a comment
|
||||
- Fix: sort milestones by due date once again (Greg Smethells)
|
||||
- Migrate all CI::Services and CI::WebHooks to Services and WebHooks
|
||||
@@ -58,6 +68,7 @@ v 8.3.0 (unreleased)
|
||||
- Do not show build status unless builds are enabled and `.gitlab-ci.yml` is present
|
||||
- Persist runners registration token in database
|
||||
- Fix online editor should not remove newlines at the end of the file
|
||||
- Expose Git's version in the admin area
|
||||
|
||||
v 8.2.3
|
||||
- Fix application settings cache not expiring after changes (Stan Hu)
|
||||
|
||||
@@ -358,7 +358,7 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
|
||||
[core team]: https://about.gitlab.com/core-team/
|
||||
[getting help page]: https://about.gitlab.com/getting-help/
|
||||
[Codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq
|
||||
[up-for-grabs]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=up+for+grabs
|
||||
[up-for-grabs]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=up-for-grabs
|
||||
[medium-up-for-grabs]: https://medium.com/@kentcdodds/first-timers-only-78281ea47455
|
||||
[ce-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/issues
|
||||
[ee-tracker]: https://gitlab.com/gitlab-org/gitlab-ee/issues
|
||||
|
||||
@@ -23,6 +23,7 @@ gem 'devise-async', '~> 0.9.0'
|
||||
gem 'doorkeeper', '~> 2.2.0'
|
||||
gem 'omniauth', '~> 1.2.2'
|
||||
gem 'omniauth-bitbucket', '~> 0.0.2'
|
||||
gem 'omniauth-cas3', '~> 1.1.2'
|
||||
gem 'omniauth-facebook', '~> 3.0.0'
|
||||
gem 'omniauth-github', '~> 1.1.1'
|
||||
gem 'omniauth-gitlab', '~> 1.0.0'
|
||||
@@ -101,6 +102,9 @@ gem 'wikicloth', '0.8.1'
|
||||
gem 'asciidoctor', '~> 1.5.2'
|
||||
gem 'rouge', '~> 1.10.1'
|
||||
|
||||
# See https://groups.google.com/forum/#!topic/ruby-security-ann/aSbgDiwb24s
|
||||
gem 'nokogiri', '1.6.7.1'
|
||||
|
||||
# Diffs
|
||||
gem 'diffy', '~> 3.0.3'
|
||||
|
||||
@@ -186,7 +190,7 @@ gem 'mousetrap-rails', '~> 1.4.6'
|
||||
# Detect and convert string character encoding
|
||||
gem 'charlock_holmes', '~> 0.7.3'
|
||||
|
||||
gem "sass-rails", '~> 4.0.5'
|
||||
gem "sass-rails", '~> 5.0.0'
|
||||
gem "coffee-rails", '~> 4.1.0'
|
||||
gem "uglifier", '~> 2.7.2'
|
||||
gem 'turbolinks', '~> 2.5.0'
|
||||
@@ -198,9 +202,9 @@ gem 'font-awesome-rails', '~> 4.2'
|
||||
gem 'gitlab_emoji', '~> 0.2.0'
|
||||
gem 'gon', '~> 6.0.1'
|
||||
gem 'jquery-atwho-rails', '~> 1.3.2'
|
||||
gem 'jquery-rails', '~> 3.1.3'
|
||||
gem 'jquery-rails', '~> 4.0.0'
|
||||
gem 'jquery-scrollto-rails', '~> 1.4.3'
|
||||
gem 'jquery-ui-rails', '~> 4.2.1'
|
||||
gem 'jquery-ui-rails', '~> 5.0.0'
|
||||
gem 'nprogress-rails', '~> 0.1.6.7'
|
||||
gem 'raphael-rails', '~> 2.1.2'
|
||||
gem 'request_store', '~> 1.2.0'
|
||||
|
||||
@@ -372,15 +372,16 @@ GEM
|
||||
inflecto (0.0.2)
|
||||
ipaddress (0.8.0)
|
||||
jquery-atwho-rails (1.3.2)
|
||||
jquery-rails (3.1.4)
|
||||
railties (>= 3.0, < 5.0)
|
||||
jquery-rails (4.0.5)
|
||||
rails-dom-testing (~> 1.0)
|
||||
railties (>= 4.2.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
jquery-scrollto-rails (1.4.3)
|
||||
railties (> 3.1, < 5.0)
|
||||
jquery-turbolinks (2.1.0)
|
||||
railties (>= 3.1.0)
|
||||
turbolinks
|
||||
jquery-ui-rails (4.2.1)
|
||||
jquery-ui-rails (5.0.5)
|
||||
railties (>= 3.2.16)
|
||||
json (1.8.3)
|
||||
jwt (1.5.2)
|
||||
@@ -420,7 +421,7 @@ GEM
|
||||
grape
|
||||
newrelic_rpm
|
||||
newrelic_rpm (3.9.4.245)
|
||||
nokogiri (1.6.7)
|
||||
nokogiri (1.6.7.1)
|
||||
mini_portile2 (~> 2.0.0.rc2)
|
||||
nprogress-rails (0.1.6.7)
|
||||
oauth (0.4.7)
|
||||
@@ -439,6 +440,10 @@ GEM
|
||||
multi_json (~> 1.7)
|
||||
omniauth (~> 1.1)
|
||||
omniauth-oauth (~> 1.0)
|
||||
omniauth-cas3 (1.1.3)
|
||||
addressable (~> 2.3)
|
||||
nokogiri (~> 1.6.6)
|
||||
omniauth (~> 1.2)
|
||||
omniauth-facebook (3.0.0)
|
||||
omniauth-oauth2 (~> 1.2)
|
||||
omniauth-github (1.1.2)
|
||||
@@ -643,12 +648,13 @@ GEM
|
||||
safe_yaml (1.0.4)
|
||||
sanitize (2.1.0)
|
||||
nokogiri (>= 1.4.4)
|
||||
sass (3.2.19)
|
||||
sass-rails (4.0.5)
|
||||
sass (3.4.20)
|
||||
sass-rails (5.0.4)
|
||||
railties (>= 4.0.0, < 5.0)
|
||||
sass (~> 3.2.2)
|
||||
sprockets (~> 2.8, < 3.0)
|
||||
sprockets-rails (~> 2.0)
|
||||
sass (~> 3.1)
|
||||
sprockets (>= 2.8, < 4.0)
|
||||
sprockets-rails (>= 2.0, < 4.0)
|
||||
tilt (>= 1.1, < 3)
|
||||
sawyer (0.6.0)
|
||||
addressable (~> 2.3.5)
|
||||
faraday (~> 0.8, < 0.10)
|
||||
@@ -874,10 +880,10 @@ DEPENDENCIES
|
||||
html-pipeline (~> 1.11.0)
|
||||
httparty (~> 0.13.3)
|
||||
jquery-atwho-rails (~> 1.3.2)
|
||||
jquery-rails (~> 3.1.3)
|
||||
jquery-rails (~> 4.0.0)
|
||||
jquery-scrollto-rails (~> 1.4.3)
|
||||
jquery-turbolinks (~> 2.1.0)
|
||||
jquery-ui-rails (~> 4.2.1)
|
||||
jquery-ui-rails (~> 5.0.0)
|
||||
kaminari (~> 0.16.3)
|
||||
letter_opener (~> 1.1.2)
|
||||
mail_room (~> 0.6.1)
|
||||
@@ -888,11 +894,13 @@ DEPENDENCIES
|
||||
net-ssh (~> 3.0.1)
|
||||
newrelic-grape
|
||||
newrelic_rpm (~> 3.9.4.245)
|
||||
nokogiri (= 1.6.7.1)
|
||||
nprogress-rails (~> 0.1.6.7)
|
||||
oauth2 (~> 1.0.0)
|
||||
octokit (~> 3.7.0)
|
||||
omniauth (~> 1.2.2)
|
||||
omniauth-bitbucket (~> 0.0.2)
|
||||
omniauth-cas3 (~> 1.1.2)
|
||||
omniauth-facebook (~> 3.0.0)
|
||||
omniauth-github (~> 1.1.1)
|
||||
omniauth-gitlab (~> 1.0.0)
|
||||
@@ -928,7 +936,7 @@ DEPENDENCIES
|
||||
rubocop (~> 0.35.0)
|
||||
ruby-fogbugz (~> 0.2.1)
|
||||
sanitize (~> 2.0)
|
||||
sass-rails (~> 4.0.5)
|
||||
sass-rails (~> 5.0.0)
|
||||
sdoc (~> 0.3.20)
|
||||
seed-fu (~> 2.3.5)
|
||||
select2-rails (~> 3.5.9)
|
||||
|
||||
|
After Width: | Height: | Size: 813 KiB |
@@ -5,7 +5,7 @@
|
||||
# the compiled file.
|
||||
#
|
||||
#= require jquery
|
||||
#= require jquery.ui.all
|
||||
#= require jquery-ui
|
||||
#= require jquery_ujs
|
||||
#= require jquery.cookie
|
||||
#= require jquery.endless-scroll
|
||||
|
||||
@@ -1,12 +1,23 @@
|
||||
class @AwardsHandler
|
||||
constructor: (@post_emoji_url, @noteable_type, @noteable_id, @aliases) ->
|
||||
$(".add-award").click (event)->
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
$(".emoji-menu").show()
|
||||
|
||||
$("html").click ->
|
||||
if !$(event.target).closest(".emoji-menu").length
|
||||
if $(".emoji-menu").is(":visible")
|
||||
$(".emoji-menu").hide()
|
||||
|
||||
addAward: (emoji) ->
|
||||
emoji = @normilizeEmojiName(emoji)
|
||||
@postEmoji emoji, =>
|
||||
@addAwardToEmojiBar(emoji)
|
||||
|
||||
$(".emoji-menu").hide()
|
||||
|
||||
addAwardToEmojiBar: (emoji, custom_path = '') ->
|
||||
addAwardToEmojiBar: (emoji) ->
|
||||
emoji = @normilizeEmojiName(emoji)
|
||||
if @exist(emoji)
|
||||
if @isActive(emoji)
|
||||
@@ -17,7 +28,7 @@ class @AwardsHandler
|
||||
counter.parent().addClass("active")
|
||||
@addMeToAuthorList(emoji)
|
||||
else
|
||||
@createEmoji(emoji, custom_path)
|
||||
@createEmoji(emoji)
|
||||
|
||||
exist: (emoji) ->
|
||||
@findEmojiIcon(emoji).length > 0
|
||||
@@ -54,31 +65,29 @@ class @AwardsHandler
|
||||
resetTooltip: (award) ->
|
||||
award.tooltip("destroy")
|
||||
|
||||
# "destroy" call is asynchronous, this is why we need to set timeout.
|
||||
# "destroy" call is asynchronous and there is no appropriate callback on it, this is why we need to set timeout.
|
||||
setTimeout (->
|
||||
award.tooltip()
|
||||
), 200
|
||||
|
||||
|
||||
createEmoji: (emoji, custom_path) ->
|
||||
createEmoji: (emoji) ->
|
||||
emojiCssClass = @resolveNameToCssClass(emoji)
|
||||
|
||||
nodes = []
|
||||
nodes.push("<div class='award active' title='me'>")
|
||||
nodes.push("<div class='icon' data-emoji='" + emoji + "'>")
|
||||
nodes.push(@getImage(emoji, custom_path))
|
||||
nodes.push("<div class='icon emoji-icon " + emojiCssClass + "' data-emoji='" + emoji + "'></div>")
|
||||
nodes.push("<div class='counter'>1</div>")
|
||||
nodes.push("</div>")
|
||||
nodes.push("<div class='counter'>1")
|
||||
nodes.push("</div></div>")
|
||||
|
||||
$(".awards-controls").before(nodes.join("\n"))
|
||||
emoji_node = $(nodes.join("\n")).insertBefore(".awards-controls").find(".emoji-icon").data("emoji", emoji)
|
||||
|
||||
$(".award").tooltip()
|
||||
|
||||
getImage: (emoji, custom_path) ->
|
||||
if custom_path
|
||||
$("<img>").attr({src: custom_path, width: 20, height: 20}).wrap("<div>").parent().html()
|
||||
else
|
||||
$("li[data-emoji='" + emoji + "']").html()
|
||||
resolveNameToCssClass: (emoji) ->
|
||||
unicodeName = $(".emoji-menu-content [data-emoji='?']".replace("?", emoji)).data("unicode-name")
|
||||
|
||||
"emoji-" + unicodeName
|
||||
|
||||
postEmoji: (emoji, callback) ->
|
||||
$.post @post_emoji_url, { note: {
|
||||
@@ -90,7 +99,7 @@ class @AwardsHandler
|
||||
callback.call()
|
||||
|
||||
findEmojiIcon: (emoji) ->
|
||||
$(".icon[data-emoji='" + emoji + "']")
|
||||
$(".award [data-emoji='" + emoji + "']")
|
||||
|
||||
scrollToAwards: ->
|
||||
$('body, html').animate({
|
||||
|
||||
@@ -127,7 +127,7 @@ class @Notes
|
||||
@initTaskList()
|
||||
|
||||
if note.award
|
||||
awards_handler.addAwardToEmojiBar(note.note, note.emoji_path)
|
||||
awards_handler.addAwardToEmojiBar(note.note)
|
||||
awards_handler.scrollToAwards()
|
||||
|
||||
###
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
* This is a manifest file that'll automatically include all the stylesheets available in this directory
|
||||
* and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
|
||||
* the top of the compiled file, but it's generally better to create a new file per style scope.
|
||||
*= require jquery.ui.datepicker
|
||||
*= require jquery.ui.autocomplete
|
||||
*= require jquery-ui/datepicker
|
||||
*= require jquery-ui/autocomplete
|
||||
*= require jquery.atwho
|
||||
*= require select2
|
||||
*= require_self
|
||||
@@ -48,4 +48,4 @@
|
||||
/*
|
||||
* Styles for JS behaviors.
|
||||
*/
|
||||
@import "behaviors.scss";
|
||||
@import "behaviors.scss";
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
|
||||
.cover-block {
|
||||
text-align: center;
|
||||
background: #f7f8fa;
|
||||
background: $background-color;
|
||||
margin: -$gl-padding;
|
||||
margin-bottom: 0;
|
||||
padding: 44px $gl-padding;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
@mixin btn-default {
|
||||
@include border-radius(2px);
|
||||
@include border-radius(3px);
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
text-transform: uppercase;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
line-height: 18px;
|
||||
padding: 11px $gl-padding;
|
||||
letter-spacing: .4px;
|
||||
@@ -18,7 +17,7 @@
|
||||
|
||||
@mixin btn-middle {
|
||||
@include btn-default;
|
||||
@include border-radius(2px);
|
||||
@include border-radius(3px);
|
||||
padding: 11px 24px;
|
||||
}
|
||||
|
||||
@@ -51,6 +50,10 @@
|
||||
@include btn-color($blue-light, $border-blue-light, $blue-normal, $border-blue-normal, $blue-dark, $border-blue-dark, #FFFFFF);
|
||||
}
|
||||
|
||||
@mixin btn-blue-medium {
|
||||
@include btn-color($blue-medium-light, $border-blue-light, $blue-medium, $border-blue-normal, $blue-medium-dark, $border-blue-dark, #FFFFFF);
|
||||
}
|
||||
|
||||
@mixin btn-orange {
|
||||
@include btn-color($orange-light, $border-orange-light, $orange-normal, $border-orange-normal, $orange-dark, $border-orange-dark, #FFFFFF);
|
||||
}
|
||||
@@ -60,7 +63,7 @@
|
||||
}
|
||||
|
||||
@mixin btn-gray {
|
||||
@include btn-color($gray-light, $border-gray-light, $gray-normal, $border-gray-normal, $gray-dark, $border-gray-dark, #313236);
|
||||
@include btn-color($gray-light, $border-gray-light, $gray-normal, $border-gray-light, $gray-dark, $border-gray-dark, #313236);
|
||||
}
|
||||
|
||||
@mixin btn-white {
|
||||
@@ -75,6 +78,10 @@
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
&.btn-nr {
|
||||
padding: 7px 10px;
|
||||
}
|
||||
|
||||
&.btn-xs {
|
||||
padding: 1px 5px;
|
||||
}
|
||||
@@ -91,11 +98,15 @@
|
||||
@include btn-gray;
|
||||
}
|
||||
|
||||
&.btn-primary,
|
||||
&.btn-primary {
|
||||
@include btn-blue-medium;
|
||||
}
|
||||
|
||||
&.btn-info {
|
||||
@include btn-blue;
|
||||
}
|
||||
|
||||
&.btn-close,
|
||||
&.btn-warning {
|
||||
@include btn-orange;
|
||||
}
|
||||
@@ -110,20 +121,8 @@
|
||||
float: right;
|
||||
}
|
||||
|
||||
&.btn-close {
|
||||
color: $gl-danger;
|
||||
border-color: $gl-danger;
|
||||
&:hover {
|
||||
color: #B94A48;
|
||||
}
|
||||
}
|
||||
|
||||
&.btn-reopen {
|
||||
color: $gl-success;
|
||||
border-color: $gl-success;
|
||||
&:hover {
|
||||
color: #468847;
|
||||
}
|
||||
/* should be same as parent class for now */
|
||||
}
|
||||
|
||||
&.btn-grouped {
|
||||
|
||||
@@ -374,7 +374,7 @@ table {
|
||||
}
|
||||
}
|
||||
|
||||
.center-top-menu {
|
||||
.center-top-menu, .left-top-menu {
|
||||
@include nav-menu;
|
||||
text-align: center;
|
||||
margin-top: 5px;
|
||||
@@ -408,6 +408,11 @@ table {
|
||||
}
|
||||
}
|
||||
|
||||
.left-top-menu {
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #EEE;
|
||||
}
|
||||
|
||||
.center-middle-menu {
|
||||
@include nav-menu;
|
||||
padding: 0;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
.status-box {
|
||||
@include border-radius(2px);
|
||||
@include border-radius(3px);
|
||||
|
||||
display: block;
|
||||
float: left;
|
||||
@@ -25,7 +25,7 @@
|
||||
}
|
||||
|
||||
&.status-box-open {
|
||||
background-color: #019875;
|
||||
background-color: $green-light;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ html {
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #EAEBEC !important;
|
||||
background-color: #F3F3F3 !important;
|
||||
|
||||
&.navless {
|
||||
background-color: white !important;
|
||||
|
||||
@@ -123,7 +123,6 @@
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
margin-top: 5px;
|
||||
height: 56px;
|
||||
|
||||
li {
|
||||
@@ -131,9 +130,9 @@
|
||||
|
||||
a {
|
||||
padding: 14px;
|
||||
font-size: 17px;
|
||||
font-size: 15px;
|
||||
line-height: 28px;
|
||||
color: #7f8fa4;
|
||||
color: #959494;
|
||||
border-bottom: 2px solid transparent;
|
||||
|
||||
&:hover, &:active, &:focus {
|
||||
@@ -143,8 +142,8 @@
|
||||
}
|
||||
|
||||
&.active a {
|
||||
color: #4c4e54;
|
||||
border-bottom: 2px solid #1cacfc;
|
||||
color: #616060;
|
||||
border-bottom: 2px solid #4688f1;
|
||||
}
|
||||
|
||||
.badge {
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.center-top-menu {
|
||||
.center-top-menu, .left-top-menu {
|
||||
li a {
|
||||
font-size: 14px;
|
||||
padding: 19px 10px;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
$hover: #FFFAF1;
|
||||
$hover: #faf9f9;
|
||||
$gl-text-color: #54565B;
|
||||
$gl-text-green: #4A2;
|
||||
$gl-text-red: #D12F19;
|
||||
$gl-text-orange: #D90;
|
||||
$gl-header-color: #4c4e54;
|
||||
$gl-header-color: #323232;
|
||||
$gl-link-color: #333c48;
|
||||
$md-text-color: #444;
|
||||
$md-link-color: #3084bb;
|
||||
@@ -15,13 +15,14 @@ $sidebar_width: 230px;
|
||||
$avatar_radius: 50%;
|
||||
$code_font_size: 13px;
|
||||
$code_line_height: 1.5;
|
||||
$border-color: #dce0e6;
|
||||
$border-color: #efeff1;
|
||||
$table-border-color: #eef0f2;
|
||||
$background-color: #F7F8FA;
|
||||
$background-color: #faf9f9;
|
||||
$header-height: 58px;
|
||||
$fixed-layout-width: 1280px;
|
||||
$gl-gray: #7f8fa4;
|
||||
$gl-gray: #5a5a5a;
|
||||
$gl-padding: 16px;
|
||||
$gl-padding-top:10px;
|
||||
$gl-avatar-size: 46px;
|
||||
|
||||
/*
|
||||
@@ -29,12 +30,12 @@ $gl-avatar-size: 46px;
|
||||
*/
|
||||
|
||||
$white-light: #FFFFFF;
|
||||
$white-normal: #DCE0E5;
|
||||
$white-dark: #E4E7ED;
|
||||
$white-normal: #ededed;
|
||||
$white-dark: #ededed;
|
||||
|
||||
$gray-light: #F0F2F5;
|
||||
$gray-normal: #DCE0E5;
|
||||
$gray-dark: #E4E7ED;
|
||||
$gray-light: #f7f7f7;
|
||||
$gray-normal: #ededed;
|
||||
$gray-dark: #ededed;
|
||||
|
||||
$green-light: #31AF64;
|
||||
$green-normal: #2FAA60;
|
||||
@@ -44,6 +45,10 @@ $blue-light: #2EA8E5;
|
||||
$blue-normal: #2D9FD8;
|
||||
$blue-dark: #2897CE;
|
||||
|
||||
$blue-medium-light: #3498CB;
|
||||
$blue-medium: #2F8EBF;
|
||||
$blue-medium-dark: #2D86B4;
|
||||
|
||||
$orange-light: #FC6443;
|
||||
$orange-normal: #E75E40;
|
||||
$orange-dark: #CE5237;
|
||||
@@ -52,11 +57,11 @@ $red-light: #F43263;
|
||||
$red-normal: #E52C5A;
|
||||
$red-dark: #D22852;
|
||||
|
||||
$border-white-light: #E3E7EC;
|
||||
$border-white-light: #F1F2F4;
|
||||
$border-white-normal: #D6DAE2;
|
||||
$border-white-dark: #C6CACF;
|
||||
|
||||
$border-gray-light: #DCE0E5;
|
||||
$border-gray-light: #d1d1d1;
|
||||
$border-gray-normal: #D6DAE2;
|
||||
$border-gray-dark: #C6CACF;
|
||||
|
||||
@@ -76,6 +81,8 @@ $border-red-light: #E52C5A;
|
||||
$border-red-normal: #D22852;
|
||||
$border-red-dark: #CA264F;
|
||||
|
||||
/* header */
|
||||
$light-grey-header: #faf9f9;
|
||||
|
||||
/*
|
||||
* State colors:
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
@include clearfix;
|
||||
line-height: 34px;
|
||||
|
||||
.emoji-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin: 7px 0 0 5px;
|
||||
}
|
||||
|
||||
.award {
|
||||
@include border-radius(5px);
|
||||
|
||||
@@ -40,6 +46,7 @@
|
||||
}
|
||||
|
||||
.awards-controls {
|
||||
position: relative;
|
||||
margin-left: 10px;
|
||||
float: left;
|
||||
|
||||
@@ -55,32 +62,60 @@
|
||||
}
|
||||
}
|
||||
|
||||
.awards-menu {
|
||||
padding: $gl-padding;
|
||||
min-width: 214px;
|
||||
.emoji-menu{
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
display: none;
|
||||
float: left;
|
||||
min-width: 160px;
|
||||
padding: 5px 0;
|
||||
margin: 2px 0 0;
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
list-style: none;
|
||||
background-color: #fff;
|
||||
-webkit-background-clip: padding-box;
|
||||
background-clip: padding-box;
|
||||
border: 1px solid #ccc;
|
||||
border: 1px solid rgba(0,0,0,.15);
|
||||
border-radius: 4px;
|
||||
-webkit-box-shadow: 0 6px 12px rgba(0,0,0,.175);
|
||||
box-shadow: 0 6px 12px rgba(0,0,0,.175);
|
||||
|
||||
> li {
|
||||
cursor: pointer;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
text-align: center;
|
||||
@include border-radius(5px);
|
||||
.emoji-menu-content {
|
||||
padding: $gl-padding;
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
overflow-y: scroll;
|
||||
|
||||
img {
|
||||
margin-bottom: 2px;
|
||||
h5 {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #ccc;
|
||||
ul {
|
||||
list-style-type: none;
|
||||
margin-left: -20px;
|
||||
margin-bottom: 20px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
li {
|
||||
cursor: pointer;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
text-align: center;
|
||||
float: left;
|
||||
margin: 3px;
|
||||
list-decorate: none;
|
||||
@include border-radius(5px);
|
||||
|
||||
&:hover {
|
||||
background-color: #ccc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.awards-menu{
|
||||
li {
|
||||
float: left;
|
||||
margin: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
border-bottom: 1px solid $border-color;
|
||||
color: #5c5d5e;
|
||||
font-size: 16px;
|
||||
line-height: 42px;
|
||||
line-height: 34px;
|
||||
|
||||
.author {
|
||||
color: #5c5d5e;
|
||||
|
||||
@@ -75,16 +75,15 @@
|
||||
|
||||
.common-note-form {
|
||||
margin: 0;
|
||||
background: #F7F8FA;
|
||||
background: #fff;
|
||||
padding: $gl-padding;
|
||||
margin-left: -$gl-padding;
|
||||
margin-right: -$gl-padding;
|
||||
border-top: 1px solid $border-color;
|
||||
margin-bottom: -$gl-padding;
|
||||
}
|
||||
|
||||
.note-form-actions {
|
||||
background: #F9F9F9;
|
||||
background: #fff;
|
||||
|
||||
.note-form-option {
|
||||
margin-top: 8px;
|
||||
|
||||
@@ -128,7 +128,7 @@ ul.notes {
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
border-bottom: 1px solid $border-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,6 +335,36 @@ ul.nav.nav-projects-tabs {
|
||||
}
|
||||
}
|
||||
|
||||
.top-area {
|
||||
border-bottom: 1px solid #EEE;
|
||||
|
||||
ul.left-top-menu {
|
||||
display: inline-block;
|
||||
width: 50%;
|
||||
margin-bottom: 0px;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.projects-search-form {
|
||||
width: 50%;
|
||||
display: inline-block;
|
||||
float: right;
|
||||
padding-top: 7px;
|
||||
text-align: right;
|
||||
|
||||
.btn-green {
|
||||
margin-top: -2px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $screen-xs-max) {
|
||||
.projects-search-form {
|
||||
padding-top: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fork-namespaces {
|
||||
.fork-thumbnail {
|
||||
text-align: center;
|
||||
@@ -412,11 +442,18 @@ pre.light-well {
|
||||
|
||||
.projects-search-form {
|
||||
margin: -$gl-padding;
|
||||
background-color: #f8fafc;
|
||||
padding: $gl-padding;
|
||||
margin-bottom: 0px;
|
||||
border-top: 1px solid #e7e9ed;
|
||||
border-bottom: 1px solid #e7e9ed;
|
||||
|
||||
input {
|
||||
display: inline-block;
|
||||
width: calc(100% - 151px);
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
width: 135px;
|
||||
}
|
||||
}
|
||||
|
||||
.git-empty {
|
||||
|
||||
@@ -1,6 +1,21 @@
|
||||
class Admin::IdentitiesController < Admin::ApplicationController
|
||||
before_action :user
|
||||
before_action :identity, except: :index
|
||||
before_action :identity, except: [:index, :new, :create]
|
||||
|
||||
def new
|
||||
@identity = Identity.new
|
||||
end
|
||||
|
||||
def create
|
||||
@identity = Identity.new(identity_params)
|
||||
@identity.user_id = user.id
|
||||
|
||||
if @identity.save
|
||||
redirect_to admin_user_identities_path(@user), notice: 'User identity was successfully created.'
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def index
|
||||
@identities = @user.identities
|
||||
|
||||
@@ -10,6 +10,7 @@ class ApplicationController < ActionController::Base
|
||||
|
||||
before_action :authenticate_user_from_token!
|
||||
before_action :authenticate_user!
|
||||
before_action :validate_user_service_ticket!
|
||||
before_action :reject_blocked!
|
||||
before_action :check_password_expiration
|
||||
before_action :ldap_security_check
|
||||
@@ -202,6 +203,20 @@ class ApplicationController < ActionController::Base
|
||||
end
|
||||
end
|
||||
|
||||
def validate_user_service_ticket!
|
||||
return unless signed_in? && session[:service_tickets]
|
||||
|
||||
valid = session[:service_tickets].all? do |provider, ticket|
|
||||
Gitlab::OAuth::Session.valid?(provider, ticket)
|
||||
end
|
||||
|
||||
unless valid
|
||||
session[:service_tickets] = nil
|
||||
sign_out current_user
|
||||
redirect_to new_user_session_path
|
||||
end
|
||||
end
|
||||
|
||||
def check_password_expiration
|
||||
if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now && !current_user.ldap_user?
|
||||
redirect_to new_profile_password_path and return
|
||||
|
||||
@@ -19,8 +19,10 @@ module Ci
|
||||
@error = e.message
|
||||
@status = false
|
||||
rescue
|
||||
@error = "Undefined error"
|
||||
@error = 'Undefined error'
|
||||
@status = false
|
||||
ensure
|
||||
render :show
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||
|
||||
protect_from_forgery except: [:kerberos, :saml]
|
||||
protect_from_forgery except: [:kerberos, :saml, :cas3]
|
||||
|
||||
Gitlab.config.omniauth.providers.each do |provider|
|
||||
define_method provider['name'] do
|
||||
@@ -42,6 +42,14 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||
render 'errors/omniauth_error', layout: "errors", status: 422
|
||||
end
|
||||
|
||||
def cas3
|
||||
ticket = params['ticket']
|
||||
if ticket
|
||||
handle_service_ticket oauth['provider'], ticket
|
||||
end
|
||||
handle_omniauth
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def handle_omniauth
|
||||
@@ -84,6 +92,12 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||
redirect_to new_user_session_path
|
||||
end
|
||||
|
||||
def handle_service_ticket provider, ticket
|
||||
Gitlab::OAuth::Session.create provider, ticket
|
||||
session[:service_tickets] ||= {}
|
||||
session[:service_tickets][provider] = ticket
|
||||
end
|
||||
|
||||
def oauth
|
||||
@oauth ||= request.env['omniauth.auth']
|
||||
end
|
||||
|
||||
@@ -139,7 +139,6 @@ class Projects::NotesController < Projects::ApplicationController
|
||||
discussion_id: note.discussion_id,
|
||||
html: note_to_html(note),
|
||||
award: note.is_award,
|
||||
emoji_path: note.is_award ? view_context.image_url(::AwardEmoji.path_to_emoji_image(note.note)) : "",
|
||||
note: note.note,
|
||||
discussion_html: note_to_discussion_html(note),
|
||||
discussion_with_diff_html: note_to_discussion_with_diff_html(note)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class Projects::ServicesController < Projects::ApplicationController
|
||||
ALLOWED_PARAMS = [:title, :token, :type, :active, :api_key, :api_version, :subdomain,
|
||||
ALLOWED_PARAMS = [:title, :token, :type, :active, :api_key, :api_url, :api_version, :subdomain,
|
||||
:room, :recipients, :project_url, :webhook,
|
||||
:user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
|
||||
:build_key, :server, :teamcity_url, :drone_url, :build_type,
|
||||
@@ -10,7 +10,8 @@ class Projects::ServicesController < Projects::ApplicationController
|
||||
:notify_only_broken_builds, :add_pusher,
|
||||
:send_from_committer_email, :disable_diffs, :external_wiki_url,
|
||||
:notify, :color,
|
||||
:server_host, :server_port, :default_irc_uri, :enable_ssl_verification]
|
||||
:server_host, :server_port, :default_irc_uri, :enable_ssl_verification,
|
||||
:jira_issue_transition_id]
|
||||
|
||||
# Parameters to ignore if no value is specified
|
||||
FILTER_BLANK_PARAMS = [:password]
|
||||
|
||||
@@ -98,11 +98,13 @@ module IssuesHelper
|
||||
end.sort.to_sentence(last_word_connector: ', or ')
|
||||
end
|
||||
|
||||
def url_to_emoji(name)
|
||||
emoji_path = ::AwardEmoji.path_to_emoji_image(name)
|
||||
url_to_image(emoji_path)
|
||||
rescue StandardError
|
||||
""
|
||||
def emoji_icon(name, unicode = nil)
|
||||
unicode ||= Emoji.emoji_filename(name)
|
||||
|
||||
content_tag :div, "",
|
||||
class: "icon emoji-icon emoji-#{unicode}",
|
||||
"data-emoji" => name,
|
||||
"data-unicode-name" => unicode
|
||||
end
|
||||
|
||||
def emoji_author_list(notes, current_user)
|
||||
@@ -113,10 +115,6 @@ module IssuesHelper
|
||||
list.join(", ")
|
||||
end
|
||||
|
||||
def emoji_list
|
||||
::AwardEmoji::EMOJI_LIST
|
||||
end
|
||||
|
||||
def note_active_class(notes, current_user)
|
||||
if current_user && notes.pluck(:author_id).include?(current_user.id)
|
||||
"active"
|
||||
|
||||
@@ -27,7 +27,16 @@ module MergeRequestsHelper
|
||||
end
|
||||
|
||||
def ci_build_details_path(merge_request)
|
||||
merge_request.source_project.ci_service.build_page(merge_request.last_commit.sha, merge_request.source_branch)
|
||||
build_url = merge_request.source_project.ci_service.build_page(merge_request.last_commit.sha, merge_request.source_branch)
|
||||
return nil unless build_url
|
||||
|
||||
parsed_url = URI.parse(build_url)
|
||||
|
||||
unless parsed_url.userinfo.blank?
|
||||
parsed_url.userinfo = ''
|
||||
end
|
||||
|
||||
parsed_url.to_s
|
||||
end
|
||||
|
||||
def merge_path_description(merge_request, separator)
|
||||
|
||||
@@ -105,6 +105,14 @@ module ProjectsHelper
|
||||
end
|
||||
end
|
||||
|
||||
def user_max_access_in_project(user_id, project)
|
||||
level = project.team.max_member_access(user_id)
|
||||
|
||||
if level
|
||||
Gitlab::Access.options_with_owner.key(level)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_project_nav_tabs(project, current_user)
|
||||
@@ -277,14 +285,6 @@ module ProjectsHelper
|
||||
end
|
||||
end
|
||||
|
||||
def user_max_access_in_project(user, project)
|
||||
level = project.team.max_member_access(user)
|
||||
|
||||
if level
|
||||
Gitlab::Access.options_with_owner.key(level)
|
||||
end
|
||||
end
|
||||
|
||||
def leave_project_message(project)
|
||||
"Are you sure you want to leave \"#{project.name}\" project?"
|
||||
end
|
||||
|
||||
@@ -134,4 +134,8 @@ class ApplicationSetting < ActiveRecord::Base
|
||||
/x)
|
||||
self.restricted_signup_domains.reject! { |d| d.empty? }
|
||||
end
|
||||
|
||||
def runners_registration_token
|
||||
ensure_runners_registration_token!
|
||||
end
|
||||
end
|
||||
|
||||
@@ -135,6 +135,16 @@ module Ci
|
||||
predefined_variables + yaml_variables + project_variables + trigger_variables
|
||||
end
|
||||
|
||||
def merge_request
|
||||
merge_requests = MergeRequest.includes(:merge_request_diff)
|
||||
.where(source_branch: ref, source_project_id: commit.gl_project_id)
|
||||
.reorder(iid: :asc)
|
||||
|
||||
merge_requests.find do |merge_request|
|
||||
merge_request.commits.any? { |ci| ci.id == commit.sha }
|
||||
end
|
||||
end
|
||||
|
||||
def project
|
||||
commit.project
|
||||
end
|
||||
@@ -170,7 +180,8 @@ module Ci
|
||||
|
||||
def extract_coverage(text, regex)
|
||||
begin
|
||||
matches = text.gsub(Regexp.new(regex)).to_a.last
|
||||
matches = text.scan(Regexp.new(regex)).last
|
||||
matches = matches.last if matches.kind_of?(Array)
|
||||
coverage = matches.gsub(/\d+(\.\d+)?/).first
|
||||
|
||||
if coverage.present?
|
||||
|
||||
@@ -37,7 +37,7 @@ module Participable
|
||||
|
||||
# Be aware that this method makes a lot of sql queries.
|
||||
# Save result into variable if you are going to reuse it inside same request
|
||||
def participants(current_user = self.author, load_lazy_references: true)
|
||||
def participants(current_user = self.author)
|
||||
participants =
|
||||
Gitlab::ReferenceExtractor.lazily do
|
||||
self.class.participant_attrs.flat_map do |attr|
|
||||
|
||||
@@ -18,15 +18,16 @@ module TokenAuthenticatable
|
||||
|
||||
define_method("ensure_#{token_field}") do
|
||||
current_token = read_attribute(token_field)
|
||||
if current_token.blank?
|
||||
write_attribute(token_field, generate_token_for(token_field))
|
||||
else
|
||||
current_token
|
||||
end
|
||||
current_token.blank? ? write_new_token(token_field) : current_token
|
||||
end
|
||||
|
||||
define_method("ensure_#{token_field}!") do
|
||||
send("reset_#{token_field}!") if read_attribute(token_field).blank?
|
||||
read_attribute(token_field)
|
||||
end
|
||||
|
||||
define_method("reset_#{token_field}!") do
|
||||
write_attribute(token_field, generate_token_for(token_field))
|
||||
write_new_token(token_field)
|
||||
save!
|
||||
end
|
||||
end
|
||||
@@ -34,7 +35,12 @@ module TokenAuthenticatable
|
||||
|
||||
private
|
||||
|
||||
def generate_token_for(token_field)
|
||||
def write_new_token(token_field)
|
||||
new_token = generate_token(token_field)
|
||||
write_attribute(token_field, new_token)
|
||||
end
|
||||
|
||||
def generate_token(token_field)
|
||||
loop do
|
||||
token = Devise.friendly_token
|
||||
break token unless self.class.unscoped.find_by(token_field => token)
|
||||
|
||||
@@ -86,7 +86,7 @@ class Issue < ActiveRecord::Base
|
||||
def referenced_merge_requests
|
||||
Gitlab::ReferenceExtractor.lazily do
|
||||
[self, *notes].flat_map do |note|
|
||||
note.all_references(load_lazy_references: false).merge_requests
|
||||
note.all_references.merge_requests
|
||||
end
|
||||
end.sort_by(&:iid)
|
||||
end
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
class JiraIssue < ExternalIssue
|
||||
end
|
||||
@@ -335,7 +335,7 @@ class MergeRequest < ActiveRecord::Base
|
||||
issues = commits.flat_map { |c| c.closes_issues(current_user) }
|
||||
issues.push(*Gitlab::ClosingIssueExtractor.new(project, current_user).
|
||||
closed_by_message(description))
|
||||
issues.uniq
|
||||
issues.uniq(&:id)
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
@@ -499,6 +499,10 @@ class Project < ActiveRecord::Base
|
||||
@ci_service ||= ci_services.find(&:activated?)
|
||||
end
|
||||
|
||||
def jira_tracker?
|
||||
issues_tracker.to_param == 'jira'
|
||||
end
|
||||
|
||||
def avatar_type
|
||||
unless self.avatar.image?
|
||||
self.errors.add :avatar, 'only images allowed'
|
||||
@@ -799,6 +803,10 @@ class Project < ActiveRecord::Base
|
||||
false
|
||||
end
|
||||
|
||||
def jira_tracker_active?
|
||||
jira_tracker? && jira_service.active
|
||||
end
|
||||
|
||||
def ci_commit(sha)
|
||||
ci_commits.find_by(sha: sha)
|
||||
end
|
||||
|
||||
@@ -18,6 +18,11 @@
|
||||
# note_events :boolean default(TRUE), not null
|
||||
#
|
||||
|
||||
# TODO(ayufan): The GitLabCiService is deprecated and the type should be removed when the database entries are removed
|
||||
class GitlabCiService < CiService
|
||||
# this is no longer used
|
||||
# We override the active accessor to always make GitLabCiService disabled
|
||||
# Otherwise the GitLabCiService can be picked, but should never be since it's deprecated
|
||||
def active
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
@@ -19,9 +19,24 @@
|
||||
#
|
||||
|
||||
class JiraService < IssueTrackerService
|
||||
include HTTParty
|
||||
include Gitlab::Application.routes.url_helpers
|
||||
|
||||
prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
|
||||
DEFAULT_API_VERSION = 2
|
||||
|
||||
prop_accessor :username, :password, :api_url, :jira_issue_transition_id,
|
||||
:title, :description, :project_url, :issues_url, :new_issue_url
|
||||
|
||||
before_validation :set_api_url, :set_jira_issue_transition_id
|
||||
|
||||
before_update :reset_password
|
||||
|
||||
def reset_password
|
||||
# don't reset the password if a new one is provided
|
||||
if api_url_changed? && !password_touched?
|
||||
self.password = nil
|
||||
end
|
||||
end
|
||||
|
||||
def help
|
||||
line1 = 'Setting `project_url`, `issues_url` and `new_issue_url` will '\
|
||||
@@ -54,4 +69,228 @@ class JiraService < IssueTrackerService
|
||||
def to_param
|
||||
'jira'
|
||||
end
|
||||
|
||||
def fields
|
||||
super.push(
|
||||
{ type: 'text', name: 'api_url', placeholder: 'https://jira.example.com/rest/api/2' },
|
||||
{ type: 'text', name: 'username', placeholder: '' },
|
||||
{ type: 'password', name: 'password', placeholder: '' },
|
||||
{ type: 'text', name: 'jira_issue_transition_id', placeholder: '2' }
|
||||
)
|
||||
end
|
||||
|
||||
def execute(push, issue = nil)
|
||||
if issue.nil?
|
||||
# No specific issue, that means
|
||||
# we just want to test settings
|
||||
test_settings
|
||||
else
|
||||
close_issue(push, issue)
|
||||
end
|
||||
end
|
||||
|
||||
def create_cross_reference_note(mentioned, noteable, author)
|
||||
issue_name = mentioned.id
|
||||
project = self.project
|
||||
noteable_name = noteable.class.name.underscore.downcase
|
||||
noteable_id = if noteable.is_a?(Commit)
|
||||
noteable.id
|
||||
else
|
||||
noteable.iid
|
||||
end
|
||||
|
||||
entity_url = build_entity_url(noteable_name.to_sym, noteable_id)
|
||||
|
||||
data = {
|
||||
user: {
|
||||
name: author.name,
|
||||
url: resource_url(user_path(author)),
|
||||
},
|
||||
project: {
|
||||
name: project.path_with_namespace,
|
||||
url: resource_url(namespace_project_path(project.namespace, project))
|
||||
},
|
||||
entity: {
|
||||
name: noteable_name.humanize.downcase,
|
||||
url: entity_url
|
||||
}
|
||||
}
|
||||
|
||||
add_comment(data, issue_name)
|
||||
end
|
||||
|
||||
def test_settings
|
||||
result = JiraService.get(
|
||||
jira_api_test_url,
|
||||
headers: {
|
||||
'Content-Type' => 'application/json',
|
||||
'Authorization' => "Basic #{auth}"
|
||||
}
|
||||
)
|
||||
|
||||
case result.code
|
||||
when 201, 200
|
||||
Rails.logger.info("#{self.class.name} SUCCESS #{result.code}: Successfully connected to #{api_url}.")
|
||||
true
|
||||
else
|
||||
Rails.logger.info("#{self.class.name} ERROR #{result.code}: #{result.parsed_response}")
|
||||
false
|
||||
end
|
||||
rescue Errno::ECONNREFUSED => e
|
||||
Rails.logger.info "#{self.class.name} ERROR: #{e.message}. API URL: #{api_url}."
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_api_url_from_project_url
|
||||
server = URI(project_url)
|
||||
default_ports = [["http",80],["https",443]].include?([server.scheme,server.port])
|
||||
server_url = "#{server.scheme}://#{server.host}"
|
||||
server_url.concat(":#{server.port}") unless default_ports
|
||||
"#{server_url}/rest/api/#{DEFAULT_API_VERSION}"
|
||||
rescue
|
||||
"" # looks like project URL was not valid
|
||||
end
|
||||
|
||||
def set_api_url
|
||||
self.api_url = build_api_url_from_project_url if self.api_url.blank?
|
||||
end
|
||||
|
||||
def set_jira_issue_transition_id
|
||||
self.jira_issue_transition_id ||= "2"
|
||||
end
|
||||
|
||||
def close_issue(entity, issue)
|
||||
commit_id = if entity.is_a?(Commit)
|
||||
entity.id
|
||||
elsif entity.is_a?(MergeRequest)
|
||||
entity.last_commit.id
|
||||
end
|
||||
commit_url = build_entity_url(:commit, commit_id)
|
||||
|
||||
# Depending on the JIRA project's workflow, a comment during transition
|
||||
# may or may not be allowed. Split the operation in to two calls so the
|
||||
# comment always works.
|
||||
transition_issue(issue)
|
||||
add_issue_solved_comment(issue, commit_id, commit_url)
|
||||
end
|
||||
|
||||
def transition_issue(issue)
|
||||
message = {
|
||||
transition: {
|
||||
id: jira_issue_transition_id
|
||||
}
|
||||
}
|
||||
send_message(close_issue_url(issue.iid), message.to_json)
|
||||
end
|
||||
|
||||
def add_issue_solved_comment(issue, commit_id, commit_url)
|
||||
comment = {
|
||||
body: "Issue solved with [#{commit_id}|#{commit_url}]."
|
||||
}
|
||||
|
||||
send_message(comment_url(issue.iid), comment.to_json)
|
||||
end
|
||||
|
||||
def add_comment(data, issue_name)
|
||||
url = comment_url(issue_name)
|
||||
user_name = data[:user][:name]
|
||||
user_url = data[:user][:url]
|
||||
entity_name = data[:entity][:name]
|
||||
entity_url = data[:entity][:url]
|
||||
project_name = data[:project][:name]
|
||||
|
||||
message = {
|
||||
body: "[#{user_name}|#{user_url}] mentioned this issue in [a #{entity_name} of #{project_name}|#{entity_url}]."
|
||||
}
|
||||
|
||||
unless existing_comment?(issue_name, message[:body])
|
||||
send_message(url, message.to_json)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def auth
|
||||
require 'base64'
|
||||
Base64.urlsafe_encode64("#{self.username}:#{self.password}")
|
||||
end
|
||||
|
||||
def send_message(url, message)
|
||||
result = JiraService.post(
|
||||
url,
|
||||
body: message,
|
||||
headers: {
|
||||
'Content-Type' => 'application/json',
|
||||
'Authorization' => "Basic #{auth}"
|
||||
}
|
||||
)
|
||||
|
||||
message = case result.code
|
||||
when 201, 200, 204
|
||||
"#{self.class.name} SUCCESS #{result.code}: Successfully posted to #{url}."
|
||||
when 401
|
||||
"#{self.class.name} ERROR 401: Unauthorized. Check the #{self.username} credentials and JIRA access permissions and try again."
|
||||
else
|
||||
"#{self.class.name} ERROR #{result.code}: #{result.parsed_response}"
|
||||
end
|
||||
|
||||
Rails.logger.info(message)
|
||||
message
|
||||
rescue URI::InvalidURIError, Errno::ECONNREFUSED => e
|
||||
Rails.logger.info "#{self.class.name} ERROR: #{e.message}. Hostname: #{url}."
|
||||
end
|
||||
|
||||
def existing_comment?(issue_name, new_comment)
|
||||
result = JiraService.get(
|
||||
comment_url(issue_name),
|
||||
headers: {
|
||||
'Content-Type' => 'application/json',
|
||||
'Authorization' => "Basic #{auth}"
|
||||
}
|
||||
)
|
||||
|
||||
case result.code
|
||||
when 201, 200
|
||||
existing_comments = JSON.parse(result.body)['comments']
|
||||
|
||||
if existing_comments.present?
|
||||
return existing_comments.map { |comment| comment['body'].include?(new_comment) }.any?
|
||||
end
|
||||
end
|
||||
|
||||
false
|
||||
rescue JSON::ParserError
|
||||
false
|
||||
end
|
||||
|
||||
def resource_url(resource)
|
||||
"#{Settings.gitlab['url'].chomp("/")}#{resource}"
|
||||
end
|
||||
|
||||
def build_entity_url(entity_name, entity_id)
|
||||
resource_url(
|
||||
polymorphic_url(
|
||||
[
|
||||
self.project.namespace.becomes(Namespace),
|
||||
self.project,
|
||||
entity_name
|
||||
],
|
||||
id: entity_id,
|
||||
routing_type: :path
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
def close_issue_url(issue_name)
|
||||
"#{self.api_url}/issue/#{issue_name}/transitions"
|
||||
end
|
||||
|
||||
def comment_url(issue_name)
|
||||
"#{self.api_url}/issue/#{issue_name}/comment"
|
||||
end
|
||||
|
||||
def jira_api_test_url
|
||||
"#{self.api_url}/myself"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
# bio :string(255)
|
||||
# failed_attempts :integer default(0)
|
||||
# locked_at :datetime
|
||||
# unlock_token :string(255)
|
||||
# username :string(255)
|
||||
# can_create_group :boolean default(TRUE), not null
|
||||
# can_create_team :boolean default(TRUE), not null
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
module Issues
|
||||
class CloseService < Issues::BaseService
|
||||
def execute(issue, commit = nil)
|
||||
if project.jira_tracker? && project.jira_service.active
|
||||
project.jira_service.execute(commit, issue)
|
||||
return issue
|
||||
end
|
||||
|
||||
if project.default_issues_tracker? && issue.close
|
||||
event_service.close_issue(issue, current_user)
|
||||
create_note(issue, commit)
|
||||
|
||||
@@ -241,9 +241,14 @@ class SystemNoteService
|
||||
note_options.merge!(noteable: noteable)
|
||||
end
|
||||
|
||||
create_note(note_options)
|
||||
if noteable.is_a?(ExternalIssue)
|
||||
noteable.project.issues_tracker.create_cross_reference_note(noteable, mentioner, author)
|
||||
else
|
||||
create_note(note_options)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def self.cross_reference?(note_text)
|
||||
note_text.start_with?(cross_reference_note_prefix)
|
||||
end
|
||||
@@ -259,7 +264,7 @@ class SystemNoteService
|
||||
#
|
||||
# Returns Boolean
|
||||
def self.cross_reference_disallowed?(noteable, mentioner)
|
||||
return true if noteable.is_a?(ExternalIssue)
|
||||
return true if noteable.is_a?(ExternalIssue) && !noteable.project.jira_tracker_active?
|
||||
return false unless mentioner.is_a?(MergeRequest)
|
||||
return false unless noteable.is_a?(Commit)
|
||||
|
||||
|
||||
@@ -79,6 +79,10 @@
|
||||
GitLab API
|
||||
%span.pull-right
|
||||
= API::API::version
|
||||
%p
|
||||
Git
|
||||
%span.pull-right
|
||||
= Gitlab::Git.version
|
||||
%p
|
||||
Ruby
|
||||
%span.pull-right
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
- page_title "Identities", @user.name, "Users"
|
||||
= render 'admin/users/head'
|
||||
|
||||
= link_to 'New Identity', new_admin_user_identity_path, class: 'pull-right btn btn-new'
|
||||
- if @identities.present?
|
||||
.table-holder
|
||||
%table.table
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
- page_title "New Identity"
|
||||
%h3.page-title New identity
|
||||
%hr
|
||||
= render 'form'
|
||||
@@ -3,7 +3,7 @@
|
||||
To register a new runner you should enter the following registration token.
|
||||
With this token the runner will request a unique runner token and use that for future communication.
|
||||
Registration token is
|
||||
%code{ id: 'runners-token' } #{current_application_settings.ensure_runners_registration_token}
|
||||
%code{ id: 'runners-token' } #{current_application_settings.runners_registration_token}
|
||||
|
||||
.bs-callout.clearfix
|
||||
.pull-left
|
||||
|
||||
@@ -41,5 +41,3 @@
|
||||
%i.fa.fa-remove.incorrect-syntax
|
||||
%b Error:
|
||||
= @error
|
||||
|
||||
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
:plain
|
||||
$(".results").html("#{escape_javascript(render "create")}")
|
||||
@@ -1,27 +1,17 @@
|
||||
%h2 Check your .gitlab-ci.yml
|
||||
%hr
|
||||
|
||||
= form_tag ci_lint_path, method: :post, remote: true do
|
||||
.control-group
|
||||
= label_tag :content, "Content of .gitlab-ci.yml", class: 'control-label'
|
||||
.controls
|
||||
= text_area_tag :content, nil, class: 'form-control span1', rows: 7, require: true
|
||||
.row
|
||||
= form_tag ci_lint_path, method: :post do
|
||||
.form-group
|
||||
= label_tag :content, 'Content of .gitlab-ci.yml', class: 'control-label text-nowrap'
|
||||
.col-sm-12
|
||||
= text_area_tag :content, nil, class: 'form-control span1', rows: 7, require: true
|
||||
.col-sm-12
|
||||
.pull-left.prepend-top-10
|
||||
= submit_tag 'Validate', class: 'btn btn-success submit-yml'
|
||||
|
||||
.control-group.clearfix
|
||||
.controls.pull-left.prepend-top-10
|
||||
= submit_tag "Validate", class: 'btn btn-success submit-yml'
|
||||
|
||||
|
||||
%p.text-center.loading
|
||||
%i.fa.fa-refresh.fa-spin
|
||||
|
||||
.results.prepend-top-20
|
||||
|
||||
:javascript
|
||||
$(".loading").hide();
|
||||
$('form').bind('ajax:beforeSend', function() {
|
||||
$(".loading").show();
|
||||
});
|
||||
$('form').bind('ajax:complete', function() {
|
||||
$(".loading").hide();
|
||||
});
|
||||
.row.prepend-top-20
|
||||
.col-sm-12
|
||||
.results
|
||||
= render partial: 'create' if defined?(@status)
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
= content_for :flash_message do
|
||||
= render 'shared/project_limit'
|
||||
.top-area
|
||||
%ul.left-top-menu
|
||||
= nav_link(page: [dashboard_projects_path, root_path]) do
|
||||
= link_to dashboard_projects_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
|
||||
Your Projects
|
||||
= nav_link(page: starred_dashboard_projects_path) do
|
||||
= link_to starred_dashboard_projects_path, title: 'Starred Projects', data: {placement: 'right'} do
|
||||
Starred Projects
|
||||
= nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path], html_options: { class: 'hidden-xs' }) do
|
||||
= link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do
|
||||
Explore Projects
|
||||
|
||||
%ul.center-top-menu
|
||||
= nav_link(page: [dashboard_projects_path, root_path]) do
|
||||
= link_to dashboard_projects_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
|
||||
Your Projects
|
||||
= nav_link(page: starred_dashboard_projects_path) do
|
||||
= link_to starred_dashboard_projects_path, title: 'Starred Projects', data: {placement: 'right'} do
|
||||
Starred Projects
|
||||
= nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path], html_options: { class: 'hidden-xs' }) do
|
||||
= link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do
|
||||
Explore Projects
|
||||
.projects-search-form
|
||||
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name...', class: 'projects-list-filter form-control hidden-xs', spellcheck: false
|
||||
- if current_user.can_create_project?
|
||||
= link_to new_project_path, class: 'btn btn-green' do
|
||||
%i.fa.fa-plus
|
||||
New Project
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
.projects-list-holder
|
||||
.projects-search-form
|
||||
.input-group
|
||||
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false
|
||||
- if current_user.can_create_project?
|
||||
%span.input-group-btn
|
||||
= link_to new_project_path, class: 'btn btn-green' do
|
||||
%i.fa.fa-plus
|
||||
New Project
|
||||
|
||||
= render 'shared/projects/list', projects: @projects, ci: true
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<p>Hello <%= @resource.email %>!</p>
|
||||
|
||||
<p>Your account has been locked due to an excessive amount of unsuccessful sign in attempts.</p>
|
||||
|
||||
<p>Click the link below to unlock your account:</p>
|
||||
|
||||
<p><%= link_to 'Unlock your account', unlock_url(@resource, unlock_token: @token) %></p>
|
||||
@@ -0,0 +1,10 @@
|
||||
%p
|
||||
Hello #{@resource.name}!
|
||||
|
||||
%p
|
||||
Your GitLab account has been locked due to an excessive amount of unsuccessful
|
||||
sign in attempts. Your account will automatically unlock in
|
||||
= time_ago_in_words(Devise.unlock_in.from_now)
|
||||
or you may click the link below to unlock now.
|
||||
|
||||
%p= link_to 'Unlock your account', unlock_url(@resource, unlock_token: @token)
|
||||
@@ -1,12 +0,0 @@
|
||||
<h2>Resend unlock instructions</h2>
|
||||
|
||||
<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %>
|
||||
<%= devise_error_messages! %>
|
||||
|
||||
<div><%= f.label :email %><br />
|
||||
<%= f.email_field :email %></div>
|
||||
|
||||
<div><%= f.submit "Resend unlock instructions" %></div>
|
||||
<% end %>
|
||||
|
||||
<%= render partial: "devise/shared/links" %>
|
||||
@@ -0,0 +1,14 @@
|
||||
.login-box
|
||||
.login-heading
|
||||
%h3 Resend unlock email
|
||||
.login-body
|
||||
= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f|
|
||||
.devise-errors
|
||||
= devise_error_messages!
|
||||
.clearfix.append-bottom-20
|
||||
= f.email_field :email, class: 'form-control', placeholder: 'Email', autofocus: 'autofocus', autocapitalize: 'off', autocorrect: 'off'
|
||||
.clearfix
|
||||
= f.submit 'Resend unlock instructions', class: 'btn btn-success'
|
||||
|
||||
.clearfix.prepend-top-20
|
||||
= render 'devise/shared/sign_in_link'
|
||||
@@ -1,5 +1,5 @@
|
||||
.panel.panel-default.projects-list-holder
|
||||
.panel-heading.clearfix
|
||||
.projects-list-holder
|
||||
.projects-search-form
|
||||
.input-group
|
||||
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false
|
||||
- if can? current_user, :create_projects, @group
|
||||
|
||||
@@ -5,37 +5,47 @@
|
||||
- if current_user
|
||||
= auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity")
|
||||
|
||||
.dashboard
|
||||
.header-with-avatar.clearfix
|
||||
= image_tag group_icon(@group), class: "avatar group-avatar s90"
|
||||
%h3
|
||||
= @group.name
|
||||
.username
|
||||
@#{@group.path}
|
||||
- if @group.description.present?
|
||||
.description
|
||||
= markdown(@group.description, pipeline: :description)
|
||||
%hr
|
||||
.cover-block
|
||||
.avatar-holder
|
||||
= link_to group_icon(@group), target: '_blank' do
|
||||
= image_tag group_icon(@group), class: "avatar group-avatar s90"
|
||||
.cover-title
|
||||
= @group.name
|
||||
|
||||
= render 'shared/show_aside'
|
||||
.cover-desc.username
|
||||
@#{@group.path}
|
||||
|
||||
- if can?(current_user, :read_group, @group)
|
||||
.row
|
||||
%section.activities.col-md-7
|
||||
.hidden-xs
|
||||
- if current_user
|
||||
= render "events/event_last_push", event: @last_push
|
||||
.pull-right
|
||||
= link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed", class: 'btn rss-btn' do
|
||||
%i.fa.fa-rss
|
||||
- if @group.description.present?
|
||||
.cover-desc.description
|
||||
= markdown(@group.description, pipeline: :description)
|
||||
|
||||
= render 'shared/event_filter'
|
||||
%hr
|
||||
- if can?(current_user, :read_group, @group)
|
||||
%ul.center-top-menu.no-top
|
||||
%li.active
|
||||
= link_to "#activity", 'data-toggle' => 'tab' do
|
||||
Activity
|
||||
- if @projects.present?
|
||||
%li
|
||||
= link_to "#projects", 'data-toggle' => 'tab' do
|
||||
Projects
|
||||
|
||||
.content_list
|
||||
= spinner
|
||||
%aside.side.col-md-5
|
||||
= render "projects", projects: @projects
|
||||
- else
|
||||
%p
|
||||
This group does not have public projects
|
||||
.tab-content
|
||||
.tab-pane.active#activity
|
||||
.gray-content-block.activity-filter-block
|
||||
- if current_user
|
||||
= render "events/event_last_push", event: @last_push
|
||||
.pull-right
|
||||
= link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed", class: 'btn rss-btn' do
|
||||
%i.fa.fa-rss
|
||||
|
||||
= render 'shared/event_filter'
|
||||
|
||||
.content_list
|
||||
= spinner
|
||||
|
||||
.tab-pane#projects
|
||||
= render "projects", projects: @projects
|
||||
|
||||
- else
|
||||
%p
|
||||
This group does not have public projects
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
%strong.monospace= link_to @build.commit.short_sha, ci_status_path(@build.commit)
|
||||
from
|
||||
= link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref)
|
||||
- merge_request = @build.merge_request
|
||||
- if merge_request
|
||||
via
|
||||
= link_to "merge request ##{merge_request.iid}", merge_request_path(merge_request)
|
||||
|
||||
#up-build-trace
|
||||
- if @commit.matrix_for_ref?(@build.ref)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
- content_for :note_actions do
|
||||
- if can?(current_user, :update_issue, @issue)
|
||||
- if @issue.closed?
|
||||
= link_to 'Reopen Issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true), method: :put, class: 'btn btn-grouped btn-reopen js-note-target-reopen', title: 'Reopen Issue'
|
||||
= link_to 'Reopen Issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true), method: :put, class: 'btn btn-nr btn-grouped btn-reopen js-note-target-reopen', title: 'Reopen Issue'
|
||||
- else
|
||||
= link_to 'Close Issue', issue_path(@issue, issue: {state_event: :close}, status_only: true), method: :put, class: 'btn btn-grouped btn-close js-note-target-close', title: 'Close Issue'
|
||||
= link_to 'Close Issue', issue_path(@issue, issue: {state_event: :close}, status_only: true), method: :put, class: 'btn btn-nr btn-grouped btn-close js-note-target-close', title: 'Close Issue'
|
||||
|
||||
#notes
|
||||
= render 'projects/notes/notes_with_form'
|
||||
|
||||
@@ -20,14 +20,14 @@
|
||||
|
||||
.pull-right
|
||||
- if can?(current_user, :create_issue, @project)
|
||||
= link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'btn btn-grouped new-issue-link', title: 'New Issue', id: 'new_issue_link' do
|
||||
= link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'btn btn-nr btn-grouped new-issue-link btn-success', title: 'New Issue', id: 'new_issue_link' do
|
||||
= icon('plus')
|
||||
New Issue
|
||||
- if can?(current_user, :update_issue, @issue)
|
||||
= link_to 'Reopen', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen Issue'
|
||||
= link_to 'Close', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close Issue'
|
||||
= link_to 'Reopen', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen Issue'
|
||||
= link_to 'Close', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close Issue'
|
||||
|
||||
= link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'btn btn-grouped issuable-edit' do
|
||||
= link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'btn btn-nr btn-grouped issuable-edit' do
|
||||
= icon('pencil-square-o')
|
||||
Edit
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
- content_for :note_actions do
|
||||
- if can?(current_user, :update_merge_request, @merge_request)
|
||||
- if @merge_request.open?
|
||||
= link_to 'Close', merge_request_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close close-mr-link js-note-target-close", title: "Close merge request"
|
||||
= link_to 'Close', merge_request_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-nr btn-grouped btn-close close-mr-link js-note-target-close", title: "Close merge request"
|
||||
- if @merge_request.closed?
|
||||
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request"
|
||||
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-nr btn-grouped btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request"
|
||||
|
||||
#notes= render "projects/notes/notes_with_form"
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
.issue-btn-group.pull-right
|
||||
- if can?(current_user, :update_merge_request, @merge_request)
|
||||
- if @merge_request.open?
|
||||
= link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: 'btn btn-grouped btn-close', title: 'Close merge request'
|
||||
= link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-grouped issuable-edit', id: 'edit_merge_request' do
|
||||
= link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: 'btn btn-nr btn-grouped btn-close', title: 'Close merge request'
|
||||
= link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-nr btn-grouped issuable-edit', id: 'edit_merge_request' do
|
||||
%i.fa.fa-pencil-square-o
|
||||
Edit
|
||||
- if @merge_request.closed?
|
||||
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'btn btn-grouped btn-reopen reopen-mr-link', title: 'Reopen merge request'
|
||||
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'btn btn-nr btn-grouped btn-reopen reopen-mr-link', title: 'Reopen merge request'
|
||||
|
||||
@@ -13,6 +13,6 @@
|
||||
.error-alert
|
||||
|
||||
.note-form-actions.clearfix
|
||||
= f.submit 'Add Comment', class: "btn btn-create comment-btn btn-grouped js-comment-button"
|
||||
= f.submit 'Add Comment', class: "btn btn-nr btn-create comment-btn btn-grouped js-comment-button"
|
||||
= yield(:note_actions)
|
||||
%a.btn.btn-cancel.js-close-discussion-note-form Cancel
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
= render default_project_view
|
||||
|
||||
- if current_user
|
||||
- access = user_max_access_in_project(current_user, @project)
|
||||
- access = user_max_access_in_project(current_user.id, @project)
|
||||
- if access
|
||||
.prepend-top-20.project-footer
|
||||
.gray-content-block.footer-block.center
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
.awards.votes-block
|
||||
- votable.notes.awards.grouped_awards.each do |emoji, notes|
|
||||
.award{class: (note_active_class(notes, current_user)), title: emoji_author_list(notes, current_user)}
|
||||
.icon{"data-emoji" => "#{emoji}"}
|
||||
= image_tag url_to_emoji(emoji), height: "20px", width: "20px"
|
||||
= emoji_icon(emoji)
|
||||
.counter
|
||||
= notes.count
|
||||
|
||||
- if current_user
|
||||
.dropdown.awards-controls
|
||||
.awards-controls
|
||||
%a.add-award{"data-toggle" => "dropdown", "data-target" => "#", "href" => "#"}
|
||||
= icon('smile-o')
|
||||
%ul.dropdown-menu.awards-menu
|
||||
- emoji_list.each do |emoji|
|
||||
%li{"data-emoji" => "#{emoji}"}= image_tag url_to_emoji(emoji), height: "20px", width: "20px"
|
||||
.emoji-menu
|
||||
.emoji-menu-content
|
||||
- AwardEmoji.emoji_by_category.each do |category, emojis|
|
||||
%h5= AwardEmoji::CATEGORIES[category]
|
||||
%ul
|
||||
- emojis.each do |emoji|
|
||||
%li
|
||||
= emoji_icon(emoji["name"], emoji["unicode"])
|
||||
|
||||
- if current_user
|
||||
:coffeescript
|
||||
@@ -20,10 +24,16 @@
|
||||
noteable_type = "#{votable.class.name.underscore}"
|
||||
noteable_id = "#{votable.id}"
|
||||
aliases = #{AwardEmoji::ALIASES.to_json}
|
||||
window.awards_handler = new AwardsHandler(post_emoji_url, noteable_type, noteable_id, aliases)
|
||||
|
||||
$(".awards-menu li").click (e)->
|
||||
emoji = $(this).data("emoji")
|
||||
window.awards_handler = new AwardsHandler(
|
||||
post_emoji_url,
|
||||
noteable_type,
|
||||
noteable_id,
|
||||
aliases
|
||||
)
|
||||
|
||||
$(".emoji-menu-content li").click (e)->
|
||||
emoji = $(this).find(".emoji-icon").data("emoji")
|
||||
awards_handler.addAward(emoji)
|
||||
|
||||
$(".awards").on "click", ".award", (e)->
|
||||
@@ -31,3 +41,5 @@
|
||||
awards_handler.addAward(emoji)
|
||||
|
||||
$(".award").tooltip()
|
||||
|
||||
$(".emoji-menu-content").niceScroll({cursorwidth: "7px", autohidemode: false})
|
||||
|
||||
@@ -144,6 +144,15 @@ production: &base
|
||||
# plain_url: "http://..." # default: http://www.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon
|
||||
# ssl_url: "https://..." # default: https://secure.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon
|
||||
|
||||
## Auxiliary jobs
|
||||
# Periodically executed jobs, to self-heal Gitlab, do external synchronizations, etc.
|
||||
# Please read here for more information: https://github.com/ondrejbartas/sidekiq-cron#adding-cron-job
|
||||
cron_jobs:
|
||||
# Flag stuck CI builds as failed
|
||||
stuck_ci_builds_worker:
|
||||
cron: "0 0 * * *"
|
||||
|
||||
|
||||
#
|
||||
# 2. GitLab CI settings
|
||||
# ==========================
|
||||
@@ -287,6 +296,15 @@ production: &base
|
||||
# arguments, followed by optional 'args' which can be either a hash or an array.
|
||||
# Documentation for this is available at http://doc.gitlab.com/ce/integration/omniauth.html
|
||||
providers:
|
||||
# See omniauth-cas3 for more configuration details
|
||||
# - { name: 'cas3',
|
||||
# label: 'cas3',
|
||||
# args: {
|
||||
# url: 'https://sso.example.com',
|
||||
# disable_ssl_verification: false,
|
||||
# login_url: '/cas/login',
|
||||
# service_validate_url: '/cas/p3/serviceValidate',
|
||||
# logout_url: '/cas/logout'} }
|
||||
# - { name: 'github',
|
||||
# app_id: 'YOUR_APP_ID',
|
||||
# app_secret: 'YOUR_APP_SECRET',
|
||||
@@ -324,6 +342,10 @@ production: &base
|
||||
# application_name: 'YOUR_APP_NAME',
|
||||
# application_password: 'YOUR_APP_PASSWORD' } }
|
||||
|
||||
# SSO maximum session duration in seconds. Defaults to CAS default of 8 hours.
|
||||
# cas3:
|
||||
# session_duration: 28800
|
||||
|
||||
# Shared file storage settings
|
||||
shared:
|
||||
# path: /mnt/gitlab # Default: shared
|
||||
|
||||
@@ -126,6 +126,10 @@ Settings.omniauth['block_auto_created_users'] = true if Settings.omniauth['block
|
||||
Settings.omniauth['auto_link_ldap_user'] = false if Settings.omniauth['auto_link_ldap_user'].nil?
|
||||
|
||||
Settings.omniauth['providers'] ||= []
|
||||
Settings.omniauth['cas3'] ||= Settingslogic.new({})
|
||||
Settings.omniauth.cas3['session_duration'] ||= 8.hours
|
||||
Settings.omniauth['session_tickets'] ||= Settingslogic.new({})
|
||||
Settings.omniauth.session_tickets['cas3'] = 'ticket'
|
||||
|
||||
Settings['shared'] ||= Settingslogic.new({})
|
||||
Settings.shared['path'] = File.expand_path(Settings.shared['path'] || "shared", Rails.root)
|
||||
@@ -164,7 +168,7 @@ Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].
|
||||
Settings.gitlab['twitter_sharing_enabled'] ||= true if Settings.gitlab['twitter_sharing_enabled'].nil?
|
||||
Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], [])
|
||||
Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil?
|
||||
Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?))+)' if Settings.gitlab['issue_closing_pattern'].nil?
|
||||
Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z]*-\d*))+)' if Settings.gitlab['issue_closing_pattern'].nil?
|
||||
Settings.gitlab['default_projects_features'] ||= {}
|
||||
Settings.gitlab['webhook_timeout'] ||= 10
|
||||
Settings.gitlab['max_attachment_size'] ||= 10
|
||||
@@ -224,6 +228,15 @@ Settings.gravatar['plain_url'] ||= 'http://www.gravatar.com/avatar/%{hash}?s=%{
|
||||
Settings.gravatar['ssl_url'] ||= 'https://secure.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon'
|
||||
Settings.gravatar['host'] = Settings.get_host_without_www(Settings.gravatar['plain_url'])
|
||||
|
||||
#
|
||||
# Cron Jobs
|
||||
#
|
||||
Settings['cron_jobs'] ||= Settingslogic.new({})
|
||||
Settings.cron_jobs['stuck_ci_builds_worker'] ||= Settingslogic.new({})
|
||||
Settings.cron_jobs['stuck_ci_builds_worker']['cron'] ||= '0 0 * * *'
|
||||
Settings.cron_jobs['stuck_ci_builds_worker']['job_class'] = 'StuckCiBuildsWorker'
|
||||
|
||||
|
||||
#
|
||||
# GitLab Shell
|
||||
#
|
||||
|
||||
@@ -121,14 +121,14 @@ Devise.setup do |config|
|
||||
config.lock_strategy = :failed_attempts
|
||||
|
||||
# Defines which key will be used when locking and unlocking an account
|
||||
# config.unlock_keys = [ :email ]
|
||||
config.unlock_keys = [ :email ]
|
||||
|
||||
# Defines which strategy will be used to unlock an account.
|
||||
# :email = Sends an unlock link to the user email
|
||||
# :time = Re-enables login after a certain amount of time (see :unlock_in below)
|
||||
# :both = Enables both strategies
|
||||
# :none = No unlock strategy. You should handle unlocking by yourself.
|
||||
config.unlock_strategy = :time
|
||||
config.unlock_strategy = :both
|
||||
|
||||
# Number of authentication tries before locking an account if lock_strategy
|
||||
# is failed attempts.
|
||||
@@ -241,6 +241,16 @@ Devise.setup do |config|
|
||||
# An Array from the configuration will be expanded.
|
||||
provider_arguments.concat provider['args']
|
||||
when Hash
|
||||
# Add procs for handling SLO
|
||||
if provider['name'] == 'cas3'
|
||||
provider['args'][:on_single_sign_out] = lambda do |request|
|
||||
ticket = request.params[:session_index]
|
||||
raise "Service Ticket not found." unless Gitlab::OAuth::Session.valid?(:cas3, ticket)
|
||||
Gitlab::OAuth::Session.destroy(:cas3, ticket)
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
# A Hash from the configuration will be passed as is.
|
||||
provider_arguments << provider['args'].symbolize_keys
|
||||
end
|
||||
|
||||
@@ -18,11 +18,12 @@ Sidekiq.configure_server do |config|
|
||||
chain.add Gitlab::SidekiqMiddleware::MemoryKiller if ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS']
|
||||
end
|
||||
|
||||
# Sidekiq-cron: load recurring jobs from schedule.yml
|
||||
schedule_file = 'config/schedule.yml'
|
||||
if File.exists?(schedule_file)
|
||||
Sidekiq::Cron::Job.load_from_hash YAML.load_file(schedule_file)
|
||||
end
|
||||
# Sidekiq-cron: load recurring jobs from gitlab.yml
|
||||
# UGLY Hack to get nested hash from settingslogic
|
||||
cron_jobs = JSON.parse(Gitlab.config.cron_jobs.to_json)
|
||||
# UGLY hack: Settingslogic doesn't allow 'class' key
|
||||
cron_jobs.each { |k,v| cron_jobs[k]['class'] = cron_jobs[k].delete('job_class') }
|
||||
Sidekiq::Cron::Job.load_from_hash! cron_jobs
|
||||
|
||||
# Database pool should be at least `sidekiq_concurrency` + 2
|
||||
# For more info, see: https://github.com/mperham/sidekiq/blob/master/4.0-Upgrade.md
|
||||
|
||||
@@ -188,7 +188,7 @@ Rails.application.routes.draw do
|
||||
namespace :admin do
|
||||
resources :users, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } do
|
||||
resources :keys, only: [:show, :destroy]
|
||||
resources :identities, only: [:index, :edit, :update, :destroy]
|
||||
resources :identities, except: [:show]
|
||||
|
||||
delete 'stop_impersonation' => 'impersonation#destroy', on: :collection
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
# Here is a list of jobs that are scheduled to run periodically.
|
||||
# We use a UNIX cron notation to specify execution schedule.
|
||||
#
|
||||
# Please read here for more information:
|
||||
# https://github.com/ondrejbartas/sidekiq-cron#adding-cron-job
|
||||
|
||||
stuck_ci_builds_worker:
|
||||
cron: "0 0 * * *"
|
||||
class: "StuckCiBuildsWorker"
|
||||
queue: "default"
|
||||
@@ -0,0 +1,50 @@
|
||||
class SetJiraServiceApiUrl < ActiveRecord::Migration
|
||||
# This migration can be performed online without errors, but some Jira API calls may be missed
|
||||
# when doing so because api_url is not yet available.
|
||||
|
||||
def build_api_url_from_project_url(project_url, api_version)
|
||||
# this is the exact logic previously used to build the Jira API URL from project_url
|
||||
server = URI(project_url)
|
||||
default_ports = [80, 443].include?(server.port)
|
||||
server_url = "#{server.scheme}://#{server.host}"
|
||||
server_url.concat(":#{server.port}") unless default_ports
|
||||
"#{server_url}/rest/api/#{api_version}"
|
||||
end
|
||||
|
||||
def get_api_version_from_api_url(api_url)
|
||||
match = /\/rest\/api\/(?<api_version>\w+)$/.match(api_url)
|
||||
match && match['api_version']
|
||||
end
|
||||
|
||||
def change
|
||||
reversible do |dir|
|
||||
select_all("SELECT id, properties FROM services WHERE services.type IN ('JiraService')").each do |jira_service|
|
||||
id = jira_service["id"]
|
||||
properties = JSON.parse(jira_service["properties"])
|
||||
properties_was = properties.clone
|
||||
|
||||
dir.up do
|
||||
# remove api_version and set api_url
|
||||
if properties['api_version'].present? && properties['project_url'].present?
|
||||
begin
|
||||
properties['api_url'] ||= build_api_url_from_project_url(properties['project_url'], properties['api_version'])
|
||||
rescue
|
||||
# looks like project_url was not a valid URL. Do nothing.
|
||||
end
|
||||
end
|
||||
properties.delete('api_version') if properties.include?('api_version')
|
||||
end
|
||||
|
||||
dir.down do
|
||||
# remove api_url and set api_version (default to '2')
|
||||
properties['api_version'] ||= get_api_version_from_api_url(properties['api_url']) || '2'
|
||||
properties.delete('api_url') if properties.include?('api_url')
|
||||
end
|
||||
|
||||
if properties != properties_was
|
||||
execute("UPDATE services SET properties = '#{quote_string(properties.to_json)}' WHERE id = #{id}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,5 +1,5 @@
|
||||
class AddBuildEventsToServices < ActiveRecord::Migration
|
||||
def up
|
||||
def change
|
||||
add_column :services, :build_events, :boolean, default: false, null: false
|
||||
add_column :web_hooks, :build_events, :boolean, default: false, null: false
|
||||
end
|
||||
|
||||
@@ -10,4 +10,7 @@ class MigrateCiWebHooks < ActiveRecord::Migration
|
||||
'JOIN projects ON ci_projects.gitlab_id = projects.id'
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
class AddUnlockTokenToUser < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :users, :unlock_token, :string
|
||||
end
|
||||
end
|
||||
@@ -1,5 +1,5 @@
|
||||
class AddCiToProject < ActiveRecord::Migration
|
||||
def up
|
||||
def change
|
||||
add_column :projects, :ci_id, :integer
|
||||
add_column :projects, :builds_enabled, :boolean, default: true, null: false
|
||||
add_column :projects, :shared_runners_enabled, :boolean, default: true, null: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class AddProjectIdToCi < ActiveRecord::Migration
|
||||
def up
|
||||
def change
|
||||
add_column :ci_builds, :gl_project_id, :integer
|
||||
add_column :ci_runner_projects, :gl_project_id, :integer
|
||||
add_column :ci_triggers, :gl_project_id, :integer
|
||||
|
||||
@@ -14,6 +14,10 @@ class MigrateCiToProject < ActiveRecord::Migration
|
||||
migrate_ci_service
|
||||
end
|
||||
|
||||
def down
|
||||
# We can't reverse the data
|
||||
end
|
||||
|
||||
def migrate_project_id_for_table(table)
|
||||
subquery = "SELECT gitlab_id FROM ci_projects WHERE ci_projects.id = #{table}.project_id"
|
||||
execute("UPDATE #{table} SET gl_project_id=(#{subquery}) WHERE gl_project_id IS NULL")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class AddIndexToCiTables < ActiveRecord::Migration
|
||||
def up
|
||||
def change
|
||||
add_index :ci_builds, :gl_project_id
|
||||
add_index :ci_runner_projects, :gl_project_id
|
||||
add_index :ci_triggers, :gl_project_id
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class DropNullForCiTables < ActiveRecord::Migration
|
||||
def up
|
||||
def change
|
||||
remove_index :ci_variables, :project_id
|
||||
remove_index :ci_runner_projects, :project_id
|
||||
change_column_null :ci_triggers, :project_id, true
|
||||
|
||||
@@ -837,6 +837,7 @@ ActiveRecord::Schema.define(version: 20151210125932) do
|
||||
t.integer "consumed_timestep"
|
||||
t.integer "layout", default: 0
|
||||
t.boolean "hide_project_limit", default: false
|
||||
t.string "unlock_token"
|
||||
end
|
||||
|
||||
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
|
||||
|
||||
@@ -118,6 +118,16 @@ Parameters:
|
||||
"path": "brightbox",
|
||||
"updated_at": "2013-09-30T13:46:02Z"
|
||||
},
|
||||
"permissions": {
|
||||
"project_access": {
|
||||
"access_level": 10,
|
||||
"notification_level": 3
|
||||
},
|
||||
"group_access": {
|
||||
"access_level": 50,
|
||||
"notification_level": 3
|
||||
}
|
||||
},
|
||||
"archived": false,
|
||||
"avatar_url": null
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
### Examples
|
||||
|
||||
+ [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml)
|
||||
+ [Test and deploy Ruby applications to Heroku](examples/test-and-deploy-ruby-application-to-heroku.md)
|
||||
+ [Test and deploy Python applications to Heroku](examples/test-and-deploy-python-application-to-heroku.md)
|
||||
+ [Test Clojure applications](examples/test-clojure-application.md)
|
||||
|
||||
@@ -4,10 +4,12 @@ GitLab integrates with multiple third-party services to allow external issue tra
|
||||
|
||||
See the documentation below for details on how to configure these services.
|
||||
|
||||
- [Jira](jira.md) Integrate with the JIRA issue tracker
|
||||
- [External issue tracker](external-issue-tracker.md) Redmine, JIRA, etc.
|
||||
- [LDAP](ldap.md) Set up sign in via LDAP
|
||||
- [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, GitLab, and Google via OAuth.
|
||||
- [SAML](saml.md) Configure GitLab as a SAML 2.0 Service Provider
|
||||
- [CAS](cas.md) Configure GitLab to sign in using CAS
|
||||
- [Slack](slack.md) Integrate with the Slack chat service
|
||||
- [OAuth2 provider](oauth_provider.md) OAuth2 application creation
|
||||
- [Gmail actions buttons](gmail_action_buttons_for_gitlab.md) Adds GitLab actions to messages
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# CAS OmniAuth Provider
|
||||
|
||||
To enable the CAS OmniAuth provider you must register your application with your CAS instance. This requires the service URL GitLab will supply to CAS. It should be something like: `https://gitlab.example.com:443/users/auth/cas3/callback?url`. By default handling for SLO is enabled, you only need to configure CAS for backchannel logout.
|
||||
|
||||
1. On your GitLab server, open the configuration file.
|
||||
|
||||
For omnibus package:
|
||||
|
||||
```sh
|
||||
sudo editor /etc/gitlab/gitlab.rb
|
||||
```
|
||||
|
||||
For installations from source:
|
||||
|
||||
```sh
|
||||
cd /home/git/gitlab
|
||||
|
||||
sudo -u git -H editor config/gitlab.yml
|
||||
```
|
||||
|
||||
1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
|
||||
|
||||
1. Add the provider configuration:
|
||||
|
||||
For omnibus package:
|
||||
|
||||
```ruby
|
||||
gitlab_rails['omniauth_providers'] = [
|
||||
{
|
||||
name: "cas3",
|
||||
label: "cas",
|
||||
args: {
|
||||
url: 'CAS_SERVER',
|
||||
login_url: '/CAS_PATH/login',
|
||||
service_validate_url: '/CAS_PATH/p3/serviceValidate',
|
||||
logout_url: '/CAS_PATH/logout'} }
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
For installations from source:
|
||||
|
||||
```
|
||||
- { name: 'cas3',
|
||||
label: 'cas',
|
||||
args: {
|
||||
url: 'CAS_SERVER',
|
||||
login_url: '/CAS_PATH/login',
|
||||
service_validate_url: '/CAS_PATH/p3/serviceValidate',
|
||||
logout_url: '/CAS_PATH/logout'} }
|
||||
```
|
||||
|
||||
1. Change 'CAS_PATH' to the root of your CAS instance (ie. `cas`).
|
||||
|
||||
1. If your CAS instance does not use default TGC lifetimes, update the `cas3.session_duration` to at least the current TGC maximum lifetime. To explicitly disable SLO, regardless of CAS settings, set this to 0.
|
||||
|
||||
1. Save the configuration file.
|
||||
|
||||
1. Restart GitLab for the changes to take effect.
|
||||
|
||||
On the sign in page there should now be a CAS tab in the sign in form.
|
||||
@@ -0,0 +1,113 @@
|
||||
# GitLab Jira integration
|
||||
|
||||
GitLab can be configured to interact with Jira.
|
||||
Configuration happens via username and password.
|
||||
Connecting to a Jira server via CAS is not possible.
|
||||
|
||||
Each project can be configured to connect to a different Jira instance, configuration is explained [here](#configuration).
|
||||
If you have one Jira instance you can pre-fill the settings page with a default template. To configure the template [see external issue tracker document](external-issue-tracker.md#service-template)).
|
||||
|
||||
Once the project is connected to Jira, you can reference and close the issues in Jira directly from GitLab.
|
||||
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Referencing Jira Issues from GitLab](#referencing-jira-issues)
|
||||
* [Closing Jira Issues from GitLab](#closing-jira-issues)
|
||||
* [Configuration](#configuration)
|
||||
|
||||
### Referencing Jira Issues
|
||||
|
||||
When GitLab project has Jira issue tracker configured and enabled, mentioning Jira issue in GitLab will automatically add a comment in Jira issue with the link back to GitLab. This means that in comments in merge requests and commits referencing an issue, eg. `PROJECT-7`, will add a comment in Jira issue in the format:
|
||||
|
||||
|
||||
```
|
||||
USER mentioned this issue in LINK_TO_THE_MENTION
|
||||
```
|
||||
|
||||
* `USER` A user that mentioned the issue. This is the link to the user profile in GitLab.
|
||||
* `LINK_TO_THE_MENTION` Link to the origin of mention with a name of the entity where Jira issue was mentioned.
|
||||
Can be commit or merge request.
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
### Closing Jira Issues
|
||||
|
||||
Jira issues can be closed directly from GitLab by using trigger words, eg. `Resolves PROJECT-1`, `Closes PROJECT-1` or `Fixes PROJECT-1`, in commits and merge requests.
|
||||
When a commit which contains the trigger word in the commit message is pushed, GitLab will add a comment in the mentioned Jira issue.
|
||||
|
||||
For example, for project named PROJECT in Jira, we implemented a new feature and created a merge request in GitLab.
|
||||
|
||||
This feature was requested in Jira issue PROJECT-7. Merge request in GitLab contains the improvement and in merge request description we say that this merge request `Closes PROJECT-7` issue.
|
||||
|
||||
Once this merge request is merged, Jira issue will be automatically closed with a link to the commit that resolved the issue.
|
||||
|
||||

|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
### Configuring JIRA
|
||||
|
||||
We need to create a user in JIRA which will have access to all projects that need to integrate with GitLab.
|
||||
Login to your JIRA instance as admin and under Administration go to User Management and create a new user.
|
||||
As an example, we'll create a user named `gitlab` and add it to `jira-developers` group.
|
||||
|
||||
**It is important that the user `gitlab` has write-access to projects in JIRA**
|
||||
|
||||
### Configuring GitLab
|
||||
|
||||
### GitLab 7.8 EE and up with JIRA v6.x
|
||||
|
||||
To enable JIRA integration in a project, navigate to the project Settings page and go to Services. Here you will find JIRA.
|
||||
|
||||
Fill in the required details on the page:
|
||||
|
||||

|
||||
|
||||
* `description` A name for the issue tracker (to differentiate between instances, for instance).
|
||||
* `project url` The URL to the JIRA project which is being linked to this GitLab project.
|
||||
* `issues url` The URL to the JIRA project issues overview for the project that is linked to this GitLab project.
|
||||
* `new issue url` This is the URL to create a new issue in JIRA for the project linked to this GitLab project.
|
||||
* `api url` The base URL of the JIRA API. It may be omitted, in which case GitLab will automatically use API version `2` based on the `project url`, i.e. `https://jira.example.com/rest/api/2`.
|
||||
* `username` The username of the user created in [configuring JIRA step](#configuring-jira).
|
||||
* `password` The password of the user created in [configuring JIRA step](#configuring-jira).
|
||||
* `Jira issue transition` This is the id of a transition that moves issues to a closed state. You can find this number under [JIRA workflow administration, see screenshot](jira_workflow_screenshot.png). By default, this id is `2`. (In the example image, this is `2` as well)
|
||||
|
||||
After saving the configuration, your GitLab project will be able to interact with the linked JIRA project.
|
||||
|
||||
|
||||
### GitLab 6.x-7.7 with JIRA v6.x
|
||||
|
||||
**Note: GitLab 7.8 and up contain various integration improvements. We strongly recommend upgrading.**
|
||||
|
||||
|
||||
In `gitlab.yml` enable [JIRA issue tracker section by uncommenting the lines](https://gitlab.com/subscribers/gitlab-ee/blob/6-8-stable-ee/config/gitlab.yml.example#L111-115).
|
||||
This will make sure that all issues within GitLab are pointing to the JIRA issue tracker.
|
||||
|
||||
We can also enable JIRA service that will allow us to interact with JIRA issues.
|
||||
|
||||
For example, we can close issues in JIRA by a commit in GitLab.
|
||||
|
||||
Go to project settings page and fill in the project name for the JIRA project:
|
||||
|
||||

|
||||
|
||||
Next, go to the services page and find JIRA.
|
||||
|
||||

|
||||
|
||||
1. Tick the active check box to enable the service.
|
||||
1. Supply the url to JIRA server, for example http://jira.sample
|
||||
1. Supply the username of a user we created under `Configuring JIRA` section, for example `gitlab`
|
||||
1. Supply the password of the user
|
||||
1. Optional: supply the JIRA api version, default is version
|
||||
1. Optional: supply the JIRA issue transition ID (issue transition to closed). This is dependant on JIRA settings, default is 2
|
||||
1. Save
|
||||
|
||||
Now we should be able to interact with JIRA issues.
|
||||
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 159 KiB |
|
After Width: | Height: | Size: 119 KiB |
@@ -13,6 +13,11 @@ Feature: Award Emoji
|
||||
Then I have award added
|
||||
And I can remove it by clicking to icon
|
||||
|
||||
@javascript
|
||||
Scenario: I can see the list of emoji categories
|
||||
Given I click to emoji-picker
|
||||
Then I can see the activity and food categories
|
||||
|
||||
@javascript
|
||||
Scenario: I add award emoji using regular comment
|
||||
Given I leave comment with a single emoji
|
||||
|
||||
@@ -55,6 +55,12 @@ Feature: Project Services
|
||||
And I fill email on push settings
|
||||
Then I should see email on push service settings saved
|
||||
|
||||
Scenario: Activate JIRA service
|
||||
When I visit project "Shop" services page
|
||||
And I click jira service link
|
||||
And I fill jira settings
|
||||
Then I should see jira service settings saved
|
||||
|
||||
Scenario: Activate Irker (IRC Gateway) service
|
||||
When I visit project "Shop" services page
|
||||
And I click Irker service link
|
||||
|
||||
@@ -15,8 +15,8 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
|
||||
end
|
||||
|
||||
step 'I click to emoji in the picker' do
|
||||
page.within '.awards-menu' do
|
||||
page.first('img').click
|
||||
page.within '.emoji-menu' do
|
||||
page.first('.emoji-icon').click
|
||||
end
|
||||
end
|
||||
|
||||
@@ -27,6 +27,13 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
|
||||
end
|
||||
end
|
||||
|
||||
step 'I can see the activity and food categories' do
|
||||
page.within '.emoji-menu' do
|
||||
expect(page).to_not have_selector 'Activity'
|
||||
expect(page).to_not have_selector 'Food'
|
||||
end
|
||||
end
|
||||
|
||||
step 'I have award added' do
|
||||
page.within '.awards' do
|
||||
expect(page).to have_selector '.award'
|
||||
|
||||
@@ -173,6 +173,24 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
|
||||
expect(find_field('Sound').find('option[selected]').value).to eq 'bike'
|
||||
end
|
||||
|
||||
step 'I click jira service link' do
|
||||
click_link 'JIRA'
|
||||
end
|
||||
|
||||
step 'I fill jira settings' do
|
||||
fill_in 'Project url', with: 'http://jira.example'
|
||||
fill_in 'Username', with: 'gitlab'
|
||||
fill_in 'Password', with: 'gitlab'
|
||||
fill_in 'Api url', with: 'http://jira.example/rest/api/2'
|
||||
click_button 'Save'
|
||||
end
|
||||
|
||||
step 'I should see jira service settings saved' do
|
||||
expect(find_field('Project url').value).to eq 'http://jira.example'
|
||||
expect(find_field('Username').value).to eq 'gitlab'
|
||||
expect(find_field('Api url').value).to eq 'http://jira.example/rest/api/2'
|
||||
end
|
||||
|
||||
step 'I click Atlassian Bamboo CI service link' do
|
||||
click_link 'Atlassian Bamboo CI'
|
||||
end
|
||||
|
||||