Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
b794758ce4
commit
a32e60a7ea
48 changed files with 839 additions and 536 deletions
1
Gemfile
1
Gemfile
|
@ -341,7 +341,6 @@ group :metrics do
|
|||
|
||||
# Prometheus
|
||||
gem 'prometheus-client-mmap', '~> 0.12.0'
|
||||
gem 'raindrops', '~> 0.18'
|
||||
end
|
||||
|
||||
group :development do
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -120,7 +120,7 @@ export default {
|
|||
}, DEBOUNCE_DELAY);
|
||||
},
|
||||
handleTokenValueSelected(activeTokenValue) {
|
||||
if (this.isRecentTokenValuesEnabled) {
|
||||
if (this.isRecentTokenValuesEnabled && activeTokenValue) {
|
||||
setTokenValueToRecentlyUsed(this.recentTokenValuesStorageKey, activeTokenValue);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
13
app/helpers/keyset_helper.rb
Normal file
13
app/helpers/keyset_helper.rb
Normal 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
|
|
@ -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')
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
30
app/views/kaminari/gitlab/_keyset_paginator.html.haml
Normal file
30
app/views/kaminari/gitlab/_keyset_paginator.html.haml
Normal 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)
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Remove Unicorn Sampler
|
||||
merge_request: 62090
|
||||
author:
|
||||
type: removed
|
5
changelogs/unreleased/330737_fix_blob_preview.yml
Normal file
5
changelogs/unreleased/330737_fix_blob_preview.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix blob preview error
|
||||
merge_request: 62128
|
||||
author:
|
||||
type: fixed
|
5
changelogs/unreleased/dz-redirect-old-repo-routes.yml
Normal file
5
changelogs/unreleased/dz-redirect-old-repo-routes.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Redirect some of deprecated repository routes
|
||||
merge_request: 34867
|
||||
author:
|
||||
type: removed
|
5
changelogs/unreleased/handle-missing-branch-error.yml
Normal file
5
changelogs/unreleased/handle-missing-branch-error.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Return 404 from branches API when repository does not exist
|
||||
merge_request:
|
||||
author:
|
||||
type: fixed
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
12
config/initializers/active_record_keyset_pagination.rb
Normal file
12
config/initializers/active_record_keyset_pagination.rb
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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`.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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`
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
176
lib/gitlab/pagination/keyset/paginator.rb
Normal file
176
lib/gitlab/pagination/keyset/paginator.rb
Normal 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
|
|
@ -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?
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
94
spec/helpers/keyset_helper_spec.rb
Normal file
94
spec/helpers/keyset_helper_spec.rb
Normal 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
|
|
@ -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
|
120
spec/lib/gitlab/pagination/keyset/paginator_spec.rb
Normal file
120
spec/lib/gitlab/pagination/keyset/paginator_spec.rb
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue