Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-05-20 12:10:26 +00:00
parent b794758ce4
commit a32e60a7ea
48 changed files with 839 additions and 536 deletions

View file

@ -341,7 +341,6 @@ group :metrics do
# Prometheus
gem 'prometheus-client-mmap', '~> 0.12.0'
gem 'raindrops', '~> 0.18'
end
group :development do

View file

@ -1592,7 +1592,6 @@ DEPENDENCIES
rails-controller-testing
rails-i18n (~> 6.0)
rainbow (~> 3.0)
raindrops (~> 0.18)
rblineprof (~> 0.3.6)
rbtrace (~> 0.4)
rdoc (~> 6.1.2)

View file

@ -81,7 +81,7 @@ GitLab is a Ruby on Rails application that runs on the following software:
- Ubuntu/Debian/CentOS/RHEL/OpenSUSE
- Ruby (MRI) 2.7.2
- Git 2.31+
- Redis 4.0+
- Redis 5.0+
- PostgreSQL 11+
For more information please see the [architecture](https://docs.gitlab.com/ee/development/architecture.html) and [requirements](https://docs.gitlab.com/ee/install/requirements.html) documentation.

View file

@ -21,7 +21,7 @@ export const DEFAULT_ITERATIONS = DEFAULT_NONE_ANY.concat([
{ value: FILTER_CURRENT, text: __(FILTER_CURRENT) },
]);
export const DEFAULT_LABELS = [{ value: 'No label', text: __('No label') }]; // eslint-disable-line @gitlab/require-i18n-strings
export const DEFAULT_LABELS = [DEFAULT_LABEL_NONE, DEFAULT_LABEL_ANY];
export const DEFAULT_MILESTONES = DEFAULT_NONE_ANY.concat([
{ value: 'Upcoming', text: __('Upcoming') }, // eslint-disable-line @gitlab/require-i18n-strings

View file

@ -120,7 +120,7 @@ export default {
}, DEBOUNCE_DELAY);
},
handleTokenValueSelected(activeTokenValue) {
if (this.isRecentTokenValuesEnabled) {
if (this.isRecentTokenValuesEnabled && activeTokenValue) {
setTokenValueToRecentlyUsed(this.recentTokenValuesStorageKey, activeTokenValue);
}
},

View file

@ -1,27 +1,20 @@
<script>
import {
GlToken,
GlFilteredSearchToken,
GlFilteredSearchSuggestion,
GlDropdownDivider,
GlLoadingIcon,
} from '@gitlab/ui';
import { debounce } from 'lodash';
import { GlToken, GlFilteredSearchSuggestion } from '@gitlab/ui';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { __ } from '~/locale';
import { DEFAULT_LABELS, DEBOUNCE_DELAY } from '../constants';
import { DEFAULT_LABELS } from '../constants';
import { stripQuotes } from '../filtered_search_utils';
import BaseToken from './base_token.vue';
export default {
components: {
BaseToken,
GlToken,
GlFilteredSearchToken,
GlFilteredSearchSuggestion,
GlDropdownDivider,
GlLoadingIcon,
},
props: {
config: {
@ -32,43 +25,24 @@ export default {
type: Object,
required: true,
},
active: {
type: Boolean,
required: true,
},
},
data() {
return {
labels: this.config.initialLabels || [],
defaultLabels: this.config.defaultLabels || DEFAULT_LABELS,
loading: true,
loading: false,
};
},
computed: {
currentValue() {
return this.value.data.toLowerCase();
},
activeLabel() {
return this.labels.find(
(label) => this.getLabelName(label).toLowerCase() === stripQuotes(this.currentValue),
methods: {
getActiveLabel(labels, currentValue) {
return labels.find(
(label) => this.getLabelName(label).toLowerCase() === stripQuotes(currentValue),
);
},
containerStyle() {
if (this.activeLabel) {
const { color, textColor } = convertObjectPropsToCamelCase(this.activeLabel);
return { backgroundColor: color, color: textColor };
}
return {};
},
},
watch: {
active: {
immediate: true,
handler(newValue) {
if (!newValue && !this.labels.length) {
this.fetchLabelBySearchTerm(this.value.data);
}
},
},
},
methods: {
/**
* There's an inconsistency between private and public API
* for labels where label name is included in a different
@ -84,6 +58,16 @@ export default {
getLabelName(label) {
return label.name || label.title;
},
getContainerStyle(activeLabel) {
if (activeLabel) {
const { color: backgroundColor, textColor: color } = convertObjectPropsToCamelCase(
activeLabel,
);
return { backgroundColor, color };
}
return {};
},
fetchLabelBySearchTerm(searchTerm) {
this.loading = true;
this.config
@ -99,50 +83,47 @@ export default {
this.loading = false;
});
},
searchLabels: debounce(function debouncedSearch({ data }) {
if (!this.loading) this.fetchLabelBySearchTerm(data);
}, DEBOUNCE_DELAY),
},
};
</script>
<template>
<gl-filtered-search-token
:config="config"
v-bind="{ ...$props, ...$attrs }"
v-on="$listeners"
@input="searchLabels"
<base-token
:token-config="config"
:token-value="value"
:token-active="active"
:tokens-list-loading="loading"
:token-values="labels"
:fn-active-token-value="getActiveLabel"
:default-token-values="defaultLabels"
:recent-token-values-storage-key="config.recentTokenValuesStorageKey"
@fetch-token-values="fetchLabelBySearchTerm"
>
<template #view-token="{ inputValue, cssClasses, listeners }">
<gl-token variant="search-value" :class="cssClasses" :style="containerStyle" v-on="listeners"
>~{{ activeLabel ? getLabelName(activeLabel) : inputValue }}</gl-token
<template
#view-token="{ viewTokenProps: { inputValue, cssClasses, listeners, activeTokenValue } }"
>
<gl-token
variant="search-value"
:class="cssClasses"
:style="getContainerStyle(activeTokenValue)"
v-on="listeners"
>~{{ activeTokenValue ? getLabelName(activeTokenValue) : inputValue }}</gl-token
>
</template>
<template #suggestions>
<template #token-values-list="{ tokenValues }">
<gl-filtered-search-suggestion
v-for="label in defaultLabels"
:key="label.value"
:value="label.value"
v-for="label in tokenValues"
:key="label.id"
:value="getLabelName(label)"
>
{{ label.text }}
<div class="gl-display-flex gl-align-items-center">
<span
:style="{ backgroundColor: label.color }"
class="gl-display-inline-block mr-2 p-2"
></span>
<div>{{ getLabelName(label) }}</div>
</div>
</gl-filtered-search-suggestion>
<gl-dropdown-divider v-if="defaultLabels.length" />
<gl-loading-icon v-if="loading" />
<template v-else>
<gl-filtered-search-suggestion
v-for="label in labels"
:key="label.id"
:value="getLabelName(label)"
>
<div class="gl-display-flex gl-align-items-center">
<span
:style="{ backgroundColor: label.color }"
class="gl-display-inline-block mr-2 p-2"
></span>
<div>{{ getLabelName(label) }}</div>
</div>
</gl-filtered-search-suggestion>
</template>
</template>
</gl-filtered-search-token>
</base-token>
</template>

View file

@ -92,7 +92,7 @@ class Projects::BlobController < Projects::ApplicationController
@blob.load_all_data!
diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3', include_diff_info: true)
diff_lines = diffy.diff.scan(/.*\n/)[2..-1]
diff_lines = Gitlab::Diff::Parser.new.parse(diff_lines)
diff_lines = Gitlab::Diff::Parser.new.parse(diff_lines).to_a
@diff_lines = Gitlab::Diff::Highlight.new(diff_lines, repository: @repository).highlight
render layout: false

View file

@ -0,0 +1,13 @@
# frozen_string_literal: true
module KeysetHelper
def keyset_paginate(paginator, without_first_and_last_pages: false)
page_params = params.to_unsafe_h
render('kaminari/gitlab/keyset_paginator', {
paginator: paginator,
without_first_and_last_pages: without_first_and_last_pages,
page_params: page_params
})
end
end

View file

@ -39,6 +39,11 @@ class ProjectHook < WebHook
def rate_limit
project.actual_limits.limit_for(:web_hook_calls)
end
override :application_context
def application_context
super.merge(project: project)
end
end
ProjectHook.prepend_mod_with('ProjectHook')

View file

@ -80,6 +80,11 @@ class WebHook < ApplicationRecord
nil
end
# Custom attributes to be included in the worker context.
def application_context
{ related_class: type }
end
private
def web_hooks_disable_failed?

View file

@ -93,7 +93,9 @@ class WebHookService
if rate_limited?(hook)
log_rate_limit(hook)
else
WebHookWorker.perform_async(hook.id, data, hook_name)
Gitlab::ApplicationContext.with_context(hook.application_context) do
WebHookWorker.perform_async(hook.id, data, hook_name)
end
end
end

View file

@ -108,20 +108,6 @@
}
]
},
{
"name": "kubesec",
"label": "Kubesec",
"enabled" : true,
"description": "Kubernetes manifests, Helm Charts",
"variables": []
},
{
"name": "nodejs-scan",
"label": "Node.js Scan",
"enabled" : true,
"description": "Node.js",
"variables": []
},
{
"name": "gosec",
"label": "Gosec",
@ -139,6 +125,20 @@
}
]
},
{
"name": "kubesec",
"label": "Kubesec",
"enabled" : true,
"description": "Kubernetes manifests, Helm Charts",
"variables": []
},
{
"name": "nodejs-scan",
"label": "Node.js Scan",
"enabled" : true,
"description": "Node.js",
"variables": []
},
{
"name": "phpcs-security-audit",
"label": "PHP Security Audit",

View file

@ -0,0 +1,30 @@
- previous_path = url_for(page_params.merge(cursor: paginator.cursor_for_previous_page))
- next_path = url_for(page_params.merge(cursor: paginator.cursor_for_next_page))
.gl-pagination.gl-mt-3
%ul.pagination.justify-content-center
- if paginator.has_previous_page?
- unless without_first_and_last_pages
%li.page-item
- first_page_path = url_for(page_params.merge(cursor: paginator.cursor_for_first_page))
= link_to first_page_path, rel: 'first', class: 'page-link' do
= sprite_icon('angle-double-left', size: 8)
= s_('Pagination|First')
%li.page-item.prev
= link_to previous_path, rel: 'prev', class: 'page-link' do
= sprite_icon('angle-left', size: 8)
= s_('Pagination|Prev')
- if paginator.has_next_page?
%li.page-item.next
= link_to next_path, rel: 'next', class: 'page-link' do
= s_('Pagination|Next')
= sprite_icon('angle-right', size: 8)
- unless without_first_and_last_pages
%li.page-item
- last_page_path = url_for(page_params.merge(cursor: paginator.cursor_for_last_page))
= link_to last_page_path, rel: 'last', class: 'page-link' do
= s_('Pagination|Last')
= sprite_icon('angle-double-right', size: 8)

View file

@ -0,0 +1,5 @@
---
title: Remove Unicorn Sampler
merge_request: 62090
author:
type: removed

View file

@ -0,0 +1,5 @@
---
title: Fix blob preview error
merge_request: 62128
author:
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Redirect some of deprecated repository routes
merge_request: 34867
author:
type: removed

View file

@ -0,0 +1,5 @@
---
title: Return 404 from branches API when repository does not exist
merge_request:
author:
type: fixed

View file

@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/329390
milestone: '13.12'
type: development
group: group::continuous integration
default_enabled: true
default_enabled: false

View file

@ -33,7 +33,7 @@ production: &base
host: localhost
port: 80 # Set to 443 if using HTTPS, see installation.md#using-https for additional HTTPS configuration details
https: false # Set to true if using HTTPS, see installation.md#using-https for additional HTTPS configuration details
# The maximum time unicorn/puma can spend on the request. This needs to be smaller than the worker timeout.
# The maximum time Puma can spend on the request. This needs to be smaller than the worker timeout.
# Default is 95% of the worker timeout
max_request_duration_seconds: 57
@ -153,7 +153,7 @@ production: &base
### GraphQL Settings
# Tells the rails application how long it has to complete a GraphQL request.
# We suggest this value to be higher than the database timeout value
# and lower than the worker timeout set in unicorn/puma. (default: 30)
# and lower than the worker timeout set in Puma. (default: 30)
# graphql_timeout: 30
## Repository downloads directory
@ -1212,8 +1212,6 @@ production: &base
## Monitoring
# Built in monitoring settings
monitoring:
# Time between sampling of unicorn socket metrics, in seconds
# unicorn_sampler_interval: 10
# IP whitelist to access monitoring endpoints
ip_whitelist:
- 127.0.0.0/8
@ -1225,7 +1223,7 @@ production: &base
# address: localhost
# port: 8082
# Web exporter is webserver built in to Unicorn/Puma to expose Prometheus metrics
# Web exporter is a dedicated Rack server running alongside Puma to expose Prometheus metrics
# It runs alongside the `/metrics` endpoints to ease the publish of metrics
web_exporter:
# enabled: true

View file

@ -902,7 +902,6 @@ Settings.webpack.dev_server['https'] ||= false
#
Settings['monitoring'] ||= Settingslogic.new({})
Settings.monitoring['ip_whitelist'] ||= ['127.0.0.1/8']
Settings.monitoring['unicorn_sampler_interval'] ||= 10
Settings.monitoring['sidekiq_exporter'] ||= Settingslogic.new({})
Settings.monitoring.sidekiq_exporter['enabled'] ||= false
Settings.monitoring.sidekiq_exporter['log_enabled'] ||= false

View file

@ -8,8 +8,6 @@ def prometheus_default_multiproc_dir
if Gitlab::Runtime.sidekiq?
Rails.root.join('tmp/prometheus_multiproc_dir/sidekiq')
elsif Gitlab::Runtime.unicorn?
Rails.root.join('tmp/prometheus_multiproc_dir/unicorn')
elsif Gitlab::Runtime.puma?
Rails.root.join('tmp/prometheus_multiproc_dir/puma')
else
@ -49,9 +47,7 @@ if !Rails.env.test? && Gitlab::Metrics.prometheus_metrics_enabled?
::Prometheus::Client.reinitialize_on_pid_change(force: true)
if Gitlab::Runtime.unicorn?
Gitlab::Metrics::Samplers::UnicornSampler.instance(Settings.monitoring.unicorn_sampler_interval).start
elsif Gitlab::Runtime.puma?
if Gitlab::Runtime.puma?
Gitlab::Metrics::Samplers::PumaSampler.instance.start
end

View file

@ -0,0 +1,12 @@
# frozen_string_literal: true
module PaginatorExtension
# This method loads the records for the requested page and returns a keyset paginator object.
def keyset_paginate(cursor: nil, per_page: 20)
Gitlab::Pagination::Keyset::Paginator.new(scope: self.dup, cursor: cursor, per_page: per_page)
end
end
ActiveSupport.on_load(:active_record) do
ActiveRecord::Relation.include(PaginatorExtension)
end

View file

@ -1,6 +1,6 @@
---
key_path: counts.cycle_analytics_views
description:
description: Total visits to VSA (both group- and project-level) all time
product_section: dev
product_stage: manage
product_group: group::optimize
@ -8,9 +8,11 @@ product_category:
value_type: number
status: data_available
time_frame: all
data_source: database
data_source: redis
distribution:
- ce
- ee
tier:
- free
skip_validation: true
- premium
- ultimate

View file

@ -386,7 +386,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
# The wiki and repository routing contains wildcard characters so
# its preferable to keep it below all other project routes
draw :repository_scoped
draw :repository
draw :wiki
namespace :import do
@ -584,6 +583,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
# Introduced in 12.0.
# Should be removed with https://gitlab.com/gitlab-org/gitlab/issues/28848.
Gitlab::Routing.redirect_legacy_paths(self, :mirror, :tags, :hooks,
:commits, :commit, :find_file, :files, :compare,
:cycle_analytics, :mattermost, :variables, :triggers,
:environments, :protected_environments, :error_tracking, :alert_management,
:tracing,

View file

@ -1,84 +1,36 @@
# frozen_string_literal: true
# All routing related to repository browsing
#
# NOTE: Add new routes to repository_scoped.rb instead (see
# https://docs.gitlab.com/ee/development/routing.html#project-routes).
resource :repository, only: [:create]
resources :commit, only: [:show], constraints: { id: /\h{7,40}/ } do
member do
get :branches
get :pipelines
post :revert
post :cherry_pick
get :diff_for_path
get :diff_files
get :merge_requests
end
end
# NOTE: Add new routes to repository_scoped.rb instead (see
# https://docs.gitlab.com/ee/development/routing.html#project-routes).
#
# Don't use format parameter as file extension (old 3.0.x behavior)
# See http://guides.rubyonrails.org/routing.html#route-globbing-and-wildcard-segments
scope format: false do
get '/compare/:from...:to', to: 'compare#show', as: 'compare', constraints: { from: /.+/, to: /.+/ }
get '/refs/switch',
to: redirect('%{namespace_id}/%{project_id}/-/refs/switch')
resources :compare, only: [:index, :create] do
collection do
get :diff_for_path
get :signatures
end
end
get '/refs/:id/logs_tree',
to: redirect('%{namespace_id}/%{project_id}/-/refs/%{id}/logs_tree'),
constraints: { id: Gitlab::PathRegex.git_reference_regex }
resources :refs, only: [] do
collection do
get 'switch'
end
member do
# tree viewer logs
get 'logs_tree', constraints: { id: Gitlab::PathRegex.git_reference_regex }
# Directories with leading dots erroneously get rejected if git
# ref regex used in constraints. Regex verification now done in controller.
get 'logs_tree/*path', action: :logs_tree, as: :logs_file, format: false, constraints: {
id: /.*/,
path: /[^\0]*/
}
end
end
get '/refs/:id/logs_tree/*path',
to: redirect('%{namespace_id}/%{project_id}/-/refs/%{id}/logs_tree/%{path}'),
constraints: { id: /.*/, path: /[^\0]*/ }
scope constraints: { id: /[^\0]+/ } do
scope controller: :blob do
get '/new/*id', action: :new, as: :new_blob
post '/create/*id', action: :create, as: :create_blob
get '/edit/*id', action: :edit, as: :edit_blob
put '/update/*id', action: :update, as: :update_blob
post '/preview/*id', action: :preview, as: :preview_blob
# Deprecated. Keep for compatibility.
# Issue https://gitlab.com/gitlab-org/gitlab/-/issues/118849
get '/tree/*id', to: 'tree#show', as: :deprecated_tree
get '/blob/*id', to: 'blob#show', as: :deprecated_blob
get '/raw/*id', to: 'raw#show', as: :deprecated_raw
get '/blame/*id', to: 'blame#show', as: :deprecated_blame
scope path: '/blob/*id', as: :blob do
get :diff
get '/', action: :show
delete '/', action: :destroy
post '/', action: :create
put '/', action: :update
end
end
get '/tree/*id', to: 'tree#show', as: :tree
get '/raw/*id', to: 'raw#show', as: :raw
get '/blame/*id', to: 'blame#show', as: :blame
get '/commits', to: 'commits#commits_root', as: :commits_root
get '/commits/*id/signatures', to: 'commits#signatures', as: :signatures
get '/commits/*id', to: 'commits#show', as: :commits
post '/create_dir/*id', to: 'tree#create_dir', as: :create_dir
scope controller: :find_file do
get '/find_file/*id', action: :show, as: :find_file
get '/files/*id', action: :list, as: :files
end
# Redirect those explicitly since `redirect_legacy_paths` conflicts with project new/edit actions
get '/new/*id', to: redirect('%{namespace_id}/%{project_id}/-/new/%{id}')
get '/edit/*id', to: redirect('%{namespace_id}/%{project_id}/-/edit/%{id}')
end
end

View file

@ -6,6 +6,33 @@
# Don't use format parameter as file extension (old 3.0.x behavior)
# See http://guides.rubyonrails.org/routing.html#route-globbing-and-wildcard-segments
scope format: false do
get '/compare/:from...:to', to: 'compare#show', as: 'compare', constraints: { from: /.+/, to: /.+/ }
resources :compare, only: [:index, :create] do
collection do
get :diff_for_path
get :signatures
end
end
resources :refs, only: [] do
collection do
get 'switch'
end
member do
# tree viewer logs
get 'logs_tree', constraints: { id: Gitlab::PathRegex.git_reference_regex }
# Directories with leading dots erroneously get rejected if git
# ref regex used in constraints. Regex verification now done in controller.
get 'logs_tree/*path', action: :logs_tree, as: :logs_file, format: false, constraints: {
id: /.*/,
path: /[^\0]*/
}
end
end
scope constraints: { id: Gitlab::PathRegex.git_reference_regex } do
resources :network, only: [:show]
@ -38,4 +65,51 @@ scope format: false do
end
end
end
scope constraints: { id: /[^\0]+/ } do
scope controller: :blob do
get '/new/*id', action: :new, as: :new_blob
post '/create/*id', action: :create, as: :create_blob
get '/edit/*id', action: :edit, as: :edit_blob
put '/update/*id', action: :update, as: :update_blob
post '/preview/*id', action: :preview, as: :preview_blob
scope path: '/blob/*id', as: :blob do
get :diff
get '/', action: :show
delete '/', action: :destroy
post '/', action: :create
put '/', action: :update
end
end
get '/tree/*id', to: 'tree#show', as: :tree
get '/raw/*id', to: 'raw#show', as: :raw
get '/blame/*id', to: 'blame#show', as: :blame
get '/commits', to: 'commits#commits_root', as: :commits_root
get '/commits/*id/signatures', to: 'commits#signatures', as: :signatures
get '/commits/*id', to: 'commits#show', as: :commits
post '/create_dir/*id', to: 'tree#create_dir', as: :create_dir
scope controller: :find_file do
get '/find_file/*id', action: :show, as: :find_file
get '/files/*id', action: :list, as: :files
end
end
end
resources :commit, only: [:show], constraints: { id: /\h{7,40}/ } do
member do
get :branches
get :pipelines
post :revert
post :cherry_pick
get :diff_for_path
get :diff_files
get :merge_requests
end
end
resource :repository, only: [:create]

View file

@ -69,6 +69,5 @@ The following environment variables are recognized:
- `DATABASE_SAMPLER_INTERVAL_SECONDS`
- `ACTION_CABLE_SAMPLER_INTERVAL_SECONDS`
- `PUMA_SAMPLER_INTERVAL_SECONDS`
- `UNICORN_SAMPLER_INTERVAL_SECONDS`
- `THREADS_SAMPLER_INTERVAL_SECONDS`
- `GLOBAL_SEARCH_SAMPLER_INTERVAL_SECONDS`

View file

@ -308,20 +308,8 @@ Some basic Ruby runtime metrics are available:
| `ruby_process_proportional_memory_bytes` | Gauge | 13.0 | Memory usage by process (PSS/Proportional Set Size) |
| `ruby_process_start_time_seconds` | Gauge | 12.0 | UNIX timestamp of process start time |
## Unicorn Metrics
Unicorn specific metrics, when Unicorn is used.
| Metric | Type | Since | Description |
|:-----------------------------|:------|:------|:---------------------------------------------------|
| `unicorn_active_connections` | Gauge | 11.0 | The number of active Unicorn connections (workers) |
| `unicorn_queued_connections` | Gauge | 11.0 | The number of queued Unicorn connections |
| `unicorn_workers` | Gauge | 12.0 | The number of Unicorn workers |
## Puma Metrics
When Puma is used instead of Unicorn, the following metrics are available:
| Metric | Type | Since | Description |
|:--------------------------------- |:------- |:----- |:----------- |
| `puma_workers` | Gauge | 12.0 | Total number of workers |
@ -352,8 +340,8 @@ instance (`cache`, `shared_state` etc.).
## Metrics shared directory
The GitLab Prometheus client requires a directory to store metrics data shared between multi-process services.
Those files are shared among all instances running under Unicorn server.
The directory must be accessible to all running Unicorn's processes, or
Those files are shared among all instances running under Puma server.
The directory must be accessible to all running Puma's processes, or
metrics can't function correctly.
This directory's location is configured using environment variable `prometheus_multiproc_dir`.

View file

@ -863,55 +863,6 @@ license.save
License.current # check to make sure it applied
```
## Unicorn
From [Zendesk ticket #91083](https://gitlab.zendesk.com/agent/tickets/91083) (internal)
### Poll Unicorn requests by seconds
```ruby
require 'rubygems'
require 'unicorn'
# Usage for this program
def usage
puts "ruby unicorn_status.rb <path to unix socket> <poll interval in seconds>"
puts "Polls the given Unix socket every interval in seconds. Will not allow you to drop below 3 second poll intervals."
puts "Example: /opt/gitlab/embedded/bin/ruby poll_unicorn.rb /var/opt/gitlab/gitlab-rails/sockets/gitlab.socket 10"
end
# Look for required args. Throw usage and exit if they don't exist.
if ARGV.count < 2
usage
exit 1
end
# Get the socket and threshold values.
socket = ARGV[0]
threshold = (ARGV[1]).to_i
# Check threshold - is it less than 3? If so, set to 3 seconds. Safety first!
if threshold.to_i < 3
threshold = 3
end
# Check - does that socket exist?
unless File.exist?(socket)
puts "Socket file not found: #{socket}"
exit 1
end
# Poll the given socket every THRESHOLD seconds as specified above.
puts "Running infinite loop. Use CTRL+C to exit."
puts "------------------------------------------"
loop do
Raindrops::Linux.unix_listener_stats([socket]).each do |addr, stats|
puts DateTime.now.to_s + " Active: " + stats.active.to_s + " Queued: " + stats.queued.to_s
end
sleep threshold
end
```
## Registry
### Registry Disk Space Usage by Project

View file

@ -444,7 +444,15 @@ There are several tools for developing and testing Snowplow Event
**{check-circle}** Available, **{status_preparing}** In progress, **{dotted-circle}** Not Planned
### Snowplow Analytics Debugger Chrome Extension
### Test frontend events
To test frontend events in development:
- [Enable Snowplow in the admin area](#enabling-snowplow).
- Turn off any ad blockers that would prevent Snowplow JS from loading in your environment.
- Turn off "Do Not Track" (DNT) in your browser.
#### Snowplow Analytics Debugger Chrome Extension
Snowplow Analytics Debugger is a browser extension for testing frontend events. This works on production, staging and local development environments.
@ -452,7 +460,7 @@ Snowplow Analytics Debugger is a browser extension for testing frontend events.
1. Open Chrome DevTools to the Snowplow Analytics Debugger tab.
1. Learn more at [Igloo Analytics](https://www.iglooanalytics.com/blog/snowplow-analytics-debugger-chrome-extension.html).
### Snowplow Inspector Chrome Extension
#### Snowplow Inspector Chrome Extension
Snowplow Inspector Chrome Extension is a browser extension for testing frontend events. This works on production, staging and local development environments.

View file

@ -936,7 +936,7 @@ Tiers: `free`
### `counts.cycle_analytics_views`
Missing description
Total visits to VSA (both group- and project-level) all time
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216174832_cycle_analytics_views.yml)
@ -944,7 +944,7 @@ Group: `group::optimize`
Status: `data_available`
Tiers: `free`
Tiers: `free`, `premium`, `ultimate`
### `counts.dast_jobs`
@ -4128,7 +4128,7 @@ Tiers: `free`, `premium`, `ultimate`
### `counts.productivity_analytics_views`
Missing description
Total visits to /groups/:group/-/analytics/productivity_analytics all time
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_all/20210216174834_productivity_analytics_views.yml)
@ -4136,7 +4136,7 @@ Group: `group::optimize`
Status: `data_available`
Tiers: `free`
Tiers: `free`, `premium`, `ultimate`
### `counts.project_clusters_disabled`

View file

@ -17,6 +17,10 @@ module API
authorize! :download_code, user_project
end
rescue_from Gitlab::Git::Repository::NoRepository do
not_found!
end
helpers do
params :filter_params do
optional :search, type: String, desc: 'Return list of branches matching the search criteria'

View file

@ -1,10 +1,21 @@
# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/container_scanning/
# Use this template to enable container scanning in your project.
# You should add this template to an existing `.gitlab-ci.yml` file by using the `include:` keyword.
# The template should work without modifications but you can customize the template settings if
# needed: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings
#
# Requirements:
# - You must define the image to be scanned in the DOCKER_IMAGE variable. If DOCKER_IMAGE is the
# same as $CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG, you can skip this.
# - Container registry credentials defined by `DOCKER_USER` and `DOCKER_PASSWORD` variables if the image to be scanned is in a private registry.
# - For auto-remediation, a readable Dockerfile in the root of the project or as defined by the
# DOCKERFILE_PATH variable.
#
# For more information, see https://docs.gitlab.com/ee/user/application_security/container_scanning/#available-variables
variables:
# Setting this variable will affect all Security templates
# (SAST, Dependency Scanning, ...)
# Setting this variable will affect all Security templates (e.g.: SAST, Dependency Scanning)
SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers"
CS_MAJOR_VERSION: 3
CS_MAJOR_VERSION: 3 # The major version of the analyzer image to be used for scanning
.cs_common:
stage: test

View file

@ -1,73 +0,0 @@
# frozen_string_literal: true
module Gitlab
module Metrics
module Samplers
class UnicornSampler < BaseSampler
DEFAULT_SAMPLING_INTERVAL_SECONDS = 5
def metrics
@metrics ||= init_metrics
end
def init_metrics
{
unicorn_active_connections: ::Gitlab::Metrics.gauge(:unicorn_active_connections, 'Unicorn active connections', {}, :max),
unicorn_queued_connections: ::Gitlab::Metrics.gauge(:unicorn_queued_connections, 'Unicorn queued connections', {}, :max),
unicorn_workers: ::Gitlab::Metrics.gauge(:unicorn_workers, 'Unicorn workers')
}
end
def enabled?
# Raindrops::Linux.tcp_listener_stats is only present on Linux
unicorn_with_listeners? && Raindrops::Linux.respond_to?(:tcp_listener_stats)
end
def sample
Raindrops::Linux.tcp_listener_stats(tcp_listeners).each do |addr, stats|
set_unicorn_connection_metrics('tcp', addr, stats)
end
Raindrops::Linux.unix_listener_stats(unix_listeners).each do |addr, stats|
set_unicorn_connection_metrics('unix', addr, stats)
end
metrics[:unicorn_workers].set({}, unicorn_workers_count)
end
private
def tcp_listeners
@tcp_listeners ||= Unicorn.listener_names.grep(%r{\A[^/]+:\d+\z})
end
def set_unicorn_connection_metrics(type, addr, stats)
labels = { socket_type: type, socket_address: addr }
metrics[:unicorn_active_connections].set(labels, stats.active)
metrics[:unicorn_queued_connections].set(labels, stats.queued)
end
def unix_listeners
@unix_listeners ||= Unicorn.listener_names - tcp_listeners
end
def unicorn_with_listeners?
defined?(Unicorn) && Unicorn.listener_names.any?
end
def unicorn_workers_count
http_servers.sum(&:worker_processes)
end
# Traversal of ObjectSpace is expensive, on fully loaded application
# it takes around 80ms. The instances of HttpServers are not a subject
# to change so we can cache the list of servers.
def http_servers
return [] unless Gitlab::Runtime.unicorn?
@http_servers ||= ObjectSpace.each_object(::Unicorn::HttpServer).to_a
end
end
end
end
end

View file

@ -0,0 +1,176 @@
# frozen_string_literal: true
module Gitlab
module Pagination
module Keyset
class Paginator
include Enumerable
module Base64CursorConverter
def self.dump(cursor_attributes)
Base64.urlsafe_encode64(Gitlab::Json.dump(cursor_attributes))
end
def self.parse(cursor)
Gitlab::Json.parse(Base64.urlsafe_decode64(cursor)).with_indifferent_access
end
end
FORWARD_DIRECTION = 'n'
BACKWARD_DIRECTION = 'p'
UnsupportedScopeOrder = Class.new(StandardError)
# scope - ActiveRecord::Relation object with order by clause
# cursor - Encoded cursor attributes as String. Empty value will requests the first page.
# per_page - Number of items per page.
# cursor_converter - Object that serializes and de-serializes the cursor attributes. Implements dump and parse methods.
# direction_key - Symbol that will be the hash key of the direction within the cursor. (default: _kd => keyset direction)
def initialize(scope:, cursor: nil, per_page: 20, cursor_converter: Base64CursorConverter, direction_key: :_kd)
@keyset_scope = build_scope(scope)
@order = Gitlab::Pagination::Keyset::Order.extract_keyset_order_object(@keyset_scope)
@per_page = per_page
@cursor_converter = cursor_converter
@direction_key = direction_key
@has_another_page = false
@at_last_page = false
@at_first_page = false
@cursor_attributes = decode_cursor_attributes(cursor)
set_pagination_helper_flags!
end
# rubocop: disable CodeReuse/ActiveRecord
def records
@records ||= begin
items = if paginate_backward?
reversed_order
.apply_cursor_conditions(keyset_scope, cursor_attributes)
.reorder(reversed_order)
.limit(per_page_plus_one)
.to_a
else
order
.apply_cursor_conditions(keyset_scope, cursor_attributes)
.limit(per_page_plus_one)
.to_a
end
@has_another_page = items.size == per_page_plus_one
items.pop if @has_another_page
items.reverse! if paginate_backward?
items
end
end
# rubocop: enable CodeReuse/ActiveRecord
# This and has_previous_page? methods are direction aware. In case we paginate backwards,
# has_next_page? will mean that we have a previous page.
def has_next_page?
records
if at_last_page?
false
elsif paginate_forward?
@has_another_page
elsif paginate_backward?
true
end
end
def has_previous_page?
records
if at_first_page?
false
elsif paginate_backward?
@has_another_page
elsif paginate_forward?
true
end
end
def cursor_for_next_page
if has_next_page?
data = order.cursor_attributes_for_node(records.last)
data[direction_key] = FORWARD_DIRECTION
cursor_converter.dump(data)
else
nil
end
end
def cursor_for_previous_page
if has_previous_page?
data = order.cursor_attributes_for_node(records.first)
data[direction_key] = BACKWARD_DIRECTION
cursor_converter.dump(data)
end
end
def cursor_for_first_page
cursor_converter.dump({ direction_key => FORWARD_DIRECTION })
end
def cursor_for_last_page
cursor_converter.dump({ direction_key => BACKWARD_DIRECTION })
end
delegate :each, :empty?, :any?, to: :records
private
attr_reader :keyset_scope, :order, :per_page, :cursor_converter, :direction_key, :cursor_attributes
delegate :reversed_order, to: :order
def at_last_page?
@at_last_page
end
def at_first_page?
@at_first_page
end
def per_page_plus_one
per_page + 1
end
def decode_cursor_attributes(cursor)
cursor.blank? ? {} : cursor_converter.parse(cursor)
end
def set_pagination_helper_flags!
@direction = cursor_attributes.delete(direction_key.to_s)
if cursor_attributes.blank? && @direction.blank?
@at_first_page = true
@direction = FORWARD_DIRECTION
elsif cursor_attributes.blank?
if paginate_forward?
@at_first_page = true
else
@at_last_page = true
end
end
end
def paginate_backward?
@direction == BACKWARD_DIRECTION
end
def paginate_forward?
@direction == FORWARD_DIRECTION
end
def build_scope(scope)
keyset_aware_scope, success = Gitlab::Pagination::Keyset::SimpleOrderBuilder.build(scope)
raise(UnsupportedScopeOrder, 'The order on the scope does not support keyset pagination') unless success
keyset_aware_scope
end
end
end
end
end

View file

@ -26,6 +26,8 @@ module Gitlab
def build
order = if order_values.empty?
primary_key_descending_order
elsif Gitlab::Pagination::Keyset::Order.keyset_aware?(scope)
Gitlab::Pagination::Keyset::Order.extract_keyset_order_object(scope)
elsif ordered_by_primary_key?
primary_key_order
elsif ordered_by_other_column?

View file

@ -23703,6 +23703,9 @@ msgstr ""
msgid "Pages Domain"
msgstr ""
msgid "Pagination|First"
msgstr ""
msgid "Pagination|Go to first page"
msgstr ""
@ -23715,6 +23718,9 @@ msgstr ""
msgid "Pagination|Go to previous page"
msgstr ""
msgid "Pagination|Last"
msgstr ""
msgid "Pagination|Last »"
msgstr ""

View file

@ -444,6 +444,40 @@ RSpec.describe Projects::BlobController do
end
end
describe 'POST preview' do
subject(:request) { post :preview, params: default_params }
let(:user) { create(:user) }
let(:filename) { 'preview.md' }
let(:default_params) do
{
namespace_id: project.namespace,
project_id: project,
id: "#{project.default_branch}/#{filename}",
content: "Bar\n"
}
end
before do
project.add_developer(user)
sign_in(user)
project.repository.create_file(
project.creator,
filename,
"Foo\n",
message: 'Test',
branch_name: project.default_branch
)
end
it 'is successful' do
request
expect(response).to be_successful
end
end
describe 'POST create' do
let(:user) { create(:user) }
let(:default_params) do

View file

@ -1,5 +1,4 @@
import {
GlFilteredSearchToken,
GlFilteredSearchSuggestion,
GlFilteredSearchTokenSegment,
GlDropdownDivider,
@ -18,6 +17,7 @@ import {
DEFAULT_LABELS,
DEFAULT_NONE_ANY,
} from '~/vue_shared/components/filtered_search_bar/constants';
import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label_token.vue';
import { mockLabelToken } from '../mock_data';
@ -25,6 +25,7 @@ import { mockLabelToken } from '../mock_data';
jest.mock('~/flash');
const defaultStubs = {
Portal: true,
BaseToken,
GlFilteredSearchSuggestionList: {
template: '<div></div>',
methods: {
@ -68,55 +69,17 @@ describe('LabelToken', () => {
wrapper.destroy();
});
describe('computed', () => {
beforeEach(async () => {
// Label title with spaces is always enclosed in quotations by component.
wrapper = createComponent({ value: { data: `"${mockRegularLabel.title}"` } });
wrapper.setData({
labels: mockLabels,
});
await wrapper.vm.$nextTick();
});
describe('currentValue', () => {
it('returns lowercase string for `value.data`', () => {
expect(wrapper.vm.currentValue).toBe('"foo label"');
});
});
describe('activeLabel', () => {
it('returns object for currently present `value.data`', () => {
expect(wrapper.vm.activeLabel).toEqual(mockRegularLabel);
});
});
describe('containerStyle', () => {
it('returns object containing `backgroundColor` and `color` properties based on `activeLabel` value', () => {
expect(wrapper.vm.containerStyle).toEqual({
backgroundColor: mockRegularLabel.color,
color: mockRegularLabel.textColor,
});
});
it('returns empty object when `activeLabel` is not set', async () => {
wrapper.setData({
labels: [],
});
await wrapper.vm.$nextTick();
expect(wrapper.vm.containerStyle).toEqual({});
});
});
});
describe('methods', () => {
beforeEach(() => {
wrapper = createComponent();
});
describe('getActiveLabel', () => {
it('returns label object from labels array based on provided `currentValue` param', () => {
expect(wrapper.vm.getActiveLabel(mockLabels, 'foo label')).toEqual(mockRegularLabel);
});
});
describe('getLabelName', () => {
it('returns value of `name` or `title` property present in provided label param', () => {
let mockLabel = {
@ -187,8 +150,14 @@ describe('LabelToken', () => {
await wrapper.vm.$nextTick();
});
it('renders gl-filtered-search-token component', () => {
expect(wrapper.find(GlFilteredSearchToken).exists()).toBe(true);
it('renders base-token component', () => {
const baseTokenEl = wrapper.find(BaseToken);
expect(baseTokenEl.exists()).toBe(true);
expect(baseTokenEl.props()).toMatchObject({
tokenValues: mockLabels,
fnActiveTokenValue: wrapper.vm.getActiveLabel,
});
});
it('renders token item when value is selected', () => {

View file

@ -0,0 +1,94 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe KeysetHelper, type: :controller do
controller(Admin::UsersController) do
def index
@users = User
.where(admin: false)
.order(id: :desc)
.keyset_paginate(cursor: params[:cursor], per_page: 2)
render inline: "<%= keyset_paginate @users %>", layout: false # rubocop: disable Rails/RenderInline
end
end
render_views
let(:admin) { create(:admin) }
before do
sign_in(admin)
end
context 'with admin mode', :enable_admin_mode do
context 'when no users are present' do
it 'does not render pagination links' do
get :index
expect(response.body).not_to include(s_('Pagination|First'))
expect(response.body).not_to include(s_('Pagination|Prev'))
expect(response.body).not_to include(s_('Pagination|Next'))
expect(response.body).not_to include(s_('Pagination|Last'))
end
end
context 'when one user is present' do
before do
create(:user)
end
it 'does not render pagination links' do
get :index
expect(response.body).not_to include(s_('Pagination|First'))
expect(response.body).not_to include(s_('Pagination|Prev'))
expect(response.body).not_to include(s_('Pagination|Next'))
expect(response.body).not_to include(s_('Pagination|Last'))
end
end
context 'when more users are present' do
let_it_be(:users) { create_list(:user, 5) }
let(:paginator) { User.where(admin: false).order(id: :desc).keyset_paginate(per_page: 2) }
context 'when on the first page' do
it 'renders the next and last links' do
get :index
expect(response.body).not_to include(s_('Pagination|First'))
expect(response.body).not_to include(s_('Pagination|Prev'))
expect(response.body).to include(s_('Pagination|Next'))
expect(response.body).to include(s_('Pagination|Last'))
end
end
context 'when at the last page' do
it 'renders the prev and first links' do
cursor = paginator.cursor_for_last_page
get :index, params: { cursor: cursor }
expect(response.body).to include(s_('Pagination|First'))
expect(response.body).to include(s_('Pagination|Prev'))
expect(response.body).not_to include(s_('Pagination|Next'))
expect(response.body).not_to include(s_('Pagination|Last'))
end
end
context 'when at the second page' do
it 'renders all links' do
cursor = paginator.cursor_for_next_page
get :index, params: { cursor: cursor }
expect(response.body).to include(s_('Pagination|First'))
expect(response.body).to include(s_('Pagination|Prev'))
expect(response.body).to include(s_('Pagination|Next'))
expect(response.body).to include(s_('Pagination|Last'))
end
end
end
end
end

View file

@ -1,141 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Metrics::Samplers::UnicornSampler do
subject { described_class.new(1.second) }
it_behaves_like 'metrics sampler', 'UNICORN_SAMPLER'
describe '#sample' do
let(:unicorn) { Module.new }
let(:raindrops) { double('raindrops') }
let(:stats) { double('stats') }
before do
stub_const('Unicorn', unicorn)
stub_const('Raindrops::Linux', raindrops)
allow(raindrops).to receive(:unix_listener_stats).and_return({})
allow(raindrops).to receive(:tcp_listener_stats).and_return({})
end
context 'unicorn listens on unix sockets' do
let(:socket_address) { '/some/sock' }
let(:sockets) { [socket_address] }
before do
allow(unicorn).to receive(:listener_names).and_return(sockets)
end
it 'samples socket data' do
expect(raindrops).to receive(:unix_listener_stats).with(sockets)
subject.sample
end
context 'stats collected' do
before do
allow(stats).to receive(:active).and_return('active')
allow(stats).to receive(:queued).and_return('queued')
allow(raindrops).to receive(:unix_listener_stats).and_return({ socket_address => stats })
end
it 'updates metrics type unix and with addr' do
labels = { socket_type: 'unix', socket_address: socket_address }
expect(subject.metrics[:unicorn_active_connections]).to receive(:set).with(labels, 'active')
expect(subject.metrics[:unicorn_queued_connections]).to receive(:set).with(labels, 'queued')
subject.sample
end
end
end
context 'unicorn listens on tcp sockets' do
let(:tcp_socket_address) { '0.0.0.0:8080' }
let(:tcp_sockets) { [tcp_socket_address] }
before do
allow(unicorn).to receive(:listener_names).and_return(tcp_sockets)
end
it 'samples socket data' do
expect(raindrops).to receive(:tcp_listener_stats).with(tcp_sockets)
subject.sample
end
context 'stats collected' do
before do
allow(stats).to receive(:active).and_return('active')
allow(stats).to receive(:queued).and_return('queued')
allow(raindrops).to receive(:tcp_listener_stats).and_return({ tcp_socket_address => stats })
end
it 'updates metrics type unix and with addr' do
labels = { socket_type: 'tcp', socket_address: tcp_socket_address }
expect(subject.metrics[:unicorn_active_connections]).to receive(:set).with(labels, 'active')
expect(subject.metrics[:unicorn_queued_connections]).to receive(:set).with(labels, 'queued')
subject.sample
end
end
end
context 'unicorn workers' do
before do
allow(unicorn).to receive(:listener_names).and_return([])
end
context 'without http server' do
it "does set unicorn_workers to 0" do
expect(subject.metrics[:unicorn_workers]).to receive(:set).with({}, 0)
subject.sample
end
end
context 'with http server' do
let(:http_server_class) { Struct.new(:worker_processes) }
let!(:http_server) { http_server_class.new(5) }
before do
stub_const('Unicorn::HttpServer', http_server_class)
end
it "sets additional metrics" do
expect(subject.metrics[:unicorn_workers]).to receive(:set).with({}, 5)
subject.sample
end
end
end
end
describe '#start' do
context 'when enabled' do
before do
allow(subject).to receive(:enabled?).and_return(true)
end
it 'creates new thread' do
expect(Thread).to receive(:new)
subject.start
end
end
context 'when disabled' do
before do
allow(subject).to receive(:enabled?).and_return(false)
end
it "doesn't create new thread" do
expect(Thread).not_to receive(:new)
subject.start
end
end
end
end

View file

@ -0,0 +1,120 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Pagination::Keyset::Paginator do
let_it_be(:project_1) { create(:project, created_at: 10.weeks.ago) }
let_it_be(:project_2) { create(:project, created_at: 2.weeks.ago) }
let_it_be(:project_3) { create(:project, created_at: 3.weeks.ago) }
let_it_be(:project_4) { create(:project, created_at: 5.weeks.ago) }
let_it_be(:project_5) { create(:project, created_at: 2.weeks.ago) }
describe 'pagination' do
let(:per_page) { 10 }
let(:cursor) { nil }
let(:scope) { Project.order(created_at: :asc, id: :asc) }
let(:expected_order) { [project_1, project_4, project_3, project_2, project_5] }
subject(:paginator) { scope.keyset_paginate(cursor: cursor, per_page: per_page) }
context 'when per_page is greater than the record count' do
it { expect(paginator.records).to eq(expected_order) }
it { is_expected.not_to have_next_page }
it { is_expected.not_to have_previous_page }
it 'has no next and previous cursor values' do
expect(paginator.cursor_for_next_page).to be_nil
expect(paginator.cursor_for_previous_page).to be_nil
end
end
context 'when 0 records are returned' do
let(:scope) { Project.where(id: non_existing_record_id).order(created_at: :asc, id: :asc) }
it { expect(paginator.records).to be_empty }
it { is_expected.not_to have_next_page }
it { is_expected.not_to have_previous_page }
end
context 'when page size is smaller than the record count' do
let(:per_page) { 2 }
it { expect(paginator.records).to eq(expected_order.first(2)) }
it { is_expected.to have_next_page }
it { is_expected.not_to have_previous_page }
it 'has next page cursor' do
expect(paginator.cursor_for_next_page).not_to be_nil
end
it 'does not have previous page cursor' do
expect(paginator.cursor_for_previous_page).to be_nil
end
context 'when on the second page' do
let(:cursor) { scope.keyset_paginate(per_page: per_page).cursor_for_next_page }
it { expect(paginator.records).to eq(expected_order[2...4]) }
it { is_expected.to have_next_page }
it { is_expected.to have_previous_page }
context 'and then going back to the first page' do
let(:previous_page_cursor) { scope.keyset_paginate(cursor: cursor, per_page: per_page).cursor_for_previous_page }
subject(:paginator) { scope.keyset_paginate(cursor: previous_page_cursor, per_page: per_page) }
it { expect(paginator.records).to eq(expected_order.first(2)) }
it { is_expected.to have_next_page }
it { is_expected.not_to have_previous_page }
end
end
context 'when jumping to the last page' do
let(:cursor) { scope.keyset_paginate(per_page: per_page).cursor_for_last_page }
it { expect(paginator.records).to eq(expected_order.last(2)) }
it { is_expected.not_to have_next_page }
it { is_expected.to have_previous_page }
context 'when paginating backwards' do
let(:previous_page_cursor) { scope.keyset_paginate(cursor: cursor, per_page: per_page).cursor_for_previous_page }
subject(:paginator) { scope.keyset_paginate(cursor: previous_page_cursor, per_page: per_page) }
it { expect(paginator.records).to eq(expected_order[-4...-2]) }
it { is_expected.to have_next_page }
it { is_expected.to have_previous_page }
end
context 'when jumping to the first page' do
let(:first_page_cursor) { scope.keyset_paginate(cursor: cursor, per_page: per_page).cursor_for_first_page }
subject(:paginator) { scope.keyset_paginate(cursor: first_page_cursor, per_page: per_page) }
it { expect(paginator.records).to eq(expected_order.first(2)) }
it { is_expected.to have_next_page }
it { is_expected.not_to have_previous_page }
end
end
end
describe 'default keyset direction parameter' do
let(:cursor_converter_class) { Gitlab::Pagination::Keyset::Paginator::Base64CursorConverter }
let(:per_page) { 2 }
it 'exposes the direction parameter in the cursor' do
cursor = paginator.cursor_for_next_page
expect(cursor_converter_class.parse(cursor)[:_kd]).to eq(described_class::FORWARD_DIRECTION)
end
end
end
context 'when unsupported order is given' do
it 'raises error' do
scope = Project.order(path: :asc, name: :asc, id: :desc) # Cannot build 3 column order automatically
expect { scope.keyset_paginate }.to raise_error(/does not support keyset pagination/)
end
end
end

View file

@ -39,4 +39,15 @@ RSpec.describe ProjectHook do
expect(hook.rate_limit).to be(100)
end
end
describe '#application_context' do
let_it_be(:hook) { build(:project_hook) }
it 'includes the type and project' do
expect(hook.application_context).to include(
related_class: 'ProjectHook',
project: hook.project
)
end
end
end

View file

@ -30,4 +30,14 @@ RSpec.describe ServiceHook do
expect(hook.rate_limit).to be_nil
end
end
describe '#application_context' do
let(:hook) { build(:service_hook) }
it 'includes the type' do
expect(hook.application_context).to eq(
related_class: 'ServiceHook'
)
end
end
end

View file

@ -177,4 +177,14 @@ RSpec.describe SystemHook do
expect(hook.rate_limit).to be_nil
end
end
describe '#application_context' do
let(:hook) { build(:system_hook) }
it 'includes the type' do
expect(hook.application_context).to eq(
related_class: 'SystemHook'
)
end
end
end

View file

@ -8,8 +8,8 @@ RSpec.describe API::Branches do
let(:guest) { create(:user).tap { |u| project.add_guest(u) } }
let(:branch_name) { 'feature' }
let(:branch_sha) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' }
let(:branch_with_dot) { project.repository.find_branch('ends-with.json') }
let(:branch_with_slash) { project.repository.find_branch('improve/awesome') }
let(:branch_with_dot) { 'ends-with.json' }
let(:branch_with_slash) { 'improve/awesome' }
let(:project_id) { project.id }
let(:current_user) { nil }
@ -285,6 +285,13 @@ RSpec.describe API::Branches do
let(:request) { get api(route, current_user) }
end
end
context 'when repository does not exist' do
it_behaves_like '404 response' do
let(:project) { create(:project, creator: user) }
let(:request) { get api(route, current_user) }
end
end
end
context 'when unauthenticated', 'and project is public' do
@ -320,19 +327,19 @@ RSpec.describe API::Branches do
end
context 'when branch contains a dot' do
let(:branch_name) { branch_with_dot.name }
let(:branch_name) { branch_with_dot }
it_behaves_like 'repository branch'
end
context 'when branch contains dot txt' do
let(:branch_name) { project.repository.find_branch('ends-with.txt').name }
let(:branch_name) { 'ends-with.txt' }
it_behaves_like 'repository branch'
end
context 'when branch contains a slash' do
let(:branch_name) { branch_with_slash.name }
let(:branch_name) { branch_with_slash }
it_behaves_like '404 response' do
let(:request) { get api(route, current_user) }
@ -340,7 +347,7 @@ RSpec.describe API::Branches do
end
context 'when branch contains an escaped slash' do
let(:branch_name) { CGI.escape(branch_with_slash.name) }
let(:branch_name) { CGI.escape(branch_with_slash) }
it_behaves_like 'repository branch'
end
@ -351,7 +358,7 @@ RSpec.describe API::Branches do
it_behaves_like 'repository branch'
context 'when branch contains a dot' do
let(:branch_name) { branch_with_dot.name }
let(:branch_name) { branch_with_dot }
it_behaves_like 'repository branch'
end
@ -475,13 +482,13 @@ RSpec.describe API::Branches do
it_behaves_like 'repository new protected branch'
context 'when branch contains a dot' do
let(:branch_name) { branch_with_dot.name }
let(:branch_name) { branch_with_dot }
it_behaves_like 'repository new protected branch'
end
context 'when branch contains a slash' do
let(:branch_name) { branch_with_slash.name }
let(:branch_name) { branch_with_slash }
it_behaves_like '404 response' do
let(:request) { put api(route, current_user) }
@ -489,7 +496,7 @@ RSpec.describe API::Branches do
end
context 'when branch contains an escaped slash' do
let(:branch_name) { CGI.escape(branch_with_slash.name) }
let(:branch_name) { CGI.escape(branch_with_slash) }
it_behaves_like 'repository new protected branch'
end
@ -500,7 +507,7 @@ RSpec.describe API::Branches do
it_behaves_like 'repository new protected branch'
context 'when branch contains a dot' do
let(:branch_name) { branch_with_dot.name }
let(:branch_name) { branch_with_dot }
it_behaves_like 'repository new protected branch'
end
@ -609,13 +616,13 @@ RSpec.describe API::Branches do
it_behaves_like 'repository unprotected branch'
context 'when branch contains a dot' do
let(:branch_name) { branch_with_dot.name }
let(:branch_name) { branch_with_dot }
it_behaves_like 'repository unprotected branch'
end
context 'when branch contains a slash' do
let(:branch_name) { branch_with_slash.name }
let(:branch_name) { branch_with_slash }
it_behaves_like '404 response' do
let(:request) { put api(route, current_user) }
@ -623,7 +630,7 @@ RSpec.describe API::Branches do
end
context 'when branch contains an escaped slash' do
let(:branch_name) { CGI.escape(branch_with_slash.name) }
let(:branch_name) { CGI.escape(branch_with_slash) }
it_behaves_like 'repository unprotected branch'
end
@ -634,7 +641,7 @@ RSpec.describe API::Branches do
it_behaves_like 'repository unprotected branch'
context 'when branch contains a dot' do
let(:branch_name) { branch_with_dot.name }
let(:branch_name) { branch_with_dot }
it_behaves_like 'repository unprotected branch'
end
@ -732,7 +739,7 @@ RSpec.describe API::Branches do
end
it 'removes a branch with dots in the branch name' do
delete api("/projects/#{project.id}/repository/branches/#{branch_with_dot.name}", user)
delete api("/projects/#{project.id}/repository/branches/#{branch_with_dot}", user)
expect(response).to have_gitlab_http_status(:no_content)
end

View file

@ -202,6 +202,16 @@ RSpec.describe 'project routing' do
namespace_id: 'gitlab', project_id: 'gitlabhq',
id: "stable", path: "new\n\nline.txt" })
end
it_behaves_like 'redirecting a legacy path', '/gitlab/gitlabhq/refs/switch', '/gitlab/gitlabhq/-/refs/switch'
it_behaves_like 'redirecting a legacy path',
'/gitlab/gitlabhq/refs/feature%2345/logs_tree',
'/gitlab/gitlabhq/-/refs/feature%2345/logs_tree'
it_behaves_like 'redirecting a legacy path',
'/gitlab/gitlabhq/refs/stable/logs_tree/new%0A%0Aline.txt',
'/gitlab/gitlabhq/-/refs/stable/logs_tree/new%0A%0Aline.txt'
end
describe Projects::MergeRequestsController, 'routing' do
@ -357,9 +367,7 @@ RSpec.describe 'project routing' do
expect(get('/gitlab/gitlabhq/-/commit/4246fbd13872934f72a8fd0d6fb1317b47b59cb5')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd13872934f72a8fd0d6fb1317b47b59cb5')
end
it 'to #show unscoped routing' do
expect(get('/gitlab/gitlabhq/commit/4246fbd')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd')
end
it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/commit/4246fbd", "/gitlab/gitlabhq/-/commit/4246fbd"
end
# patch_project_commit GET /:project_id/commits/:id/patch(.:format) commits#patch
@ -376,9 +384,7 @@ RSpec.describe 'project routing' do
expect(get('/gitlab/gitlabhq/-/commits/master.atom')).to route_to('projects/commits#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master.atom')
end
it 'to #show unscoped routing' do
expect(get('/gitlab/gitlabhq/commits/master.atom')).to route_to('projects/commits#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master.atom')
end
it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/commits/master", "/gitlab/gitlabhq/-/commits/master"
end
# project_project_members GET /:project_id/project_members(.:format) project_members#index
@ -517,6 +523,7 @@ RSpec.describe 'project routing' do
end
it 'to #show from unscoped routing' do
expect(get('/gitlab/gitlabhq/tree/master')).to route_to('projects/tree#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master')
expect(get('/gitlab/gitlabhq/tree/master/app/models/project.rb')).to route_to('projects/tree#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/project.rb')
end
end
@ -545,6 +552,9 @@ RSpec.describe 'project routing' do
namespace_id: 'gitlab', project_id: 'gitlabhq',
id: "#{newline_file}" })
end
it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/find_file", "/gitlab/gitlabhq/-/find_file"
it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/files/master", "/gitlab/gitlabhq/-/files/master"
end
describe Projects::BlobController, 'routing' do
@ -575,6 +585,9 @@ RSpec.describe 'project routing' do
namespace_id: 'gitlab', project_id: 'gitlabhq',
id: "master/docs/#{newline_file}" })
end
it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/new/master", "/gitlab/gitlabhq/-/new/master"
it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/edit/master/README", "/gitlab/gitlabhq/-/edit/master/README"
end
# project_raw GET /:project_id/-/raw/:id(.:format) raw#show {id: /[^\0]+/, project_id: /[^\/]+/}
@ -610,6 +623,9 @@ RSpec.describe 'project routing' do
expect(get('/gitlab/gitlabhq/-/compare/master...stable')).to route_to('projects/compare#show', namespace_id: 'gitlab', project_id: 'gitlabhq', from: 'master', to: 'stable')
expect(get('/gitlab/gitlabhq/-/compare/issue/1234...stable')).to route_to('projects/compare#show', namespace_id: 'gitlab', project_id: 'gitlabhq', from: 'issue/1234', to: 'stable')
end
it_behaves_like 'redirecting a legacy path', '/gitlab/gitlabhq/compare', '/gitlab/gitlabhq/-/compare'
it_behaves_like 'redirecting a legacy path', '/gitlab/gitlabhq/compare/master...stable', '/gitlab/gitlabhq/-/compare/master...stable'
end
describe Projects::NetworkController, 'routing' do

View file

@ -429,5 +429,19 @@ RSpec.describe WebHookService do
end
end
end
context 'when hook has custom context attributes' do
it 'includes the attributes in the worker context' do
expect(WebHookWorker).to receive(:perform_async) do
expect(Gitlab::ApplicationContext.current).to include(
'meta.project' => project_hook.project.full_path,
'meta.root_namespace' => project.root_ancestor.path,
'meta.related_class' => 'ProjectHook'
)
end
service_instance.async_execute
end
end
end
end