BigW Consortium Gitlab

Commit f94565ea by Robert Speicher

Merge branch '10-0-stable-prepare-rc1' into '10-0-stable'

Prepare 10.0 RC1 release See merge request !14143
parents d1002d1e 16cf8604
......@@ -407,4 +407,4 @@ gem 'flipper-active_record', '~> 0.10.2'
# Structured logging
gem 'lograge', '~> 0.5'
gem 'grape_logging', '~> 1.6'
gem 'grape_logging', '~> 1.7'
......@@ -355,7 +355,7 @@ GEM
activesupport
grape (>= 0.16.0)
rake
grape_logging (1.6.0)
grape_logging (1.7.0)
grape
grpc (1.4.5)
google-protobuf (~> 3.1)
......@@ -1037,7 +1037,7 @@ DEPENDENCIES
grape (~> 1.0)
grape-entity (~> 0.6.0)
grape-route-helpers (~> 2.1.0)
grape_logging (~> 1.6)
grape_logging (~> 1.7)
haml_lint (~> 0.26.0)
hamlit (~> 2.6.1)
hashie-forbidden_attributes
......
......@@ -332,7 +332,14 @@ class FilteredSearchManager {
const removeElements = [];
[].forEach.call(this.tokensContainer.children, (t) => {
if (t.classList.contains('js-visual-token')) {
let canClearToken = t.classList.contains('js-visual-token');
if (canClearToken) {
const tokenKey = t.querySelector('.name').textContent.trim();
canClearToken = this.canEdit && this.canEdit(tokenKey);
}
if (canClearToken) {
removeElements.push(t);
}
});
......@@ -411,8 +418,14 @@ class FilteredSearchManager {
});
}
// allows for modifying params array when a param can't be included in the URL (e.g. Service Desk)
getAllParams(urlParams) {
return this.modifyUrlParams ? this.modifyUrlParams(urlParams) : urlParams;
}
loadSearchParamsFromURL() {
const params = gl.utils.getUrlParamsArray();
const urlParams = gl.utils.getUrlParamsArray();
const params = this.getAllParams(urlParams);
const usernameParams = this.getUsernameParams();
let hasFilteredSearch = false;
......
......@@ -13,7 +13,7 @@ export function formatRelevantDigits(number) {
let relevantDigits = 0;
let formattedNumber = '';
if (!isNaN(Number(number))) {
digitsLeft = number.split('.')[0];
digitsLeft = number.toString().split('.')[0];
switch (digitsLeft.length) {
case 1:
relevantDigits = 3;
......
......@@ -3,7 +3,7 @@
import GraphLegend from './graph/legend.vue';
import GraphFlag from './graph/flag.vue';
import GraphDeployment from './graph/deployment.vue';
import monitoringPaths from './monitoring_paths.vue';
import GraphPath from './graph_path.vue';
import MonitoringMixin from '../mixins/monitoring_mixins';
import eventHub from '../event_hub';
import measurements from '../utils/measurements';
......@@ -40,8 +40,6 @@
graphHeightOffset: 120,
margin: {},
unitOfDisplay: '',
areaColorRgb: '#8fbce8',
lineColorRgb: '#1f78d1',
yAxisLabel: '',
legendTitle: '',
reducedDeploymentData: [],
......@@ -63,7 +61,7 @@
GraphLegend,
GraphFlag,
GraphDeployment,
monitoringPaths,
GraphPath,
},
computed: {
......@@ -143,7 +141,7 @@
},
renderAxesPaths() {
this.timeSeries = createTimeSeries(this.graphData.queries[0].result,
this.timeSeries = createTimeSeries(this.graphData.queries[0],
this.graphWidth,
this.graphHeight,
this.graphHeightOffset);
......@@ -162,7 +160,7 @@
const xAxis = d3.svg.axis()
.scale(axisXScale)
.ticks(measurements.xTicks)
.ticks(d3.time.minute, 60)
.tickFormat(timeScaleFormat)
.orient('bottom');
......@@ -238,7 +236,7 @@
class="graph-data"
:viewBox="innerViewBox"
ref="graphData">
<monitoring-paths
<graph-path
v-for="(path, index) in timeSeries"
:key="index"
:generated-line-path="path.linePath"
......@@ -246,7 +244,7 @@
:line-color="path.lineColor"
:area-color="path.areaColor"
/>
<monitoring-deployment
<graph-deployment
:show-deploy-info="showDeployInfo"
:deployment-data="reducedDeploymentData"
:graph-height="graphHeight"
......
......@@ -81,6 +81,13 @@
formatMetricUsage(series) {
return `${formatRelevantDigits(series.values[this.currentDataIndex].value)} ${this.unitOfDisplay}`;
},
createSeriesString(index, series) {
if (series.metricTag) {
return `${series.metricTag} ${this.formatMetricUsage(series)}`;
}
return `${this.legendTitle} series ${index + 1} ${this.formatMetricUsage(series)}`;
},
},
mounted() {
this.$nextTick(() => {
......@@ -164,7 +171,7 @@
ref="legendTitleSvg"
x="38"
:y="graphHeight - 30">
{{legendTitle}} Series {{index + 1}} {{formatMetricUsage(series)}}
{{createSeriesString(index, series)}}
</text>
<text
v-else
......
import d3 from 'd3';
import _ from 'underscore';
export default function createTimeSeries(seriesData, graphWidth, graphHeight, graphHeightOffset) {
const maxValues = seriesData.map((timeSeries, index) => {
const defaultColorPalette = {
blue: ['#1f78d1', '#8fbce8'],
orange: ['#fc9403', '#feca81'],
red: ['#db3b21', '#ed9d90'],
green: ['#1aaa55', '#8dd5aa'],
purple: ['#6666c4', '#d1d1f0'],
};
const defaultColorOrder = ['blue', 'orange', 'red', 'green', 'purple'];
export default function createTimeSeries(queryData, graphWidth, graphHeight, graphHeightOffset) {
let usedColors = [];
function pickColor(name) {
let pick;
if (name && defaultColorPalette[name]) {
pick = name;
} else {
const unusedColors = _.difference(defaultColorOrder, usedColors);
if (unusedColors.length > 0) {
pick = unusedColors[0];
} else {
usedColors = [];
pick = defaultColorOrder[0];
}
}
usedColors.push(pick);
return defaultColorPalette[pick];
}
const maxValues = queryData.result.map((timeSeries, index) => {
const maxValue = d3.max(timeSeries.values.map(d => d.value));
return {
maxValue,
......@@ -12,10 +41,11 @@ export default function createTimeSeries(seriesData, graphWidth, graphHeight, gr
const maxValueFromSeries = _.max(maxValues, val => val.maxValue);
let timeSeriesNumber = 1;
let lineColor = '#1f78d1';
let areaColor = '#8fbce8';
return seriesData.map((timeSeries) => {
return queryData.result.map((timeSeries, timeSeriesNumber) => {
let metricTag = '';
let lineColor = '';
let areaColor = '';
const timeSeriesScaleX = d3.time.scale()
.range([0, graphWidth - 70]);
......@@ -23,49 +53,30 @@ export default function createTimeSeries(seriesData, graphWidth, graphHeight, gr
.range([graphHeight - graphHeightOffset, 0]);
timeSeriesScaleX.domain(d3.extent(timeSeries.values, d => d.time));
timeSeriesScaleX.ticks(d3.time.minute, 60);
timeSeriesScaleY.domain([0, maxValueFromSeries.maxValue]);
const lineFunction = d3.svg.line()
.interpolate('linear')
.x(d => timeSeriesScaleX(d.time))
.y(d => timeSeriesScaleY(d.value));
const areaFunction = d3.svg.area()
.interpolate('linear')
.x(d => timeSeriesScaleX(d.time))
.y0(graphHeight - graphHeightOffset)
.y1(d => timeSeriesScaleY(d.value))
.interpolate('linear');
switch (timeSeriesNumber) {
case 1:
lineColor = '#1f78d1';
areaColor = '#8fbce8';
break;
case 2:
lineColor = '#fc9403';
areaColor = '#feca81';
break;
case 3:
lineColor = '#db3b21';
areaColor = '#ed9d90';
break;
case 4:
lineColor = '#1aaa55';
areaColor = '#8dd5aa';
break;
case 5:
lineColor = '#6666c4';
areaColor = '#d1d1f0';
break;
default:
lineColor = '#1f78d1';
areaColor = '#8fbce8';
break;
}
.y1(d => timeSeriesScaleY(d.value));
if (timeSeriesNumber <= 5) {
timeSeriesNumber = timeSeriesNumber += 1;
const timeSeriesMetricLabel = timeSeries.metric[Object.keys(timeSeries.metric)[0]];
const seriesCustomizationData = queryData.series != null &&
_.findWhere(queryData.series[0].when,
{ value: timeSeriesMetricLabel });
if (seriesCustomizationData != null) {
metricTag = seriesCustomizationData.value || timeSeriesMetricLabel;
[lineColor, areaColor] = pickColor(seriesCustomizationData.color);
} else {
timeSeriesNumber = 1;
metricTag = timeSeriesMetricLabel || `series ${timeSeriesNumber + 1}`;
[lineColor, areaColor] = pickColor();
}
return {
......@@ -75,6 +86,7 @@ export default function createTimeSeries(seriesData, graphWidth, graphHeight, gr
values: timeSeries.values,
lineColor,
areaColor,
metricTag,
};
});
}
......@@ -63,7 +63,7 @@ export default class NewNavSidebar {
if (breakpoint === 'sm' || breakpoint === 'md') {
this.toggleCollapsedSidebar(true);
} else if (breakpoint === 'lg') {
const collapse = this.$sidebar.hasClass('sidebar-icons-only');
const collapse = Cookies.get('sidebar_collapsed') === 'true';
this.toggleCollapsedSidebar(collapse);
}
}
......
......@@ -17,8 +17,11 @@
max-width: $limited-layout-width-sm;
margin-left: 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 @@
+ .files,
+ .alert {
margin-top: 30px;
margin-top: 32px;
}
}
}
......
......@@ -10,6 +10,22 @@ module IssuableCollections
private
def set_issues_index
@collection_type = "Issue"
@issues = issues_collection
@issues = @issues.page(params[:page])
@issuable_meta_data = issuable_meta_data(@issues, @collection_type)
@total_pages = issues_page_count(@issues)
return if redirect_out_of_range(@issues, @total_pages)
if params[:label_name].present?
@labels = LabelsFinder.new(current_user, project_id: @project.id, title: params[:label_name]).execute
end
@users = []
end
def issues_collection
issues_finder.execute.preload(:project, :author, :assignees, :labels, :milestone, project: :namespace)
end
......
......@@ -48,7 +48,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
ProjectsFinder
.new(params: finder_params, current_user: current_user)
.execute
.includes(:route, :creator, namespace: :route)
.includes(:route, :creator, namespace: [:route, :owner])
end
def load_events
......
......@@ -10,6 +10,7 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :check_issues_available!
before_action :issue, except: [:index, :new, :create, :bulk_update]
before_action :set_issues_index, only: [:index]
# Allow write(create) issue
before_action :authorize_create_issue!, only: [:new, :create]
......@@ -23,20 +24,6 @@ class Projects::IssuesController < Projects::ApplicationController
respond_to :html
def index
@collection_type = "Issue"
@issues = issues_collection
@issues = @issues.page(params[:page])
@issuable_meta_data = issuable_meta_data(@issues, @collection_type)
@total_pages = issues_page_count(@issues)
return if redirect_out_of_range(@issues, @total_pages)
if params[:label_name].present?
@labels = LabelsFinder.new(current_user, project_id: @project.id, title: params[:label_name]).execute
end
@users = []
if params[:assignee_id].present?
assignee = User.find_by_id(params[:assignee_id])
@users.push(assignee) if assignee
......
......@@ -137,15 +137,7 @@ module ProjectsHelper
end
def last_push_event
return unless current_user
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)
current_user&.recent_push(@project)
end
def project_feature_access_select(field)
......
......@@ -104,7 +104,7 @@ module TreeHelper
subtree = Gitlab::Git::Tree.where(@repository, @commit.id, tree.path)
if subtree.count == 1 && subtree.first.dir?
return tree_join(tree.name, flatten_tree(subtree.first))
return tree_join(tree.name, flatten_tree(root_path, subtree.first))
else
return tree.name
end
......
......@@ -49,7 +49,7 @@ class Event < ActiveRecord::Base
belongs_to :author, class_name: "User"
belongs_to :project
belongs_to :target, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
has_one :push_event_payload, foreign_key: :event_id
has_one :push_event_payload
# Callbacks
after_create :reset_project_activity
......
......@@ -30,6 +30,44 @@ class PushEvent < Event
delegate :commit_count, to: :push_event_payload
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
PUSHED
end
......
......@@ -651,20 +651,13 @@ class User < ActiveRecord::Base
@personal_projects_count ||= personal_projects.count
end
def recent_push(project_ids = nil)
# Get push events not earlier than 2 hours ago
events = recent_events.code_push.where("created_at > ?", Time.now - 2.hours)
events = events.where(project_id: project_ids) if project_ids
def recent_push(project = nil)
service = Users::LastPushEventService.new(self)
# Use the latest event that has not been pushed or merged recently
events.includes(:project).recent.find do |event|
next unless event.project.repository.branch_exists?(event.branch_name)
merge_requests = MergeRequest.where("created_at >= ?", event.created_at)
.where(source_project_id: event.project.id,
source_branch: event.branch_name)
merge_requests.empty?
if project
service.last_event_for_project(project)
else
service.last_event_for_user
end
end
......
......@@ -74,12 +74,19 @@ class EventCreateService
# 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
# rolled back as well.
Event.transaction do
event = create_event(project, current_user, Event::PUSHED)
event = Event.transaction do
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
Users::LastPushEventService.new(current_user)
.cache_last_push_event(event)
Users::ActivityService.new(current_user, 'push').execute
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 @@
= icon('wrench')
.sidebar-context-title Admin Area
%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
.nav-icon-container
= custom_icon('overview')
......@@ -14,7 +14,7 @@
Overview
%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
%strong.fly-out-top-item-name
#{ _('Overview') }
......@@ -52,16 +52,16 @@
%span
ConvDev Index
= nav_link(controller: %w(conversational_development_index system_info background_jobs logs health_check requests_profiles)) do
= sidebar_link admin_conversational_development_index_path, title: _('Monitoring') do
= nav_link(controller: %w(system_info background_jobs logs health_check requests_profiles)) do
= sidebar_link admin_system_info_path, title: _('Monitoring') do
.nav-icon-container
= custom_icon('monitoring')
%span.nav-item-name
Monitoring
%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
= link_to admin_conversational_development_index_path 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_system_info_path do
%strong.fly-out-top-item-name
#{ _('Monitoring') }
%li.divider.fly-out-top-item
......
......@@ -108,7 +108,7 @@
%span.badge.count.issue_counter.fly-out-badge
= number_with_delimiter(@project.open_issues_count)
%li.divider.fly-out-top-item
= nav_link(controller: :issues) do
= nav_link(controller: :issues, action: :index) do
= link_to project_issues_path(@project), title: 'Issues' do
%span
List
......
......@@ -36,10 +36,10 @@
.preview= image_tag "#{scheme.css_class}-scheme-preview.png"
= f.radio_button :color_scheme_id, scheme.id
= scheme.name
.col-sm-12
%hr
.col-sm-12
%hr
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
Behavior
......
- empty_state_path = local_assigns.fetch(:empty_state_path, 'shared/empty_states/issues')
%ul.content-list.issues-list.issuable-list
= render partial: "projects/issues/issue", collection: @issues
- if @issues.blank?
= render 'shared/empty_states/issues'
= render empty_state_path
- if @issues.present?
= paginate @issues, theme: "gitlab", total_pages: @total_pages
---
title: Fixed merge request changes bar jumping
merge_request:
author:
type: fixed
---
title: Fix ConvDev Index nav item and Monitoring submenu regression
merge_request: !14124
author:
type: fixed
---
title: Eager load namespace owners for project dashboards
merge_request:
author:
type: other
---
title: Added support for specific labels and colors
merge_request:
author:
type: changed
---
title: Rework how recent push events are retrieved
merge_request:
author:
type: other
......@@ -51,7 +51,7 @@ module Gitlab
# Configure sensitive parameters which will be filtered from the log file.
#
# Parameters filtered:
# - Any parameter ending with `_token`
# - Any parameter ending with `token`
# - Any parameter containing `password`
# - Any parameter containing `secret`
# - Two-factor tokens (:otp_attempt)
......@@ -61,7 +61,7 @@ module Gitlab
# - Webhook URLs (:hook)
# - Sentry DSN (:sentry_dsn)
# - Deploy keys (:key)
config.filter_parameters += [/_token$/, /password/, /secret/]
config.filter_parameters += [/token$/, /password/, /secret/]
config.filter_parameters += %i(
certificate
encrypted_key
......
......@@ -4,12 +4,21 @@
- title: "Throughput"
y_label: "Requests / Sec"
required_metrics:
- nginx_upstream_requests_total
- nginx_upstream_responses_total
weight: 1
queries:
- query_range: 'sum(rate(nginx_upstream_requests_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m]))'
label: Total
- query_range: 'sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) by (status_code)'
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"
y_label: "Latency (ms)"
required_metrics:
......@@ -37,9 +46,17 @@
- haproxy_frontend_http_requests_total
weight: 1
queries:
- query_range: 'sum(rate(haproxy_frontend_http_requests_total{%{environment_filter}}[2m]))'
label: Total
- query_range: 'sum(rate(haproxy_frontend_http_requests_total{%{environment_filter}}[2m])) by (code)'
unit: req / sec
series:
- label: code
when:
- value: 2xx
color: green
- value: 4xx
color: yellow
- value: 5xx
color: red
- title: "HTTP Error Rate"
y_label: "Error Rate (%)"
required_metrics:
......@@ -86,12 +103,21 @@
- title: "Throughput"
y_label: "Requests / Sec"
required_metrics:
- nginx_requests_total
- nginx_responses_total
weight: 1
queries:
- query_range: 'sum(rate(nginx_requests_total{server_zone!="*", server_zone!="_", %{environment_filter}}[2m]))'
label: Total
- query_range: 'sum(rate(nginx_responses_total{server_zone!="*", server_zone!="_", %{environment_filter}}[2m])) by (status_code)'
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"
y_label: "Latency (ms)"
required_metrics:
......@@ -128,6 +154,8 @@
- container_cpu_usage_seconds_total
weight: 1
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'
label: Average
- query_range: 'sum(rate(container_cpu_usage_seconds_total{container_name!="POD",%{environment_filter}}[2m])) by (cpu) * 100'
label: CPU
unit: "%"
series:
- label: cpu
......@@ -7,6 +7,10 @@ class SwapEventMigrationTables < ActiveRecord::Migration
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
class Event < ActiveRecord::Base
self.table_name = 'events'
end
def up
rename_tables
end
......@@ -19,5 +23,25 @@ class SwapEventMigrationTables < ActiveRecord::Migration
rename_table :events, :events_old
rename_table :events_for_migration, :events
rename_table :events_old, :events_for_migration
# Once swapped we need to reset the primary key of the new "events" table to
# make sure that data created starts with the right value. This isn't
# necessary for events_for_migration since we replicate existing primary key
# values to it.
if Gitlab::Database.postgresql?
reset_primary_key_for_postgresql
else
reset_primary_key_for_mysql
end
end
def reset_primary_key_for_postgresql
reset_pk_sequence!(Event.table_name)
end
def reset_primary_key_for_mysql
amount = Event.pluck('COALESCE(MAX(id), 1)').first
execute "ALTER TABLE #{Event.table_name} AUTO_INCREMENT = #{amount}"
end
end
......@@ -1366,25 +1366,31 @@ variables:
GIT_DEPTH: "3"
```
## Hidden keys
## Hidden keys (jobs)
> Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
Keys that start with a dot (`.`) will be not processed by GitLab CI. You can
use this feature to ignore jobs, or use the
[special YAML features](#special-yaml-features) and transform the hidden keys
into templates.
If you want to temporarily 'disable' a job, rather than commenting out all the
lines where the job is defined:
```
#hidden_job:
# script:
# - run test
```
In the following example, `.key_name` will be ignored:
you can instead start its name with a dot (`.`) and it will not be processed by
GitLab CI. In the following example, `.hidden_job` will be ignored:
```yaml
.key_name:
.hidden_job:
script:
- rake spec
- run test
```
Hidden keys can be hashes like normal CI jobs, but you are also allowed to use
different types of structures to leverage special YAML features.
Use this feature to ignore jobs, or use the
[special YAML features](#special-yaml-features) and transform the hidden keys
into templates.
## Special YAML features
......@@ -1400,7 +1406,7 @@ Read more about the various [YAML features](https://learnxinyminutes.com/docs/ya
YAML has a handy feature called 'anchors', which lets you easily duplicate
content across your document. Anchors can be used to duplicate/inherit
properties, and is a perfect example to be used with [hidden keys](#hidden-keys)
properties, and is a perfect example to be used with [hidden keys](#hidden-keys-jobs)
to provide templates for your jobs.
The following example uses anchors and map merging. It will create two jobs,
......
......@@ -299,9 +299,9 @@ sudo usermod -aG redis git
### Clone the Source
# 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
......
......@@ -15,8 +15,7 @@ There are also two other sets of charts:
## Official GitLab Helm Charts
These charts utilize our [GitLab Omnibus Docker images](https://docs.gitlab.com/omnibus/docker/README.html). You can report any issues and feedback related to these charts at
https://gitlab.com/charts/charts.gitlab.io/issues.
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).
### Deploying GitLab on Kubernetes
> **Note**: This chart will eventually be replaced by the [cloud native charts](#upcoming-cloud-native-helm-charts), which are presently in development.
......@@ -34,7 +33,7 @@ It offers a quick way to configure and deploy the Runner on Kubernetes, regardle
### Advanced deployment of GitLab
> **Note**: This chart will be replaced by the [gitlab-omnibus](gitlab_omnibus.md) chart, once it supports [additional configuration options](https://gitlab.com/charts/charts.gitlab.io/issues/68).
If advanced configuration of GitLab is required, the beta [gitlab](gitlab_chart.md) chart can be used which deploys the GitLab service along with optional Postgres and Redis. It offers extensive configuration, but requires deep knowledge of Kubernetes and Helm to use.
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.
For most deployments we recommend using our [gitlab-omnibus](gitlab_omnibus.md) chart.
......
......@@ -236,7 +236,7 @@ ActionMailer::Base.delivery_method = :smtp
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
......
......@@ -236,7 +236,7 @@ ActionMailer::Base.delivery_method = :smtp
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
......
......@@ -194,7 +194,7 @@ ActionMailer::Base.delivery_method = :smtp
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
......
......@@ -230,7 +230,7 @@ ActionMailer::Base.delivery_method = :smtp
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
......
......@@ -243,7 +243,7 @@ ActionMailer::Base.delivery_method = :smtp
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
......
......@@ -252,7 +252,7 @@ ActionMailer::Base.delivery_method = :smtp
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
......
......@@ -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.
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
[ping]: ../settings/usage_statistics.md#usage-ping
......@@ -7,7 +7,7 @@ GitLab has support for automatically detecting and monitoring HAProxy. This is p
| 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])) |
## Configuring Prometheus to monitor for HAProxy metrics
......
......@@ -8,7 +8,7 @@ GitLab has support for automatically detecting and monitoring Kubernetes metrics
| 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 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
......
......@@ -7,7 +7,7 @@ GitLab has support for automatically detecting and monitoring NGINX. This is pro
| 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}}) |
| 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
| 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}-.*"}) |
| 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
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
In order to isolate and only display relevant metrics for a given environment
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>-*`.
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>-*`.
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.
![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 search and filter UI currently uses dropdowns. In a future release, the same
dynamic UI as above will be carried over here.
## Search history
......
......@@ -3,20 +3,19 @@ module QA
module Main
class Menu < Page::Base
def go_to_groups
within_global_menu { click_link 'Groups' }
within_top_menu { click_link 'Groups' }
end
def go_to_projects
within_global_menu { click_link 'Projects' }
within_top_menu { click_link 'Projects' }
end
def go_to_admin_area
within_user_menu { click_link 'Admin area' }
within_top_menu { click_link 'Admin area' }
end
def sign_out
within_user_menu do
find('.header-user-dropdown-toggle').click
click_link('Sign out')
end
end
......@@ -27,17 +26,19 @@ module QA
private
def within_global_menu
find('.global-dropdown-toggle').click
page.within('.global-dropdown-menu') do
def within_top_menu
page.within('.navbar') do
yield
end
end
def within_user_menu
page.within('.navbar-nav') do
yield
within_top_menu do
find('.header-user-dropdown-toggle').click
page.within('.dropdown-menu-nav') do
yield
end
end
end
end
......
......@@ -13,7 +13,7 @@ describe 'Issue Boards', js: true do
project.team << [user, :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)
end
......
......@@ -83,12 +83,14 @@ feature 'Dashboard Projects' do
end
end
context 'last push widget' do
context 'last push widget', :use_clean_rails_memory_store_caching do
before do
event = create(:push_event, project: project, author: user)
create(:push_event_payload, event: event, ref: 'feature', action: :created)
Users::LastPushEventService.new(user).cache_last_push_event(event)
visit dashboard_projects_path
end
......
......@@ -28,7 +28,7 @@ describe 'Visual tokens', js: true do
sign_in(user)
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)
end
......
......@@ -22,7 +22,7 @@ feature 'Diff note avatars', js: true do
project.team << [user, :master]
sign_in user
allow_any_instance_of(ApplicationHelper).to receive(:collapsed_sidebar?).and_return(true)
page.driver.set_cookie('sidebar_collapsed', 'true')
end
context 'discussion tab' do
......
......@@ -6,7 +6,7 @@ feature 'Merge requests > User posts diff notes', :js do
let(:project) { merge_request.source_project }
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)
sign_in(user)
......
require 'spec_helper'
# This is a regression test for https://gitlab.com/gitlab-org/gitlab-ce/issues/37569
describe 'User browses a tree with a folder containing only a folder' do
let(:project) { create(:project, :empty_repo) }
let(:user) { project.creator }
before do
# We need to disable the tree.flat_path provided by Gitaly to reproduce the issue
allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(false)
project.repository.create_dir(user, 'foo/bar', branch_name: 'master', message: 'Add the foo/bar folder')
sign_in(user)
visit(project_tree_path(project, project.repository.root_ref))
end
it 'shows the nested folder on a single row' do
expect(page).to have_content('foo/bar')
end
end
......@@ -313,23 +313,10 @@ describe ProjectsHelper do
it 'returns recent push on the current project' do
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)
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
describe "#project_feature_access_select" do
......
......@@ -411,4 +411,26 @@ describe('Filtered Search Manager', () => {
expect(document.querySelector('.filtered-search-box').classList.contains('focus')).toEqual(false);
});
});
describe('getAllParams', () => {
beforeEach(() => {
this.paramsArr = ['key=value', 'otherkey=othervalue'];
initializeManager();
});
it('correctly modifies params when custom modifier is passed', () => {
const modifedParams = manager.getAllParams.call({
modifyUrlParams: paramsArr => paramsArr.reverse(),
}, [].concat(this.paramsArr));
expect(modifedParams[0]).toBe(this.paramsArr[1]);
});
it('does not modify params when no custom modifier is passed', () => {
const modifedParams = manager.getAllParams.call({}, this.paramsArr);
expect(modifedParams[1]).toBe(this.paramsArr[1]);
});
});
});
......@@ -28,7 +28,7 @@ const defaultValuesComponent = {
currentDataIndex: 0,
};
const timeSeries = createTimeSeries(convertedMetrics[0].queries[0].result,
const timeSeries = createTimeSeries(convertedMetrics[0].queries[0],
defaultValuesComponent.graphWidth, defaultValuesComponent.graphHeight,
defaultValuesComponent.graphHeightOffset);
......@@ -89,13 +89,12 @@ describe('GraphLegend', () => {
expect(component.$el.querySelectorAll('.rect-axis-text').length).toEqual(2);
});
it('contains text to signal the usage, title and time', () => {
it('contains text to signal the usage, title and time with multiple time series', () => {
const component = createComponent(defaultValuesComponent);
const titles = component.$el.querySelectorAll('.legend-metric-title');
expect(getTextFromNode(component, '.legend-metric-title').indexOf(component.legendTitle)).not.toEqual(-1);
expect(titles[0].textContent.indexOf('Title')).not.toEqual(-1);
expect(titles[1].textContent.indexOf('Series')).not.toEqual(-1);
expect(titles[0].textContent.indexOf('1xx')).not.toEqual(-1);
expect(titles[1].textContent.indexOf('2xx')).not.toEqual(-1);
expect(getTextFromNode(component, '.y-label-text')).toEqual(component.yAxisLabel);
});
......
import Vue from 'vue';
import MonitoringPaths from '~/monitoring/components/monitoring_paths.vue';
import GraphPath from '~/monitoring/components/graph_path.vue';
import createTimeSeries from '~/monitoring/utils/multiple_time_series';
import { singleRowMetricsMultipleSeries, convertDatesMultipleSeries } from './mock_data';
const createComponent = (propsData) => {
const Component = Vue.extend(MonitoringPaths);
const Component = Vue.extend(GraphPath);
return new Component({
propsData,
......@@ -13,22 +13,23 @@ const createComponent = (propsData) => {
const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries);
const timeSeries = createTimeSeries(convertedMetrics[0].queries[0].result, 428, 272, 120);
const timeSeries = createTimeSeries(convertedMetrics[0].queries[0], 428, 272, 120);
const firstTimeSeries = timeSeries[0];
describe('Monitoring Paths', () => {
it('renders two paths to represent a line and the area underneath it', () => {
const component = createComponent({
generatedLinePath: timeSeries[0].linePath,
generatedAreaPath: timeSeries[0].areaPath,
lineColor: '#ccc',
areaColor: '#fff',
generatedLinePath: firstTimeSeries.linePath,
generatedAreaPath: firstTimeSeries.areaPath,
lineColor: firstTimeSeries.lineColor,
areaColor: firstTimeSeries.areaColor,
});
const metricArea = component.$el.querySelector('.metric-area');
const metricLine = component.$el.querySelector('.metric-line');
expect(metricArea.getAttribute('fill')).toBe('#fff');
expect(metricArea.getAttribute('d')).toBe(timeSeries[0].areaPath);
expect(metricLine.getAttribute('stroke')).toBe('#ccc');
expect(metricLine.getAttribute('d')).toBe(timeSeries[0].linePath);
expect(metricArea.getAttribute('fill')).toBe('#8fbce8');
expect(metricArea.getAttribute('d')).toBe(firstTimeSeries.areaPath);
expect(metricLine.getAttribute('stroke')).toBe('#1f78d1');
expect(metricLine.getAttribute('d')).toBe(firstTimeSeries.linePath);
});
});
......@@ -6346,7 +6346,13 @@ export const singleRowMetricsMultipleSeries = [
}
]
},
]
],
'when': [
{
'value': 'hundred(s)',
'color': 'green',
},
],
}
]
},
......
......@@ -2,16 +2,17 @@ import createTimeSeries from '~/monitoring/utils/multiple_time_series';
import { convertDatesMultipleSeries, singleRowMetricsMultipleSeries } from '../mock_data';
const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries);
const timeSeries = createTimeSeries(convertedMetrics[0].queries[0].result, 428, 272, 120);
const timeSeries = createTimeSeries(convertedMetrics[0].queries[0], 428, 272, 120);
const firstTimeSeries = timeSeries[0];
describe('Multiple time series', () => {
it('createTimeSeries returned array contains an object for each element', () => {
expect(typeof timeSeries[0].linePath).toEqual('string');
expect(typeof timeSeries[0].areaPath).toEqual('string');
expect(typeof timeSeries[0].timeSeriesScaleX).toEqual('function');
expect(typeof timeSeries[0].areaColor).toEqual('string');
expect(typeof timeSeries[0].lineColor).toEqual('string');
expect(timeSeries[0].values instanceof Array).toEqual(true);
expect(typeof firstTimeSeries.linePath).toEqual('string');
expect(typeof firstTimeSeries.areaPath).toEqual('string');
expect(typeof firstTimeSeries.timeSeriesScaleX).toEqual('function');
expect(typeof firstTimeSeries.areaColor).toEqual('string');
expect(typeof firstTimeSeries.lineColor).toEqual('string');
expect(firstTimeSeries.values instanceof Array).toEqual(true);
});
it('createTimeSeries returns an array', () => {
......
......@@ -11,6 +11,94 @@ describe PushEvent do
event
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
it 'returns Event::PUSHED' do
expect(described_class.sti_name).to eq(Event::PUSHED)
......
......@@ -1349,56 +1349,24 @@ describe User do
end
describe "#recent_push" do
subject { create(:user) }
let!(:project1) { create(:project, :repository) }
let!(:project2) { create(:project, :repository, forked_from_project: project1) }
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
let(:user) { build(:user) }
let(:project) { build(:project) }
let(:event) { build(:push_event) }
it "excludes push event if MR is opened for it" do
create(:merge_request, source_project: project2, target_project: project1, source_branch: project2.default_branch, target_branch: 'fix', author: subject)
it 'returns the last push event for the user' do
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
it "includes push events on any of the provided projects" do
expect(subject.recent_push(project1)).to eq(nil)
expect(subject.recent_push(project2)).to eq(push_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')
it 'returns the last push event for a project when one is given' do
expect_any_instance_of(Users::LastPushEventService)
.to receive(:last_event_for_project)
.and_return(event)
expect(subject.recent_push([project1, project2])).to eq(push_event1) # Newest
expect(user.recent_push(project)).to eq(event)
end
end
......
......@@ -149,6 +149,14 @@ describe EventCreateService do
.to change { user_activity(user) }
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
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
......@@ -16,7 +16,9 @@ module MigrationsHelpers
end
def reset_column_in_migration_models
ActiveRecord::Base.clear_cache!
ActiveRecord::Base.connection_pool.connections.each do |conn|
conn.schema_cache.clear!
end
described_class.constants.sort.each do |name|
const = described_class.const_get(name)
......
The canonical repository for `.gitlab-ci.yml` templates is
https://gitlab.com/gitlab-org/gitlab-ci-yml.
## Contributing
Thank you for your interest in contributing to this GitLab project! We welcome
all contributions. By participating in this project, you agree to abide by the
[code of conduct](#code-of-conduct).
## Contributor license agreement
By submitting code as an individual you agree to the [individual contributor
license agreement][individual-agreement].
By submitting code as an entity you agree to the [corporate contributor license
agreement][corporate-agreement].
## Code of conduct
As contributors and maintainers of this project, we pledge to respect all people
who contribute through reporting issues, posting feature requests, updating
documentation, submitting pull requests or patches, and other activities.
We are committed to making participation in this project a harassment-free
experience for everyone, regardless of level of experience, gender, gender
identity and expression, sexual orientation, disability, personal appearance,
body size, race, ethnicity, age, or religion.
Examples of unacceptable behavior by participants include the use of sexual
language or imagery, derogatory comments or personal attacks, trolling, public
or private harassment, insults, or other unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct. Project maintainers who do not follow the
Code of Conduct may be removed from the project team.
This code of conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community.
Instances of abusive, harassing, or otherwise unacceptable behavior can be
reported by emailing contact@gitlab.com.
This Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant], version 1.1.0,
available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/).
[contributor-covenant]: http://contributor-covenant.org
[individual-agreement]: https://docs.gitlab.com/ee/legal/individual_contributor_license_agreement.html
[corporate-agreement]: https://docs.gitlab.com/ee/legal/corporate_contributor_license_agreement.html
GitLab only mirrors the templates. Please submit your merge requests to
https://gitlab.com/gitlab-org/gitlab-ci-yml.
# This template has been DEPRECATED. Consider using Auto DevOps instead:
# https://docs.gitlab.com/ee/topics/autodevops
# Explanation on the scripts:
# https://gitlab.com/gitlab-examples/kubernetes-deploy/blob/master/README.md
image: registry.gitlab.com/gitlab-examples/kubernetes-deploy
......
# This template has been DEPRECATED. Consider using Auto DevOps instead:
# https://docs.gitlab.com/ee/topics/autodevops
# Explanation on the scripts:
# https://gitlab.com/gitlab-examples/kubernetes-deploy/blob/master/README.md
image: registry.gitlab.com/gitlab-examples/kubernetes-deploy
......
# This template has been DEPRECATED. Consider using Auto DevOps instead:
# https://docs.gitlab.com/ee/topics/autodevops
# Explanation on the scripts:
# https://gitlab.com/gitlab-examples/openshift-deploy/blob/master/README.md
image: registry.gitlab.com/gitlab-examples/openshift-deploy
......
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