Commit e3a3242a authored by Robert Speicher's avatar Robert Speicher

Merge branch 'ce-to-ee-master' into 'master'

CE to EE master

See merge request !465
parents 09ab819e 7c71d0a5
...@@ -8,7 +8,8 @@ services: ...@@ -8,7 +8,8 @@ services:
cache: cache:
key: "ruby21" key: "ruby21"
paths: paths:
- vendor - vendor/apt
- vendor/ruby
variables: variables:
MYSQL_ALLOW_EMPTY_PASSWORD: "1" MYSQL_ALLOW_EMPTY_PASSWORD: "1"
...@@ -93,9 +94,7 @@ update-knapsack: ...@@ -93,9 +94,7 @@ update-knapsack:
- export KNAPSACK_REPORT_PATH=knapsack/spinach_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json - export KNAPSACK_REPORT_PATH=knapsack/spinach_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
- export KNAPSACK_GENERATE_REPORT=true - export KNAPSACK_GENERATE_REPORT=true
- cp knapsack/spinach_report.json ${KNAPSACK_REPORT_PATH} - cp knapsack/spinach_report.json ${KNAPSACK_REPORT_PATH}
- knapsack spinach "-r rerun" - knapsack spinach "-r rerun" || retry '[ ! -e tmp/spinach-rerun.txt ] || bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt)'
# retry failed tests 3 times
- retry '[ ! -e tmp/spinach-rerun.txt ] || bin/spinach -r rerun $(cat tmp/spinach-rerun.txt)'
artifacts: artifacts:
paths: paths:
- knapsack/ - knapsack/
......
...@@ -349,7 +349,7 @@ Style/MultilineArrayBraceLayout: ...@@ -349,7 +349,7 @@ Style/MultilineArrayBraceLayout:
# Avoid multi-line chains of blocks. # Avoid multi-line chains of blocks.
Style/MultilineBlockChain: Style/MultilineBlockChain:
Enabled: false Enabled: true
# Ensures newlines after multiline block do statements. # Ensures newlines after multiline block do statements.
Style/MultilineBlockLayout: Style/MultilineBlockLayout:
......
...@@ -2,14 +2,18 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -2,14 +2,18 @@ Please view this file on the master branch, on stable branches it's out of date.
v 8.9.0 (unreleased) v 8.9.0 (unreleased)
- Fix Error 500 when using closes_issues API with an external issue tracker - Fix Error 500 when using closes_issues API with an external issue tracker
- Add more information into RSS feed for issues (Alexander Matyushentsev)
- Bulk assign/unassign labels to issues. - Bulk assign/unassign labels to issues.
- Ability to prioritize labels !4009 / !3205 (Thijs Wouters) - Ability to prioritize labels !4009 / !3205 (Thijs Wouters)
- Fix endless redirections when accessing user OAuth applications when they are disabled - Fix endless redirections when accessing user OAuth applications when they are disabled
- Allow enabling wiki page events from Webhook management UI - Allow enabling wiki page events from Webhook management UI
- Bump rouge to 1.11.0 - Bump rouge to 1.11.0
- Fix issue with arrow keys not working in search autocomplete dropdown - Fix issue with arrow keys not working in search autocomplete dropdown
- Fix an issue where note polling stopped working if a window was in the
background during a refresh.
- Make EmailsOnPushWorker use Sidekiq mailers queue - Make EmailsOnPushWorker use Sidekiq mailers queue
- Fix wiki page events' webhook to point to the wiki repository - Fix wiki page events' webhook to point to the wiki repository
- Don't show tags for revert and cherry-pick operations
- Fix issue todo not remove when leave project !4150 (Long Nguyen) - Fix issue todo not remove when leave project !4150 (Long Nguyen)
- Allow customisable text on the 'nearly there' page after a user signs up - Allow customisable text on the 'nearly there' page after a user signs up
- Bump recaptcha gem to 3.0.0 to remove deprecated stoken support - Bump recaptcha gem to 3.0.0 to remove deprecated stoken support
...@@ -20,9 +24,14 @@ v 8.9.0 (unreleased) ...@@ -20,9 +24,14 @@ v 8.9.0 (unreleased)
- Reduce number of fog gem dependencies - Reduce number of fog gem dependencies
- Remove project notification settings associated with deleted projects - Remove project notification settings associated with deleted projects
- Fix 404 page when viewing TODOs that contain milestones or labels in different projects - Fix 404 page when viewing TODOs that contain milestones or labels in different projects
- Add a metric for the number of new Redis connections created by a transaction
- Fix Error 500 when viewing a blob with binary characters after the 1024-byte mark
- Redesign navigation for project pages - Redesign navigation for project pages
- Fix groups API to list only user's accessible projects - Fix groups API to list only user's accessible projects
- Add Environments and Deployments
- Redesign account and email confirmation emails - Redesign account and email confirmation emails
- Don't fail builds for projects that are deleted
- Support Docker Registry manifest v1
- `git clone https://host/namespace/project` now works, in addition to using the `.git` suffix - `git clone https://host/namespace/project` now works, in addition to using the `.git` suffix
- Bump nokogiri to 1.6.8 - Bump nokogiri to 1.6.8
- Use gitlab-shell v3.0.0 - Use gitlab-shell v3.0.0
...@@ -36,6 +45,9 @@ v 8.9.0 (unreleased) ...@@ -36,6 +45,9 @@ v 8.9.0 (unreleased)
- Links from a wiki page to other wiki pages should be rewritten as expected - Links from a wiki page to other wiki pages should be rewritten as expected
- Add option to project to only allow merge requests to be merged if the build succeeds (Rui Santos) - Add option to project to only allow merge requests to be merged if the build succeeds (Rui Santos)
- Fix issues filter when ordering by milestone - Fix issues filter when ordering by milestone
- Added artifacts:when to .gitlab-ci.yml - this requires GitLab Runner 1.3
- Bamboo Service: Fix missing credentials & URL handling when base URL contains a path (Benjamin Schmid)
- TeamCity Service: Fix URL handling when base URL contains a path
- Todos will display target state if issuable target is 'Closed' or 'Merged' - Todos will display target state if issuable target is 'Closed' or 'Merged'
- Fix bug when sorting issues by milestone due date and filtering by two or more labels - Fix bug when sorting issues by milestone due date and filtering by two or more labels
- Add support for using Yubikeys (U2F) for two-factor authentication - Add support for using Yubikeys (U2F) for two-factor authentication
...@@ -52,22 +64,42 @@ v 8.9.0 (unreleased) ...@@ -52,22 +64,42 @@ v 8.9.0 (unreleased)
- Use Knapsack only in CI environment - Use Knapsack only in CI environment
- Cache project build count in sidebar nav - Cache project build count in sidebar nav
- Add milestone expire date to the right sidebar - Add milestone expire date to the right sidebar
- Manually mark a issue or merge request as a todo
- Fix markdown_spec to use before instead of before(:all) to properly cleanup database after testing - Fix markdown_spec to use before instead of before(:all) to properly cleanup database after testing
- Reduce number of queries needed to render issue labels in the sidebar - Reduce number of queries needed to render issue labels in the sidebar
- Improve error handling importing projects - Improve error handling importing projects
- Remove duplicated notification settings - Remove duplicated notification settings
- Put project Files and Commits tabs under Code tab - Put project Files and Commits tabs under Code tab
- Decouple global notification level from user model
- Replace Colorize with Rainbow for coloring console output in Rake tasks. - Replace Colorize with Rainbow for coloring console output in Rake tasks.
- Add workhorse controller and API helpers - Add workhorse controller and API helpers
- An indicator is now displayed at the top of the comment field for confidential issues. - An indicator is now displayed at the top of the comment field for confidential issues.
- RepositoryCheck::SingleRepositoryWorker public and private methods are now instrumented - RepositoryCheck::SingleRepositoryWorker public and private methods are now instrumented
- Improve issuables APIs performance when accessing notes !4471 - Improve issuables APIs performance when accessing notes !4471
- External links now open in a new tab - External links now open in a new tab
- Prevent default actions of disabled buttons and links
- Markdown editor now correctly resets the input value on edit cancellation !4175 - Markdown editor now correctly resets the input value on edit cancellation !4175
- Toggling a task list item in a issue/mr description does not creates a Todo for mentions - Toggling a task list item in a issue/mr description does not creates a Todo for mentions
- Improved UX of date pickers on issue & milestone forms - Improved UX of date pickers on issue & milestone forms
- Cache on the database if a project has an active external issue tracker. - Cache on the database if a project has an active external issue tracker.
- Put project Labels and Milestones pages links under Issues and Merge Requests tabs as subnav - Put project Labels and Milestones pages links under Issues and Merge Requests tabs as subnav
- All classes in the Banzai::ReferenceParser namespace are now instrumented
- Remove deprecated issues_tracker and issues_tracker_id from project model
- Allow users to create confidential issues in private projects
- Measure CPU time for instrumented methods
- Instrument private methods and private instance methods by default instead just public methods
- Only show notes through JSON on confidential issues that the user has access to
- Updated the allocations Gem to version 1.0.5
- The background sampler now ignores classes without names
- Update design for `Close` buttons
- New custom icons for navigation
- Horizontally scrolling navigation on project, group, and profile settings pages
- Hide global side navigation by default
- Fix project Star/Unstar project button tooltip
- Remove tanuki logo from side navigation; center on top nav
- Include user relationships when retrieving award_emoji
- Various associations are now eager loaded when parsing issue references to reduce the number of queries executed
- Set inverse_of for Project/Service association to reduce the number of queries
v 8.8.5 (unreleased) v 8.8.5 (unreleased)
- Ensure branch cleanup regardless of whether the GitHub import process succeeds - Ensure branch cleanup regardless of whether the GitHub import process succeeds
......
...@@ -220,6 +220,9 @@ gem 'mousetrap-rails', '~> 1.4.6' ...@@ -220,6 +220,9 @@ gem 'mousetrap-rails', '~> 1.4.6'
# Detect and convert string character encoding # Detect and convert string character encoding
gem 'charlock_holmes', '~> 0.7.3' gem 'charlock_holmes', '~> 0.7.3'
# Parse duration
gem 'chronic_duration', '~> 0.10.6'
gem "sass-rails", '~> 5.0.0' gem "sass-rails", '~> 5.0.0'
gem "coffee-rails", '~> 4.1.0' gem "coffee-rails", '~> 4.1.0'
gem "uglifier", '~> 2.7.2' gem "uglifier", '~> 2.7.2'
...@@ -256,7 +259,7 @@ end ...@@ -256,7 +259,7 @@ end
group :development do group :development do
gem "foreman" gem "foreman"
gem 'brakeman', '~> 3.2.0', require: false gem 'brakeman', '~> 3.3.0', require: false
gem 'letter_opener_web', '~> 1.3.0' gem 'letter_opener_web', '~> 1.3.0'
gem 'quiet_assets', '~> 1.0.2' gem 'quiet_assets', '~> 1.0.2'
......
...@@ -50,7 +50,7 @@ GEM ...@@ -50,7 +50,7 @@ GEM
after_commit_queue (1.3.0) after_commit_queue (1.3.0)
activerecord (>= 3.0) activerecord (>= 3.0)
akismet (2.0.0) akismet (2.0.0)
allocations (1.0.4) allocations (1.0.5)
arel (6.0.3) arel (6.0.3)
asana (0.4.0) asana (0.4.0)
faraday (~> 0.9) faraday (~> 0.9)
...@@ -97,16 +97,7 @@ GEM ...@@ -97,16 +97,7 @@ GEM
bootstrap-sass (3.3.6) bootstrap-sass (3.3.6)
autoprefixer-rails (>= 5.2.1) autoprefixer-rails (>= 5.2.1)
sass (>= 3.3.4) sass (>= 3.3.4)
brakeman (3.2.1) brakeman (3.3.2)
erubis (~> 2.6)
haml (>= 3.0, < 5.0)
highline (>= 1.6.20, < 2.0)
ruby2ruby (~> 2.3.0)
ruby_parser (~> 3.8.1)
safe_yaml (>= 1.0)
sass (~> 3.0)
slim (>= 1.3.6, < 4.0)
terminal-table (~> 1.4)
browser (2.0.3) browser (2.0.3)
builder (3.2.2) builder (3.2.2)
bullet (5.0.0) bullet (5.0.0)
...@@ -133,6 +124,8 @@ GEM ...@@ -133,6 +124,8 @@ GEM
mime-types (>= 1.16) mime-types (>= 1.16)
cause (0.1) cause (0.1)
charlock_holmes (0.7.3) charlock_holmes (0.7.3)
chronic_duration (0.10.6)
numerizer (~> 0.1.1)
chunky_png (1.3.5) chunky_png (1.3.5)
cliver (0.3.2) cliver (0.3.2)
coderay (1.1.0) coderay (1.1.0)
...@@ -306,7 +299,7 @@ GEM ...@@ -306,7 +299,7 @@ GEM
gitlab-license (0.0.4) gitlab-license (0.0.4)
gitlab_emoji (0.3.1) gitlab_emoji (0.3.1)
gemojione (~> 2.2, >= 2.2.1) gemojione (~> 2.2, >= 2.2.1)
gitlab_git (10.1.0) gitlab_git (10.1.3)
activesupport (~> 4.0) activesupport (~> 4.0)
charlock_holmes (~> 0.7.3) charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
...@@ -362,7 +355,6 @@ GEM ...@@ -362,7 +355,6 @@ GEM
hashie (3.4.3) hashie (3.4.3)
health_check (1.5.1) health_check (1.5.1)
rails (>= 2.3.0) rails (>= 2.3.0)
highline (1.7.8)
hipchat (1.5.2) hipchat (1.5.2)
httparty httparty
mimemagic mimemagic
...@@ -432,7 +424,7 @@ GEM ...@@ -432,7 +424,7 @@ GEM
mime-types (>= 1.16, < 4) mime-types (>= 1.16, < 4)
mail_room (0.7.0) mail_room (0.7.0)
method_source (0.8.2) method_source (0.8.2)
mime-types (2.99.1) mime-types (2.99.2)
mimemagic (0.3.0) mimemagic (0.3.0)
mini_portile2 (2.1.0) mini_portile2 (2.1.0)
minitest (5.7.0) minitest (5.7.0)
...@@ -448,6 +440,7 @@ GEM ...@@ -448,6 +440,7 @@ GEM
nokogiri (1.6.8) nokogiri (1.6.8)
mini_portile2 (~> 2.1.0) mini_portile2 (~> 2.1.0)
pkg-config (~> 1.1.7) pkg-config (~> 1.1.7)
numerizer (0.1.1)
oauth (0.4.7) oauth (0.4.7)
oauth2 (1.0.0) oauth2 (1.0.0)
faraday (>= 0.8, < 0.10) faraday (>= 0.8, < 0.10)
...@@ -666,10 +659,7 @@ GEM ...@@ -666,10 +659,7 @@ GEM
ruby-saml (1.1.2) ruby-saml (1.1.2)
nokogiri (>= 1.5.10) nokogiri (>= 1.5.10)
uuid (~> 2.3) uuid (~> 2.3)
ruby2ruby (2.3.0) ruby_parser (3.8.2)
ruby_parser (~> 3.1)
sexp_processor (~> 4.0)
ruby_parser (3.8.1)
sexp_processor (~> 4.1) sexp_processor (~> 4.1)
rubyntlm (0.5.2) rubyntlm (0.5.2)
rubypants (0.2.0) rubypants (0.2.0)
...@@ -679,7 +669,7 @@ GEM ...@@ -679,7 +669,7 @@ GEM
safe_yaml (1.0.4) safe_yaml (1.0.4)
sanitize (2.1.0) sanitize (2.1.0)
nokogiri (>= 1.4.4) nokogiri (>= 1.4.4)
sass (3.4.21) sass (3.4.22)
sass-rails (5.0.4) sass-rails (5.0.4)
railties (>= 4.0.0, < 5.0) railties (>= 4.0.0, < 5.0)
sass (~> 3.1) sass (~> 3.1)
...@@ -728,9 +718,6 @@ GEM ...@@ -728,9 +718,6 @@ GEM
tilt (>= 1.3, < 3) tilt (>= 1.3, < 3)
six (0.2.0) six (0.2.0)
slack-notifier (1.2.1) slack-notifier (1.2.1)
slim (3.0.6)
temple (~> 0.7.3)
tilt (>= 1.3.3, < 2.1)
slop (3.6.0) slop (3.6.0)
spinach (0.8.10) spinach (0.8.10)
colorize colorize
...@@ -771,10 +758,8 @@ GEM ...@@ -771,10 +758,8 @@ GEM
railties (>= 3.2.5, < 6) railties (>= 3.2.5, < 6)
teaspoon-jasmine (2.2.0) teaspoon-jasmine (2.2.0)
teaspoon (>= 1.0.0) teaspoon (>= 1.0.0)
temple (0.7.6)
term-ansicolor (1.3.2) term-ansicolor (1.3.2)
tins (~> 1.0) tins (~> 1.0)
terminal-table (1.5.2)
test_after_commit (0.4.2) test_after_commit (0.4.2)
activerecord (>= 3.2) activerecord (>= 3.2)
thin (1.6.4) thin (1.6.4)
...@@ -783,7 +768,7 @@ GEM ...@@ -783,7 +768,7 @@ GEM
rack (~> 1.0) rack (~> 1.0)
thor (0.19.1) thor (0.19.1)
thread_safe (0.3.5) thread_safe (0.3.5)
tilt (2.0.2) tilt (2.0.5)
timecop (0.8.1) timecop (0.8.1)
timfel-krb5-auth (0.8.3) timfel-krb5-auth (0.8.3)
tinder (1.10.1) tinder (1.10.1)
...@@ -875,7 +860,7 @@ DEPENDENCIES ...@@ -875,7 +860,7 @@ DEPENDENCIES
better_errors (~> 1.0.1) better_errors (~> 1.0.1)
binding_of_caller (~> 0.7.2) binding_of_caller (~> 0.7.2)
bootstrap-sass (~> 3.3.0) bootstrap-sass (~> 3.3.0)
brakeman (~> 3.2.0) brakeman (~> 3.3.0)
browser (~> 2.0.3) browser (~> 2.0.3)
bullet bullet
bundler-audit bundler-audit
...@@ -884,6 +869,7 @@ DEPENDENCIES ...@@ -884,6 +869,7 @@ DEPENDENCIES
capybara-screenshot (~> 1.0.0) capybara-screenshot (~> 1.0.0)
carrierwave (~> 0.10.0) carrierwave (~> 0.10.0)
charlock_holmes (~> 0.7.3) charlock_holmes (~> 0.7.3)
chronic_duration (~> 0.10.6)
coffee-rails (~> 4.1.0) coffee-rails (~> 4.1.0)
connection_pool (~> 2.0) connection_pool (~> 2.0)
coveralls (~> 0.8.2) coveralls (~> 0.8.2)
......
...@@ -128,6 +128,7 @@ window.onload = -> ...@@ -128,6 +128,7 @@ window.onload = ->
setTimeout shiftWindow, 100 setTimeout shiftWindow, 100
$ -> $ ->
gl.utils.preventDisabledButtons()
bootstrapBreakpoint = bp.getBreakpointSize() bootstrapBreakpoint = bp.getBreakpointSize()
$(".nicescroll").niceScroll(cursoropacitymax: '0.4', cursorcolor: '#FFF', cursorborder: "1px solid #FFF") $(".nicescroll").niceScroll(cursoropacitymax: '0.4', cursorcolor: '#FFF', cursorborder: "1px solid #FFF")
......
...@@ -17,6 +17,8 @@ class @CiBuild ...@@ -17,6 +17,8 @@ class @CiBuild
.off 'resize.build' .off 'resize.build'
.on 'resize.build', @hideSidebar .on 'resize.build', @hideSidebar
@updateArtifactRemoveDate()
if $('#build-trace').length if $('#build-trace').length
@getInitialBuildTrace() @getInitialBuildTrace()
@initScrollButtonAffix() @initScrollButtonAffix()
...@@ -103,3 +105,10 @@ class @CiBuild ...@@ -103,3 +105,10 @@ class @CiBuild
$('.js-build-sidebar') $('.js-build-sidebar')
.removeClass 'right-sidebar-collapsed' .removeClass 'right-sidebar-collapsed'
.addClass 'right-sidebar-expanded' .addClass 'right-sidebar-expanded'
updateArtifactRemoveDate: ->
$date = $('.js-artifacts-remove')
if $date.length
date = $date.text()
$date.text $.timefor(new Date(date), ' ')
...@@ -53,9 +53,13 @@ class Dispatcher ...@@ -53,9 +53,13 @@ class Dispatcher
new Diff() new Diff()
shortcut_handler = new ShortcutsIssuable(true) shortcut_handler = new ShortcutsIssuable(true)
new ZenMode() new ZenMode()
new MergedButtons()
when 'projects:merge_requests:commits', 'projects:merge_requests:builds'
new MergedButtons()
when "projects:merge_requests:diffs" when "projects:merge_requests:diffs"
new Diff() new Diff()
new ZenMode() new ZenMode()
new MergedButtons()
when 'projects:merge_requests:index' when 'projects:merge_requests:index'
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
Issuable.init() Issuable.init()
......
...@@ -56,13 +56,6 @@ issuable_created = false ...@@ -56,13 +56,6 @@ issuable_created = false
Issuable.filterResults $('.filter-form') Issuable.filterResults $('.filter-form')
$('.js-label-select').trigger('update.label') $('.js-label-select').trigger('update.label')
toggleLabelFilters: ->
$filteredLabels = $('.filtered-labels')
if $filteredLabels.find('.label-row').length > 0
$filteredLabels.removeClass('hidden')
else
$filteredLabels.addClass('hidden')
filterResults: (form) => filterResults: (form) =>
formData = form.serialize() formData = form.serialize()
...@@ -71,58 +64,16 @@ issuable_created = false ...@@ -71,58 +64,16 @@ issuable_created = false
issuesUrl = formAction issuesUrl = formAction
issuesUrl += ("#{if formAction.indexOf('?') < 0 then '?' else '&'}") issuesUrl += ("#{if formAction.indexOf('?') < 0 then '?' else '&'}")
issuesUrl += formData issuesUrl += formData
$.ajax
type: 'GET'
url: formAction
data: formData
complete: ->
$('.issues-holder, .merge-requests-holder').css('opacity', '1.0')
success: (data) ->
$('.issues-holder, .merge-requests-holder').html(data.html)
# Change url so if user reload a page - search results are saved
history.replaceState {page: issuesUrl}, document.title, issuesUrl
Issuable.reload()
Issuable.updateStateFilters()
$filteredLabels = $('.filtered-labels')
if typeof Issuable.labelRow is 'function'
$filteredLabels.html(Issuable.labelRow(data))
Issuable.toggleLabelFilters() Turbolinks.visit(issuesUrl);
dataType: "json"
reload: ->
if Issuable.created
Issuable.initChecks()
$('#filter_issue_search').val($('#issue_search').val())
initChecks: -> initChecks: ->
$('.check_all_issues').on 'click', -> $('.check_all_issues').off('click').on('click', ->
$('.selected_issue').prop('checked', @checked) $('.selected_issue').prop('checked', @checked)
Issuable.checkChanged() Issuable.checkChanged()
)
$('.selected_issue').on 'change', Issuable.checkChanged $('.selected_issue').off('change').on('change', Issuable.checkChanged)
updateStateFilters: ->
stateFilters = $('.issues-state-filters, .dropdown-menu-sort')
newParams = {}
paramKeys = ['author_id', 'milestone_title', 'assignee_id', 'issue_search', 'issue_search']
for paramKey in paramKeys
newParams[paramKey] = gl.utils.getParameterValues(paramKey)[0] or ''
if stateFilters.length
stateFilters.find('a').each ->
initialUrl = gl.utils.removeParamQueryString($(this).attr('href'), 'label_name[]')
labelNameValues = gl.utils.getParameterValues('label_name[]')
if labelNameValues
labelNameQueryString = ("label_name[]=#{value}" for value in labelNameValues).join('&')
newUrl = "#{gl.utils.mergeUrlParams(newParams, initialUrl)}&#{labelNameQueryString}"
else
newUrl = gl.utils.mergeUrlParams(newParams, initialUrl)
$(this).attr 'href', newUrl
checkChanged: -> checkChanged: ->
checked_issues = $('.selected_issue:checked') checked_issues = $('.selected_issue:checked')
......
...@@ -102,6 +102,10 @@ class @IssuableForm ...@@ -102,6 +102,10 @@ class @IssuableForm
return { return {
results: data results: data
} }
data: (query) ->
{
search: query
}
formatResult: (project) -> formatResult: (project) ->
project.name_with_namespace project.name_with_namespace
formatSelection: (project) -> formatSelection: (project) ->
......
...@@ -9,6 +9,9 @@ class @IssuableBulkActions ...@@ -9,6 +9,9 @@ class @IssuableBulkActions
@bindEvents() @bindEvents()
# Fixes bulk-assign not working when navigating through pages
Issuable.initChecks();
getElement: (selector) -> getElement: (selector) ->
@container.find selector @container.find selector
...@@ -97,13 +100,22 @@ class @IssuableBulkActions ...@@ -97,13 +100,22 @@ class @IssuableBulkActions
$labels = @form.find('.labels-filter input[name="update[label_ids][]"]') $labels = @form.find('.labels-filter input[name="update[label_ids][]"]')
$labels.each (k, label) -> $labels.each (k, label) ->
labelIds.push $(label).val() if label labelIds.push parseInt($(label).val()) if label
labelIds labelIds
###* ###*
* Just an alias of @getUnmarkedIndeterminedLabels * Returns Label IDs that will be removed from issue selection
* @return {Array} Array of labels * @return {Array} Array of labels IDs
### ###
getLabelsToRemove: -> getLabelsToRemove: ->
@getUnmarkedIndeterminedLabels() result = []
indeterminatedLabels = @getUnmarkedIndeterminedLabels()
labelsToApply = @getLabelsToApply()
indeterminatedLabels.map (id) ->
# We need to exclude label IDs that will be applied
# By not doing this will cause issues from selection to not add labels at all
result.push(id) if labelsToApply.indexOf(id) is -1
result
class @LayoutNav hideEndFade = ($scrollingTabs) ->
$ -> $scrollingTabs.each ->
$('.fade-left').addClass('end-scroll') $this = $(@)
$('.scrolling-tabs').on 'scroll', (event) ->
$this = $(this) $this
$el = $(event.target) .find('.fade-right')
currentPosition = $this.scrollLeft() .toggleClass('end-scroll', $this.width() is $this.prop('scrollWidth'))
size = bp.getBreakpointSize()
controlBtnWidth = $('.controls').width() $ ->
maxPosition = $this.get(0).scrollWidth - $this.parent().width() $('.fade-left').addClass('end-scroll')
maxPosition += controlBtnWidth if size isnt 'xs' and $('.nav-control').length
hideEndFade($('.scrolling-tabs'))
$el.find('.fade-left').toggleClass('end-scroll', currentPosition is 0)
$el.find('.fade-right').toggleClass('end-scroll', currentPosition is maxPosition) $(window)
.off 'resize.nav'
.on 'resize.nav', ->
hideEndFade($('.scrolling-tabs'))
$('.scrolling-tabs').on 'scroll', (event) ->
$this = $(this)
currentPosition = $this.scrollLeft()
maxPosition = $this.prop('scrollWidth') - $this.outerWidth()
$this.find('.fade-left').toggleClass('end-scroll', currentPosition is 0)
$this.find('.fade-right').toggleClass('end-scroll', currentPosition is maxPosition)
((w) -> ((w) ->
window.gl or= {}
window.gl.utils or= {}
jQuery.timefor = (time, suffix, expiredLabel) -> jQuery.timefor = (time, suffix, expiredLabel) ->
return '' unless time return '' unless time
...@@ -21,4 +24,20 @@ ...@@ -21,4 +24,20 @@
return timefor return timefor
gl.utils.updateTooltipTitle = ($tooltipEl, newTitle) ->
$tooltipEl
.tooltip 'destroy'
.attr 'title', newTitle
.tooltip 'fixTitle'
gl.utils.preventDisabledButtons = ->
$('.btn').click (e) ->
if $(this).hasClass 'disabled'
e.preventDefault()
e.stopImmediatePropagation()
return false
) window ) window
...@@ -42,9 +42,3 @@ work = -> ...@@ -42,9 +42,3 @@ work = ->
$(document).on('page:fetch', start) $(document).on('page:fetch', start)
$(document).on('page:change', stop) $(document).on('page:change', stop)
$ ->
# Make logo clickable as part of a workaround for Safari visited
# link behaviour (See !2690).
$('#logo').on 'click', ->
Turbolinks.visit('/')
class @MergedButtons
constructor: ->
@$removeBranchWidget = $('.remove_source_branch_widget')
@$removeBranchProgress = $('.remove_source_branch_in_progress')
@$removeBranchFailed = $('.remove_source_branch_widget.failed')
@cleanEventListeners()
@initEventListeners()
cleanEventListeners: ->
$(document).off 'click', '.remove_source_branch'
$(document).off 'ajax:success', '.remove_source_branch'
$(document).off 'ajax:error', '.remove_source_branch'
initEventListeners: ->
$(document).on 'click', '.remove_source_branch', @removeSourceBranch
$(document).on 'ajax:success', '.remove_source_branch', @removeBranchSuccess
$(document).on 'ajax:error', '.remove_source_branch', @removeBranchError
removeSourceBranch: =>
@$removeBranchWidget.hide()
@$removeBranchProgress.show()
removeBranchSuccess: ->
location.reload()
removeBranchError: ->
@$removeBranchWidget.hide()
@$removeBranchProgress.hide()
@$removeBranchFailed.show()
...@@ -116,7 +116,7 @@ class @MilestoneSelect ...@@ -116,7 +116,7 @@ class @MilestoneSelect
.val() .val()
data = {} data = {}
data[abilityName] = {} data[abilityName] = {}
data[abilityName].milestone_id = selected data[abilityName].milestone_id = if selected? then selected else null
$loading $loading
.fadeIn() .fadeIn()
$dropdown.trigger('loading.gl.dropdown') $dropdown.trigger('loading.gl.dropdown')
......
...@@ -115,12 +115,14 @@ class @Notes ...@@ -115,12 +115,14 @@ class @Notes
, @pollingInterval , @pollingInterval
refresh: => refresh: =>
return if @refreshing is true
@refreshing = true
if not document.hidden and document.URL.indexOf(@noteable_url) is 0 if not document.hidden and document.URL.indexOf(@noteable_url) is 0
@getContent() @getContent()
getContent: -> getContent: ->
return if @refreshing
@refreshing = true
$.ajax $.ajax
url: @notes_url url: @notes_url
data: "last_fetched_at=" + @last_fetched_at data: "last_fetched_at=" + @last_fetched_at
......
...@@ -43,6 +43,55 @@ class @Sidebar ...@@ -43,6 +43,55 @@ class @Sidebar
$('.right-sidebar') $('.right-sidebar')
.hasClass('right-sidebar-collapsed'), { path: '/' }) .hasClass('right-sidebar-collapsed'), { path: '/' })
$(document)
.off 'click', '.js-issuable-todo'
.on 'click', '.js-issuable-todo', @toggleTodo
toggleTodo: (e) =>
$this = $(e.currentTarget)
$todoLoading = $('.js-issuable-todo-loading')
$btnText = $('.js-issuable-todo-text', $this)
ajaxType = if $this.attr('data-id') then 'PATCH' else 'POST'
ajaxUrlExtra = if $this.attr('data-id') then "/#{$this.attr('data-id')}" else ''
$.ajax(
url: "#{$this.data('url')}#{ajaxUrlExtra}"
type: ajaxType
dataType: 'json'
data:
issuable_id: $this.data('issuable')
issuable_type: $this.data('issuable-type')
beforeSend: =>
@beforeTodoSend($this, $todoLoading)
).done (data) =>
@todoUpdateDone(data, $this, $btnText, $todoLoading)
beforeTodoSend: ($btn, $todoLoading) ->
$btn.disable()
$todoLoading.removeClass 'hidden'
todoUpdateDone: (data, $btn, $btnText, $todoLoading) ->
$todoPendingCount = $('.todos-pending-count')
$todoPendingCount.text data.count
$btn.enable()
$todoLoading.addClass 'hidden'
if data.count is 0
$todoPendingCount.addClass 'hidden'
else
$todoPendingCount.removeClass 'hidden'
if data.todo?
$btn
.attr 'aria-label', $btn.data('mark-text')
.attr 'data-id', data.todo.id
$btnText.text $btn.data('mark-text')
else
$btn
.attr 'aria-label', $btn.data('todo-text')
.removeAttr 'data-id'
$btnText.text $btn.data('todo-text')
sidebarDropdownLoading: (e) -> sidebarDropdownLoading: (e) ->
$sidebarCollapsedIcon = $(@).closest('.block').find('.sidebar-collapsed-icon') $sidebarCollapsedIcon = $(@).closest('.block').find('.sidebar-collapsed-icon')
...@@ -117,5 +166,3 @@ class @Sidebar ...@@ -117,5 +166,3 @@ class @Sidebar
getBlock: (name) -> getBlock: (name) ->
@sidebar.find(".block.#{name}") @sidebar.find(".block.#{name}")
...@@ -9,9 +9,11 @@ class @Star ...@@ -9,9 +9,11 @@ class @Star
$this.parent().find('.star-count').text data.star_count $this.parent().find('.star-count').text data.star_count
if isStarred if isStarred
$starSpan.removeClass('starred').text 'Star' $starSpan.removeClass('starred').text 'Star'
gl.utils.updateTooltipTitle $this, 'Star project'
$starIcon.removeClass('fa-star').addClass 'fa-star-o' $starIcon.removeClass('fa-star').addClass 'fa-star-o'
else else
$starSpan.addClass('starred').text 'Unstar' $starSpan.addClass('starred').text 'Unstar'
gl.utils.updateTooltipTitle $this, 'Unstar project'
$starIcon.removeClass('fa-star-o').addClass 'fa-star' $starIcon.removeClass('fa-star-o').addClass 'fa-star'
return return
......
...@@ -31,7 +31,7 @@ class @UsersSelect ...@@ -31,7 +31,7 @@ class @UsersSelect
assignTo = (selected) -> assignTo = (selected) ->
data = {} data = {}
data[abilityName] = {} data[abilityName] = {}
data[abilityName].assignee_id = selected data[abilityName].assignee_id = if selected? then selected else null
$loading $loading
.fadeIn() .fadeIn()
$dropdown.trigger('loading.gl.dropdown') $dropdown.trigger('loading.gl.dropdown')
......
...@@ -74,6 +74,7 @@ ...@@ -74,6 +74,7 @@
.container-fluid { .container-fluid {
background-color: $background-color; background-color: $background-color;
margin-bottom: 0;
} }
li { li {
...@@ -280,11 +281,10 @@ ...@@ -280,11 +281,10 @@
} }
.dropdown { .dropdown {
margin-left: 7px; position: absolute;
top: 7px;
@media (max-width: $screen-xs-min) { right: 15px;
margin-left: 0; z-index: 2;
}
li.active { li.active {
font-weight: bold; font-weight: bold;
......
...@@ -83,6 +83,12 @@ ...@@ -83,6 +83,12 @@
margin-top: 10px; margin-top: 10px;
} }
.icon-container {
width: 34px;
display: inline-block;
text-align: center;
}
a { a {
width: $sidebar_width; width: $sidebar_width;
padding: 7px 15px 7px 23px; padding: 7px 15px 7px 23px;
......
.environments {
.commit-title {
margin: 0;
}
}
...@@ -34,6 +34,10 @@ ...@@ -34,6 +34,10 @@
color: inherit; color: inherit;
} }
.issuable-header-text {
margin-top: 7px;
}
.block { .block {
@include clearfix; @include clearfix;
padding: $gl-padding 0; padding: $gl-padding 0;
...@@ -60,10 +64,6 @@ ...@@ -60,10 +64,6 @@
margin-top: 0; margin-top: 0;
} }
.issuable-count {
margin-top: 7px;
}
.gutter-toggle { .gutter-toggle {
margin-left: 20px; margin-left: 20px;
padding-left: 10px; padding-left: 10px;
...@@ -250,7 +250,7 @@ ...@@ -250,7 +250,7 @@
} }
} }
.issuable-pager { .issuable-header-btn {
background: $gray-normal; background: $gray-normal;
border: 1px solid $border-gray-normal; border: 1px solid $border-gray-normal;
&:hover { &:hover {
...@@ -263,7 +263,7 @@ ...@@ -263,7 +263,7 @@
} }
} }
a:not(.issuable-pager) { a {
&:hover { &:hover {
color: $md-link-color; color: $md-link-color;
text-decoration: none; text-decoration: none;
......
...@@ -313,3 +313,13 @@ ...@@ -313,3 +313,13 @@
} }
} }
} }
.merged-buttons {
.btn {
float: left;
&:not(:last-child) {
margin-right: 10px;
}
}
}
...@@ -43,6 +43,7 @@ class AutocompleteController < ApplicationController ...@@ -43,6 +43,7 @@ class AutocompleteController < ApplicationController
project = Project.find_by_id(params[:project_id]) project = Project.find_by_id(params[:project_id])
projects = current_user.authorized_projects projects = current_user.authorized_projects
projects = projects.search(params[:search]) if params[:search].present?
projects = projects.select do |project| projects = projects.select do |project|
current_user.can?(:admin_issue, project) current_user.can?(:admin_issue, project)
end end
......
...@@ -42,7 +42,7 @@ class JwtController < ApplicationController ...@@ -42,7 +42,7 @@ class JwtController < ApplicationController
end end
def authenticate_user(login, password) def authenticate_user(login, password)
user = Gitlab::Auth.find_in_gitlab_or_ldap(login, password) user = Gitlab::Auth.find_with_user_password(login, password)
Gitlab::Auth.rate_limit!(request.ip, success: user.present?, login: login) Gitlab::Auth.rate_limit!(request.ip, success: user.present?, login: login)
user user
end end
......
class Profiles::NotificationsController < Profiles::ApplicationController class Profiles::NotificationsController < Profiles::ApplicationController
def show def show
@user = current_user @user = current_user
@group_notifications = current_user.notification_settings.for_groups @group_notifications = current_user.notification_settings.for_groups
@project_notifications = current_user.notification_settings.for_projects @project_notifications = current_user.notification_settings.for_projects
@global_notification_setting = current_user.global_notification_setting
end end
def update def update
if current_user.update_attributes(user_params) if current_user.update_attributes(user_params) && update_notification_settings
flash[:notice] = "Notification settings saved" flash[:notice] = "Notification settings saved"
else else
flash[:alert] = "Failed to save new settings" flash[:alert] = "Failed to save new settings"
...@@ -16,6 +17,18 @@ class Profiles::NotificationsController < Profiles::ApplicationController ...@@ -16,6 +17,18 @@ class Profiles::NotificationsController < Profiles::ApplicationController
end end
def user_params def user_params
params.require(:user).permit(:notification_email, :notification_level) params.require(:user).permit(:notification_email)
end
def global_notification_setting_params
params.require(:global_notification_setting).permit(:level)
end
private
def update_notification_settings
return true unless global_notification_setting_params
current_user.global_notification_setting.update_attributes(global_notification_setting_params)
end end
end end
class Projects::ArtifactsController < Projects::ApplicationController class Projects::ArtifactsController < Projects::ApplicationController
layout 'project' layout 'project'
before_action :authorize_read_build! before_action :authorize_read_build!
before_action :authorize_update_build!, only: [:keep]
before_action :validate_artifacts!
def download def download
unless artifacts_file.file_storage? unless artifacts_file.file_storage?
return redirect_to artifacts_file.url return redirect_to artifacts_file.url
end end
unless artifacts_file.exists?
return render_404
end
send_file artifacts_file.path, disposition: 'attachment' send_file artifacts_file.path, disposition: 'attachment'
end end
def browse def browse
return render_404 unless build.artifacts?
directory = params[:path] ? "#{params[:path]}/" : '' directory = params[:path] ? "#{params[:path]}/" : ''
@entry = build.artifacts_metadata_entry(directory) @entry = build.artifacts_metadata_entry(directory)
...@@ -34,8 +30,17 @@ class Projects::ArtifactsController < Projects::ApplicationController ...@@ -34,8 +30,17 @@ class Projects::ArtifactsController < Projects::ApplicationController
end end
end end
def keep
build.keep_artifacts!
redirect_to namespace_project_build_path(project.namespace, project, build)
end
private private
def validate_artifacts!
render_404 unless build.artifacts?
end
def build def build
@build ||= project.builds.find_by!(id: params[:build_id]) @build ||= project.builds.find_by!(id: params[:build_id])
end end
......
...@@ -51,7 +51,7 @@ class Projects::BuildsController < Projects::ApplicationController ...@@ -51,7 +51,7 @@ class Projects::BuildsController < Projects::ApplicationController
return render_404 return render_404
end end
build = Ci::Build.retry(@build) build = Ci::Build.retry(@build, current_user)
redirect_to build_path(build) redirect_to build_path(build)
end end
......
...@@ -46,7 +46,7 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -46,7 +46,7 @@ class Projects::CommitController < Projects::ApplicationController
def retry_builds def retry_builds
ci_builds.latest.failed.each do |build| ci_builds.latest.failed.each do |build|
if build.retryable? if build.retryable?
Ci::Build.retry(build) Ci::Build.retry(build, current_user)
end end
end end
......
class Projects::EnvironmentsController < Projects::ApplicationController
layout 'project'
before_action :authorize_read_environment!
before_action :authorize_create_environment!, only: [:new, :create]
before_action :authorize_update_environment!, only: [:destroy]
before_action :environment, only: [:show, :destroy]
def index
@environments = project.environments
end
def show
@deployments = environment.deployments.order(id: :desc).page(params[:page])
end
def new
@environment = project.environments.new
end
def create
@environment = project.environments.create(create_params)
if @environment.persisted?
redirect_to namespace_project_environment_path(project.namespace, project, @environment)
else
render 'new'
end
end
def destroy
if @environment.destroy
flash[:notice] = 'Environment was successfully removed.'
else
flash[:alert] = 'Failed to remove environment.'
end
redirect_to namespace_project_environments_path(project.namespace, project)
end
private
def create_params
params.require(:environment).permit(:name)
end
def environment
@environment ||= project.environments.find(params[:id])
end
end
...@@ -43,7 +43,7 @@ class Projects::GitHttpController < Projects::ApplicationController ...@@ -43,7 +43,7 @@ class Projects::GitHttpController < Projects::ApplicationController
return if project && project.public? && upload_pack? return if project && project.public? && upload_pack?
authenticate_or_request_with_http_basic do |login, password| authenticate_or_request_with_http_basic do |login, password|
auth_result = Gitlab::Auth.find(login, password, project: project, ip: request.ip) auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip)
if auth_result.type == :ci && upload_pack? if auth_result.type == :ci && upload_pack?
@ci = true @ci = true
......
...@@ -32,7 +32,7 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -32,7 +32,7 @@ class Projects::PipelinesController < Projects::ApplicationController
end end
def retry def retry
pipeline.retry_failed pipeline.retry_failed(current_user)
redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project) redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project)
end end
......
class Projects::TodosController < Projects::ApplicationController
def create
todos = TodoService.new.mark_todo(issuable, current_user)
render json: {
todo: todos,
count: current_user.todos.pending.count,
}
end
def update
current_user.todos.find_by_id(params[:id]).update(state: :done)
render json: {
count: current_user.todos.pending.count,
}
end
private
def issuable
@issuable ||= begin
case params[:issuable_type]
when "issue"
@project.issues.find(params[:issuable_id])
when "merge_request"
@project.merge_requests.find(params[:issuable_id])
end
end
end
end
...@@ -16,6 +16,9 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -16,6 +16,9 @@ class Projects::WikisController < Projects::ApplicationController
if @page if @page
render 'show' render 'show'
elsif file = @project_wiki.find_file(params[:id], params[:version_id]) elsif file = @project_wiki.find_file(params[:id], params[:version_id])
response.headers['Content-Security-Policy'] = "default-src 'none'"
response.headers['X-Content-Security-Policy'] = "default-src 'none'"
if file.on_disk? if file.on_disk?
send_file file.on_disk_path, disposition: 'inline' send_file file.on_disk_path, disposition: 'inline'
else else
......
...@@ -41,7 +41,7 @@ class SessionsController < Devise::SessionsController ...@@ -41,7 +41,7 @@ class SessionsController < Devise::SessionsController
# Handle an "initial setup" state, where there's only one user, it's an admin, # Handle an "initial setup" state, where there's only one user, it's an admin,
# and they require a password change. # and they require a password change.
def check_initial_setup def check_initial_setup
return unless User.count == 1 return unless User.limit(2).count == 1 # Count as much 2 to know if we have exactly one
user = User.admins.last user = User.admins.last
......
...@@ -12,7 +12,7 @@ class NotesFinder ...@@ -12,7 +12,7 @@ class NotesFinder
when "commit" when "commit"
project.notes.for_commit_id(target_id).non_diff_notes project.notes.for_commit_id(target_id).non_diff_notes
when "issue" when "issue"
project.issues.find(target_id).notes.inc_author project.issues.visible_to_user(current_user).find(target_id).notes.inc_author
when "merge_request" when "merge_request"
project.merge_requests.find(target_id).mr_and_commit_notes.inc_author project.merge_requests.find(target_id).mr_and_commit_notes.inc_author
when "snippet", "project_snippet" when "snippet", "project_snippet"
......
...@@ -51,7 +51,7 @@ class SnippetsFinder ...@@ -51,7 +51,7 @@ class SnippetsFinder
snippets = project.snippets.fresh snippets = project.snippets.fresh
if current_user if current_user
if project.team.member?(current_user.id) || current_user.admin? if project.team.member?(current_user) || current_user.admin?
snippets snippets
else else
snippets.public_and_internal snippets.public_and_internal
......
...@@ -36,7 +36,7 @@ class TodosFinder ...@@ -36,7 +36,7 @@ class TodosFinder
private private
def action_id? def action_id?
action_id.present? && [Todo::ASSIGNED, Todo::MENTIONED, Todo::BUILD_FAILED].include?(action_id.to_i) action_id.present? && [Todo::ASSIGNED, Todo::MENTIONED, Todo::BUILD_FAILED, Todo::MARKED].include?(action_id.to_i)
end end
def action_id def action_id
......
...@@ -116,7 +116,7 @@ module BlobHelper ...@@ -116,7 +116,7 @@ module BlobHelper
end end
def blob_text_viewable?(blob) def blob_text_viewable?(blob)
blob && blob.text? && !blob.lfs_pointer? blob && blob.text? && !blob.lfs_pointer? && !blob.only_display_raw?
end end
def blob_size(blob) def blob_size(blob)
......
...@@ -14,4 +14,8 @@ module BranchesHelper ...@@ -14,4 +14,8 @@ module BranchesHelper
::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(branch_name) ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(branch_name)
end end
def project_branches
options_for_select(@project.repository.branch_names, @project.default_branch)
end
end end
...@@ -129,7 +129,7 @@ module CommitsHelper ...@@ -129,7 +129,7 @@ module CommitsHelper
tooltip = "Revert this #{commit.change_type_title} in a new merge request" if has_tooltip tooltip = "Revert this #{commit.change_type_title} in a new merge request" if has_tooltip
if can_collaborate_with_project? if can_collaborate_with_project?
btn_class = "btn btn-grouped btn-close btn-#{btn_class}" unless btn_class.nil? btn_class = "btn btn-warning btn-#{btn_class}" unless btn_class.nil?
link_to 'Revert', '#modal-revert-commit', 'data-toggle' => 'modal', 'data-container' => 'body', title: (tooltip if has_tooltip), class: "#{btn_class} #{'has-tooltip' if has_tooltip}" link_to 'Revert', '#modal-revert-commit', 'data-toggle' => 'modal', 'data-container' => 'body', title: (tooltip if has_tooltip), class: "#{btn_class} #{'has-tooltip' if has_tooltip}"
elsif can?(current_user, :fork_project, @project) elsif can?(current_user, :fork_project, @project)
continue_params = { continue_params = {
...@@ -141,7 +141,7 @@ module CommitsHelper ...@@ -141,7 +141,7 @@ module CommitsHelper
namespace_key: current_user.namespace.id, namespace_key: current_user.namespace.id,
continue: continue_params) continue: continue_params)
btn_class = "btn btn-grouped btn-close" unless btn_class.nil? btn_class = "btn btn-grouped btn-warning" unless btn_class.nil?
link_to 'Revert', fork_path, class: btn_class, method: :post, 'data-toggle' => 'tooltip', 'data-container' => 'body', title: (tooltip if has_tooltip) link_to 'Revert', fork_path, class: btn_class, method: :post, 'data-toggle' => 'tooltip', 'data-container' => 'body', title: (tooltip if has_tooltip)
end end
...@@ -153,7 +153,7 @@ module CommitsHelper ...@@ -153,7 +153,7 @@ module CommitsHelper
tooltip = "Cherry-pick this #{commit.change_type_title} in a new merge request" tooltip = "Cherry-pick this #{commit.change_type_title} in a new merge request"
if can_collaborate_with_project? if can_collaborate_with_project?
btn_class = "btn btn-default btn-grouped btn-#{btn_class}" unless btn_class.nil? btn_class = "btn btn-default btn-#{btn_class}" unless btn_class.nil?
link_to 'Cherry-pick', '#modal-cherry-pick-commit', 'data-toggle' => 'modal', 'data-container' => 'body', title: (tooltip if has_tooltip), class: "#{btn_class} #{'has-tooltip' if has_tooltip}" link_to 'Cherry-pick', '#modal-cherry-pick-commit', 'data-toggle' => 'modal', 'data-container' => 'body', title: (tooltip if has_tooltip), class: "#{btn_class} #{'has-tooltip' if has_tooltip}"
elsif can?(current_user, :fork_project, @project) elsif can?(current_user, :fork_project, @project)
continue_params = { continue_params = {
......
...@@ -42,6 +42,10 @@ module GitlabRoutingHelper ...@@ -42,6 +42,10 @@ module GitlabRoutingHelper
namespace_project_pipelines_path(project.namespace, project, *args) namespace_project_pipelines_path(project.namespace, project, *args)
end end
def project_environments_path(project, *args)
namespace_project_environments_path(project.namespace, project, *args)
end
def project_builds_path(project, *args) def project_builds_path(project, *args)
namespace_project_builds_path(project.namespace, project, *args) namespace_project_builds_path(project.namespace, project, *args)
end end
......
...@@ -67,6 +67,12 @@ module IssuablesHelper ...@@ -67,6 +67,12 @@ module IssuablesHelper
end end
end end
def has_todo(issuable)
unless current_user.nil?
current_user.todos.find_by(target_id: issuable.id, state: :pending)
end
end
private private
def sidebar_gutter_collapsed? def sidebar_gutter_collapsed?
......
...@@ -6,6 +6,12 @@ module MembersHelper ...@@ -6,6 +6,12 @@ module MembersHelper
"#{action}_#{member.type.underscore}".to_sym "#{action}_#{member.type.underscore}".to_sym
end end
def can_see_member_roles?(source:, user: nil)
return false unless user
user.is_admin? || source.members.exists?(user_id: user.id)
end
def remove_member_message(member, user: nil) def remove_member_message(member, user: nil)
user = current_user if defined?(current_user) user = current_user if defined?(current_user)
......
...@@ -61,4 +61,23 @@ module NotificationsHelper ...@@ -61,4 +61,23 @@ module NotificationsHelper
end end
end end
end end
def notification_level_radio_buttons
html = ""
NotificationSetting.levels.each_key do |level|
level = level.to_sym
next if level == :global
html << content_tag(:div, class: "radio") do
content_tag(:label, { value: level }) do
radio_button_tag(:"global_notification_setting[level]", level, @global_notification_setting.level.to_sym == level) +
content_tag(:div, level.to_s.capitalize, class: "level-title") +
content_tag(:p, notification_description(level))
end
end
end
html.html_safe
end
end end
...@@ -140,6 +140,10 @@ module ProjectsHelper ...@@ -140,6 +140,10 @@ module ProjectsHelper
nav_tabs << :container_registry nav_tabs << :container_registry
end end
if can?(current_user, :read_environment, project)
nav_tabs << :environments
end
if can?(current_user, :admin_project, project) if can?(current_user, :admin_project, project)
nav_tabs << :settings nav_tabs << :settings
end end
......
...@@ -20,7 +20,6 @@ module TimeHelper ...@@ -20,7 +20,6 @@ module TimeHelper
end end
end end
def date_from_to(from, to) def date_from_to(from, to)
"#{from.to_s(:short)} - #{to.to_s(:short)}" "#{from.to_s(:short)} - #{to.to_s(:short)}"
end end
......
...@@ -12,6 +12,7 @@ module TodosHelper ...@@ -12,6 +12,7 @@ module TodosHelper
when Todo::ASSIGNED then 'assigned you' when Todo::ASSIGNED then 'assigned you'
when Todo::MENTIONED then 'mentioned you on' when Todo::MENTIONED then 'mentioned you on'
when Todo::BUILD_FAILED then 'The build failed for your' when Todo::BUILD_FAILED then 'The build failed for your'
when Todo::MARKED then 'marked this as a Todo for'
end end
end end
......
...@@ -10,7 +10,6 @@ class Ability ...@@ -10,7 +10,6 @@ class Ability
when CommitStatus then commit_status_abilities(user, subject) when CommitStatus then commit_status_abilities(user, subject)
when Project then project_abilities(user, subject) when Project then project_abilities(user, subject)
when Issue then issue_abilities(user, subject) when Issue then issue_abilities(user, subject)
when ExternalIssue then external_issue_abilities(user, subject)
when Note then note_abilities(user, subject) when Note then note_abilities(user, subject)
when ProjectSnippet then project_snippet_abilities(user, subject) when ProjectSnippet then project_snippet_abilities(user, subject)
when PersonalSnippet then personal_snippet_abilities(user, subject) when PersonalSnippet then personal_snippet_abilities(user, subject)
...@@ -20,6 +19,7 @@ class Ability ...@@ -20,6 +19,7 @@ class Ability
when GroupMember then group_member_abilities(user, subject) when GroupMember then group_member_abilities(user, subject)
when ProjectMember then project_member_abilities(user, subject) when ProjectMember then project_member_abilities(user, subject)
when User then user_abilities when User then user_abilities
when ExternalIssue, Deployment, Environment then project_abilities(user, subject.project)
else [] else []
end.concat(global_abilities(user)) end.concat(global_abilities(user))
...@@ -244,6 +244,8 @@ class Ability ...@@ -244,6 +244,8 @@ class Ability
:read_build, :read_build,
:read_container_image, :read_container_image,
:read_pipeline, :read_pipeline,
:read_environment,
:read_deployment
] ]
end end
...@@ -262,6 +264,8 @@ class Ability ...@@ -262,6 +264,8 @@ class Ability
:push_code, :push_code,
:create_container_image, :create_container_image,
:update_container_image, :update_container_image,
:create_environment,
:create_deployment
] ]
end end
...@@ -281,6 +285,8 @@ class Ability ...@@ -281,6 +285,8 @@ class Ability
:push_code_to_protected_branches, :push_code_to_protected_branches,
:update_project_snippet, :update_project_snippet,
:update_pages, :update_pages,
:update_environment,
:update_deployment,
:admin_milestone, :admin_milestone,
:admin_project_snippet, :admin_project_snippet,
:admin_project_member, :admin_project_member,
...@@ -293,6 +299,8 @@ class Ability ...@@ -293,6 +299,8 @@ class Ability
:admin_container_image, :admin_container_image,
:admin_pages, :admin_pages,
:admin_pipeline, :admin_pipeline,
:admin_environment,
:admin_deployment
] ]
end end
...@@ -337,6 +345,8 @@ class Ability ...@@ -337,6 +345,8 @@ class Ability
unless project.builds_enabled unless project.builds_enabled
rules += named_abilities('build') rules += named_abilities('build')
rules += named_abilities('pipeline') rules += named_abilities('pipeline')
rules += named_abilities('environment')
rules += named_abilities('deployment')
end end
unless project.container_registry_enabled unless project.container_registry_enabled
...@@ -535,10 +545,6 @@ class Ability ...@@ -535,10 +545,6 @@ class Ability
end end
end end
def external_issue_abilities(user, subject)
project_abilities(user, subject.project)
end
private private
def restricted_public_level? def restricted_public_level?
...@@ -557,7 +563,7 @@ class Ability ...@@ -557,7 +563,7 @@ class Ability
def filter_confidential_issues_abilities(user, issue, rules) def filter_confidential_issues_abilities(user, issue, rules)
return rules if user.admin? || !issue.confidential? return rules if user.admin? || !issue.confidential?
unless issue.author == user || issue.assignee == user || issue.project.team.member?(user.id) unless issue.author == user || issue.assignee == user || issue.project.team.member?(user, Gitlab::Access::REPORTER)
rules.delete(:admin_issue) rules.delete(:admin_issue)
rules.delete(:read_issue) rules.delete(:read_issue)
rules.delete(:update_issue) rules.delete(:update_issue)
......
...@@ -24,7 +24,7 @@ class Blob < SimpleDelegator ...@@ -24,7 +24,7 @@ class Blob < SimpleDelegator
end end
def only_display_raw? def only_display_raw?
size && size > 5.megabytes size && truncated?
end end
def svg? def svg?
......
...@@ -11,6 +11,8 @@ module Ci ...@@ -11,6 +11,8 @@ module Ci
scope :unstarted, ->() { where(runner_id: nil) } scope :unstarted, ->() { where(runner_id: nil) }
scope :ignore_failures, ->() { where(allow_failure: false) } scope :ignore_failures, ->() { where(allow_failure: false) }
scope :with_artifacts, ->() { where.not(artifacts_file: nil) }
scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) }
mount_uploader :artifacts_file, ArtifactUploader mount_uploader :artifacts_file, ArtifactUploader
mount_uploader :artifacts_metadata, ArtifactUploader mount_uploader :artifacts_metadata, ArtifactUploader
...@@ -38,7 +40,7 @@ module Ci ...@@ -38,7 +40,7 @@ module Ci
new_build.save new_build.save
end end
def retry(build) def retry(build, user = nil)
new_build = Ci::Build.new(status: 'pending') new_build = Ci::Build.new(status: 'pending')
new_build.ref = build.ref new_build.ref = build.ref
new_build.tag = build.tag new_build.tag = build.tag
...@@ -52,6 +54,7 @@ module Ci ...@@ -52,6 +54,7 @@ module Ci
new_build.stage = build.stage new_build.stage = build.stage
new_build.stage_idx = build.stage_idx new_build.stage_idx = build.stage_idx
new_build.trigger_request = build.trigger_request new_build.trigger_request = build.trigger_request
new_build.user = user
new_build.save new_build.save
MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build) MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build)
new_build new_build
...@@ -73,6 +76,17 @@ module Ci ...@@ -73,6 +76,17 @@ module Ci
build.update_coverage build.update_coverage
build.execute_hooks build.execute_hooks
end end
after_transition any => [:success] do |build|
if build.environment.present?
service = CreateDeploymentService.new(build.project, build.user,
environment: build.environment,
sha: build.sha,
ref: build.ref,
tag: build.tag)
service.execute(build)
end
end
end end
def retryable? def retryable?
...@@ -83,10 +97,6 @@ module Ci ...@@ -83,10 +97,6 @@ module Ci
!self.pipeline.statuses.latest.include?(self) !self.pipeline.statuses.latest.include?(self)
end end
def retry
Ci::Build.retry(self)
end
def depends_on_builds def depends_on_builds
# Get builds of the same type # Get builds of the same type
latest_builds = self.pipeline.builds.latest latest_builds = self.pipeline.builds.latest
...@@ -318,7 +328,7 @@ module Ci ...@@ -318,7 +328,7 @@ module Ci
end end
def artifacts? def artifacts?
artifacts_file.exists? !artifacts_expired? && artifacts_file.exists?
end end
def artifacts_metadata? def artifacts_metadata?
...@@ -329,11 +339,15 @@ module Ci ...@@ -329,11 +339,15 @@ module Ci
Gitlab::Ci::Build::Artifacts::Metadata.new(artifacts_metadata.path, path, **options).to_entry Gitlab::Ci::Build::Artifacts::Metadata.new(artifacts_metadata.path, path, **options).to_entry
end end
def erase_artifacts!
remove_artifacts_file!
remove_artifacts_metadata!
end
def erase(opts = {}) def erase(opts = {})
return false unless erasable? return false unless erasable?
remove_artifacts_file! erase_artifacts!
remove_artifacts_metadata!
erase_trace! erase_trace!
update_erased!(opts[:erased_by]) update_erased!(opts[:erased_by])
end end
...@@ -346,6 +360,25 @@ module Ci ...@@ -346,6 +360,25 @@ module Ci
!self.erased_at.nil? !self.erased_at.nil?
end end
def artifacts_expired?
artifacts_expire_at && artifacts_expire_at < Time.now
end
def artifacts_expire_in
artifacts_expire_at - Time.now if artifacts_expire_at
end
def artifacts_expire_in=(value)
self.artifacts_expire_at =
if value
Time.now + ChronicDuration.parse(value)
end
end
def keep_artifacts!
self.update(artifacts_expire_at: nil)
end
private private
def erase_trace! def erase_trace!
...@@ -353,7 +386,7 @@ module Ci ...@@ -353,7 +386,7 @@ module Ci
end end
def update_erased!(user = nil) def update_erased!(user = nil)
self.update(erased_by: user, erased_at: Time.now) self.update(erased_by: user, erased_at: Time.now, artifacts_expire_at: nil)
end end
def yaml_variables def yaml_variables
......
...@@ -76,8 +76,10 @@ module Ci ...@@ -76,8 +76,10 @@ module Ci
builds.running_or_pending.each(&:cancel) builds.running_or_pending.each(&:cancel)
end end
def retry_failed def retry_failed(user)
builds.latest.failed.select(&:retryable?).each(&:retry) builds.latest.failed.select(&:retryable?).each do |build|
Ci::Build.retry(build, user)
end
end end
def latest? def latest?
...@@ -161,6 +163,10 @@ module Ci ...@@ -161,6 +163,10 @@ module Ci
git_commit_message =~ /(\[ci skip\])/ if git_commit_message git_commit_message =~ /(\[ci skip\])/ if git_commit_message
end end
def environments
builds.where.not(environment: nil).success.pluck(:environment).uniq
end
private private
def update_state def update_state
......
...@@ -5,7 +5,7 @@ module Awardable ...@@ -5,7 +5,7 @@ module Awardable
has_many :award_emoji, as: :awardable, dependent: :destroy has_many :award_emoji, as: :awardable, dependent: :destroy
if self < Participable if self < Participable
participant :award_emoji participant :award_emoji_with_associations
end end
end end
...@@ -34,8 +34,12 @@ module Awardable ...@@ -34,8 +34,12 @@ module Awardable
end end
end end
def award_emoji_with_associations
award_emoji.includes(:user)
end
def grouped_awards(with_thumbs: true) def grouped_awards(with_thumbs: true)
awards = award_emoji.group_by(&:name) awards = award_emoji_with_associations.group_by(&:name)
if with_thumbs if with_thumbs
awards[AwardEmoji::UPVOTE_NAME] ||= [] awards[AwardEmoji::UPVOTE_NAME] ||= []
......
class Deployment < ActiveRecord::Base
include InternalId
belongs_to :project, required: true, validate: true
belongs_to :environment, required: true, validate: true
belongs_to :user
belongs_to :deployable, polymorphic: true
validates :sha, presence: true
validates :ref, presence: true
delegate :name, to: :environment, prefix: true
def commit
project.commit(sha)
end
def commit_title
commit.try(:title)
end
def short_sha
Commit.truncate_sha(sha)
end
def last?
self == environment.last_deployment
end
end
class Environment < ActiveRecord::Base
belongs_to :project, required: true, validate: true
has_many :deployments
validates :name,
presence: true,
uniqueness: { scope: :project_id },
length: { within: 0..255 },
format: { with: Gitlab::Regex.environment_name_regex,
message: Gitlab::Regex.environment_name_regex_message }
def last_deployment
deployments.last
end
end
...@@ -57,10 +57,18 @@ class Issue < ActiveRecord::Base ...@@ -57,10 +57,18 @@ class Issue < ActiveRecord::Base
end end
def self.visible_to_user(user) def self.visible_to_user(user)
return where(confidential: false) if user.blank? return where('issues.confidential IS NULL OR issues.confidential IS FALSE') if user.blank?
return all if user.admin? return all if user.admin?
where('issues.confidential = false OR (issues.confidential = true AND (issues.author_id = :user_id OR issues.assignee_id = :user_id OR issues.project_id IN(:project_ids)))', user_id: user.id, project_ids: user.authorized_projects.select(:id)) where('
issues.confidential IS NULL
OR issues.confidential IS FALSE
OR (issues.confidential = TRUE
AND (issues.author_id = :user_id
OR issues.assignee_id = :user_id
OR issues.project_id IN(:project_ids)))',
user_id: user.id,
project_ids: user.authorized_projects(Gitlab::Access::REPORTER).select(:id))
end end
def self.reference_prefix def self.reference_prefix
......
...@@ -45,6 +45,12 @@ class GroupMember < Member ...@@ -45,6 +45,12 @@ class GroupMember < Member
super super
end end
def send_request
notification_service.new_group_access_request(self)
super
end
def post_create_hook def post_create_hook
notification_service.new_group_member(self) unless @skip_notification notification_service.new_group_member(self) unless @skip_notification
...@@ -76,4 +82,10 @@ class GroupMember < Member ...@@ -76,4 +82,10 @@ class GroupMember < Member
super super
end end
def post_decline_request
notification_service.decline_group_access_request(self)
super
end
end end
...@@ -117,6 +117,12 @@ class ProjectMember < Member ...@@ -117,6 +117,12 @@ class ProjectMember < Member
super super
end end
def send_request
notification_service.new_project_access_request(self)
super
end
def post_create_hook def post_create_hook
unless owner? unless owner?
event_service.join_project(self.project, self.user) event_service.join_project(self.project, self.user)
...@@ -158,6 +164,12 @@ class ProjectMember < Member ...@@ -158,6 +164,12 @@ class ProjectMember < Member
super super
end end
def post_decline_request
notification_service.decline_project_access_request(self)
super
end
def event_service def event_service
EventCreateService.new EventCreateService.new
end end
......
...@@ -90,22 +90,9 @@ class Note < ActiveRecord::Base ...@@ -90,22 +90,9 @@ class Note < ActiveRecord::Base
table = arel_table table = arel_table
pattern = "%#{query}%" pattern = "%#{query}%"
found_notes = joins('LEFT JOIN issues ON issues.id = noteable_id'). Note.joins('LEFT JOIN issues ON issues.id = noteable_id').
where(table[:note].matches(pattern)) where(table[:note].matches(pattern)).
merge(Issue.visible_to_user(as_user))
if as_user
found_notes.where('
issues.confidential IS NULL
OR issues.confidential IS FALSE
OR (issues.confidential IS TRUE
AND (issues.author_id = :user_id
OR issues.assignee_id = :user_id
OR issues.project_id IN(:project_ids)))',
user_id: as_user.id,
project_ids: as_user.authorized_projects.select(:id))
else
found_notes.where('issues.confidential IS NULL OR issues.confidential IS FALSE')
end
end end
end end
...@@ -206,6 +193,10 @@ class Note < ActiveRecord::Base ...@@ -206,6 +193,10 @@ class Note < ActiveRecord::Base
award_emoji_supported? && contains_emoji_only? award_emoji_supported? && contains_emoji_only?
end end
def emoji_awardable?
!system?
end
def clear_blank_line_code! def clear_blank_line_code!
self.line_code = nil if self.line_code.blank? self.line_code = nil if self.line_code.blank?
end end
......
...@@ -7,7 +7,6 @@ class NotificationSetting < ActiveRecord::Base ...@@ -7,7 +7,6 @@ class NotificationSetting < ActiveRecord::Base
belongs_to :source, polymorphic: true belongs_to :source, polymorphic: true
validates :user, presence: true validates :user, presence: true
validates :source, presence: true
validates :level, presence: true validates :level, presence: true
validates :user_id, uniqueness: { scope: [:source_type, :source_id], validates :user_id, uniqueness: { scope: [:source_type, :source_id],
message: "already exists in source", message: "already exists in source",
......
...@@ -79,7 +79,7 @@ class Project < ActiveRecord::Base ...@@ -79,7 +79,7 @@ class Project < ActiveRecord::Base
has_one :jira_service, dependent: :destroy has_one :jira_service, dependent: :destroy
has_one :redmine_service, dependent: :destroy has_one :redmine_service, dependent: :destroy
has_one :custom_issue_tracker_service, dependent: :destroy has_one :custom_issue_tracker_service, dependent: :destroy
has_one :gitlab_issue_tracker_service, dependent: :destroy has_one :gitlab_issue_tracker_service, dependent: :destroy, inverse_of: :project
has_one :external_wiki_service, dependent: :destroy has_one :external_wiki_service, dependent: :destroy
has_one :index_status, dependent: :destroy has_one :index_status, dependent: :destroy
...@@ -130,6 +130,8 @@ class Project < ActiveRecord::Base ...@@ -130,6 +130,8 @@ class Project < ActiveRecord::Base
has_many :variables, dependent: :destroy, class_name: 'Ci::Variable', foreign_key: :gl_project_id has_many :variables, dependent: :destroy, class_name: 'Ci::Variable', foreign_key: :gl_project_id
has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger', foreign_key: :gl_project_id has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger', foreign_key: :gl_project_id
has_many :remote_mirrors, dependent: :destroy has_many :remote_mirrors, dependent: :destroy
has_many :environments, dependent: :destroy
has_many :deployments, dependent: :destroy
accepts_nested_attributes_for :variables, allow_destroy: true accepts_nested_attributes_for :variables, allow_destroy: true
accepts_nested_attributes_for :remote_mirrors, accepts_nested_attributes_for :remote_mirrors,
...@@ -153,7 +155,6 @@ class Project < ActiveRecord::Base ...@@ -153,7 +155,6 @@ class Project < ActiveRecord::Base
message: Gitlab::Regex.project_path_regex_message } message: Gitlab::Regex.project_path_regex_message }
validates :issues_enabled, :merge_requests_enabled, validates :issues_enabled, :merge_requests_enabled,
:wiki_enabled, inclusion: { in: [true, false] } :wiki_enabled, inclusion: { in: [true, false] }
validates :issues_tracker_id, length: { maximum: 255 }, allow_blank: true
validates :namespace, presence: true validates :namespace, presence: true
validates_uniqueness_of :name, scope: :namespace_id validates_uniqueness_of :name, scope: :namespace_id
validates_uniqueness_of :path, scope: :namespace_id validates_uniqueness_of :path, scope: :namespace_id
...@@ -690,10 +691,6 @@ class Project < ActiveRecord::Base ...@@ -690,10 +691,6 @@ class Project < ActiveRecord::Base
update_column(:has_external_issue_tracker, services.external_issue_trackers.any?) update_column(:has_external_issue_tracker, services.external_issue_trackers.any?)
end end
def can_have_issues_tracker_id?
self.issues_enabled && !self.default_issues_tracker?
end
def build_missing_services def build_missing_services
services_templates = Service.where(template: true) services_templates = Service.where(template: true)
......
class BambooService < CiService class BambooService < CiService
include HTTParty
prop_accessor :bamboo_url, :build_key, :username, :password prop_accessor :bamboo_url, :build_key, :username, :password
validates :bamboo_url, presence: true, url: true, if: :activated? validates :bamboo_url, presence: true, url: true, if: :activated?
...@@ -61,18 +59,7 @@ class BambooService < CiService ...@@ -61,18 +59,7 @@ class BambooService < CiService
end end
def build_info(sha) def build_info(sha)
url = URI.join(bamboo_url, "/rest/api/latest/result?label=#{sha}").to_s @response = get_path("rest/api/latest/result?label=#{sha}")
if username.blank? && password.blank?
@response = HTTParty.get(url, verify: false)
else
url << '&os_authType=basic'
auth = {
username: username,
password: password
}
@response = HTTParty.get(url, verify: false, basic_auth: auth)
end
end end
def build_page(sha, ref) def build_page(sha, ref)
...@@ -80,11 +67,11 @@ class BambooService < CiService ...@@ -80,11 +67,11 @@ class BambooService < CiService
if @response.code != 200 || @response['results']['results']['size'] == '0' if @response.code != 200 || @response['results']['results']['size'] == '0'
# If actual build link can't be determined, send user to build summary page. # If actual build link can't be determined, send user to build summary page.
URI.join(bamboo_url, "/browse/#{build_key}").to_s URI.join("#{bamboo_url}/", "browse/#{build_key}").to_s
else else
# If actual build link is available, go to build result page. # If actual build link is available, go to build result page.
result_key = @response['results']['results']['result']['planResultKey']['key'] result_key = @response['results']['results']['result']['planResultKey']['key']
URI.join(bamboo_url, "/browse/#{result_key}").to_s URI.join("#{bamboo_url}/", "browse/#{result_key}").to_s
end end
end end
...@@ -112,8 +99,27 @@ class BambooService < CiService ...@@ -112,8 +99,27 @@ class BambooService < CiService
def execute(data) def execute(data)
return unless supported_events.include?(data[:object_kind]) return unless supported_events.include?(data[:object_kind])
# Bamboo requires a GET and does not take any data. get_path("updateAndBuild.action?buildKey=#{build_key}")
url = URI.join(bamboo_url, "/updateAndBuild.action?buildKey=#{build_key}").to_s end
self.class.get(url, verify: false)
private
def build_url(path)
URI.join("#{bamboo_url}/", path).to_s
end
def get_path(path)
url = build_url(path)
if username.blank? && password.blank?
HTTParty.get(url, verify: false)
else
url << '&os_authType=basic'
HTTParty.get(url, verify: false,
basic_auth: {
username: username,
password: password
})
end
end end
end end
...@@ -42,9 +42,9 @@ class IssueTrackerService < Service ...@@ -42,9 +42,9 @@ class IssueTrackerService < Service
if enabled_in_gitlab_config if enabled_in_gitlab_config
self.properties = { self.properties = {
title: issues_tracker['title'], title: issues_tracker['title'],
project_url: add_issues_tracker_id(issues_tracker['project_url']), project_url: issues_tracker['project_url'],
issues_url: add_issues_tracker_id(issues_tracker['issues_url']), issues_url: issues_tracker['issues_url'],
new_issue_url: add_issues_tracker_id(issues_tracker['new_issue_url']) new_issue_url: issues_tracker['new_issue_url']
} }
else else
self.properties = {} self.properties = {}
...@@ -87,16 +87,4 @@ class IssueTrackerService < Service ...@@ -87,16 +87,4 @@ class IssueTrackerService < Service
def issues_tracker def issues_tracker
Gitlab.config.issues_tracker[to_param] Gitlab.config.issues_tracker[to_param]
end end
def add_issues_tracker_id(url)
if self.project
id = self.project.issues_tracker_id
if id
url = url.gsub(":issues_tracker_id", id)
end
end
url
end
end end
class TeamcityService < CiService class TeamcityService < CiService
include HTTParty
prop_accessor :teamcity_url, :build_type, :username, :password prop_accessor :teamcity_url, :build_type, :username, :password
validates :teamcity_url, presence: true, url: true, if: :activated? validates :teamcity_url, presence: true, url: true, if: :activated?
...@@ -64,15 +62,7 @@ class TeamcityService < CiService ...@@ -64,15 +62,7 @@ class TeamcityService < CiService
end end
def build_info(sha) def build_info(sha)
url = URI.join( @response = get_path("httpAuth/app/rest/builds/branch:unspecified:any,number:#{sha}")
teamcity_url,
"/httpAuth/app/rest/builds/branch:unspecified:any,number:#{sha}"
).to_s
auth = {
username: username,
password: password
}
@response = HTTParty.get(url, verify: false, basic_auth: auth)
end end
def build_page(sha, ref) def build_page(sha, ref)
...@@ -81,14 +71,11 @@ class TeamcityService < CiService ...@@ -81,14 +71,11 @@ class TeamcityService < CiService
if @response.code != 200 if @response.code != 200
# If actual build link can't be determined, # If actual build link can't be determined,
# send user to build summary page. # send user to build summary page.
URI.join(teamcity_url, "/viewLog.html?buildTypeId=#{build_type}").to_s build_url("viewLog.html?buildTypeId=#{build_type}")
else else
# If actual build link is available, go to build result page. # If actual build link is available, go to build result page.
built_id = @response['build']['id'] built_id = @response['build']['id']
URI.join( build_url("viewLog.html?buildId=#{built_id}&buildTypeId=#{build_type}")
teamcity_url,
"/viewLog.html?buildId=#{built_id}&buildTypeId=#{build_type}"
).to_s
end end
end end
...@@ -123,8 +110,8 @@ class TeamcityService < CiService ...@@ -123,8 +110,8 @@ class TeamcityService < CiService
branch = Gitlab::Git.ref_name(data[:ref]) branch = Gitlab::Git.ref_name(data[:ref])
self.class.post( HTTParty.post(
URI.join(teamcity_url, '/httpAuth/app/rest/buildQueue').to_s, build_url('httpAuth/app/rest/buildQueue'),
body: "<build branchName=\"#{branch}\">"\ body: "<build branchName=\"#{branch}\">"\
"<buildType id=\"#{build_type}\"/>"\ "<buildType id=\"#{build_type}\"/>"\
'</build>', '</build>',
...@@ -132,4 +119,18 @@ class TeamcityService < CiService ...@@ -132,4 +119,18 @@ class TeamcityService < CiService
basic_auth: auth basic_auth: auth
) )
end end
private
def build_url(path)
URI.join("#{teamcity_url}/", path).to_s
end
def get_path(path)
HTTParty.get(build_url(path), verify: false,
basic_auth: {
username: username,
password: password
})
end
end end
...@@ -120,8 +120,14 @@ class ProjectTeam ...@@ -120,8 +120,14 @@ class ProjectTeam
max_member_access(user.id) == Gitlab::Access::MASTER max_member_access(user.id) == Gitlab::Access::MASTER
end end
def member?(user_id) def member?(user, min_member_access = nil)
!!find_member(user_id) member = !!find_member(user.id)
if min_member_access
member && max_member_access(user.id) >= min_member_access
else
member
end
end end
def human_max_access(user_id) def human_max_access(user_id)
......
...@@ -498,7 +498,7 @@ class Repository ...@@ -498,7 +498,7 @@ class Repository
def blob_at(sha, path) def blob_at(sha, path)
unless Gitlab::Git.blank_ref?(sha) unless Gitlab::Git.blank_ref?(sha)
Gitlab::Git::Blob.find(self, sha, path) Blob.decorate(Gitlab::Git::Blob.find(self, sha, path))
end end
end end
......
...@@ -18,7 +18,7 @@ class Service < ActiveRecord::Base ...@@ -18,7 +18,7 @@ class Service < ActiveRecord::Base
after_commit :reset_updated_properties after_commit :reset_updated_properties
after_commit :cache_project_has_external_issue_tracker after_commit :cache_project_has_external_issue_tracker
belongs_to :project belongs_to :project, inverse_of: :services
has_one :service_hook has_one :service_hook
validates :project_id, presence: true, unless: Proc.new { |service| service.template? } validates :project_id, presence: true, unless: Proc.new { |service| service.template? }
......
...@@ -2,6 +2,7 @@ class Todo < ActiveRecord::Base ...@@ -2,6 +2,7 @@ class Todo < ActiveRecord::Base
ASSIGNED = 1 ASSIGNED = 1
MENTIONED = 2 MENTIONED = 2
BUILD_FAILED = 3 BUILD_FAILED = 3
MARKED = 4
belongs_to :author, class_name: "User" belongs_to :author, class_name: "User"
belongs_to :note belongs_to :note
......
...@@ -10,6 +10,8 @@ class User < ActiveRecord::Base ...@@ -10,6 +10,8 @@ class User < ActiveRecord::Base
include CaseSensitivity include CaseSensitivity
include TokenAuthenticatable include TokenAuthenticatable
DEFAULT_NOTIFICATION_LEVEL = :participating
add_authentication_token_field :authentication_token add_authentication_token_field :authentication_token
default_value_for :admin, false default_value_for :admin, false
...@@ -100,7 +102,6 @@ class User < ActiveRecord::Base ...@@ -100,7 +102,6 @@ class User < ActiveRecord::Base
presence: true, presence: true,
uniqueness: { case_sensitive: false } uniqueness: { case_sensitive: false }
validates :notification_level, presence: true
validate :namespace_uniq, if: ->(user) { user.username_changed? } validate :namespace_uniq, if: ->(user) { user.username_changed? }
validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? } validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
validate :unique_email, if: ->(user) { user.email_changed? } validate :unique_email, if: ->(user) { user.email_changed? }
...@@ -134,13 +135,6 @@ class User < ActiveRecord::Base ...@@ -134,13 +135,6 @@ class User < ActiveRecord::Base
# Note: When adding an option, it MUST go on the end of the array. # Note: When adding an option, it MUST go on the end of the array.
enum project_view: [:readme, :activity, :files] enum project_view: [:readme, :activity, :files]
# Notification level
# Note: When adding an option, it MUST go on the end of the array.
#
# TODO: Add '_prefix: :notification' to enum when update to Rails 5. https://github.com/rails/rails/pull/19813
# Because user.notification_disabled? is much better than user.disabled?
enum notification_level: [:disabled, :participating, :watch, :global, :mention]
alias_attribute :private_token, :authentication_token alias_attribute :private_token, :authentication_token
delegate :path, to: :namespace, allow_nil: true, prefix: true delegate :path, to: :namespace, allow_nil: true, prefix: true
...@@ -442,8 +436,8 @@ class User < ActiveRecord::Base ...@@ -442,8 +436,8 @@ class User < ActiveRecord::Base
end end
# Returns projects user is authorized to access. # Returns projects user is authorized to access.
def authorized_projects def authorized_projects(min_access_level = nil)
Project.where("projects.id IN (#{projects_union.to_sql})") Project.where("projects.id IN (#{projects_union(min_access_level).to_sql})")
end end
def viewable_starred_projects def viewable_starred_projects
...@@ -835,6 +829,17 @@ class User < ActiveRecord::Base ...@@ -835,6 +829,17 @@ class User < ActiveRecord::Base
notification_settings.find_or_initialize_by(source: source) notification_settings.find_or_initialize_by(source: source)
end end
# Lazy load global notification setting
# Initializes User setting with Participating level if setting not persisted
def global_notification_setting
return @global_notification_setting if defined?(@global_notification_setting)
@global_notification_setting = notification_settings.find_or_initialize_by(source: nil)
@global_notification_setting.update_attributes(level: NotificationSetting.levels[DEFAULT_NOTIFICATION_LEVEL]) unless @global_notification_setting.persisted?
@global_notification_setting
end
def assigned_open_merge_request_count(force: false) def assigned_open_merge_request_count(force: false)
Rails.cache.fetch(['users', id, 'assigned_open_merge_request_count'], force: force) do Rails.cache.fetch(['users', id, 'assigned_open_merge_request_count'], force: force) do
assigned_merge_requests.opened.count assigned_merge_requests.opened.count
...@@ -854,11 +859,19 @@ class User < ActiveRecord::Base ...@@ -854,11 +859,19 @@ class User < ActiveRecord::Base
private private
def projects_union def projects_union(min_access_level = nil)
Gitlab::SQL::Union.new([personal_projects.select(:id), relations = [personal_projects.select(:id),
groups_projects.select(:id), groups_projects.select(:id),
projects.select(:id), projects.select(:id),
groups.joins(:shared_projects).select(:project_id)]) groups.joins(:shared_projects).select(:project_id)]
if min_access_level
scope = { access_level: Gitlab::Access.values.select { |access| access >= min_access_level } }
relations = [relations.shift] + relations.map { |relation| relation.where(members: scope) }
end
Gitlab::SQL::Union.new(relations)
end end
def ci_projects_union def ci_projects_union
......
...@@ -29,7 +29,8 @@ module Ci ...@@ -29,7 +29,8 @@ module Ci
:options, :options,
:allow_failure, :allow_failure,
:stage, :stage,
:stage_idx) :stage_idx,
:environment)
build_attrs.merge!(ref: @pipeline.ref, build_attrs.merge!(ref: @pipeline.ref,
tag: @pipeline.tag, tag: @pipeline.tag,
......
require_relative 'base_service'
class CreateDeploymentService < BaseService
def execute(deployable = nil)
environment = project.environments.find_or_create_by(
name: params[:environment]
)
project.deployments.create(
environment: environment,
ref: params[:ref],
tag: params[:tag],
sha: params[:sha],
user: current_user,
deployable: deployable
)
end
end
...@@ -3,7 +3,7 @@ class GitHooksService ...@@ -3,7 +3,7 @@ class GitHooksService
def execute(user, repo_path, oldrev, newrev, ref) def execute(user, repo_path, oldrev, newrev, ref)
@repo_path = repo_path @repo_path = repo_path
@user = Gitlab::ShellEnv.gl_id(user) @user = Gitlab::GlId.gl_id(user)
@oldrev = oldrev @oldrev = oldrev
@newrev = newrev @newrev = newrev
@ref = ref @ref = ref
......
...@@ -21,8 +21,6 @@ module MergeRequests ...@@ -21,8 +21,6 @@ module MergeRequests
end end
def rebase def rebase
Gitlab::ShellEnv.set_env(current_user)
if merge_request.rebase_in_progress? if merge_request.rebase_in_progress?
log('Rebase task canceled: Another rebase is already in progress') log('Rebase task canceled: Another rebase is already in progress')
return false return false
...@@ -61,7 +59,6 @@ module MergeRequests ...@@ -61,7 +59,6 @@ module MergeRequests
log(ex.message) log(ex.message)
ensure ensure
clean_dir clean_dir
Gitlab::ShellEnv.reset_env
end end
def source_project def source_project
......
...@@ -299,10 +299,11 @@ class NotificationService ...@@ -299,10 +299,11 @@ class NotificationService
end end
def users_with_global_level_watch(ids) def users_with_global_level_watch(ids)
User.where( NotificationSetting.where(
id: ids, user_id: ids,
notification_level: NotificationSetting.levels[:watch] source_type: nil,
).pluck(:id) level: NotificationSetting.levels[:watch]
).pluck(:user_id)
end end
# Build a list of users based on project notifcation settings # Build a list of users based on project notifcation settings
...@@ -372,7 +373,9 @@ class NotificationService ...@@ -372,7 +373,9 @@ class NotificationService
users = users.reject(&:blocked?) users = users.reject(&:blocked?)
users.reject do |user| users.reject do |user|
next user.notification_level == level unless project global_notification_setting = user.global_notification_setting
next global_notification_setting.level == level unless project
setting = user.notification_settings_for(project) setting = user.notification_settings_for(project)
...@@ -381,13 +384,13 @@ class NotificationService ...@@ -381,13 +384,13 @@ class NotificationService
end end
# reject users who globally set mention notification and has no setting per project/group # reject users who globally set mention notification and has no setting per project/group
next user.notification_level == level unless setting next global_notification_setting.level == level unless setting
# reject users who set mention notification in project # reject users who set mention notification in project
next true if setting.level == level next true if setting.level == level
# reject users who have mention level in project and disabled in global settings # reject users who have mention level in project and disabled in global settings
setting.global? && user.notification_level == level setting.global? && global_notification_setting.level == level
end end
end end
...@@ -476,7 +479,6 @@ class NotificationService ...@@ -476,7 +479,6 @@ class NotificationService
def build_recipients(target, project, current_user, action: nil, previous_assignee: nil) def build_recipients(target, project, current_user, action: nil, previous_assignee: nil)
recipients = target.participants(current_user) recipients = target.participants(current_user)
recipients = add_project_watchers(recipients, project) recipients = add_project_watchers(recipients, project)
recipients = reject_mention_users(recipients, project) recipients = reject_mention_users(recipients, project)
......
...@@ -139,10 +139,16 @@ class TodoService ...@@ -139,10 +139,16 @@ class TodoService
pending_todos(user, attributes).update_all(state: :done) pending_todos(user, attributes).update_all(state: :done)
end end
# When user marks an issue as todo
def mark_todo(issuable, current_user)
attributes = attributes_for_todo(issuable.project, issuable, current_user, Todo::MARKED)
create_todos(current_user, attributes)
end
private private
def create_todos(users, attributes) def create_todos(users, attributes)
Array(users).each do |user| Array(users).map do |user|
next if pending_todos(user, attributes).exists? next if pending_todos(user, attributes).exists?
Todo.create(attributes.merge(user_id: user.id)) Todo.create(attributes.merge(user_id: user.id))
end end
......
.nav-links.sub-nav
%ul{ class: (container_class) }
= nav_link(controller: :background_jobs) do
= link_to admin_background_jobs_path, title: 'Background Jobs' do
%span
Background Jobs
= nav_link(controller: :logs) do
= link_to admin_logs_path, title: 'Logs' do
%span
Logs
= nav_link(controller: :health_check) do
= link_to admin_health_check_path, title: 'Health Check' do
%span
Health Check
- @no_container = true
- page_title "Background Jobs" - page_title "Background Jobs"
%h3.page-title Background Jobs = render 'admin/background_jobs/head'
%p.light GitLab uses #{link_to "sidekiq", "http://sidekiq.org/"} library for async job processing
%hr %div{ class: (container_class) }
%h3.page-title Background Jobs
%p.light GitLab uses #{link_to "sidekiq", "http://sidekiq.org/"} library for async job processing
.panel.panel-default %hr
.panel-heading Sidekiq running processes
.panel-body
- if @sidekiq_processes.empty?
%h4.cred
%i.fa.fa-exclamation-triangle
There are no running sidekiq processes. Please restart GitLab
- else
.table-holder
%table.table
%thead
%th USER
%th PID
%th CPU
%th MEM
%th STATE
%th START
%th COMMAND
%tbody
- @sidekiq_processes.each do |process|
- next unless process.match(/(sidekiq \d+\.\d+\.\d+.+$)/)
- data = process.strip.split(' ')
%tr
%td= gitlab_config.user
- 5.times do
%td= data.shift
%td= data.join(' ')
.clearfix .panel.panel-default
%p .panel-heading Sidekiq running processes
%i.fa.fa-exclamation-circle .panel-body
If '[25 of 25 busy]' is shown, restart GitLab with 'sudo service gitlab reload'. - if @sidekiq_processes.empty?
%p %h4.cred
%i.fa.fa-exclamation-circle %i.fa.fa-exclamation-triangle
If more than one sidekiq process is listed, stop GitLab, kill the remaining sidekiq processes (sudo pkill -u #{gitlab_config.user} -f sidekiq) and restart GitLab. There are no running sidekiq processes. Please restart GitLab
- else
.table-holder
%table.table
%thead
%th USER
%th PID
%th CPU
%th MEM
%th STATE
%th START
%th COMMAND
%tbody
- @sidekiq_processes.each do |process|
- next unless process.match(/(sidekiq \d+\.\d+\.\d+.+$)/)
- data = process.strip.split(' ')
%tr
%td= gitlab_config.user
- 5.times do
%td= data.shift
%td= data.join(' ')
.clearfix
%p
%i.fa.fa-exclamation-circle
If '[25 of 25 busy]' is shown, restart GitLab with 'sudo service gitlab reload'.
%p
%i.fa.fa-exclamation-circle
If more than one sidekiq process is listed, stop GitLab, kill the remaining sidekiq processes (sudo pkill -u #{gitlab_config.user} -f sidekiq) and restart GitLab.
.panel.panel-default
%iframe{src: sidekiq_path, width: '100%', height: 970, style: "border: none"} .panel.panel-default
%iframe{src: sidekiq_path, width: '100%', height: 970, style: "border: none"}
.top-area - @no_container = true
%ul.nav-links = render "admin/dashboard/head"
%li{class: ('active' if @scope.nil?)}
= link_to admin_builds_path do %div{ class: (container_class) }
All
%span.badge.js-totalbuilds-count= @all_builds.count(:id) .top-area
%ul.nav-links
%li{class: ('active' if @scope == 'running')} %li{class: ('active' if @scope.nil?)}
= link_to admin_builds_path(scope: :running) do = link_to admin_builds_path do
Running All
%span.badge.js-running-count= number_with_delimiter(@all_builds.running_or_pending.count(:id)) %span.badge.js-totalbuilds-count= @all_builds.count(:id)
%li{class: ('active' if @scope == 'finished')} %li{class: ('active' if @scope == 'running')}
= link_to admin_builds_path(scope: :finished) do = link_to admin_builds_path(scope: :running) do
Finished Running
%span.badge.js-running-count= number_with_delimiter(@all_builds.finished.count(:id)) %span.badge.js-running-count= number_with_delimiter(@all_builds.running_or_pending.count(:id))
.nav-controls %li{class: ('active' if @scope == 'finished')}
- if @all_builds.running_or_pending.any? = link_to admin_builds_path(scope: :finished) do
= link_to 'Cancel all', cancel_all_admin_builds_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post Finished
%span.badge.js-running-count= number_with_delimiter(@all_builds.finished.count(:id))
.row-content-block.second-block
#{(@scope || 'all').capitalize} builds .nav-controls
- if @all_builds.running_or_pending.any?
%ul.content-list = link_to 'Cancel all', cancel_all_admin_builds_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
- if @builds.blank?
%li .row-content-block.second-block
.nothing-here-block No builds to show #{(@scope || 'all').capitalize} builds
- else
.table-holder %ul.content-list
%table.table.builds - if @builds.blank?
%thead %li
%tr .nothing-here-block No builds to show
%th Status - else
%th Build ID .table-holder
%th Project %table.table.builds
%th Commit %thead
%th Ref %tr
%th Runner %th Status
%th Name %th Build ID
%th Tags %th Project
%th Duration %th Commit
%th Finished at %th Ref
%th %th Runner
%th Name
- @builds.each do |build| %th Tags
= render "admin/builds/build", build: build %th Duration
%th Finished at
= paginate @builds, theme: 'gitlab' %th
- @builds.each do |build|
= render "admin/builds/build", build: build
= paginate @builds, theme: 'gitlab'
.nav-links.sub-nav
%ul{ class: (container_class) }
= nav_link(controller: :dashboard, html_options: {class: 'home'}) do
= link_to admin_root_path, title: 'Overview' do
%span
Overview
= nav_link(controller: [:admin, :projects]) do
= link_to admin_namespaces_projects_path, title: 'Projects' do
%span
Projects
= nav_link(controller: :users) do
= link_to admin_users_path, title: 'Users' do
%span
Users
= nav_link(controller: :groups) do
= link_to admin_groups_path, title: 'Groups' do
%span
Groups
= nav_link path: 'builds#index' do
= link_to admin_builds_path, title: 'Builds' do
%span
Builds
.admin-dashboard.prepend-top-default - @no_container = true
.row = render "admin/dashboard/head"
.col-md-4
%h4 Statistics
%hr
%p
Forks
%span.light.pull-right
= number_with_delimiter(ForkedProjectLink.count)
%p
Issues
%span.light.pull-right
= number_with_delimiter(Issue.count)
%p
Merge Requests
%span.light.pull-right
= number_with_delimiter(MergeRequest.count)
%p
Notes
%span.light.pull-right
= number_with_delimiter(Note.count)
%p
Snippets
%span.light.pull-right
= number_with_delimiter(Snippet.count)
%p
SSH Keys
%span.light.pull-right
= number_with_delimiter(Key.count)
%p
Milestones
%span.light.pull-right
= number_with_delimiter(Milestone.count)
%p
Active Users
%span.light.pull-right
= number_with_delimiter(User.active.count)
.col-md-4
%h4
Features
%hr
%p
Sign up
%span.light.pull-right
= boolean_to_icon signup_enabled?
%p
LDAP
%span.light.pull-right
= boolean_to_icon Gitlab.config.ldap.enabled
%p
Gravatar
%span.light.pull-right
= boolean_to_icon gravatar_enabled?
%p
OmniAuth
%span.light.pull-right
= boolean_to_icon Gitlab.config.omniauth.enabled
%p
Reply by email
%span.light.pull-right
= boolean_to_icon Gitlab::IncomingEmail.enabled?
%p
Elasticsearch
%span.light.pull-right
= boolean_to_icon Gitlab.config.elasticsearch.enabled
%p
Geo
%span.light.pull-right
= boolean_to_icon Gitlab::Geo.enabled?
.col-md-4
%h4
Components
- if current_application_settings.version_check_enabled
.pull-right
= version_status_badge
%hr %div{ class: (container_class) }
%p .admin-dashboard.prepend-top-default
GitLab .row
%span.pull-right .col-md-4
= Gitlab::VERSION %h4 Statistics
%p %hr
GitLab Shell %p
%span.pull-right Forks
= Gitlab::Shell.new.version %span.light.pull-right
%p = number_with_delimiter(ForkedProjectLink.count)
GitLab API %p
%span.pull-right Issues
= API::API::version %span.light.pull-right
= number_with_delimiter(Issue.count)
- if Gitlab::Geo.enabled? %p
Merge Requests
%span.light.pull-right
= number_with_delimiter(MergeRequest.count)
%p
Notes
%span.light.pull-right
= number_with_delimiter(Note.count)
%p
Snippets
%span.light.pull-right
= number_with_delimiter(Snippet.count)
%p
SSH Keys
%span.light.pull-right
= number_with_delimiter(Key.count)
%p
Milestones
%span.light.pull-right
= number_with_delimiter(Milestone.count)
%p
Active Users
%span.light.pull-right
= number_with_delimiter(User.active.count)
.col-md-4
%h4
Features
%hr
%p
Sign up
%span.light.pull-right
= boolean_to_icon signup_enabled?
%p
LDAP
%span.light.pull-right
= boolean_to_icon Gitlab.config.ldap.enabled
%p
Gravatar
%span.light.pull-right
= boolean_to_icon gravatar_enabled?
%p
OmniAuth
%span.light.pull-right
= boolean_to_icon Gitlab.config.omniauth.enabled
%p
Reply by email
%span.light.pull-right
= boolean_to_icon Gitlab::IncomingEmail.enabled?
%p
Elasticsearch
%span.light.pull-right
= boolean_to_icon Gitlab.config.elasticsearch.enabled
%p %p
Geo Geo
%span.pull-right %span.light.pull-right
= Gitlab::Geo.current_node.primary ? 'Primary node' : 'Secondary node' = boolean_to_icon Gitlab::Geo.enabled?
%p .col-md-4
Git %h4
%span.pull-right Components
= Gitlab::Git.version - if current_application_settings.version_check_enabled
%p .pull-right
Ruby = version_status_badge
%span.pull-right
#{RUBY_VERSION}p#{RUBY_PATCHLEVEL}
%p
Rails
%span.pull-right
#{Rails::VERSION::STRING}
%p
= Gitlab::Database.adapter_name
%span.pull-right
= Gitlab::Database.version
%hr
.row
.col-sm-4
.light-well
%h4 Projects
.data
= link_to admin_namespaces_projects_path do
%h1= number_with_delimiter(Project.count)
%hr
= link_to('New Project', new_project_path, class: "btn btn-new")
.col-sm-4
.light-well
%h4 Users
.data
= link_to admin_users_path do
%h1= number_with_delimiter(User.count)
%hr
= link_to 'New User', new_admin_user_path, class: "btn btn-new"
.col-sm-4
.light-well
%h4 Groups
.data
= link_to admin_groups_path do
%h1= number_with_delimiter(Group.count)
%hr
= link_to 'New Group', new_admin_group_path, class: "btn btn-new"
.row.prepend-top-10 %hr
.col-md-4
%h4 Latest projects
%hr
- @projects.each do |project|
%p %p
= link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project], class: 'str-truncated' GitLab
%span.light.pull-right %span.pull-right
#{time_ago_with_tooltip(project.created_at)} = Gitlab::VERSION
%p
GitLab Shell
%span.pull-right
= Gitlab::Shell.new.version
%p
GitLab API
%span.pull-right
= API::API::version
- if Gitlab::Geo.enabled?
%p
Geo
%span.pull-right
= Gitlab::Geo.current_node.primary ? 'Primary node' : 'Secondary node'
%p
Git
%span.pull-right
= Gitlab::Git.version
%p
Ruby
%span.pull-right
#{RUBY_VERSION}p#{RUBY_PATCHLEVEL}
.col-md-4
%h4 Latest users
%hr
- @users.each do |user|
%p %p
= link_to [:admin, user], class: 'str-truncated' do Rails
= user.name %span.pull-right
%span.light.pull-right #{Rails::VERSION::STRING}
#{time_ago_with_tooltip(user.created_at)}
.col-md-4
%h4 Latest groups
%hr
- @groups.each do |group|
%p %p
= link_to [:admin, group], class: 'str-truncated' do = Gitlab::Database.adapter_name
= group.name %span.pull-right
%span.light.pull-right = Gitlab::Database.version
#{time_ago_with_tooltip(group.created_at)} %hr
.row
.col-sm-4
.light-well
%h4 Projects
.data
= link_to admin_namespaces_projects_path do
%h1= number_with_delimiter(Project.count)
%hr
= link_to('New Project', new_project_path, class: "btn btn-new")
.col-sm-4
.light-well
%h4 Users
.data
= link_to admin_users_path do
%h1= number_with_delimiter(User.count)
%hr
= link_to 'New User', new_admin_user_path, class: "btn btn-new"
.col-sm-4
.light-well
%h4 Groups
.data
= link_to admin_groups_path do
%h1= number_with_delimiter(Group.count)
%hr
= link_to 'New Group', new_admin_group_path, class: "btn btn-new"
.row.prepend-top-10
.col-md-4
%h4 Latest projects
%hr
- @projects.each do |project|
%p
= link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project], class: 'str-truncated'
%span.light.pull-right
#{time_ago_with_tooltip(project.created_at)}
.col-md-4
%h4 Latest users
%hr
- @users.each do |user|
%p
= link_to [:admin, user], class: 'str-truncated' do
= user.name
%span.light.pull-right
#{time_ago_with_tooltip(user.created_at)}
.col-md-4
%h4 Latest groups
%hr
- @groups.each do |group|
%p
= link_to [:admin, group], class: 'str-truncated' do
= group.name
%span.light.pull-right
#{time_ago_with_tooltip(group.created_at)}
- @no_container = true
- page_title "Groups" - page_title "Groups"
%h3.page-title = render "admin/dashboard/head"
Groups (#{number_with_delimiter(@groups.total_count)})
%p.light %div{ class: (container_class) }
Group allows you to keep projects organized. %h3.page-title
Use groups for uniting related projects. Groups (#{number_with_delimiter(@groups.total_count)})
.top-area %p.light
.nav-search Group allows you to keep projects organized.
= form_tag admin_groups_path, method: :get, class: 'form-inline' do Use groups for uniting related projects.
= hidden_field_tag :sort, @sort
= text_field_tag :name, params[:name], class: "form-control"
= button_tag "Search", class: "btn submit btn-primary"
.nav-controls .top-area
.dropdown.inline .nav-search
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} = form_tag admin_groups_path, method: :get, class: 'form-inline' do
%span.light = hidden_field_tag :sort, @sort
- if @sort.present? = text_field_tag :name, params[:name], class: "form-control"
= sort_options_hash[@sort] = button_tag "Search", class: "btn submit btn-primary"
- else
= sort_title_recently_created .nav-controls
%b.caret .dropdown.inline
%ul.dropdown-menu %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
%li %span.light
= link_to admin_groups_path(sort: sort_value_recently_created) do - if @sort.present?
= sort_options_hash[@sort]
- else
= sort_title_recently_created = sort_title_recently_created
= link_to admin_groups_path(sort: sort_value_oldest_created) do %b.caret
= sort_title_oldest_created %ul.dropdown-menu
= link_to admin_groups_path(sort: sort_value_recently_updated) do %li
= sort_title_recently_updated = link_to admin_groups_path(sort: sort_value_recently_created) do
= link_to admin_groups_path(sort: sort_value_oldest_updated) do = sort_title_recently_created
= sort_title_oldest_updated = link_to admin_groups_path(sort: sort_value_oldest_created) do
= link_to 'New Group', new_admin_group_path, class: "btn btn-new" = sort_title_oldest_created
= link_to admin_groups_path(sort: sort_value_recently_updated) do
= sort_title_recently_updated
= link_to admin_groups_path(sort: sort_value_oldest_updated) do
= sort_title_oldest_updated
= link_to 'New Group', new_admin_group_path, class: "btn btn-new"
%ul.content-list %ul.content-list
- @groups.each do |group| - @groups.each do |group|
= render 'group', group: group = render 'group', group: group
= paginate @groups, theme: "gitlab" = paginate @groups, theme: "gitlab"
- @no_container = true
- page_title "Health Check" - page_title "Health Check"
= render 'admin/background_jobs/head'
%h3.page-title %div{ class: (container_class) }
Health Check %h3.page-title
.bs-callout.clearfix Health Check
.pull-left .bs-callout.clearfix
%p .pull-left
Access token is %p
%code#health-check-token= current_application_settings.health_check_access_token Access token is
= button_to reset_health_check_token_admin_application_settings_path, %code#health-check-token= current_application_settings.health_check_access_token
method: :put, class: 'btn btn-default', = button_to reset_health_check_token_admin_application_settings_path,
data: { confirm: 'Are you sure you want to reset the health check token?' } do method: :put, class: 'btn btn-default',
= icon('refresh') data: { confirm: 'Are you sure you want to reset the health check token?' } do
Reset health check access token = icon('refresh')
%p.light Reset health check access token
Health information can be retrieved as plain text, JSON, or XML using: %p.light
%ul Health information can be retrieved as plain text, JSON, or XML using:
%li %ul
%code= health_check_url(token: current_application_settings.health_check_access_token) %li
%li %code= health_check_url(token: current_application_settings.health_check_access_token)
%code= health_check_url(token: current_application_settings.health_check_access_token, format: :json) %li
%li %code= health_check_url(token: current_application_settings.health_check_access_token, format: :json)
%code= health_check_url(token: current_application_settings.health_check_access_token, format: :xml) %li
%code= health_check_url(token: current_application_settings.health_check_access_token, format: :xml)
%p.light %p.light
You can also ask for the status of specific services: You can also ask for the status of specific services:
%ul %ul
%li %li
%code= health_check_url(token: current_application_settings.health_check_access_token, checks: :cache) %code= health_check_url(token: current_application_settings.health_check_access_token, checks: :cache)
%li %li
%code= health_check_url(token: current_application_settings.health_check_access_token, checks: :database) %code= health_check_url(token: current_application_settings.health_check_access_token, checks: :database)
%li %li
%code= health_check_url(token: current_application_settings.health_check_access_token, checks: :migrations) %code= health_check_url(token: current_application_settings.health_check_access_token, checks: :migrations)
%hr %hr
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
Current Status: Current Status:
- if @errors.blank? - if @errors.blank?
= icon('circle', class: 'cgreen') = icon('circle', class: 'cgreen')
Healthy Healthy
- else - else
= icon('warning', class: 'cred') = icon('warning', class: 'cred')
Unhealthy Unhealthy
.panel-body .panel-body
- if @errors.blank? - if @errors.blank?
No Health Problems Detected No Health Problems Detected
- else - else
= @errors = @errors
- @no_container = true
- page_title "Logs" - page_title "Logs"
- loggers = [Gitlab::GitLogger, Gitlab::AppLogger, - loggers = [Gitlab::GitLogger, Gitlab::AppLogger,
Gitlab::ProductionLogger, Gitlab::SidekiqLogger, Gitlab::ProductionLogger, Gitlab::SidekiqLogger,
Gitlab::RepositoryCheckLogger] Gitlab::RepositoryCheckLogger]
%ul.nav-links.log-tabs = render 'admin/background_jobs/head'
- loggers.each do |klass|
%li{ class: (klass == Gitlab::GitLogger ? 'active' : '') } %div{ class: (container_class) }
= link_to klass::file_name, "##{klass::file_name_noext}", %ul.nav-links.log-tabs
'data-toggle' => 'tab' - loggers.each do |klass|
.row-content-block %li{ class: (klass == Gitlab::GitLogger ? 'active' : '') }
To prevent performance issues admin logs output the last 2000 lines = link_to klass::file_name, "##{klass::file_name_noext}",
.tab-content 'data-toggle' => 'tab'
- loggers.each do |klass| .row-content-block
.tab-pane{ class: (klass == Gitlab::GitLogger ? 'active' : ''), To prevent performance issues admin logs output the last 2000 lines
id: klass::file_name_noext } .tab-content
.file-holder#README - loggers.each do |klass|
.file-title .tab-pane{ class: (klass == Gitlab::GitLogger ? 'active' : ''),
%i.fa.fa-file id: klass::file_name_noext }
= klass::file_name .file-holder#README
.pull-right .file-title
= link_to '#', class: 'log-bottom' do %i.fa.fa-file
%i.fa.fa-arrow-down = klass::file_name
Scroll down .pull-right
.file-content.logs = link_to '#', class: 'log-bottom' do
%ol %i.fa.fa-arrow-down
- klass.read_latest.each do |line| Scroll down
%li .file-content.logs
%p= line %ol
- klass.read_latest.each do |line|
%li
%p= line
- @no_container = true
- page_title "Projects" - page_title "Projects"
= render 'shared/show_aside' = render 'shared/show_aside'
= render "admin/dashboard/head"
.row.prepend-top-default %div{ class: (container_class) }
%aside.col-md-3 .row.prepend-top-default
.panel.admin-filter %aside.col-md-3
= form_tag admin_namespaces_projects_path, method: :get, class: '' do .panel.admin-filter
.form-group = form_tag admin_namespaces_projects_path, method: :get, class: '' do
= label_tag :name, 'Name:' .form-group
= text_field_tag :name, params[:name], class: "form-control" = label_tag :name, 'Name:'
= text_field_tag :name, params[:name], class: "form-control"
.form-group .form-group
= label_tag :namespace_id, "Namespace" = label_tag :namespace_id, "Namespace"
= namespace_select_tag :namespace_id, selected: params[:namespace_id], class: 'input-large' = namespace_select_tag :namespace_id, selected: params[:namespace_id], class: 'input-large'
.form-group .form-group
%strong Activity %strong Activity
.checkbox .checkbox
= label_tag :with_push do = label_tag :with_push do
= check_box_tag :with_push, 1, params[:with_push] = check_box_tag :with_push, 1, params[:with_push]
%span Projects with push events %span Projects with push events
.checkbox .checkbox
= label_tag :abandoned do = label_tag :abandoned do
= check_box_tag :abandoned, 1, params[:abandoned] = check_box_tag :abandoned, 1, params[:abandoned]
%span No activity over 6 month %span No activity over 6 month
.checkbox .checkbox
= label_tag :with_archived do = label_tag :with_archived do
= check_box_tag :with_archived, 1, params[:with_archived] = check_box_tag :with_archived, 1, params[:with_archived]
%span Show archived projects %span Show archived projects
%fieldset %fieldset
%strong Visibility level: %strong Visibility level:
.visibility-levels .visibility-levels
- Project.visibility_levels.each do |label, level| - Project.visibility_levels.each do |label, level|
.checkbox .checkbox
%label %label
= check_box_tag 'visibility_levels[]', level, params[:visibility_levels].present? && params[:visibility_levels].include?(level.to_s) = check_box_tag 'visibility_levels[]', level, params[:visibility_levels].present? && params[:visibility_levels].include?(level.to_s)
%span.descr %span.descr
= visibility_level_icon(level) = visibility_level_icon(level)
= label = label
%fieldset %fieldset
%strong Problems %strong Problems
.checkbox .checkbox
= label_tag :last_repository_check_failed do = label_tag :last_repository_check_failed do
= check_box_tag :last_repository_check_failed, 1, params[:last_repository_check_failed] = check_box_tag :last_repository_check_failed, 1, params[:last_repository_check_failed]
%span Last repository check failed %span Last repository check failed
= hidden_field_tag :sort, params[:sort] = hidden_field_tag :sort, params[:sort]
= button_tag "Search", class: "btn submit btn-primary" = button_tag "Search", class: "btn submit btn-primary"
= link_to "Reset", admin_namespaces_projects_path, class: "btn btn-cancel" = link_to "Reset", admin_namespaces_projects_path, class: "btn btn-cancel"
%section.col-md-9 %section.col-md-9
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
Projects (#{@projects.total_count}) Projects (#{@projects.total_count})
.controls .controls
.dropdown.inline .dropdown.inline
%button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'} %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'}
%span.light %span.light
- if @sort.present? - if @sort.present?
= sort_options_hash[@sort] = sort_options_hash[@sort]
- else - else
= sort_title_recently_created
%b.caret
%ul.dropdown-menu
%li
= link_to admin_namespaces_projects_path(sort: sort_value_recently_created) do
= sort_title_recently_created = sort_title_recently_created
= link_to admin_namespaces_projects_path(sort: sort_value_oldest_created) do %b.caret
= sort_title_oldest_created %ul.dropdown-menu
= link_to admin_namespaces_projects_path(sort: sort_value_recently_updated) do %li
= sort_title_recently_updated = link_to admin_namespaces_projects_path(sort: sort_value_recently_created) do
= link_to admin_namespaces_projects_path(sort: sort_value_oldest_updated) do = sort_title_recently_created
= sort_title_oldest_updated = link_to admin_namespaces_projects_path(sort: sort_value_oldest_created) do
= link_to admin_namespaces_projects_path(sort: sort_value_largest_repo) do = sort_title_oldest_created
= sort_title_largest_repo = link_to admin_namespaces_projects_path(sort: sort_value_recently_updated) do
= link_to 'New Project', new_project_path, class: "btn btn-sm btn-success" = sort_title_recently_updated
%ul.well-list = link_to admin_namespaces_projects_path(sort: sort_value_oldest_updated) do
- @projects.each do |project| = sort_title_oldest_updated
%li = link_to admin_namespaces_projects_path(sort: sort_value_largest_repo) do
.list-item-name = sort_title_largest_repo
%span{ class: visibility_level_color(project.visibility_level) } = link_to 'New Project', new_project_path, class: "btn btn-sm btn-success"
= visibility_level_icon(project.visibility_level) %ul.well-list
= link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project] - @projects.each do |project|
.pull-right %li
- if project.archived .list-item-name
%span.label.label-warning archived %span{ class: visibility_level_color(project.visibility_level) }
%span.label.label-gray = visibility_level_icon(project.visibility_level)
= repository_size(project) = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project]
= link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm" .pull-right
= link_to 'Destroy', [project.namespace.becomes(Namespace), project], data: { confirm: remove_project_message(project) }, method: :delete, class: "btn btn-sm btn-remove" - if project.archived
- if @projects.blank? %span.label.label-warning archived
.nothing-here-block 0 projects matches %span.label.label-gray
= paginate @projects, theme: "gitlab" = repository_size(project)
= link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm"
= link_to 'Destroy', [project.namespace.becomes(Namespace), project], data: { confirm: remove_project_message(project) }, method: :delete, class: "btn btn-sm btn-remove"
- if @projects.blank?
.nothing-here-block 0 projects matches
= paginate @projects, theme: "gitlab"
This diff is collapsed.
:plain :plain
$("##{dom_id(@group_member)}").replaceWith('#{escape_javascript(render('shared/members/member', member: @group_member))}'); $("##{dom_id(@group_member)}").replaceWith('#{escape_javascript(render(@group_member, member: @group_member))}');
...@@ -5,10 +5,28 @@ xml.entry do ...@@ -5,10 +5,28 @@ xml.entry do
xml.updated issue.created_at.xmlschema xml.updated issue.created_at.xmlschema
xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(issue.author_email)) xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(issue.author_email))
xml.author do |author| xml.author do
xml.name issue.author_name xml.name issue.author_name
xml.email issue.author_email xml.email issue.author_email
end end
xml.summary issue.title xml.summary issue.title
xml.description issue.description if issue.description
xml.milestone issue.milestone.title if issue.milestone
xml.due_date issue.due_date if issue.due_date
unless issue.labels.empty?
xml.labels do
issue.labels.each do |label|
xml.label label.name
end
end
end
if issue.assignee
xml.assignee do
xml.name issue.assignee.name
xml.email issue.assignee.email
end
end
end end
- page_title "Admin Area" - page_title "Admin Area"
- header_title "Admin Area", admin_root_path - header_title "Admin Area", admin_root_path
- sidebar "admin" - nav "admin"
= render template: "layouts/application" = render template: "layouts/application"
...@@ -27,9 +27,8 @@ ...@@ -27,9 +27,8 @@
%li %li
= link_to dashboard_todos_path, title: 'Todos', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = link_to dashboard_todos_path, title: 'Todos', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('bell fw') = icon('bell fw')
- unless todos_pending_count == 0 %span.badge.todos-pending-count{ class: ("hidden" if todos_pending_count == 0) }
%span.badge.todos-pending-count = todos_pending_count
= todos_pending_count
- if Gitlab::Geo.secondary? - if Gitlab::Geo.secondary?
%li %li
= link_to Gitlab::Geo.primary_node.url, title: 'Go to primary node', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = link_to Gitlab::Geo.primary_node.url, title: 'Go to primary node', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
...@@ -55,7 +54,7 @@ ...@@ -55,7 +54,7 @@
%h1.title= title %h1.title= title
.header-logo .header-logo
#logo = link_to root_path, class: 'home', title: 'Dashboard', id: 'logo' do
= brand_header_logo = brand_header_logo
= yield :header_content = yield :header_content
......
%ul.nav.nav-sidebar %ul.nav-links.scrolling-tabs
= nav_link(controller: :dashboard, html_options: {class: 'home'}) do .fade-left
= link_to admin_root_path, title: 'Overview' do = nav_link(controller: %w(dashboard admin projects users groups builds), html_options: {class: 'home'}) do
= icon('dashboard fw') = link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do
%span %span
Overview Overview
= nav_link(controller: [:admin, :projects]) do = nav_link(controller: %w(background_jobs logs health_check)) do
= link_to admin_namespaces_projects_path, title: 'Projects' do = link_to admin_background_jobs_path, title: 'Monitoring' do
= icon('cube fw')
%span %span
Projects Monitoring
= nav_link(controller: :users) do
= link_to admin_users_path, title: 'Users' do
= icon('user fw')
%span
Users
= nav_link(controller: :groups) do
= link_to admin_groups_path, title: 'Groups' do
= icon('group fw')
%span
Groups
= nav_link(controller: :deploy_keys) do = nav_link(controller: :deploy_keys) do
= link_to admin_deploy_keys_path, title: 'Deploy Keys' do = link_to admin_deploy_keys_path, title: 'Deploy Keys' do
= icon('key fw')
%span %span
Deploy Keys Deploy Keys
= nav_link path: ['runners#index', 'runners#show'] do = nav_link path: ['runners#index', 'runners#show'] do
= link_to admin_runners_path, title: 'Runners' do = link_to admin_runners_path, title: 'Runners' do
= icon('cog fw')
%span %span
Runners Runners
%span.count= number_with_delimiter(Ci::Runner.count(:all))
= nav_link path: 'builds#index' do
= link_to admin_builds_path, title: 'Builds' do
= icon('link fw')
%span
Builds
%span.count= number_with_delimiter(Ci::Build.count(:all))
= nav_link(controller: :logs) do
= link_to admin_logs_path, title: 'Logs' do
= icon('file-text fw')
%span
Logs
= nav_link(controller: :health_check) do
= link_to admin_health_check_path, title: 'Health Check' do
= icon('medkit fw')
%span
Health Check
= nav_link(controller: :broadcast_messages) do = nav_link(controller: :broadcast_messages) do
= link_to admin_broadcast_messages_path, title: 'Messages' do = link_to admin_broadcast_messages_path, title: 'Messages' do
= icon('bullhorn fw')
%span %span
Messages Messages
= nav_link(controller: :hooks) do = nav_link(controller: :hooks) do
= link_to admin_hooks_path, title: 'Hooks' do = link_to admin_hooks_path, title: 'Hooks' do
= icon('external-link fw')
%span %span
Hooks Hooks
= nav_link(controller: :git_hooks) do = nav_link(controller: :git_hooks) do
= link_to admin_git_hooks_path, title: 'Git Hooks', data: {placement: 'right'} do = link_to admin_git_hooks_path, title: 'Git Hooks' do
= icon('git-square fw')
%span %span
Git Hooks Git Hooks
= nav_link(controller: :background_jobs) do
= link_to admin_background_jobs_path, title: 'Background Jobs' do
= icon('cog fw')
%span
Background Jobs
= nav_link(controller: :appearances) do = nav_link(controller: :appearances) do
= link_to admin_appearances_path, title: 'Appearances' do = link_to admin_appearances_path, title: 'Appearances' do
= icon('image')
%span %span
Appearance Appearance
= nav_link(controller: :applications) do = nav_link(controller: :applications) do
= link_to admin_applications_path, title: 'Applications' do = link_to admin_applications_path, title: 'Applications' do
= icon('cloud fw')
%span %span
Applications Applications
= nav_link(controller: :services) do = nav_link(controller: :services) do
= link_to admin_application_settings_services_path, title: 'Service Templates' do = link_to admin_application_settings_services_path, title: 'Service Templates' do
= icon('copy fw')
%span %span
Service Templates Service Templates
= nav_link(controller: :labels) do = nav_link(controller: :labels) do
= link_to admin_labels_path, title: 'Labels' do = link_to admin_labels_path, title: 'Labels' do
= icon('tags fw')
%span %span
Labels Labels
= nav_link(controller: :geo_nodes) do = nav_link(controller: :geo_nodes) do
= link_to admin_geo_nodes_path, title: 'Geo Nodes' do = link_to admin_geo_nodes_path, title: 'Geo Nodes' do
= icon('globe fw')
%span %span
Geo Nodes Geo Nodes
= nav_link(controller: :abuse_reports) do = nav_link(controller: :abuse_reports) do
= link_to admin_abuse_reports_path, title: "Abuse Reports" do = link_to admin_abuse_reports_path, title: "Abuse Reports" do
= icon('exclamation-circle fw')
%span %span
Abuse Reports Abuse Reports
%span.count= number_with_delimiter(AbuseReport.count(:all)) %span.badge.count= number_with_delimiter(AbuseReport.count(:all))
- if askimet_enabled? - if askimet_enabled?
= nav_link(controller: :spam_logs) do = nav_link(controller: :spam_logs) do
= link_to admin_spam_logs_path, title: "Spam Logs" do = link_to admin_spam_logs_path, title: "Spam Logs" do
= icon('exclamation-triangle fw')
%span %span
Spam Logs Spam Logs
%span.count= number_with_delimiter(SpamLog.count(:all))
= nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do = nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do
= link_to admin_application_settings_path, title: 'Settings' do = link_to admin_application_settings_path, title: 'Settings' do
= icon('cogs fw')
%span %span
Settings Settings
= nav_link(controller: :licenses) do = nav_link(controller: :licenses) do
= link_to admin_license_path, title: 'License', data: {placement: 'right'} do = link_to admin_license_path, title: 'License' do
= icon('check fw')
%span %span
License License
.fade-right
%ul.nav.nav-sidebar %ul.nav.nav-sidebar
= nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do
= link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do = link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
= navbar_icon('project') .icon-container
= navbar_icon('project')
%span %span
Projects Projects
= nav_link(controller: :todos) do = nav_link(controller: :todos) do
= link_to dashboard_todos_path, title: 'Todos' do = link_to dashboard_todos_path, title: 'Todos' do
= icon('bell fw') .icon-container
= icon('bell fw')
%span %span
Todos Todos
%span.count= number_with_delimiter(todos_pending_count) %span.count= number_with_delimiter(todos_pending_count)
= nav_link(path: 'dashboard#activity') do = nav_link(path: 'dashboard#activity') do
= link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do
= navbar_icon('activity') .icon-container
= navbar_icon('activity')
%span %span
Activity Activity
= nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
= link_to dashboard_groups_path, title: 'Groups' do = link_to dashboard_groups_path, title: 'Groups' do
= navbar_icon('group') .icon-container
= navbar_icon('group')
%span %span
Groups Groups
= nav_link(controller: 'dashboard/milestones') do = nav_link(controller: 'dashboard/milestones') do
= link_to dashboard_milestones_path, title: 'Milestones' do = link_to dashboard_milestones_path, title: 'Milestones' do
= navbar_icon('milestones') .icon-container
= navbar_icon('milestones')
%span %span
Milestones Milestones
= nav_link(path: 'dashboard#issues') do = nav_link(path: 'dashboard#issues') do
= link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues' do = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues' do
= navbar_icon('issues') .icon-container
= navbar_icon('issues')
%span %span
Issues Issues
%span.count= number_with_delimiter(current_user.assigned_issues.opened.count) %span.count= number_with_delimiter(current_user.assigned_issues.opened.count)
= nav_link(path: 'dashboard#merge_requests') do = nav_link(path: 'dashboard#merge_requests') do
= link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do
= navbar_icon('mr') .icon-container
= navbar_icon('mr')
%span %span
Merge Requests Merge Requests
%span.count= number_with_delimiter(current_user.assigned_merge_requests.opened.count) %span.count= number_with_delimiter(current_user.assigned_merge_requests.opened.count)
= nav_link(controller: :snippets) do = nav_link(controller: :snippets) do
= link_to dashboard_snippets_path, title: 'Snippets' do = link_to dashboard_snippets_path, title: 'Snippets' do
= icon('clipboard fw') .icon-container
= icon('clipboard fw')
%span %span
Snippets Snippets
= nav_link(controller: :help) do = nav_link(controller: :help) do
= link_to help_path, title: 'Help' do = link_to help_path, title: 'Help' do
= icon('question-circle fw') .icon-container
= icon('question-circle fw')
%span %span
Help Help
= nav_link(html_options: {class: profile_tab_class}) do = nav_link(html_options: {class: profile_tab_class}) do
= link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do = link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do
= icon('user fw') .icon-container
= icon('user fw')
%span %span
Profile Settings Profile Settings
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
Code Code
- if project_nav_tab? :pipelines - if project_nav_tab? :pipelines
= nav_link(controller: :pipelines) do = nav_link(controller: [:pipelines, :builds, :environments]) do
= link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
%span %span
Pipelines Pipelines
......
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
= link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do
%span %span
Members Members
- if access && can_edit - if access && can_edit
- if @project.allowed_to_share_with_group? - if @project.allowed_to_share_with_group?
= nav_link(controller: :group_links) do = nav_link(controller: :group_links) do
...@@ -27,7 +26,6 @@ ...@@ -27,7 +26,6 @@
%span %span
Protected Branches Protected Branches
- if @project.builds_enabled? - if @project.builds_enabled?
= nav_link(controller: :runners) do = nav_link(controller: :runners) do
= link_to namespace_project_runners_path(@project.namespace, @project), title: 'Runners' do = link_to namespace_project_runners_path(@project.namespace, @project), title: 'Runners' do
...@@ -45,7 +43,6 @@ ...@@ -45,7 +43,6 @@
= link_to namespace_project_badges_path(@project.namespace, @project), title: 'Badges' do = link_to namespace_project_badges_path(@project.namespace, @project), title: 'Badges' do
%span %span
Badges Badges
= nav_link(controller: :git_hooks) do = nav_link(controller: :git_hooks) do
= link_to namespace_project_git_hooks_path(@project.namespace, @project), title: "Git Hooks" do = link_to namespace_project_git_hooks_path(@project.namespace, @project), title: "Git Hooks" do
%span %span
......
%li.notification-list-item %li.notification-list-item
%span.notification.fa.fa-holder.append-right-5 %span.notification.fa.fa-holder.append-right-5
- if setting.global? - if setting.global?
= notification_icon(current_user.notification_level) = notification_icon(current_user.global_notification_setting.level)
- else - else
= notification_icon(setting.level) = notification_icon(setting.level)
......
%li.notification-list-item %li.notification-list-item
%span.notification.fa.fa-holder.append-right-5 %span.notification.fa.fa-holder.append-right-5
- if setting.global? - if setting.global?
= notification_icon(current_user.notification_level) = notification_icon(current_user.global_notification_setting.level)
- else - else
= notification_icon(setting.level) = notification_icon(setting.level)
......
...@@ -26,33 +26,7 @@ ...@@ -26,33 +26,7 @@
= f.select :notification_email, @user.all_emails, { include_blank: false }, class: "select2" = f.select :notification_email, @user.all_emails, { include_blank: false }, class: "select2"
.form-group .form-group
= f.label :notification_level, class: 'label-light' = f.label :notification_level, class: 'label-light'
.radio = notification_level_radio_buttons
= f.label :notification_level, value: :disabled do
= f.radio_button :notification_level, :disabled
.level-title
Disabled
%p You will not get any notifications via email
.radio
= f.label :notification_level, value: :mention do
= f.radio_button :notification_level, :mention
.level-title
On Mention
%p You will receive notifications only for comments in which you were @mentioned
.radio
= f.label :notification_level, value: :participating do
= f.radio_button :notification_level, :participating
.level-title
Participating
%p You will only receive notifications from related resources (e.g. from your commits or assigned issues)
.radio
= f.label :notification_level, value: :watch do
= f.radio_button :notification_level, :watch
.level-title
Watch
%p You will receive notifications for any activity
.prepend-top-default .prepend-top-default
= f.submit 'Update settings', class: "btn btn-create" = f.submit 'Update settings', class: "btn btn-create"
......
...@@ -51,9 +51,9 @@ ...@@ -51,9 +51,9 @@
%p %p
Use a hardware device to add the second factor of authentication. Use a hardware device to add the second factor of authentication.
%p %p
As U2F devices are only supported by a few browsers, it's recommended that you set up a As U2F devices are only supported by a few browsers, we require that you set up a
two-factor authentication app as well as a U2F device so you'll always be able to log in two-factor authentication app before a U2F device. That way you'll always be able to
using an unsupported browser. log in - even when you're using an unsupported browser.
.col-lg-9 .col-lg-9
%p %p
- if @registration_key_handles.present? - if @registration_key_handles.present?
......
...@@ -11,19 +11,33 @@ ...@@ -11,19 +11,33 @@
%p.build-detail-row %p.build-detail-row
#{@build.coverage}% #{@build.coverage}%
- if can?(current_user, :read_build, @project) && @build.artifacts? - if can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?)
.block{ class: ("block-first" if !@build.coverage) } .block{ class: ("block-first" if !@build.coverage) }
.title .title
Build artifacts Build artifacts
.btn-group.btn-group-justified{ role: :group } - if @build.artifacts_expired?
= link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default' do %p.build-detail-row
Download 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_metadata? - if @build.artifacts?
= link_to browse_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default' do .btn-group.btn-group-justified{ role: :group }
Browse - 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
.block{ class: ("block-first" if !@build.coverage && !(can?(current_user, :read_build, @project) && @build.artifacts?)) } = 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 details Build details
- if @build.retryable? - if @build.retryable?
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment