Commit 08362d80 authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'master' into 37220-es-modules

* master: (35 commits)
  Use WikiPages::CreateService in spec/features/projects/wiki/user_updates_wiki_page_spec.rb
  Replace the 'project/merge_requests/revert.feature' spinach test with an rspec analog
  Docs group mrs list view search bar
  Replace the project/milestone.feature spinach test with an rspec analog
  Make all the tooltips in the same direction on the commit info box
  Reset all connection schema cache after migration tests
  Emoji was rendered as italic
  Adds Event polyfill for IE
  Add gitaly to patch update doc
  Document how to swap database tables.
  Replace 'project/wiki.feature' spinach test with an rspec analog
  Check for sidebar cookie instead of class when resizing window
  update installation and update instructions for 10.0
  Replace the 'project/merge_requests/accept.feature' spinach test with an rspec analog
  Remove confidential toggle checkbox and related code as no longer necessary
  Bump grape_logging gem to 1.7.0 to get status codes for error messages
  Expand filtered parameters to include `token`
  Replace the project/team_management.feature spinach test with an rspec analog
  Replace the profile/emails.feature spinach test with an rspec analog
  Replace project/group_links.feature spinach test with an rspec analog
  ...
parents 47ee81ff 5d3f7b13
...@@ -407,4 +407,4 @@ gem 'flipper-active_record', '~> 0.10.2' ...@@ -407,4 +407,4 @@ gem 'flipper-active_record', '~> 0.10.2'
# Structured logging # Structured logging
gem 'lograge', '~> 0.5' gem 'lograge', '~> 0.5'
gem 'grape_logging', '~> 1.6' gem 'grape_logging', '~> 1.7'
...@@ -355,7 +355,7 @@ GEM ...@@ -355,7 +355,7 @@ GEM
activesupport activesupport
grape (>= 0.16.0) grape (>= 0.16.0)
rake rake
grape_logging (1.6.0) grape_logging (1.7.0)
grape grape
grpc (1.4.5) grpc (1.4.5)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
...@@ -1037,7 +1037,7 @@ DEPENDENCIES ...@@ -1037,7 +1037,7 @@ DEPENDENCIES
grape (~> 1.0) grape (~> 1.0)
grape-entity (~> 0.6.0) grape-entity (~> 0.6.0)
grape-route-helpers (~> 2.1.0) grape-route-helpers (~> 2.1.0)
grape_logging (~> 1.6) grape_logging (~> 1.7)
haml_lint (~> 0.26.0) haml_lint (~> 0.26.0)
hamlit (~> 2.6.1) hamlit (~> 2.6.1)
hashie-forbidden_attributes hashie-forbidden_attributes
......
...@@ -12,4 +12,5 @@ import 'core-js/fn/symbol'; ...@@ -12,4 +12,5 @@ import 'core-js/fn/symbol';
// Browser polyfills // Browser polyfills
import './polyfills/custom_event'; import './polyfills/custom_event';
import './polyfills/element'; import './polyfills/element';
import './polyfills/event';
import './polyfills/nodelist'; import './polyfills/nodelist';
if (typeof window.CustomEvent !== 'function') { if (typeof window.CustomEvent !== 'function') {
window.CustomEvent = function CustomEvent(event, params) { window.CustomEvent = function CustomEvent(event, params) {
const evt = document.createEvent('CustomEvent'); const evt = document.createEvent('CustomEvent');
const evtParams = params || { bubbles: false, cancelable: false, detail: undefined }; const evtParams = {
bubbles: false,
cancelable: false,
detail: undefined,
...params,
};
evt.initCustomEvent(event, evtParams.bubbles, evtParams.cancelable, evtParams.detail); evt.initCustomEvent(event, evtParams.bubbles, evtParams.cancelable, evtParams.detail);
return evt; return evt;
}; };
......
/**
* Polyfill for IE11 support.
* new Event() is not supported by IE11.
* Although `initEvent` is deprecated for modern browsers it is the one supported by IE
*/
if (typeof window.Event !== 'function') {
window.Event = function Event(event, params) {
const evt = document.createEvent('Event');
const evtParams = {
bubbles: false,
cancelable: false,
...params,
};
evt.initEvent(event, evtParams.bubbles, evtParams.cancelable);
return evt;
};
window.Event.prototype = Event;
}
...@@ -72,10 +72,6 @@ export default { ...@@ -72,10 +72,6 @@ export default {
required: false, required: false,
default: () => [], default: () => [],
}, },
isConfidential: {
type: Boolean,
required: true,
},
markdownPreviewPath: { markdownPreviewPath: {
type: String, type: String,
required: true, required: true,
...@@ -131,7 +127,6 @@ export default { ...@@ -131,7 +127,6 @@ export default {
this.showForm = true; this.showForm = true;
this.store.setFormState({ this.store.setFormState({
title: this.state.titleText, title: this.state.titleText,
confidential: this.isConfidential,
description: this.state.descriptionText, description: this.state.descriptionText,
lockedWarningVisible: false, lockedWarningVisible: false,
updateLoading: false, updateLoading: false,
...@@ -147,8 +142,6 @@ export default { ...@@ -147,8 +142,6 @@ export default {
.then((data) => { .then((data) => {
if (location.pathname !== data.web_url) { if (location.pathname !== data.web_url) {
gl.utils.visitUrl(data.web_url); gl.utils.visitUrl(data.web_url);
} else if (data.confidential !== this.isConfidential) {
gl.utils.visitUrl(location.pathname);
} }
return this.service.getData(); return this.service.getData();
......
<script>
export default {
props: {
formState: {
type: Object,
required: true,
},
},
};
</script>
<template>
<fieldset class="checkbox">
<label for="issue-confidential">
<input
type="checkbox"
value="1"
id="issue-confidential"
v-model="formState.confidential" />
This issue is confidential and should only be visible to team members with at least Reporter access.
</label>
</fieldset>
</template>
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
import descriptionField from './fields/description.vue'; import descriptionField from './fields/description.vue';
import editActions from './edit_actions.vue'; import editActions from './edit_actions.vue';
import descriptionTemplate from './fields/description_template.vue'; import descriptionTemplate from './fields/description_template.vue';
import confidentialCheckbox from './fields/confidential_checkbox.vue';
export default { export default {
props: { props: {
...@@ -44,7 +43,6 @@ ...@@ -44,7 +43,6 @@
descriptionField, descriptionField,
descriptionTemplate, descriptionTemplate,
editActions, editActions,
confidentialCheckbox,
}, },
computed: { computed: {
hasIssuableTemplates() { hasIssuableTemplates() {
...@@ -81,8 +79,6 @@ ...@@ -81,8 +79,6 @@
:form-state="formState" :form-state="formState"
:markdown-preview-path="markdownPreviewPath" :markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath" /> :markdown-docs-path="markdownDocsPath" />
<confidential-checkbox
:form-state="formState" />
<edit-actions <edit-actions
:form-state="formState" :form-state="formState"
:can-destroy="canDestroy" /> :can-destroy="canDestroy" />
......
...@@ -35,7 +35,6 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -35,7 +35,6 @@ document.addEventListener('DOMContentLoaded', () => {
initialDescriptionHtml: this.initialDescriptionHtml, initialDescriptionHtml: this.initialDescriptionHtml,
initialDescriptionText: this.initialDescriptionText, initialDescriptionText: this.initialDescriptionText,
issuableTemplates: this.issuableTemplates, issuableTemplates: this.issuableTemplates,
isConfidential: this.isConfidential,
markdownPreviewPath: this.markdownPreviewPath, markdownPreviewPath: this.markdownPreviewPath,
markdownDocsPath: this.markdownDocsPath, markdownDocsPath: this.markdownDocsPath,
projectPath: this.projectPath, projectPath: this.projectPath,
......
...@@ -3,7 +3,6 @@ export default class Store { ...@@ -3,7 +3,6 @@ export default class Store {
this.state = initialState; this.state = initialState;
this.formState = { this.formState = {
title: '', title: '',
confidential: false,
description: '', description: '',
lockedWarningVisible: false, lockedWarningVisible: false,
updateLoading: false, updateLoading: false,
......
...@@ -68,7 +68,7 @@ export default class NewNavSidebar { ...@@ -68,7 +68,7 @@ export default class NewNavSidebar {
if (breakpoint === 'sm' || breakpoint === 'md') { if (breakpoint === 'sm' || breakpoint === 'md') {
this.toggleCollapsedSidebar(true); this.toggleCollapsedSidebar(true);
} else if (breakpoint === 'lg') { } else if (breakpoint === 'lg') {
const collapse = this.$sidebar.hasClass('sidebar-icons-only'); const collapse = Cookies.get('sidebar_collapsed') === 'true';
this.toggleCollapsedSidebar(collapse); this.toggleCollapsedSidebar(collapse);
} }
} }
......
gl-emoji { gl-emoji {
font-style: normal;
display: inline-flex; display: inline-flex;
vertical-align: middle; vertical-align: middle;
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
......
...@@ -17,8 +17,11 @@ ...@@ -17,8 +17,11 @@
max-width: $limited-layout-width-sm; max-width: $limited-layout-width-sm;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
padding-top: 64px;
padding-bottom: 64px; @media (min-width: $screen-md-min) {
padding-top: 64px;
padding-bottom: 64px;
}
} }
} }
......
...@@ -608,7 +608,7 @@ ...@@ -608,7 +608,7 @@
+ .files, + .files,
+ .alert { + .alert {
margin-top: 30px; margin-top: 32px;
} }
} }
} }
......
...@@ -213,7 +213,6 @@ module IssuablesHelper ...@@ -213,7 +213,6 @@ module IssuablesHelper
canUpdate: can?(current_user, :update_issue, issuable), canUpdate: can?(current_user, :update_issue, issuable),
canDestroy: can?(current_user, :destroy_issue, issuable), canDestroy: can?(current_user, :destroy_issue, issuable),
issuableRef: issuable.to_reference, issuableRef: issuable.to_reference,
isConfidential: issuable.confidential,
markdownPreviewPath: preview_markdown_path(@project), markdownPreviewPath: preview_markdown_path(@project),
markdownDocsPath: help_page_path('user/markdown'), markdownDocsPath: help_page_path('user/markdown'),
issuableTemplates: issuable_templates(issuable), issuableTemplates: issuable_templates(issuable),
......
...@@ -137,15 +137,7 @@ module ProjectsHelper ...@@ -137,15 +137,7 @@ module ProjectsHelper
end end
def last_push_event def last_push_event
return unless current_user current_user&.recent_push(@project)
return current_user.recent_push unless @project
project_ids = [@project.id]
if fork = current_user.fork_of(@project)
project_ids << fork.id
end
current_user.recent_push(project_ids)
end end
def project_feature_access_select(field) def project_feature_access_select(field)
......
...@@ -247,7 +247,7 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -247,7 +247,7 @@ class ApplicationSetting < ActiveRecord::Base
housekeeping_full_repack_period: 50, housekeeping_full_repack_period: 50,
housekeeping_gc_period: 200, housekeeping_gc_period: 200,
housekeeping_incremental_repack_period: 10, housekeeping_incremental_repack_period: 10,
import_sources: Gitlab::ImportSources.values, import_sources: Settings.gitlab['import_sources'],
koding_enabled: false, koding_enabled: false,
koding_url: nil, koding_url: nil,
max_artifacts_size: Settings.artifacts['max_size'], max_artifacts_size: Settings.artifacts['max_size'],
......
...@@ -49,7 +49,7 @@ class Event < ActiveRecord::Base ...@@ -49,7 +49,7 @@ class Event < ActiveRecord::Base
belongs_to :author, class_name: "User" belongs_to :author, class_name: "User"
belongs_to :project belongs_to :project
belongs_to :target, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations belongs_to :target, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
has_one :push_event_payload, foreign_key: :event_id has_one :push_event_payload
# Callbacks # Callbacks
after_create :reset_project_activity after_create :reset_project_activity
......
class GpgSignature < ActiveRecord::Base class GpgSignature < ActiveRecord::Base
include ShaAttribute include ShaAttribute
include IgnorableColumn
ignore_column :valid_signature
sha_attribute :commit_sha sha_attribute :commit_sha
sha_attribute :gpg_key_primary_keyid sha_attribute :gpg_key_primary_keyid
......
...@@ -30,6 +30,44 @@ class PushEvent < Event ...@@ -30,6 +30,44 @@ class PushEvent < Event
delegate :commit_count, to: :push_event_payload delegate :commit_count, to: :push_event_payload
alias_method :commits_count, :commit_count alias_method :commits_count, :commit_count
# Returns events of pushes that either pushed to an existing ref or created a
# new one.
def self.created_or_pushed
actions = [
PushEventPayload.actions[:pushed],
PushEventPayload.actions[:created]
]
joins(:push_event_payload)
.where(push_event_payloads: { action: actions })
end
# Returns events of pushes to a branch.
def self.branch_events
ref_type = PushEventPayload.ref_types[:branch]
joins(:push_event_payload)
.where(push_event_payloads: { ref_type: ref_type })
end
# Returns PushEvent instances for which no merge requests have been created.
def self.without_existing_merge_requests
existing_mrs = MergeRequest.except(:order)
.select(1)
.where('merge_requests.source_project_id = events.project_id')
.where('merge_requests.source_branch = push_event_payloads.ref')
# For reasons unknown the use of #eager_load will result in the
# "push_event_payload" association not being set. Because of this we're
# using "joins" here, which does mean an additional query needs to be
# executed in order to retrieve the "push_event_association" when the
# returned PushEvent is used.
joins(:push_event_payload)
.where('NOT EXISTS (?)', existing_mrs)
.created_or_pushed
.branch_events
end
def self.sti_name def self.sti_name
PUSHED PUSHED
end end
......
...@@ -650,20 +650,13 @@ class User < ActiveRecord::Base ...@@ -650,20 +650,13 @@ class User < ActiveRecord::Base
@personal_projects_count ||= personal_projects.count @personal_projects_count ||= personal_projects.count
end end
def recent_push(project_ids = nil) def recent_push(project = nil)
# Get push events not earlier than 2 hours ago service = Users::LastPushEventService.new(self)
events = recent_events.code_push.where("created_at > ?", Time.now - 2.hours)
events = events.where(project_id: project_ids) if project_ids
# Use the latest event that has not been pushed or merged recently if project
events.includes(:project).recent.find do |event| service.last_event_for_project(project)
next unless event.project.repository.branch_exists?(event.branch_name) else
service.last_event_for_user
merge_requests = MergeRequest.where("created_at >= ?", event.created_at)
.where(source_project_id: event.project.id,
source_branch: event.branch_name)
merge_requests.empty?
end end
end end
......
...@@ -74,12 +74,19 @@ class EventCreateService ...@@ -74,12 +74,19 @@ class EventCreateService
# We're using an explicit transaction here so that any errors that may occur # We're using an explicit transaction here so that any errors that may occur
# when creating push payload data will result in the event creation being # when creating push payload data will result in the event creation being
# rolled back as well. # rolled back as well.
Event.transaction do event = Event.transaction do
event = create_event(project, current_user, Event::PUSHED) new_event = create_event(project, current_user, Event::PUSHED)
PushEventPayloadService.new(event, push_data).execute PushEventPayloadService
.new(new_event, push_data)
.execute
new_event
end end
Users::LastPushEventService.new(current_user)
.cache_last_push_event(event)
Users::ActivityService.new(current_user, 'push').execute Users::ActivityService.new(current_user, 'push').execute
end end
......
module Users
# Service class for caching and retrieving the last push event of a user.
class LastPushEventService
EXPIRATION = 2.hours
def initialize(user)
@user = user
end
# Caches the given push event for the current user in the Rails cache.
#
# event - An instance of PushEvent to cache.
def cache_last_push_event(event)
keys = [
project_cache_key(event.project),
user_cache_key
]
if event.project.forked?
keys << project_cache_key(event.project.forked_from_project)
end
keys.each { |key| set_key(key, event.id) }
end
# Returns the last PushEvent for the current user.
#
# This method will return nil if no event was found.
def last_event_for_user
find_cached_event(user_cache_key)
end
# Returns the last PushEvent for the current user and the given project.
#
# project - An instance of Project for which to retrieve the PushEvent.
#
# This method will return nil if no event was found.
def last_event_for_project(project)
find_cached_event(project_cache_key(project))
end
def find_cached_event(cache_key)
event_id = get_key(cache_key)
return unless event_id
unless (event = find_event_in_database(event_id))
# We don't want to keep querying the same data over and over when a
# merge request has been created, thus we remove the key if no event
# (meaning an MR was created) is returned.
Rails.cache.delete(cache_key)
end
event
end
private
def find_event_in_database(id)
PushEvent
.without_existing_merge_requests
.find_by(id: id)
end
def user_cache_key
"last-push-event/#{@user.id}"
end
def project_cache_key(project)
"last-push-event/#{@user.id}/#{project.id}"
end
def get_key(key)
Rails.cache.read(key, raw: true)
end
def set_key(key, value)
# We're using raw values here since this takes up less space and we don't
# store complex objects.
Rails.cache.write(key, value, raw: true, expires_in: EXPIRATION)
end
end
end
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
= icon('wrench') = icon('wrench')
.sidebar-context-title Admin Area .sidebar-context-title Admin Area
%ul.sidebar-top-level-items %ul.sidebar-top-level-items
= nav_link(controller: %w(dashboard admin projects users groups jobs runners cohorts), html_options: {class: 'home'}) do = nav_link(controller: %w(dashboard admin projects users groups jobs runners cohorts conversational_development_index), html_options: {class: 'home'}) do
= sidebar_link admin_root_path, title: _('Overview'), css: 'shortcuts-tree' do = sidebar_link admin_root_path, title: _('Overview'), css: 'shortcuts-tree' do
.nav-icon-container .nav-icon-container
= custom_icon('overview') = custom_icon('overview')
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
Overview Overview
%ul.sidebar-sub-level-items %ul.sidebar-sub-level-items
= nav_link(controller: %w(dashboard admin projects users groups jobs runners cohorts), html_options: { class: "fly-out-top-item" } ) do = nav_link(controller: %w(dashboard admin projects users groups jobs runners cohorts conversational_development_index), html_options: { class: "fly-out-top-item" } ) do
= link_to admin_root_path do = link_to admin_root_path do
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
#{ _('Overview') } #{ _('Overview') }
...@@ -52,16 +52,16 @@ ...@@ -52,16 +52,16 @@
%span %span
ConvDev Index ConvDev Index
= nav_link(controller: %w(conversational_development_index system_info background_jobs logs health_check requests_profiles)) do = nav_link(controller: %w(system_info background_jobs logs health_check requests_profiles)) do
= sidebar_link admin_conversational_development_index_path, title: _('Monitoring') do = sidebar_link admin_system_info_path, title: _('Monitoring') do
.nav-icon-container .nav-icon-container
= custom_icon('monitoring') = custom_icon('monitoring')
%span.nav-item-name %span.nav-item-name
Monitoring Monitoring
%ul.sidebar-sub-level-items %ul.sidebar-sub-level-items
= nav_link(controller: %w(conversational_development_index system_info background_jobs logs health_check requests_profiles), html_options: { class: "fly-out-top-item" } ) do = nav_link(controller: %w(system_info background_jobs logs health_check requests_profiles), html_options: { class: "fly-out-top-item" } ) do
= link_to admin_conversational_development_index_path do = link_to admin_system_info_path do
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
#{ _('Monitoring') } #{ _('Monitoring') }
%li.divider.fly-out-top-item %li.divider.fly-out-top-item
......
...@@ -16,10 +16,10 @@ ...@@ -16,10 +16,10 @@
.preview= image_tag "#{scheme.css_class}-scheme-preview.png" .preview= image_tag "#{scheme.css_class}-scheme-preview.png"
= f.radio_button :color_scheme_id, scheme.id = f.radio_button :color_scheme_id, scheme.id
= scheme.name = scheme.name
.col-sm-12 .col-sm-12
%hr %hr
.col-sm-12
%hr
.col-lg-4.profile-settings-sidebar .col-lg-4.profile-settings-sidebar
%h4.prepend-top-0 %h4.prepend-top-0
Behavior Behavior
......
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
.commiter .commiter
- commit_author_link = commit_author_link(commit, avatar: false, size: 24) - commit_author_link = commit_author_link(commit, avatar: false, size: 24)
- commit_timeago = time_ago_with_tooltip(commit.committed_date) - commit_timeago = time_ago_with_tooltip(commit.committed_date, placement: 'bottom')
- commit_text = _('%{commit_author_link} committed %{commit_timeago}') % { commit_author_link: commit_author_link, commit_timeago: commit_timeago } - commit_text = _('%{commit_author_link} committed %{commit_timeago}') % { commit_author_link: commit_author_link, commit_timeago: commit_timeago }
#{ commit_text.html_safe } #{ commit_text.html_safe }
......
---
title: Fixed merge request changes bar jumping
merge_request:
author:
type: fixed
---
title: Tooltips in the commit info box now all face the same direction
merge_request:
author: Jedidiah Broadbent
type: fixed
---
title: Fix ConvDev Index nav item and Monitoring submenu regression
merge_request: !14124
author:
type: fixed
---
title: Adds Event polyfill for IE11
merge_request:
author:
type: fixed
---
title: Read import sources from setting at first initialization
merge_request: 14141
author: Visay Keo
type: fixed
---
title: Update native unicode emojis to always render as normal text (previously could render italicized)
merge_request:
author: Branka Martinovic
type: fixed
---
title: Replace the profile/emails.feature spinach test with an rspec analog
merge_request: 14172
author: Vitaliy @blackst0ne Klachkov
type: other
---
title: Replace project/group_links.feature spinach test with an rspec analog
merge_request: 14169
author: Vitaliy @blackst0ne Klachkov
type: other
---
title: Replace the project/milestone.feature spinach test with an rspec analog
merge_request: 14171
author: Vitaliy @blackst0ne Klachkov
type: other
---
title: Replace the 'project/merge_requests/accept.feature' spinach test with an rspec analog
merge_request: 14176
author: Vitaliy @blackst0ne Klachkov
type: other
---
title: Replace the 'project/merge_requests/revert.feature' spinach test with an rspec
analog
merge_request: 14201
author: Vitaliy @blackst0ne Klachkov
type: other
---
title: Replace 'project/wiki.feature' spinach test with an rspec analog
merge_request: 13856
author: Vitaliy @blackst0ne Klachkov
type: other
---
title: Replace the project/team_management.feature spinach test with an rspec analog
merge_request: 14173
author: Vitaliy @blackst0ne Klachkov
type: other
---
title: Rework how recent push events are retrieved
merge_request:
author:
type: other
...@@ -51,7 +51,7 @@ module Gitlab ...@@ -51,7 +51,7 @@ module Gitlab
# Configure sensitive parameters which will be filtered from the log file. # Configure sensitive parameters which will be filtered from the log file.
# #
# Parameters filtered: # Parameters filtered:
# - Any parameter ending with `_token` # - Any parameter ending with `token`
# - Any parameter containing `password` # - Any parameter containing `password`
# - Any parameter containing `secret` # - Any parameter containing `secret`
# - Two-factor tokens (:otp_attempt) # - Two-factor tokens (:otp_attempt)
...@@ -61,7 +61,7 @@ module Gitlab ...@@ -61,7 +61,7 @@ module Gitlab
# - Webhook URLs (:hook) # - Webhook URLs (:hook)
# - Sentry DSN (:sentry_dsn) # - Sentry DSN (:sentry_dsn)
# - Deploy keys (:key) # - Deploy keys (:key)
config.filter_parameters += [/_token$/, /password/, /secret/] config.filter_parameters += [/token$/, /password/, /secret/]
config.filter_parameters += %i( config.filter_parameters += %i(
certificate certificate
encrypted_key encrypted_key
......
...@@ -269,7 +269,7 @@ Settings.gitlab.default_projects_features['builds'] = true if Settin ...@@ -269,7 +269,7 @@ Settings.gitlab.default_projects_features['builds'] = true if Settin
Settings.gitlab.default_projects_features['container_registry'] = true if Settings.gitlab.default_projects_features['container_registry'].nil? Settings.gitlab.default_projects_features['container_registry'] = true if Settings.gitlab.default_projects_features['container_registry'].nil?
Settings.gitlab.default_projects_features['visibility_level'] = Settings.__send__(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE) Settings.gitlab.default_projects_features['visibility_level'] = Settings.__send__(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE)
Settings.gitlab['domain_whitelist'] ||= [] Settings.gitlab['domain_whitelist'] ||= []
Settings.gitlab['import_sources'] ||= %w[github bitbucket gitlab google_code fogbugz git gitlab_project gitea] Settings.gitlab['import_sources'] ||= Gitlab::ImportSources.values
Settings.gitlab['trusted_proxies'] ||= [] Settings.gitlab['trusted_proxies'] ||= []
Settings.gitlab['no_todos_messages'] ||= YAML.load_file(Rails.root.join('config', 'no_todos_messages.yml')) Settings.gitlab['no_todos_messages'] ||= YAML.load_file(Rails.root.join('config', 'no_todos_messages.yml'))
Settings.gitlab['usage_ping_enabled'] = true if Settings.gitlab['usage_ping_enabled'].nil? Settings.gitlab['usage_ping_enabled'] = true if Settings.gitlab['usage_ping_enabled'].nil?
......
...@@ -4,12 +4,21 @@ ...@@ -4,12 +4,21 @@
- title: "Throughput" - title: "Throughput"
y_label: "Requests / Sec" y_label: "Requests / Sec"
required_metrics: required_metrics:
- nginx_upstream_requests_total - nginx_upstream_responses_total
weight: 1 weight: 1
queries: queries:
- query_range: 'sum(rate(nginx_upstream_requests_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m]))' - query_range: 'sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) by (status_code)'
label: Total
unit: req / sec unit: req / sec
label: Status Code
series:
- label: status_code
when:
- value: 2xx
color: green
- value: 4xx
color: orange
- value: 5xx
color: red
- title: "Latency" - title: "Latency"
y_label: "Latency (ms)" y_label: "Latency (ms)"
required_metrics: required_metrics:
...@@ -37,9 +46,17 @@ ...@@ -37,9 +46,17 @@
- haproxy_frontend_http_requests_total - haproxy_frontend_http_requests_total
weight: 1 weight: 1
queries: queries:
- query_range: 'sum(rate(haproxy_frontend_http_requests_total{%{environment_filter}}[2m]))' - query_range: 'sum(rate(haproxy_frontend_http_requests_total{%{environment_filter}}[2m])) by (code)'
label: Total
unit: req / sec unit: req / sec
series:
- label: code
when:
- value: 2xx
color: green
- value: 4xx
color: yellow
- value: 5xx
color: red
- title: "HTTP Error Rate" - title: "HTTP Error Rate"
y_label: "Error Rate (%)" y_label: "Error Rate (%)"
required_metrics: required_metrics:
...@@ -86,12 +103,21 @@ ...@@ -86,12 +103,21 @@
- title: "Throughput" - title: "Throughput"
y_label: "Requests / Sec" y_label: "Requests / Sec"
required_metrics: required_metrics:
- nginx_requests_total - nginx_responses_total
weight: 1 weight: 1
queries: queries:
- query_range: 'sum(rate(nginx_requests_total{server_zone!="*", server_zone!="_", %{environment_filter}}[2m]))' - query_range: 'sum(rate(nginx_responses_total{server_zone!="*", server_zone!="_", %{environment_filter}}[2m])) by (status_code)'
label: Total
unit: req / sec unit: req / sec
label: Status Code
series:
- label: status_code
when:
- value: 2xx
color: green
- value: 4xx
color: orange
- value: 5xx
color: red
- title: "Latency" - title: "Latency"
y_label: "Latency (ms)" y_label: "Latency (ms)"
required_metrics: required_metrics:
...@@ -128,6 +154,8 @@ ...@@ -128,6 +154,8 @@
- container_cpu_usage_seconds_total - container_cpu_usage_seconds_total
weight: 1 weight: 1
queries: queries:
- query_range: 'sum(rate(container_cpu_usage_seconds_total{container_name!="POD",%{environment_filter}}[2m])) / count(container_cpu_usage_seconds_total{container_name!="POD",%{environment_filter}}) * 100' - query_range: 'sum(rate(container_cpu_usage_seconds_total{container_name!="POD",%{environment_filter}}[2m])) by (cpu) * 100'
label: Average label: CPU
unit: "%" unit: "%"
series:
- label: cpu
...@@ -60,6 +60,7 @@ ...@@ -60,6 +60,7 @@
- [Ordering Table Columns](ordering_table_columns.md) - [Ordering Table Columns](ordering_table_columns.md)
- [Verifying Database Capabilities](verifying_database_capabilities.md) - [Verifying Database Capabilities](verifying_database_capabilities.md)
- [Hash Indexes](hash_indexes.md) - [Hash Indexes](hash_indexes.md)
- [Swapping Tables](swapping_tables.md)
## i18n ## i18n
......
# Swapping Tables
Sometimes you need to replace one table with another. For example, when
migrating data in a very large table it's often better to create a copy of the
table and insert & migrate the data into this new table in the background.
Let's say you want to swap the table "events" with "events_for_migration". In
this case you need to follow 3 steps:
1. Rename "events" to "events_temporary"
2. Rename "events_for_migration" to "events"
3. Rename "events_temporary" to "events_for_migration"
Rails allows you to do this using the `rename_table` method:
```ruby
rename_table :events, :events_temporary
rename_table :events_for_migration, :events
rename_table :events_temporary, :events_for_migration
```
This does not require any downtime as long as the 3 `rename_table` calls are
executed in the _same_ database transaction. Rails by default uses database
transactions for migrations, but if it doesn't you'll need to start one
manually:
```ruby
Event.transaction do
rename_table :events, :events_temporary
rename_table :events_for_migration, :events
rename_table :events_temporary, :events_for_migration
end
```
Once swapped you _have to_ reset the primary key of the new table. For
PostgreSQL you can use the `reset_pk_sequence!` method like so:
```ruby
reset_pk_sequence!('events')
```
For MySQL however you need to do run the following:
```ruby
amount = Event.pluck('COALESCE(MAX(id), 1)').first
execute "ALTER TABLE events AUTO_INCREMENT = #{amount}"
```
Failure to reset the primary keys will result in newly created rows starting
with an ID value of 1. Depending on the existing data this can then lead to
duplicate key constraints from popping up, preventing users from creating new
data.
...@@ -299,9 +299,9 @@ sudo usermod -aG redis git ...@@ -299,9 +299,9 @@ sudo usermod -aG redis git
### Clone the Source ### Clone the Source
# Clone GitLab repository # Clone GitLab repository
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 9-5-stable gitlab sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 10-0-stable gitlab
**Note:** You can change `9-5-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! **Note:** You can change `10-0-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It ### Configure It
......
...@@ -19,7 +19,7 @@ should be deployed, upgraded, and configured. ...@@ -19,7 +19,7 @@ should be deployed, upgraded, and configured.
## GitLab-Omnibus Chart (Recommended) ## GitLab-Omnibus Chart (Recommended)
> **Note**: This chart is in beta while [additional features](https://gitlab.com/charts/charts.gitlab.io/issues/68) are being added. > **Note**: This chart is in beta while [additional features](https://gitlab.com/charts/charts.gitlab.io/issues/68) are being added.
This chart is the best available way to operate GitLab on Kubernetes. It deploys and configures nearly all features of GitLab, including: a [Runner](https://docs.gitlab.com/runner/), [Container Registry](https://docs.gitlab.com/ee/user/project/container_registry.html#gitlab-container-registry), [Mattermost](https://docs.gitlab.com/omnibus/gitlab-mattermost/), [automatic SSL](https://github.com/kubernetes/charts/tree/master/stable/kube-lego), and a [load balancer](https://github.com/kubernetes/ingress/tree/master/controllers/nginx). It is based on our [GitLab Omnibus Docker Images](https://docs.gitlab.com/omnibus/docker/README.html). This chart is the best available way to operate GitLab on Kubernetes. It deploys and configures nearly all features of GitLab, including: a [Runner](https://docs.gitlab.com/runner/), [Container Registry](../../user/project/container_registry.html#gitlab-container-registry), [Mattermost](https://docs.gitlab.com/omnibus/gitlab-mattermost/), [automatic SSL](https://github.com/kubernetes/charts/tree/master/stable/kube-lego), and a [load balancer](https://github.com/kubernetes/ingress/tree/master/controllers/nginx). It is based on our [GitLab Omnibus Docker Images](https://docs.gitlab.com/omnibus/docker/README.html).
Once the [cloud native charts](#upcoming-cloud-native-helm-charts) are ready for production use, this chart will be deprecated. Due to the difficulty in supporting upgrades to the new architecture, migrating will require exporting data out of this instance and importing it into the new deployment. Once the [cloud native charts](#upcoming-cloud-native-helm-charts) are ready for production use, this chart will be deprecated. Due to the difficulty in supporting upgrades to the new architecture, migrating will require exporting data out of this instance and importing it into the new deployment.
...@@ -41,7 +41,7 @@ This is a large project and will be worked on over the span of multiple releases ...@@ -41,7 +41,7 @@ This is a large project and will be worked on over the span of multiple releases
### GitLab Runner Chart ### GitLab Runner Chart
If you already have a GitLab instance running, inside or outside of Kubernetes, and you'd like to leverage the Runner's [Kubernetes capabilities](https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/blob/master/docs/executors/kubernetes.md), it can be deployed with the GitLab Runner chart. If you already have a GitLab instance running, inside or outside of Kubernetes, and you'd like to leverage the Runner's [Kubernetes capabilities](https://docs.gitlab.com/runner/executors/kubernetes.html), it can be deployed with the GitLab Runner chart.
Learn more about [gitlab-runner chart.](gitlab_runner_chart.md) Learn more about [gitlab-runner chart.](gitlab_runner_chart.md)
......
...@@ -236,7 +236,7 @@ ActionMailer::Base.delivery_method = :smtp ...@@ -236,7 +236,7 @@ ActionMailer::Base.delivery_method = :smtp
See [smtp_settings.rb.sample] as an example. See [smtp_settings.rb.sample] as an example.
[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-17-stable/config/initializers/smtp_settings.rb.sample#L13 [smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/9-0-stable/config/initializers/smtp_settings.rb.sample#L13
#### Init script #### Init script
......
...@@ -236,7 +236,7 @@ ActionMailer::Base.delivery_method = :smtp ...@@ -236,7 +236,7 @@ ActionMailer::Base.delivery_method = :smtp
See [smtp_settings.rb.sample] as an example. See [smtp_settings.rb.sample] as an example.
[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/9-0-stable/config/initializers/smtp_settings.rb.sample#L13 [smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/9-1-stable/config/initializers/smtp_settings.rb.sample#L13
#### Init script #### Init script
......
...@@ -194,7 +194,7 @@ ActionMailer::Base.delivery_method = :smtp ...@@ -194,7 +194,7 @@ ActionMailer::Base.delivery_method = :smtp
See [smtp_settings.rb.sample] as an example. See [smtp_settings.rb.sample] as an example.
[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/9-1-stable/config/initializers/smtp_settings.rb.sample#L13 [smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/9-2-stable/config/initializers/smtp_settings.rb.sample#L13
#### Init script #### Init script
......
...@@ -230,7 +230,7 @@ ActionMailer::Base.delivery_method = :smtp ...@@ -230,7 +230,7 @@ ActionMailer::Base.delivery_method = :smtp
See [smtp_settings.rb.sample] as an example. See [smtp_settings.rb.sample] as an example.
[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/9-2-stable/config/initializers/smtp_settings.rb.sample#L13 [smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/9-3-stable/config/initializers/smtp_settings.rb.sample#L13
#### Init script #### Init script
......
...@@ -243,7 +243,7 @@ ActionMailer::Base.delivery_method = :smtp ...@@ -243,7 +243,7 @@ ActionMailer::Base.delivery_method = :smtp
See [smtp_settings.rb.sample] as an example. See [smtp_settings.rb.sample] as an example.
[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/9-3-stable/config/initializers/smtp_settings.rb.sample#L13 [smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/9-4-stable/config/initializers/smtp_settings.rb.sample#L13
#### Init script #### Init script
......
...@@ -252,7 +252,7 @@ ActionMailer::Base.delivery_method = :smtp ...@@ -252,7 +252,7 @@ ActionMailer::Base.delivery_method = :smtp
See [smtp_settings.rb.sample] as an example. See [smtp_settings.rb.sample] as an example.
[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/9-4-stable/config/initializers/smtp_settings.rb.sample#L13 [smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/9-5-stable/config/initializers/smtp_settings.rb.sample#L13
#### Init script #### Init script
......
This diff is collapsed.
...@@ -74,7 +74,15 @@ cd /home/git/gitlab ...@@ -74,7 +74,15 @@ cd /home/git/gitlab
sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workhorse]" RAILS_ENV=production sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workhorse]" RAILS_ENV=production
``` ```
### 5. Update gitlab-shell to the corresponding version ### 5. Update gitaly to the corresponding version
```bash
cd /home/git/gitlab
sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly]" RAILS_ENV=production
```
### 6. Update gitlab-shell to the corresponding version
```bash ```bash
cd /home/git/gitlab-shell cd /home/git/gitlab-shell
...@@ -84,14 +92,14 @@ sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION` -b v`ca ...@@ -84,14 +92,14 @@ sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION` -b v`ca
sudo -u git -H sh -c 'if [ -x bin/compile ]; then bin/compile; fi' sudo -u git -H sh -c 'if [ -x bin/compile ]; then bin/compile; fi'
``` ```
### 6. Start application ### 7. Start application
```bash ```bash
sudo service gitlab start sudo service gitlab start
sudo service nginx restart sudo service nginx restart
``` ```
### 7. Check application status ### 8. Check application status
Check if GitLab and its environment are configured correctly: Check if GitLab and its environment are configured correctly:
......
...@@ -23,7 +23,7 @@ If you have just started using GitLab, it may take a few weeks for data to be ...@@ -23,7 +23,7 @@ If you have just started using GitLab, it may take a few weeks for data to be
collected before this feature is available. collected before this feature is available.
This feature is accessible only to a system admin, at This feature is accessible only to a system admin, at
**Admin area > Monitoring > ConvDev Index**. **Admin area > Overview > ConvDev Index**.
[ce-30469]: https://gitlab.com/gitlab-org/gitlab-ce/issues/30469 [ce-30469]: https://gitlab.com/gitlab-org/gitlab-ce/issues/30469
[ping]: ../settings/usage_statistics.md#usage-ping [ping]: ../settings/usage_statistics.md#usage-ping
...@@ -7,7 +7,7 @@ GitLab has support for automatically detecting and monitoring HAProxy. This is p ...@@ -7,7 +7,7 @@ GitLab has support for automatically detecting and monitoring HAProxy. This is p
| Name | Query | | Name | Query |
| ---- | ----- | | ---- | ----- |
| Throughput (req/sec) | sum(rate(haproxy_frontend_http_requests_total{%{environment_filter}}[2m])) | | Throughput (req/sec) | sum(rate(haproxy_frontend_http_requests_total{%{environment_filter}}[2m])) by (code) |
| HTTP Error Rate (%) | sum(rate(haproxy_frontend_http_requests_total{code="5xx",%{environment_filter}}[2m])) / sum(rate(haproxy_frontend_http_requests_total{%{environment_filter}}[2m])) | | HTTP Error Rate (%) | sum(rate(haproxy_frontend_http_requests_total{code="5xx",%{environment_filter}}[2m])) / sum(rate(haproxy_frontend_http_requests_total{%{environment_filter}}[2m])) |
## Configuring Prometheus to monitor for HAProxy metrics ## Configuring Prometheus to monitor for HAProxy metrics
......
...@@ -8,7 +8,7 @@ GitLab has support for automatically detecting and monitoring Kubernetes metrics ...@@ -8,7 +8,7 @@ GitLab has support for automatically detecting and monitoring Kubernetes metrics
| Name | Query | | Name | Query |
| ---- | ----- | | ---- | ----- |
| Average Memory Usage (MB) | (sum(container_memory_usage_bytes{container_name!="POD",%{environment_filter}}) / count(container_memory_usage_bytes{container_name!="POD",%{environment_filter}})) /1024/1024 | | Average Memory Usage (MB) | (sum(container_memory_usage_bytes{container_name!="POD",%{environment_filter}}) / count(container_memory_usage_bytes{container_name!="POD",%{environment_filter}})) /1024/1024 |
| Average CPU Utilization (%) | sum(rate(container_cpu_usage_seconds_total{container_name!="POD",%{environment_filter}}[2m])) / count(container_cpu_usage_seconds_total{container_name!="POD",%{environment_filter}}) * 100 | | Average CPU Utilization (%) | sum(rate(container_cpu_usage_seconds_total{container_name!="POD",%{environment_filter}}[2m])) by (cpu) * 100 |
## Configuring Prometheus to monitor for Kubernetes node metrics ## Configuring Prometheus to monitor for Kubernetes node metrics
......
...@@ -7,7 +7,7 @@ GitLab has support for automatically detecting and monitoring NGINX. This is pro ...@@ -7,7 +7,7 @@ GitLab has support for automatically detecting and monitoring NGINX. This is pro
| Name | Query | | Name | Query |
| ---- | ----- | | ---- | ----- |
| Throughput (req/sec) | sum(rate(nginx_requests_total{server_zone!="*", server_zone!="_", %{environment_filter}}[2m])) | | Throughput (req/sec) | sum(rate(nginx_responses_total{server_zone!="*", server_zone!="_", %{environment_filter}}[2m])) by (status_code) |
| Latency (ms) | avg(nginx_upstream_response_msecs_avg{%{environment_filter}}) | | Latency (ms) | avg(nginx_upstream_response_msecs_avg{%{environment_filter}}) |
| HTTP Error Rate (HTTP Errors / sec) | rate(nginx_responses_total{status_code="5xx", %{environment_filter}}[2m])) | | HTTP Error Rate (HTTP Errors / sec) | rate(nginx_responses_total{status_code="5xx", %{environment_filter}}[2m])) |
......
...@@ -7,19 +7,33 @@ GitLab has support for automatically detecting and monitoring the Kubernetes NGI ...@@ -7,19 +7,33 @@ GitLab has support for automatically detecting and monitoring the Kubernetes NGI
| Name | Query | | Name | Query |
| ---- | ----- | | ---- | ----- |
| Throughput (req/sec) | sum(rate(nginx_upstream_requests_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) | | Throughput (req/sec) | sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) by (status_code) |
| Latency (ms) | avg(nginx_upstream_response_msecs_avg{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}) | | Latency (ms) | avg(nginx_upstream_response_msecs_avg{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}) |
| HTTP Error Rate (HTTP Errors / sec) | sum(rate(nginx_upstream_responses_total{status_code="5xx", upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) | | HTTP Error Rate (HTTP Errors / sec) | sum(rate(nginx_upstream_responses_total{status_code="5xx", upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) |
## Configuring Prometheus to monitor for NGINX ingress metrics ## Configuring Prometheus to monitor for NGINX ingress metrics
The easiest way to get started is to use at least version 0.9.0 of [NGINX ingress](https://github.com/kubernetes/ingress/tree/master/controllers/nginx). If you are using NGINX as your Kubernetes ingress, there is [direct support](https://github.com/kubernetes/ingress/pull/423) for enabling Prometheus monitoring in the 0.9.0 release. If you have deployed with the [gitlab-omnibus](https://docs.gitlab.com/ee/install/kubernetes/gitlab_omnibus.md) Helm chart, and your application is running in the same cluster, no further action is required. The ingress metrics will be automatically enabled and annotated for Prometheus monitoring. Simply ensure Prometheus monitoring is [enabled for your project](../prometheus.md), which is on by default.
If you have deployed with the [gitlab-omnibus](https://docs.gitlab.com/ee/install/kubernetes/gitlab_omnibus.md) Helm chart, these metrics will be automatically enabled and annotated for Prometheus monitoring. For other deployments, there is some configuration required depending on your installation:
* NGINX Ingress should be version 0.9.0 or above
* NGINX Ingress should be annotated for Prometheus monitoring
* Prometheus should be configured to monitor annotated pods
### Configuring NGINX Ingress for Prometheus monitoring
Version 0.9.0 and above of [NGINX ingress](https://github.com/kubernetes/ingress/tree/master/controllers/nginx) have built-in support for exporting Prometheus metrics. To enable, a ConfigMap setting must be passed: `enable-vts-status: "true"`. Once enabled, a Prometheus metrics endpoint will start running on port 10254.
With metric data now available, Prometheus needs to be configured to collect it. The easiest way to do this is to leverage Prometheus' [built-in Kubernetes service discovery](https://prometheus.io/docs/operating/configuration/#kubernetes_sd_config), which automatically detects a variety of Kubernetes components and makes them available for monitoring. NGINX ingress metrics are exposed per pod, a sample scrape configuration [is available](https://github.com/prometheus/prometheus/blob/master/documentation/examples/prometheus-kubernetes.yml#L248). This configuration will detect pods and enable collection of metrics **only if** they have been specifically annotated for monitoring.
Depending on how NGINX ingress was deployed, typically a DaemonSet or Deployment, edit the corresponding YML spec. Two new annotations need to be added:
* `prometheus.io/port: "true"`
* `prometheus.io/port: "10254"`
Prometheus should now be collecting NGINX ingress metrics. To validate view the Prometheus Targets, available under `Status > Targets` on the Prometheus dashboard. New entries for NGINX should be listed in the kubernetes pod monitoring job, `kubernetes-pods`.
## Specifying the Environment label ## Specifying the Environment label
In order to isolate and only display relevant metrics for a given environment In order to isolate and only display relevant metrics for a given environment, GitLab needs a method to detect which labels are associated. To do this, GitLab will search for metrics with appropriate labels. In this case, the `upstream` label must be of the form `<KUBE_NAMESPACE>-<CI_ENVIRONMENT_SLUG>-*`.
however, GitLab needs a method to detect which labels are associated. To do this, GitLab will search metrics with appropriate labels. In this case, the `upstream` label must be of the form `<Kubernetes Namespace>-<CI_ENVIRONMENT_SLUG>-*`.
If you have used [Auto Deploy](https://docs.gitlab.com/ee/ci/autodeploy/index.html) to deploy your app, this format will be used automatically and metrics will be detected with no action on your part. If you have used [Auto Deploy](https://docs.gitlab.com/ee/ci/autodeploy/index.html) to deploy your app, this format will be used automatically and metrics will be detected with no action on your part.
...@@ -63,8 +63,6 @@ the same way as you do for projects. ...@@ -63,8 +63,6 @@ the same way as you do for projects.
![filter issues in a group](img/group_issues_filter.png) ![filter issues in a group](img/group_issues_filter.png)
The same process is valid for merge requests. Navigate to your project's **Merge Requests** tab. The same process is valid for merge requests. Navigate to your project's **Merge Requests** tab.
The search and filter UI currently uses dropdowns. In a future release, the same
dynamic UI as above will be carried over here.
## Search history ## Search history
......
@profile
Feature: Profile Emails
Background:
Given I sign in as a user
And I visit profile emails page
Scenario: I should see emails
Then I should see my emails
Scenario: Add new email
Given I submit new email "my@email.com"
Then I should see new email "my@email.com"
And I should see my emails
Scenario: Add duplicate email
Given I submit duplicate email @user.email
Then I should not have @user.email added
And I should see my emails
Scenario: Remove email
Given I submit new email "my@email.com"
Then I should see new email "my@email.com"
And I should see my emails
Then I click link "Remove" for "my@email.com"
Then I should not see email "my@email.com"
And I should see my emails
Feature: Project Group Links
Background:
Given I sign in as a user
And I own project "Shop"
And project "Shop" is shared with group "Ops"
And project "Shop" is not shared with group "Market"
And I visit project group links page
Scenario: I should see list of groups
Then I should see project already shared with group "Ops"
Then I should see project is not shared with group "Market"
@javascript
Scenario: I share project with group
When I select group "Market" for share
Then I should see project is shared with group "Market"
@project_merge_requests
Feature: Project Merge Requests Acceptance
Background:
Given There is an open Merge Request
And I am signed in as a developer of the project
@javascript
Scenario: Accepting the Merge Request and removing the source branch
Given I am on the Merge Request detail page
When I check the "Remove source branch" option
And I click on Accept Merge Request
Then I should see merge request merged
And I should not see the Remove Source Branch button
@javascript
Scenario: Accepting the Merge Request when URL has an anchor
Given I am on the Merge Request detail with note anchor page
When I check the "Remove source branch" option
And I click on Accept Merge Request
Then I should see merge request merged
And I should not see the Remove Source Branch button
@javascript
Scenario: Accepting the Merge Request without removing the source branch
Given I am on the Merge Request detail page
When I click on Accept Merge Request
Then I should see merge request merged
And I should see the Remove Source Branch button
@project_merge_requests
Feature: Revert Merge Requests
Background:
Given There is an open Merge Request
And I am signed in as a developer of the project
And I am on the Merge Request detail page
And I click on Accept Merge Request
And I am on the Merge Request detail page
@javascript
Scenario: I revert a merge request
Given I click on the revert button
And I revert the changes directly
Then I should see the revert merge request notice
@javascript
Scenario: I revert a merge request that was previously reverted
Given I click on the revert button
And I revert the changes directly
And I am on the Merge Request detail page
And I click on the revert button
And I revert the changes directly
Then I should see a revert error
@javascript
Scenario: I revert a merge request in a new merge request
Given I click on the revert button
And I revert the changes in a new merge request
Then I should see the new merge request notice
Feature: Project Milestone
Background:
Given I sign in as a user
And I own project "Shop"
And project "Shop" has labels: "bug", "feature", "enhancement"
And project "Shop" has milestone "v2.2"
And milestone has issue "Bugfix1" with labels: "bug", "feature"
And milestone has issue "Bugfix2" with labels: "bug", "enhancement"
@javascript
Scenario: Listing labels from labels tab
Given I visit project "Shop" milestones page
And I click link "v2.2"
And I click link "Labels"
Then I should see the list of labels
And I should see the labels "bug", "enhancement" and "feature"
Feature: Project Team Management
Background:
Given I sign in as a user
And I own project "Shop"
And gitlab user "Mike"
And gitlab user "Dmitriy"
And "Dmitriy" is "Shop" developer
And I visit project "Shop" team page
Scenario: Cancel team member
Given I click cancel link for "Dmitriy"
Then I visit project "Shop" team page
And I should not see "Dmitriy" in team list
Scenario: Import team from another project
Given I own project "Website"
And "Mike" is "Website" reporter
When I visit project "Shop" team page
And I click link "Import team from another project"
And I submit "Website" project for import team
Then I should see "Mike" in team list as "Reporter"
Scenario: See all members of projects shared group
Given I share project with group "OpenSource"
And I visit project "Shop" team page
Then I should see "Opensource" group user listing
Feature: Project Wiki
Background:
Given I sign in as a user
And I own project "Shop"
Given I visit project wiki page
Scenario: Add new page
Given I create the Wiki Home page
Then I should see the newly created wiki page
Scenario: Add new page with errors
Given I create the Wiki Home page with no content
Then I should see a "Content can't be blank" error message
When I create the Wiki Home page
Then I should see the newly created wiki page
Scenario: Pressing Cancel while editing a brand new Wiki
Given I click on the Cancel button
Then I should be redirected back to the Edit Home Wiki page
Scenario: Edit existing page
Given I have an existing Wiki page
And I browse to that Wiki page
And I click on the Edit button
And I change the content
Then I should see the updated content
Scenario: Pressing Cancel while editing an existing Wiki page
Given I have an existing Wiki page
And I browse to that Wiki page
And I click on the Edit button
And I click on the Cancel button
Then I should be redirected back to that Wiki page
Scenario: View page history
Given I have an existing wiki page
And That page has two revisions
And I browse to that Wiki page
And I click the History button
Then I should see both revisions
Scenario: Destroy Wiki page
Given I have an existing wiki page
And I browse to that Wiki page
And I click on the Edit button
And I click on the "Delete this page" button
Then The page should be deleted
Scenario: View all pages
Given I have an existing wiki page
And I browse to that Wiki page
Then I should see the existing page in the pages list
Scenario: File exists in wiki repo
Given I have an existing Wiki page with images linked on page
And I browse to wiki page with images
And I click on existing image link
Then I should see the image from wiki repo
Scenario: Image in wiki repo shown on the page
Given I have an existing Wiki page with images linked on page
And I browse to wiki page with images
Then Image should be shown on the page
Scenario: File does not exist in wiki repo
Given I have an existing Wiki page with images linked on page
And I browse to wiki page with images
And I click on image link
Then I should see the new wiki page form
@javascript
Scenario: New Wiki page that has a path
Given I create a New page with paths
Then I should see non-escaped link in the pages list
@javascript
Scenario: Edit Wiki page that has a path
Given I create a New page with paths
And I edit the Wiki page with a path
Then I should see a non-escaped path
And I should see the Editing page
And I change the content
Then I should see the updated content
@javascript
Scenario: View the page history of a Wiki page that has a path
Given I create a New page with paths
And I view the page history of a Wiki page that has a path
Then I should see a non-escaped path
And I should see the page history
@javascript
Scenario: View an old page version of a Wiki page
Given I create a New page with paths
And I edit the Wiki page with a path
Then I should see a non-escaped path
And I should see the Editing page
And I change the content
Then I click on Page History
And I should see the page history
And I should see a link with a version ID
class Spinach::Features::ProfileEmails < Spinach::FeatureSteps
include SharedAuthentication
step 'I visit profile emails page' do
visit profile_emails_path
end
step 'I should see my emails' do
expect(page).to have_content(@user.email)
@user.emails.each do |email|
expect(page).to have_content(email.email)
end
end
step 'I submit new email "my@email.com"' do
fill_in "email_email", with: "my@email.com"
click_button "Add"
end
step 'I should see new email "my@email.com"' do
email = @user.emails.find_by(email: "my@email.com")
expect(email).not_to be_nil
expect(page).to have_content("my@email.com")
end
step 'I should not see email "my@email.com"' do
email = @user.emails.find_by(email: "my@email.com")
expect(email).to be_nil
expect(page).not_to have_content("my@email.com")
end
step 'I click link "Remove" for "my@email.com"' do
# there should only be one remove button at this time
click_link "Remove"
# force these to reload as they have been cached
@user.emails.reload
end
step 'I submit duplicate email @user.email' do
fill_in "email_email", with: @user.email
click_button "Add"
end
step 'I should not have @user.email added' do
email = @user.emails.find_by(email: @user.email)
expect(email).to be_nil
end
end
class Spinach::Features::ProjectMergeRequestsAcceptance < Spinach::FeatureSteps
include LoginHelpers
include WaitForRequests
step 'I am on the Merge Request detail page' do
visit merge_request_path(@merge_request)
end
step 'I am on the Merge Request detail with note anchor page' do
visit merge_request_path(@merge_request, anchor: 'note_123')
end
step 'I uncheck the "Remove source branch" option' do
uncheck('Remove source branch')
end
step 'I check the "Remove source branch" option' do
check('Remove source branch')
end
step 'I click on Accept Merge Request' do
click_button('Merge')
end
step 'I should see the Remove Source Branch button' do
expect(page).to have_selector('.js-remove-branch-button')
# Wait for View Resource requests to complete so they don't blow up if they are
# only handled after `DatabaseCleaner` has already run
wait_for_requests
end
step 'I should not see the Remove Source Branch button' do
expect(page).not_to have_selector('.js-remove-branch-button')
# Wait for View Resource requests to complete so they don't blow up if they are
# only handled after `DatabaseCleaner` has already run
wait_for_requests
end
step 'There is an open Merge Request' do
@user = create(:user)
@project = create(:project, :public, :repository)
@project_member = create(:project_member, :developer, user: @user, project: @project)
@merge_request = create(:merge_request, :with_diffs, :simple, source_project: @project)
end
step 'I am signed in as a developer of the project' do
sign_in(@user)
end
step 'I should see merge request merged' do
expect(page).to have_content('The changes were merged into')
end
end
class Spinach::Features::RevertMergeRequests < Spinach::FeatureSteps
include LoginHelpers
include WaitForRequests
step 'I click on the revert button' do
find("a[href='#modal-revert-commit']").click
end
step 'I revert the changes directly' do
page.within('#modal-revert-commit') do
uncheck 'create_merge_request'
click_button 'Revert'
end
end
step 'I should see the revert merge request notice' do
page.should have_content('The merge request has been successfully reverted.')
wait_for_requests
end
step 'I should not see the revert button' do
expect(page).not_to have_selector(:xpath, "a[href='#modal-revert-commit']")
end
step 'I am on the Merge Request detail page' do
visit merge_request_path(@merge_request)
end
step 'I click on Accept Merge Request' do
click_button('Merge')
end
step 'I am signed in as a developer of the project' do
@user = create(:user) { |u| @project.add_developer(u) }
sign_in(@user)
end
step 'There is an open Merge Request' do
@merge_request = create(:merge_request, :with_diffs, :simple)
@project = @merge_request.source_project
end
step 'I should see a revert error' do
page.should have_content('Sorry, we cannot revert this merge request automatically.')
end
step 'I revert the changes in a new merge request' do
page.within('#modal-revert-commit') do
click_button 'Revert'
end
end
step 'I should see the new merge request notice' do
page.should have_content('The merge request has been successfully reverted. You can now submit a merge request to get this change into the original branch.')
end
end
class Spinach::Features::ProjectGroupLinks < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
include Select2Helper
step 'I should see project already shared with group "Ops"' do
page.within '.project-members-groups' do
expect(page).to have_content "Ops"
end
end
step 'I should see project is not shared with group "Market"' do
page.within '.project-members-groups' do
expect(page).not_to have_content "Market"
end
end
step 'I select group "Market" for share' do
click_link 'Share with group'
group = Group.find_by(path: 'market')
select2(group.id, from: "#link_group_id")
select "Master", from: 'link_group_access'
click_button "Share"
end
step 'I should see project is shared with group "Market"' do
page.within '.project-members-groups' do
expect(page).to have_content "Market"
end
end
step 'project "Shop" is shared with group "Ops"' do
group = create(:group, name: 'Ops')
share_link = project.project_group_links.new(group_access: Gitlab::Access::MASTER)
share_link.group_id = group.id
share_link.save!
end
step 'project "Shop" is not shared with group "Market"' do
create(:group, name: 'Market', path: 'market')
end
step 'I visit project group links page' do
visit project_group_links_path(project)
end
def project
@project ||= Project.find_by_name "Shop"
end
end
class Spinach::Features::ProjectMilestone < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
include WaitForRequests
step 'milestone has issue "Bugfix1" with labels: "bug", "feature"' do
project = Project.find_by(name: "Shop")
milestone = project.milestones.find_by(title: 'v2.2')
issue = create(:issue, title: "Bugfix1", project: project, milestone: milestone)
issue.labels << project.labels.find_by(title: 'bug')
issue.labels << project.labels.find_by(title: 'feature')
end
step 'milestone has issue "Bugfix2" with labels: "bug", "enhancement"' do
project = Project.find_by(name: "Shop")
milestone = project.milestones.find_by(title: 'v2.2')
issue = create(:issue, title: "Bugfix2", project: project, milestone: milestone)
issue.labels << project.labels.find_by(title: 'bug')
issue.labels << project.labels.find_by(title: 'enhancement')
end
step 'project "Shop" has milestone "v2.2"' do
project = Project.find_by(name: "Shop")
milestone = create(:milestone,
title: "v2.2",
project: project,
description: "# Description header"
)
3.times { create(:issue, project: project, milestone: milestone) }
end
step 'I should see the list of labels' do
expect(page).to have_selector('ul.manage-labels-list')
end
step 'I should see the labels "bug", "enhancement" and "feature"' do
wait_for_requests
page.within('#tab-issues') do
expect(page).to have_content 'bug'
expect(page).to have_content 'enhancement'
expect(page).to have_content 'feature'
end
end
step 'I should see the "bug" label listed only once' do
page.within('#tab-labels') do
expect(page).to have_content('bug', count: 1)
end
end
step 'I click link "v2.2"' do
click_link "v2.2"
end
step 'I click link "Labels"' do
page.within('.nav-sidebar') do
page.find(:xpath, "//a[@href='#tab-labels']").click
end
end
end
class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
include Select2Helper
step 'I should not see "Dmitriy" in team list' do
user = User.find_by(name: "Dmitriy")
expect(page).not_to have_content(user.name)
expect(page).not_to have_content(user.username)
end
step 'I should see "Mike" in team list as "Reporter"' do
user = User.find_by(name: 'Mike')
project_member = project.project_members.find_by(user_id: user.id)
page.within "#project_member_#{project_member.id}" do
expect(page).to have_content('Mike')
expect(page).to have_content('Reporter')
end
end
step 'gitlab user "Mike"' do
create(:user, name: "Mike")
end
step 'gitlab user "Dmitriy"' do
create(:user, name: "Dmitriy")
end
step '"Dmitriy" is "Shop" developer' do
user = User.find_by(name: "Dmitriy")
project = Project.find_by(name: "Shop")
project.team << [user, :developer]
end
step 'I own project "Website"' do
@project = create(:project, name: "Website", namespace: @user.namespace)
@project.team << [@user, :master]
end
step '"Mike" is "Website" reporter' do
user = User.find_by(name: "Mike")
project = Project.find_by(name: "Website")
project.team << [user, :reporter]
end
step 'I click link "Import team from another project"' do
page.within '.users-project-form' do
click_link "Import"
end
end
When 'I submit "Website" project for import team' do
project = Project.find_by(name: "Website")
select project.name_with_namespace, from: 'source_project_id'
click_button 'Import'
end
step 'I click cancel link for "Dmitriy"' do
project = Project.find_by(name: "Shop")
user = User.find_by(name: 'Dmitriy')
project_member = project.project_members.find_by(user_id: user.id)
page.within "#project_member_#{project_member.id}" do
click_link('Remove user from project')
end
end
step 'I share project with group "OpenSource"' do
project = Project.find_by(name: 'Shop')
os_group = create(:group, name: 'OpenSource')
create(:project, group: os_group)
@os_user1 = create(:user)
@os_user2 = create(:user)
os_group.add_owner(@os_user1)
os_group.add_user(@os_user2, Gitlab::Access::DEVELOPER)
share_link = project.project_group_links.new(group_access: Gitlab::Access::MASTER)
share_link.group_id = os_group.id
share_link.save!
end
step 'I should see "Opensource" group user listing' do
page.within '.project-members-groups' do
expect(page).to have_content('OpenSource')
expect(first('.group_member')).to have_content('Master')
end
end
end
class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedNote
include SharedPaths
step 'I click on the Cancel button' do
page.within(:css, ".wiki-form .form-actions") do
click_on "Cancel"
end
end
step 'I should be redirected back to the Edit Home Wiki page' do
expect(current_path).to eq project_wiki_path(project, :home)
end
step 'I create the Wiki Home page' do
fill_in "wiki_content", with: '[link test](test)'
page.within '.wiki-form' do
click_on "Create page"
end
end
step 'I create the Wiki Home page with no content' do
fill_in "wiki_content", with: ''
page.within '.wiki-form' do
click_on "Create page"
end
end
step 'I should see the newly created wiki page' do
expect(page).to have_content "Home"
expect(page).to have_content "link test"
click_link "link test"
expect(page).to have_content "Create page"
end
step 'I have an existing Wiki page' do
wiki.create_page("existing", "content", :markdown, "first commit")
@page = wiki.find_page("existing")
end
step 'I browse to that Wiki page' do
visit project_wiki_path(project, @page)
end
step 'I click on the Edit button' do
click_on "Edit"
end
step 'I change the content' do
fill_in "Content", with: 'Updated Wiki Content'
click_on "Save changes"
end
step 'I should see the updated content' do
expect(page).to have_content "Updated Wiki Content"
end
step 'I should be redirected back to that Wiki page' do
expect(current_path).to eq project_wiki_path(project, @page)
end
step 'That page has two revisions' do
@page.update(content: "new content", message: "second commit")
end
step 'I click the History button' do
click_on 'Page history'
end
step 'I should see both revisions' do
expect(page).to have_content current_user.name
expect(page).to have_content "first commit"
expect(page).to have_content "second commit"
end
step 'I click on the "Delete this page" button' do
click_on "Delete"
end
step 'The page should be deleted' do
expect(page).to have_content "Page was successfully deleted"
end
step 'I should see the existing page in the pages list' do
expect(page).to have_content current_user.name
expect(find('.wiki-pages')).to have_content @page.title.capitalize
end
step 'I have an existing Wiki page with images linked on page' do
wiki.create_page("pictures", "Look at this [image](image.jpg)\n\n ![alt text](image.jpg)", :markdown, "first commit")
@wiki_page = wiki.find_page("pictures")
end
step 'I browse to wiki page with images' do
visit project_wiki_path(project, @wiki_page)
end
step 'I click on existing image link' do
file = Gollum::File.new(wiki.wiki)
Gollum::Wiki.any_instance.stub(:file).with("image.jpg", "master", true).and_return(file)
Gollum::File.any_instance.stub(:mime_type).and_return("image/jpeg")
expect(page).to have_link('image', href: "#{wiki.wiki_base_path}/image.jpg")
click_on "image"
end
step 'I should see the image from wiki repo' do
expect(current_path).to match('wikis/image.jpg')
expect(page).not_to have_xpath('/html') # Page should render the image which means there is no html involved
Gollum::Wiki.any_instance.unstub(:file)
Gollum::File.any_instance.unstub(:mime_type)
end
step 'Image should be shown on the page' do
expect(page).to have_xpath("//img[@data-src=\"image.jpg\"]")
end
step 'I click on image link' do
expect(page).to have_link('image', href: "#{wiki.wiki_base_path}/image.jpg")
click_on "image"
end
step 'I should see the new wiki page form' do
expect(current_path).to match('wikis/image.jpg')
expect(page).to have_content('New Wiki Page')
expect(page).to have_content('Create page')
end
step 'I create a New page with paths' do
click_on 'New page'
fill_in 'Page slug', with: 'one/two/three-test'
page.within '#modal-new-wiki' do
click_on 'Create page'
end
fill_in "wiki_content", with: 'wiki content'
page.within '.wiki-form' do
click_on "Create page"
end
expect(current_path).to include 'one/two/three-test'
end
step 'I should see non-escaped link in the pages list' do
expect(page).to have_xpath("//a[@href='/#{project.full_path}/wikis/one/two/three-test']")
end
step 'I edit the Wiki page with a path' do
expect(find('.wiki-pages')).to have_content('Three')
click_on 'Three'
expect(find('.nav-text')).to have_content('Three')
click_on 'Edit'
end
step 'I should see a non-escaped path' do
expect(current_path).to include 'one/two/three-test'
end
step 'I should see the Editing page' do
expect(page).to have_content('Edit Page')
end
step 'I view the page history of a Wiki page that has a path' do
click_on 'Three'
click_on 'Page history'
end
step 'I click on Page History' do
click_on 'Page history'
end
step 'I should see the page history' do
page.within(:css, ".nav-text") do
expect(page).to have_content('History')
end
end
step 'I search for Wiki content' do
fill_in "Search", with: "wiki_content"
click_button "Search"
end
step 'I should see a link with a version ID' do
find('a[href*="?version_id"]')
end
step 'I should see a "Content can\'t be blank" error message' do
expect(page).to have_content('The form contains the following error:')
expect(page).to have_content('Content can\'t be blank')
end
def wiki
@project_wiki = ProjectWiki.new(project, current_user)
end
end
...@@ -121,10 +121,10 @@ module Gitlab ...@@ -121,10 +121,10 @@ module Gitlab
] ]
end end
def send_artifacts_entry(build, path) def send_artifacts_entry(build, entry)
params = { params = {
'Archive' => build.artifacts_file.path, 'Archive' => build.artifacts_file.path,
'Entry' => Base64.encode64(path.to_s) 'Entry' => Base64.encode64(entry.to_s)
} }
[ [
......
...@@ -13,7 +13,7 @@ describe 'Issue Boards', js: true do ...@@ -13,7 +13,7 @@ describe 'Issue Boards', js: true do
project.team << [user, :master] project.team << [user, :master]
project.team << [user2, :master] project.team << [user2, :master]
allow_any_instance_of(ApplicationHelper).to receive(:collapsed_sidebar?).and_return(true) page.driver.set_cookie('sidebar_collapsed', 'true')
sign_in(user) sign_in(user)
end end
......
...@@ -83,12 +83,14 @@ feature 'Dashboard Projects' do ...@@ -83,12 +83,14 @@ feature 'Dashboard Projects' do
end end
end end
context 'last push widget' do context 'last push widget', :use_clean_rails_memory_store_caching do
before do before do
event = create(:push_event, project: project, author: user) event = create(:push_event, project: project, author: user)
create(:push_event_payload, event: event, ref: 'feature', action: :created) create(:push_event_payload, event: event, ref: 'feature', action: :created)
Users::LastPushEventService.new(user).cache_last_push_event(event)
visit dashboard_projects_path visit dashboard_projects_path
end end
......
...@@ -28,7 +28,7 @@ describe 'Visual tokens', js: true do ...@@ -28,7 +28,7 @@ describe 'Visual tokens', js: true do
sign_in(user) sign_in(user)
create(:issue, project: project) create(:issue, project: project)
allow_any_instance_of(ApplicationHelper).to receive(:collapsed_sidebar?).and_return(true) page.driver.set_cookie('sidebar_collapsed', 'true')
visit project_issues_path(project) visit project_issues_path(project)
end end
......
...@@ -22,7 +22,7 @@ feature 'Diff note avatars', js: true do ...@@ -22,7 +22,7 @@ feature 'Diff note avatars', js: true do
project.team << [user, :master] project.team << [user, :master]
sign_in user sign_in user
allow_any_instance_of(ApplicationHelper).to receive(:collapsed_sidebar?).and_return(true) page.driver.set_cookie('sidebar_collapsed', 'true')
end end
context 'discussion tab' do context 'discussion tab' do
......
...@@ -6,7 +6,7 @@ feature 'Merge requests > User posts diff notes', :js do ...@@ -6,7 +6,7 @@ feature 'Merge requests > User posts diff notes', :js do
let(:project) { merge_request.source_project } let(:project) { merge_request.source_project }
before do before do
allow_any_instance_of(ApplicationHelper).to receive(:collapsed_sidebar?).and_return(true) page.driver.set_cookie('sidebar_collapsed', 'true')
project.add_developer(user) project.add_developer(user)
sign_in(user) sign_in(user)
......
require 'spec_helper'
describe 'User manages emails' do
let(:user) { create(:user) }
before do
sign_in(user)
visit(profile_emails_path)
end
it "shows user's emails" do
expect(page).to have_content(user.email)
user.emails.each do |email|
expect(page).to have_content(email.email)
end
end
it 'adds an email' do
fill_in('email_email', with: 'my@email.com')
click_button('Add')
email = user.emails.find_by(email: 'my@email.com')
expect(email).not_to be_nil
expect(page).to have_content('my@email.com')
expect(page).to have_content(user.email)
user.emails.each do |email|
expect(page).to have_content(email.email)
end
end
it 'does not add a duplicate email' do
fill_in('email_email', with: user.email)
click_button('Add')
email = user.emails.find_by(email: user.email)
expect(email).to be_nil
expect(page).to have_content(user.email)
user.emails.each do |email|
expect(page).to have_content(email.email)
end
end
it 'removes an email' do
fill_in('email_email', with: 'my@email.com')
click_button('Add')
email = user.emails.find_by(email: 'my@email.com')
expect(email).not_to be_nil
expect(page).to have_content('my@email.com')
expect(page).to have_content(user.email)
user.emails.each do |email|
expect(page).to have_content(email.email)
end
# There should be only one remove button at this time
click_link('Remove')
# Force these to reload as they have been cached
user.emails.reload
email = user.emails.find_by(email: 'my@email.com')
expect(email).to be_nil
expect(page).not_to have_content('my@email.com')
expect(page).to have_content(user.email)
user.emails.each do |email|
expect(page).to have_content(email.email)
end
end
end
require 'spec_helper'
describe 'User accepts a merge request', :js do
let(:merge_request) { create(:merge_request, :with_diffs, :simple, source_project: project) }
let(:project) { create(:project, :public, :repository) }
let(:user) { create(:user) }
before do
project.add_developer(user)
sign_in(user)
end
context 'with removing the source branch' do
before do
visit(merge_request_path(merge_request))
end
it 'accepts a merge request' do
check('Remove source branch')
click_button('Merge')
expect(page).to have_content('The changes were merged into')
expect(page).not_to have_selector('.js-remove-branch-button')
# Wait for View Resource requests to complete so they don't blow up if they are
# only handled after `DatabaseCleaner` has already run.
wait_for_requests
end
end
context 'without removing the source branch' do
before do
visit(merge_request_path(merge_request))
end
it 'accepts a merge request' do
click_button('Merge')
expect(page).to have_content('The changes were merged into')
expect(page).to have_selector('.js-remove-branch-button')
# Wait for View Resource requests to complete so they don't blow up if they are
# only handled after `DatabaseCleaner` has already run
wait_for_requests
end
end
context 'when a URL has an anchor' do
before do
visit(merge_request_path(merge_request, anchor: 'note_123'))
end
it 'accepts a merge request' do
check('Remove source branch')
click_button('Merge')
expect(page).to have_content('The changes were merged into')
expect(page).not_to have_selector('.js-remove-branch-button')
# Wait for View Resource requests to complete so they don't blow up if they are
# only handled after `DatabaseCleaner` has already run
wait_for_requests
end
end
end
require 'spec_helper'
describe 'User reverts a merge request', :js do
let(:merge_request) { create(:merge_request, :with_diffs, :simple, source_project: project) }
let(:project) { create(:project, :public, :repository) }
let(:user) { create(:user) }
before do
project.add_developer(user)
sign_in(user)
visit(merge_request_path(merge_request))
click_button('Merge')
visit(merge_request_path(merge_request))
end
it 'reverts a merge request' do
find("a[href='#modal-revert-commit']").click
page.within('#modal-revert-commit') do
uncheck('create_merge_request')
click_button('Revert')
end
expect(page).to have_content('The merge request has been successfully reverted.')
wait_for_requests
end
it 'does not revert a merge request that was previously reverted' do
find("a[href='#modal-revert-commit']").click
page.within('#modal-revert-commit') do
uncheck('create_merge_request')
click_button('Revert')
end
find("a[href='#modal-revert-commit']").click
page.within('#modal-revert-commit') do
uncheck('create_merge_request')
click_button('Revert')
end
expect(page).to have_content('Sorry, we cannot revert this merge request automatically.')
end
it 'reverts a merge request in a new merge request' do
find("a[href='#modal-revert-commit']").click
page.within('#modal-revert-commit') do
click_button('Revert')
end
expect(page).to have_content('The merge request has been successfully reverted. You can now submit a merge request to get this change into the original branch.')
end
end
require 'spec_helper'
describe 'User interacts with labels' do
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
let(:milestone) { create(:milestone, project: project, title: 'v2.2', description: '# Description header') }
let(:issue1) { create(:issue, project: project, title: 'Bugfix1', milestone: milestone) }
let(:issue2) { create(:issue, project: project, title: 'Bugfix2', milestone: milestone) }
let(:label_bug) { create(:label, project: project, title: 'bug') }
let(:label_feature) { create(:label, project: project, title: 'feature') }
let(:label_enhancement) { create(:label, project: project, title: 'enhancement') }
before do
project.add_master(user)
sign_in(user)
issue1.labels << [label_bug, label_feature]
issue2.labels << [label_bug, label_enhancement]
visit(project_milestones_path(project))
end
it 'shows the list of labels', :js do
click_link('v2.2')
page.within('.nav-sidebar') do
page.find(:xpath, "//a[@href='#tab-labels']").click
end
expect(page).to have_selector('ul.manage-labels-list')
wait_for_requests
page.within('#tab-labels') do
expect(page).to have_content(label_bug.title)
expect(page).to have_content(label_enhancement.title)
expect(page).to have_content(label_feature.title)
end
end
end
require 'spec_helper'
describe 'User manages group links' do
include Select2Helper
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
let(:group_ops) { create(:group, name: 'Ops') }
let(:group_market) { create(:group, name: 'Market', path: 'market') }
before do
project.add_master(user)
sign_in(user)
share_link = project.project_group_links.new(group_access: Gitlab::Access::MASTER)
share_link.group_id = group_ops.id
share_link.save!
visit(project_group_links_path(project))
end
it 'shows a list of groups' do
page.within('.project-members-groups') do
expect(page).to have_content('Ops')
expect(page).not_to have_content('Market')
end
end
it 'shares a project with a group', :js do
click_link('Share with group')
select2(group_market.id, from: '#link_group_id')
select('Master', from: 'link_group_access')
click_button('Share')
page.within('.project-members-groups') do
expect(page).to have_content('Market')
end
end
end
require 'spec_helper'
describe 'User manages project members' do
let(:group) { create(:group, name: 'OpenSource') }
let(:project) { create(:project) }
let(:project2) { create(:project) }
let(:user) { create(:user) }
let(:user_dmitriy) { create(:user, name: 'Dmitriy') }
let(:user_mike) { create(:user, name: 'Mike') }
before do
project.add_master(user)
project.add_developer(user_dmitriy)
sign_in(user)
end
it 'cancels a team member' do
visit(project_project_members_path(project))
project_member = project.project_members.find_by(user_id: user_dmitriy.id)
page.within("#project_member_#{project_member.id}") do
click_link('Remove user from project')
end
visit(project_project_members_path(project))
expect(page).not_to have_content(user_dmitriy.name)
expect(page).not_to have_content(user_dmitriy.username)
end
it 'imports a team from another project' do
project2.add_master(user)
project2.add_reporter(user_mike)
visit(project_project_members_path(project))
page.within('.users-project-form') do
click_link('Import')
end
select(project2.name_with_namespace, from: 'source_project_id')
click_button('Import')
project_member = project.project_members.find_by(user_id: user_mike.id)
page.within("#project_member_#{project_member.id}") do
expect(page).to have_content('Mike')
expect(page).to have_content('Reporter')
end
end
it 'shows all members of project shared group' do
group.add_owner(user)
group.add_developer(user_dmitriy)
share_link = project.project_group_links.new(group_access: Gitlab::Access::MASTER)
share_link.group_id = group.id
share_link.save!
visit(project_project_members_path(project))
page.within('.project-members-groups') do
expect(page).to have_content('OpenSource')
expect(first('.group_member')).to have_content('Master')
end
end
end
require 'spec_helper'
feature 'User deletes wiki page' do
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
let(:wiki_page) { create(:wiki_page, wiki: project.wiki) }
before do
sign_in(user)
visit(project_wiki_path(project, wiki_page))
end
it 'deletes a page' do
click_on('Edit')
click_on('Delete')
expect(page).to have_content('Page was successfully deleted')
end
end
require 'spec_helper' require 'spec_helper'
feature 'Projects > Wiki > User updates wiki page' do describe 'User updates wiki page' do
let(:user) { create(:user) } let(:user) { create(:user) }
let!(:wiki_page) { WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute }
background do before do
project.team << [user, :master] project.add_master(user)
sign_in(user) sign_in(user)
end
context 'when wiki is empty' do
before do
visit(project_wikis_path(project))
end
context 'in a user namespace' do
let(:project) { create(:project, namespace: user.namespace) }
it 'redirects back to the home edit page' do
page.within(:css, '.wiki-form .form-actions') do
click_on('Cancel')
end
expect(current_path).to eq project_wiki_path(project, :home)
end
it 'updates a page that has a path', :js do
click_on('New page')
page.within('#modal-new-wiki') do
fill_in(:new_wiki_path, with: 'one/two/three-test')
click_on('Create page')
end
page.within '.wiki-form' do
fill_in(:wiki_content, with: 'wiki content')
click_on('Create page')
end
visit project_wikis_path(project) expect(current_path).to include('one/two/three-test')
expect(find('.wiki-pages')).to have_content('Three')
click_on('Three')
expect(find('.nav-text')).to have_content('Three')
click_on('Edit')
expect(current_path).to include('one/two/three-test')
expect(page).to have_content('Edit Page')
fill_in('Content', with: 'Updated Wiki Content')
click_on('Save changes')
expect(page).to have_content('Updated Wiki Content')
end
end
end end
context 'in the user namespace' do context 'when wiki is not empty' do
let(:project) { create(:project, namespace: user.namespace) } let(:project_wiki) { create(:project_wiki, project: project, user: project.creator) }
let!(:wiki_page) { create(:wiki_page, wiki: project_wiki, attrs: { title: 'home', content: 'Home page' }) }
context 'the home page' do before do
scenario 'success when the wiki content is not empty' do visit(project_wikis_path(project))
click_link 'Edit' end
context 'in a user namespace' do
let(:project) { create(:project, namespace: user.namespace) }
it 'updates a page' do
click_link('Edit')
# Commit message field should have correct value. # Commit message field should have correct value.
expect(page).to have_field('wiki[message]', with: 'Update home') expect(page).to have_field('wiki[message]', with: 'Update home')
fill_in :wiki_content, with: 'My awesome wiki!' fill_in(:wiki_content, with: 'My awesome wiki!')
click_button 'Save changes' click_button('Save changes')
expect(page).to have_content('Home') expect(page).to have_content('Home')
expect(page).to have_content("Last edited by #{user.name}") expect(page).to have_content("Last edited by #{user.name}")
expect(page).to have_content('My awesome wiki!') expect(page).to have_content('My awesome wiki!')
end end
scenario 'failure when the wiki content is empty' do it 'shows a validation error message' do
click_link 'Edit' click_link('Edit')
fill_in :wiki_content, with: '' fill_in(:wiki_content, with: '')
click_button 'Save changes' click_button('Save changes')
expect(page).to have_selector('.wiki-form') expect(page).to have_selector('.wiki-form')
expect(page).to have_content('Edit Page') expect(page).to have_content('Edit Page')
expect(page).to have_content('The form contains the following error:') expect(page).to have_content('The form contains the following error:')
expect(page).to have_content('Content can\'t be blank') expect(page).to have_content("Content can't be blank")
expect(find('textarea#wiki_content').value).to eq '' expect(find('textarea#wiki_content').value).to eq('')
end end
scenario 'content has autocomplete', :js do it 'shows the autocompletion dropdown', :js do
click_link 'Edit' click_link('Edit')
find('#wiki_content').native.send_keys('') find('#wiki_content').native.send_keys('')
fill_in :wiki_content, with: '@' fill_in(:wiki_content, with: '@')
expect(page).to have_selector('.atwho-view') expect(page).to have_selector('.atwho-view')
end end
end
scenario 'page has been updated since the user opened the edit page' do it 'shows the error message' do
click_link 'Edit' click_link('Edit')
wiki_page.update(content: 'Update')
wiki_page.update(content: 'Update') click_button('Save changes')
expect(page).to have_content('Someone edited the page the same time you did.')
end
it 'updates a page' do
click_on('Edit')
fill_in('Content', with: 'Updated Wiki Content')
click_on('Save changes')
expect(page).to have_content('Updated Wiki Content')
end
click_button 'Save changes' it 'cancels edititng of a page' do
click_on('Edit')
expect(page).to have_content 'Someone edited the page the same time you did.' page.within(:css, '.wiki-form .form-actions') do
click_on('Cancel')
end
expect(current_path).to eq(project_wiki_path(project, wiki_page))
end
end end
end
context 'in a group namespace' do context 'in a group namespace' do
let(:project) { create(:project, namespace: create(:group, :public)) } let(:project) { create(:project, namespace: create(:group, :public)) }
scenario 'the home page' do it 'updates a page' do
click_link 'Edit' click_link('Edit')
# Commit message field should have correct value. # Commit message field should have correct value.
expect(page).to have_field('wiki[message]', with: 'Update home') expect(page).to have_field('wiki[message]', with: 'Update home')
fill_in :wiki_content, with: 'My awesome wiki!' fill_in(:wiki_content, with: 'My awesome wiki!')
click_button 'Save changes' click_button('Save changes')
expect(page).to have_content('Home') expect(page).to have_content('Home')
expect(page).to have_content("Last edited by #{user.name}") expect(page).to have_content("Last edited by #{user.name}")
expect(page).to have_content('My awesome wiki!') expect(page).to have_content('My awesome wiki!')
end
end end
end end
end end
require 'spec_helper'
feature 'Projects > Wiki > User views the wiki page' do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
let(:old_page_version_id) { wiki_page.versions.last.id }
let(:wiki_page) do
WikiPages::CreateService.new(
project,
user,
title: 'home',
content: '[some link](other-page)'
).execute
end
background do
project.team << [user, :master]
sign_in(user)
WikiPages::UpdateService.new(
project,
user,
message: 'updated home',
content: 'updated [some link](other-page)',
format: :markdown
).execute(wiki_page)
end
scenario 'Visit Wiki Page Current Commit' do
visit project_wiki_path(project, wiki_page)
expect(page).to have_selector('a.btn', text: 'Edit')
end
scenario 'Visit Wiki Page Historical Commit' do
visit project_wiki_path(project, wiki_page, version_id: old_page_version_id)
expect(page).not_to have_selector('a.btn', text: 'Edit')
end
end
require 'spec_helper'
describe 'User views a wiki page' do
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
let(:wiki_page) do
create(:wiki_page,
wiki: project.wiki,
attrs: { title: 'home', content: 'Look at this [image](image.jpg)\n\n ![alt text](image.jpg)' })
end
before do
project.add_master(user)
sign_in(user)
end
context 'when wiki is empty' do
before do
visit(project_wikis_path(project))
click_on('New page')
page.within('#modal-new-wiki') do
fill_in(:new_wiki_path, with: 'one/two/three-test')
click_on('Create page')
end
page.within('.wiki-form') do
fill_in(:wiki_content, with: 'wiki content')
click_on('Create page')
end
end
it 'shows the history of a page that has a path', :js do
expect(current_path).to include('one/two/three-test')
click_on('Three')
click_on('Page history')
expect(current_path).to include('one/two/three-test')
page.within(:css, '.nav-text') do
expect(page).to have_content('History')
end
end
it 'shows an old version of a page', :js do
expect(current_path).to include('one/two/three-test')
expect(find('.wiki-pages')).to have_content('Three')
click_on('Three')
expect(find('.nav-text')).to have_content('Three')
click_on('Edit')
expect(current_path).to include('one/two/three-test')
expect(page).to have_content('Edit Page')
fill_in('Content', with: 'Updated Wiki Content')
click_on('Save changes')
click_on('Page history')
page.within(:css, '.nav-text') do
expect(page).to have_content('History')
end
find('a[href*="?version_id"]')
end
end
context 'when a page does not have history' do
before do
visit(project_wiki_path(project, wiki_page))
end
it 'shows all the pages' do
expect(page).to have_content(user.name)
expect(find('.wiki-pages')).to have_content(wiki_page.title.capitalize)
end
it 'shows a file stored in a page' do
file = Gollum::File.new(project.wiki)
allow_any_instance_of(Gollum::Wiki).to receive(:file).with('image.jpg', 'master', true).and_return(file)
allow_any_instance_of(Gollum::File).to receive(:mime_type).and_return('image/jpeg')
expect(page).to have_xpath('//img[@data-src="image.jpg"]')
expect(page).to have_link('image', href: "#{project.wiki.wiki_base_path}/image.jpg")
click_on('image')
expect(current_path).to match('wikis/image.jpg')
expect(page).not_to have_xpath('/html') # Page should render the image which means there is no html involved
end
it 'shows the creation page if file does not exist' do
expect(page).to have_link('image', href: "#{project.wiki.wiki_base_path}/image.jpg")
click_on('image')
expect(current_path).to match('wikis/image.jpg')
expect(page).to have_content('New Wiki Page')
expect(page).to have_content('Create page')
end
end
context 'when a page has history' do
before do
wiki_page.update(message: 'updated home', content: 'updated [some link](other-page)')
end
it 'shows the page history' do
visit(project_wiki_path(project, wiki_page))
expect(page).to have_selector('a.btn', text: 'Edit')
click_on('Page history')
expect(page).to have_content(user.name)
expect(page).to have_content("#{user.username} created page: home")
expect(page).to have_content('updated home')
end
it 'does not show the "Edit" button' do
visit(project_wiki_path(project, wiki_page, version_id: wiki_page.versions.last.id))
expect(page).not_to have_selector('a.btn', text: 'Edit')
end
end
it 'opens a default wiki page', :js do
visit(project_path(project))
find('.shortcuts-wiki').trigger('click')
expect(page).to have_content('Home · Create Page')
end
end
...@@ -313,23 +313,10 @@ describe ProjectsHelper do ...@@ -313,23 +313,10 @@ describe ProjectsHelper do
it 'returns recent push on the current project' do it 'returns recent push on the current project' do
event = double(:event) event = double(:event)
expect(user).to receive(:recent_push).with([project.id]).and_return(event) expect(user).to receive(:recent_push).with(project).and_return(event)
expect(helper.last_push_event).to eq(event) expect(helper.last_push_event).to eq(event)
end end
context 'when current user has a fork of the current project' do
let(:fork) { double(:fork, id: 2) }
it 'returns recent push considering fork events' do
expect(user).to receive(:fork_of).with(project).and_return(fork)
event_on_fork = double(:event)
expect(user).to receive(:recent_push).with([project.id, fork.id]).and_return(event_on_fork)
expect(helper.last_push_event).to eq(event_on_fork)
end
end
end end
describe "#project_feature_access_select" do describe "#project_feature_access_select" do
......
...@@ -42,7 +42,6 @@ describe('Issuable output', () => { ...@@ -42,7 +42,6 @@ describe('Issuable output', () => {
initialDescriptionText: '', initialDescriptionText: '',
markdownPreviewPath: '/', markdownPreviewPath: '/',
markdownDocsPath: '/', markdownDocsPath: '/',
isConfidential: false,
projectNamespace: '/', projectNamespace: '/',
projectPath: '/', projectPath: '/',
}, },
...@@ -157,30 +156,6 @@ describe('Issuable output', () => { ...@@ -157,30 +156,6 @@ describe('Issuable output', () => {
}); });
}); });
it('reloads the page if the confidential status has changed', (done) => {
spyOn(gl.utils, 'visitUrl');
spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => {
resolve({
json() {
return {
confidential: true,
web_url: location.pathname,
};
},
});
}));
vm.updateIssuable();
setTimeout(() => {
expect(
gl.utils.visitUrl,
).toHaveBeenCalledWith(location.pathname);
done();
});
});
it('correctly updates issuable data', (done) => { it('correctly updates issuable data', (done) => {
spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => { spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => {
resolve(); resolve();
......
...@@ -11,6 +11,94 @@ describe PushEvent do ...@@ -11,6 +11,94 @@ describe PushEvent do
event event
end end
describe '.created_or_pushed' do
let(:event1) { create(:push_event) }
let(:event2) { create(:push_event) }
let(:event3) { create(:push_event) }
before do
create(:push_event_payload, event: event1, action: :pushed)
create(:push_event_payload, event: event2, action: :created)
create(:push_event_payload, event: event3, action: :removed)
end
let(:relation) { described_class.created_or_pushed }
it 'includes events for pushing to existing refs' do
expect(relation).to include(event1)
end
it 'includes events for creating new refs' do
expect(relation).to include(event2)
end
it 'does not include events for removing refs' do
expect(relation).not_to include(event3)
end
end
describe '.branch_events' do
let(:event1) { create(:push_event) }
let(:event2) { create(:push_event) }
before do
create(:push_event_payload, event: event1, ref_type: :branch)
create(:push_event_payload, event: event2, ref_type: :tag)
end
let(:relation) { described_class.branch_events }
it 'includes events for branches' do
expect(relation).to include(event1)
end
it 'does not include events for tags' do
expect(relation).not_to include(event2)
end
end
describe '.without_existing_merge_requests' do
let(:project) { create(:project, :repository) }
let(:event1) { create(:push_event, project: project) }
let(:event2) { create(:push_event, project: project) }
let(:event3) { create(:push_event, project: project) }
let(:event4) { create(:push_event, project: project) }
before do
create(:push_event_payload, event: event1, ref: 'foo', action: :created)
create(:push_event_payload, event: event2, ref: 'bar', action: :created)
create(:push_event_payload, event: event3, ref: 'baz', action: :removed)
create(:push_event_payload, event: event4, ref: 'baz', ref_type: :tag)
project.repository.create_branch('bar', 'master')
create(
:merge_request,
source_project: project,
target_project: project,
source_branch: 'bar'
)
end
let(:relation) { described_class.without_existing_merge_requests }
it 'includes events that do not have a corresponding merge request' do
expect(relation).to include(event1)
end
it 'does not include events that have a corresponding merge request' do
expect(relation).not_to include(event2)
end
it 'does not include events for removed refs' do
expect(relation).not_to include(event3)
end
it 'does not include events for pushing to tags' do
expect(relation).not_to include(event4)
end
end
describe '.sti_name' do describe '.sti_name' do
it 'returns Event::PUSHED' do it 'returns Event::PUSHED' do
expect(described_class.sti_name).to eq(Event::PUSHED) expect(described_class.sti_name).to eq(Event::PUSHED)
......
...@@ -1347,56 +1347,24 @@ describe User do ...@@ -1347,56 +1347,24 @@ describe User do
end end
describe "#recent_push" do describe "#recent_push" do
subject { create(:user) } let(:user) { build(:user) }
let!(:project1) { create(:project, :repository) } let(:project) { build(:project) }
let!(:project2) { create(:project, :repository, forked_from_project: project1) } let(:event) { build(:push_event) }
let!(:push_event) do
event = create(:push_event, project: project2, author: subject)
create(:push_event_payload,
event: event,
commit_to: '1cf19a015df3523caf0a1f9d40c98a267d6a2fc2',
commit_count: 0,
ref: 'master')
event
end
before do
project1.team << [subject, :master]
project2.team << [subject, :master]
end
it "includes push event" do
expect(subject.recent_push).to eq(push_event)
end
it "excludes push event if branch has been deleted" do
allow_any_instance_of(Repository).to receive(:branch_exists?).with('master').and_return(false)
expect(subject.recent_push).to eq(nil)
end
it "excludes push event if MR is opened for it" do it 'returns the last push event for the user' do
create(:merge_request, source_project: project2, target_project: project1, source_branch: project2.default_branch, target_branch: 'fix', author: subject) expect_any_instance_of(Users::LastPushEventService)
.to receive(:last_event_for_user)
.and_return(event)
expect(subject.recent_push).to eq(nil) expect(user.recent_push).to eq(event)
end end
it "includes push events on any of the provided projects" do it 'returns the last push event for a project when one is given' do
expect(subject.recent_push(project1)).to eq(nil) expect_any_instance_of(Users::LastPushEventService)
expect(subject.recent_push(project2)).to eq(push_event) .to receive(:last_event_for_project)
.and_return(event)
push_event1 = create(:push_event, project: project1, author: subject)
create(:push_event_payload,
event: push_event1,
commit_to: '1cf19a015df3523caf0a1f9d40c98a267d6a2fc2',
commit_count: 0,
ref: 'master')
expect(subject.recent_push([project1, project2])).to eq(push_event1) # Newest expect(user.recent_push(project)).to eq(event)
end end
end end
......
...@@ -149,6 +149,14 @@ describe EventCreateService do ...@@ -149,6 +149,14 @@ describe EventCreateService do
.to change { user_activity(user) } .to change { user_activity(user) }
end end
it 'caches the last push event for the user' do
expect_any_instance_of(Users::LastPushEventService)
.to receive(:cache_last_push_event)
.with(an_instance_of(PushEvent))
service.push(project, user, push_data)
end
it 'does not create any event data when an error is raised' do it 'does not create any event data when an error is raised' do
payload_service = double(:service) payload_service = double(:service)
......
require 'spec_helper'
describe Users::LastPushEventService do
let(:user) { build(:user, id: 1) }
let(:project) { build(:project, id: 2) }
let(:event) { build(:push_event, id: 3, author: user, project: project) }
let(:service) { described_class.new(user) }
describe '#cache_last_push_event' do
it "caches the event for the event's project and current user" do
expect(service).to receive(:set_key)
.ordered
.with('last-push-event/1/2', 3)
expect(service).to receive(:set_key)
.ordered
.with('last-push-event/1', 3)
service.cache_last_push_event(event)
end
it 'caches the event for the origin project when pushing to a fork' do
source = build(:project, id: 5)
allow(project).to receive(:forked?).and_return(true)
allow(project).to receive(:forked_from_project).and_return(source)
expect(service).to receive(:set_key)
.ordered
.with('last-push-event/1/2', 3)
expect(service).to receive(:set_key)
.ordered
.with('last-push-event/1', 3)
expect(service).to receive(:set_key)
.ordered
.with('last-push-event/1/5', 3)
service.cache_last_push_event(event)
end
end
describe '#last_event_for_user' do
it 'returns the last push event for the current user' do
expect(service).to receive(:find_cached_event)
.with('last-push-event/1')
.and_return(event)
expect(service.last_event_for_user).to eq(event)
end
it 'returns nil when no push event could be found' do
expect(service).to receive(:find_cached_event)
.with('last-push-event/1')
.and_return(nil)
expect(service.last_event_for_user).to be_nil
end
end
describe '#last_event_for_project' do
it 'returns the last push event for the given project' do
expect(service).to receive(:find_cached_event)
.with('last-push-event/1/2')
.and_return(event)
expect(service.last_event_for_project(project)).to eq(event)
end
it 'returns nil when no push event could be found' do
expect(service).to receive(:find_cached_event)
.with('last-push-event/1/2')
.and_return(nil)
expect(service.last_event_for_project(project)).to be_nil
end
end
describe '#find_cached_event', :use_clean_rails_memory_store_caching do
context 'with a non-existing cache key' do
it 'returns nil' do
expect(service.find_cached_event('bla')).to be_nil
end
end
context 'with an existing cache key' do
before do
service.cache_last_push_event(event)
end
it 'returns a PushEvent when no merge requests exist for the event' do
allow(service).to receive(:find_event_in_database)
.with(event.id)
.and_return(event)
expect(service.find_cached_event('last-push-event/1')).to eq(event)
end
it 'removes the cache key when no event could be found and returns nil' do
allow(PushEvent).to receive(:without_existing_merge_requests)
.and_return(PushEvent.none)
expect(Rails.cache).to receive(:delete)
.with('last-push-event/1')
.and_call_original
expect(service.find_cached_event('last-push-event/1')).to be_nil
end
end
end
end
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