Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
f4251f2694
commit
8c826685ec
|
@ -92,3 +92,4 @@ webpack-dev-server.json
|
||||||
/.nvimrc
|
/.nvimrc
|
||||||
.solargraph.yml
|
.solargraph.yml
|
||||||
apollo.config.js
|
apollo.config.js
|
||||||
|
/tmp/matching_foss_tests.txt
|
||||||
|
|
|
@ -324,3 +324,25 @@ db:rollback geo:
|
||||||
- bundle exec rake geo:db:migrate
|
- bundle exec rake geo:db:migrate
|
||||||
# EE: default refs (MRs, master, schedules) jobs #
|
# EE: default refs (MRs, master, schedules) jobs #
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
|
##################################################
|
||||||
|
# EE: Canonical MR pipelines
|
||||||
|
rspec foss-impact:
|
||||||
|
extends:
|
||||||
|
- .rspec-base
|
||||||
|
- .as-if-foss
|
||||||
|
- .rails:rules:ee-mr-only
|
||||||
|
- .use-pg11
|
||||||
|
script:
|
||||||
|
- install_gitlab_gem
|
||||||
|
- run_timed_command "scripts/gitaly-test-build"
|
||||||
|
- run_timed_command "scripts/gitaly-test-spawn"
|
||||||
|
- source scripts/rspec_helpers.sh
|
||||||
|
- tooling/bin/find_foss_tests tmp/matching_foss_tests.txt
|
||||||
|
- rspec_simple_job "--tag ~quarantine --tag ~geo --tag ~level:migration $(cat tmp/matching_foss_tests.txt)"
|
||||||
|
artifacts:
|
||||||
|
expire_in: 7d
|
||||||
|
paths:
|
||||||
|
- tmp/matching_foss_tests.txt
|
||||||
|
# EE: Merge Request pipelines
|
||||||
|
##################################################
|
||||||
|
|
|
@ -446,6 +446,15 @@
|
||||||
- <<: *if-master-refs
|
- <<: *if-master-refs
|
||||||
changes: *code-backstage-patterns
|
changes: *code-backstage-patterns
|
||||||
|
|
||||||
|
.rails:rules:ee-mr-only:
|
||||||
|
rules:
|
||||||
|
- <<: *if-not-ee
|
||||||
|
when: never
|
||||||
|
- <<: *if-security-merge-request
|
||||||
|
changes: *code-backstage-patterns
|
||||||
|
- <<: *if-dot-com-gitlab-org-merge-request
|
||||||
|
changes: *code-backstage-patterns
|
||||||
|
|
||||||
.rails:rules:downtime_check:
|
.rails:rules:downtime_check:
|
||||||
rules:
|
rules:
|
||||||
- <<: *if-merge-request
|
- <<: *if-merge-request
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
8.33.0
|
8.34.0
|
||||||
|
|
|
@ -41,7 +41,7 @@ export default {
|
||||||
variant="success"
|
variant="success"
|
||||||
@click="openFileUpload"
|
@click="openFileUpload"
|
||||||
>
|
>
|
||||||
{{ s__('DesignManagement|Add designs') }}
|
{{ s__('DesignManagement|Upload designs') }}
|
||||||
<gl-loading-icon v-if="isSaving" inline class="ml-1" />
|
<gl-loading-icon v-if="isSaving" inline class="ml-1" />
|
||||||
</gl-deprecated-button>
|
</gl-deprecated-button>
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
/* eslint-disable no-return-assign, consistent-return, class-methods-use-this */
|
/* eslint-disable no-return-assign, consistent-return, class-methods-use-this */
|
||||||
|
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import { escape, throttle } from 'lodash';
|
import { throttle } from 'lodash';
|
||||||
import { s__, __, sprintf } from '~/locale';
|
import { s__, __, sprintf } from '~/locale';
|
||||||
import { getIdenticonBackgroundClass, getIdenticonTitle } from '~/helpers/avatar_helper';
|
|
||||||
import axios from './lib/utils/axios_utils';
|
|
||||||
import {
|
import {
|
||||||
isInGroupsPage,
|
isInGroupsPage,
|
||||||
isInProjectPage,
|
isInProjectPage,
|
||||||
|
@ -67,15 +65,11 @@ function setSearchOptions() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SearchAutocomplete {
|
export class GlobalSearchInput {
|
||||||
constructor({ wrap, optsEl, autocompletePath, projectId, projectRef } = {}) {
|
constructor({ wrap } = {}) {
|
||||||
setSearchOptions();
|
setSearchOptions();
|
||||||
this.bindEventContext();
|
this.bindEventContext();
|
||||||
this.wrap = wrap || $('.search');
|
this.wrap = wrap || $('.search');
|
||||||
this.optsEl = optsEl || this.wrap.find('.search-autocomplete-opts');
|
|
||||||
this.autocompletePath = autocompletePath || this.optsEl.data('autocompletePath');
|
|
||||||
this.projectId = projectId || (this.optsEl.data('autocompleteProjectId') || '');
|
|
||||||
this.projectRef = projectRef || (this.optsEl.data('autocompleteProjectRef') || '');
|
|
||||||
this.dropdown = this.wrap.find('.dropdown');
|
this.dropdown = this.wrap.find('.dropdown');
|
||||||
this.dropdownToggle = this.wrap.find('.js-dropdown-search-toggle');
|
this.dropdownToggle = this.wrap.find('.js-dropdown-search-toggle');
|
||||||
this.dropdownMenu = this.dropdown.find('.dropdown-menu');
|
this.dropdownMenu = this.dropdown.find('.dropdown-menu');
|
||||||
|
@ -92,7 +86,7 @@ export class SearchAutocomplete {
|
||||||
|
|
||||||
// Only when user is logged in
|
// Only when user is logged in
|
||||||
if (gon.current_user_id) {
|
if (gon.current_user_id) {
|
||||||
this.createAutocomplete();
|
this.createGlobalSearchInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.bindEvents();
|
this.bindEvents();
|
||||||
|
@ -117,7 +111,7 @@ export class SearchAutocomplete {
|
||||||
return (this.originalState = this.serializeState());
|
return (this.originalState = this.serializeState());
|
||||||
}
|
}
|
||||||
|
|
||||||
createAutocomplete() {
|
createGlobalSearchInput() {
|
||||||
return this.searchInput.glDropdown({
|
return this.searchInput.glDropdown({
|
||||||
filterInputBlur: false,
|
filterInputBlur: false,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
|
@ -149,116 +143,17 @@ export class SearchAutocomplete {
|
||||||
if (glDropdownInstance) {
|
if (glDropdownInstance) {
|
||||||
glDropdownInstance.filter.options.callback(contents);
|
glDropdownInstance.filter.options.callback(contents);
|
||||||
}
|
}
|
||||||
this.enableAutocomplete();
|
this.enableDropdown();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent multiple ajax calls
|
const options = this.scopedSearchOptions(term);
|
||||||
if (this.loadingSuggestions) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.loadingSuggestions = true;
|
callback(options);
|
||||||
|
|
||||||
return axios
|
this.highlightFirstRow();
|
||||||
.get(this.autocompletePath, {
|
this.setScrollFade();
|
||||||
params: {
|
|
||||||
project_id: this.projectId,
|
|
||||||
project_ref: this.projectRef,
|
|
||||||
term,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
const options = this.scopedSearchOptions(term);
|
|
||||||
|
|
||||||
// List results
|
|
||||||
let lastCategory = null;
|
|
||||||
for (let i = 0, len = response.data.length; i < len; i += 1) {
|
|
||||||
const suggestion = response.data[i];
|
|
||||||
// Add group header before list each group
|
|
||||||
if (lastCategory !== suggestion.category) {
|
|
||||||
options.push({ type: 'separator' });
|
|
||||||
options.push({
|
|
||||||
type: 'header',
|
|
||||||
content: suggestion.category,
|
|
||||||
});
|
|
||||||
lastCategory = suggestion.category;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the suggestion
|
|
||||||
options.push({
|
|
||||||
id: `${suggestion.category.toLowerCase()}-${suggestion.id}`,
|
|
||||||
icon: this.getAvatar(suggestion),
|
|
||||||
category: suggestion.category,
|
|
||||||
text: suggestion.label,
|
|
||||||
url: suggestion.url,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(options);
|
|
||||||
|
|
||||||
this.loadingSuggestions = false;
|
|
||||||
this.highlightFirstRow();
|
|
||||||
this.setScrollFade();
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
this.loadingSuggestions = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getCategoryContents() {
|
|
||||||
const userName = gon.current_username;
|
|
||||||
const { projectOptions, groupOptions, dashboardOptions } = gl;
|
|
||||||
|
|
||||||
// Get options
|
|
||||||
let options;
|
|
||||||
if (isInProjectPage() && projectOptions) {
|
|
||||||
options = projectOptions[getProjectSlug()];
|
|
||||||
} else if (isInGroupsPage() && groupOptions) {
|
|
||||||
options = groupOptions[getGroupSlug()];
|
|
||||||
} else if (dashboardOptions) {
|
|
||||||
options = dashboardOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { issuesPath, mrPath, name, issuesDisabled } = options;
|
|
||||||
const baseItems = [];
|
|
||||||
|
|
||||||
if (name) {
|
|
||||||
baseItems.push({
|
|
||||||
type: 'header',
|
|
||||||
content: `${name}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const issueItems = [
|
|
||||||
{
|
|
||||||
text: s__('SearchAutocomplete|Issues assigned to me'),
|
|
||||||
url: `${issuesPath}/?assignee_username=${userName}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: s__("SearchAutocomplete|Issues I've created"),
|
|
||||||
url: `${issuesPath}/?author_username=${userName}`,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const mergeRequestItems = [
|
|
||||||
{
|
|
||||||
text: s__('SearchAutocomplete|Merge requests assigned to me'),
|
|
||||||
url: `${mrPath}/?assignee_username=${userName}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: s__("SearchAutocomplete|Merge requests I've created"),
|
|
||||||
url: `${mrPath}/?author_username=${userName}`,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
let items;
|
|
||||||
if (issuesDisabled) {
|
|
||||||
items = baseItems.concat(mergeRequestItems);
|
|
||||||
} else {
|
|
||||||
items = baseItems.concat(...issueItems, ...mergeRequestItems);
|
|
||||||
}
|
|
||||||
return items;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add option to proceed with the search for each
|
// Add option to proceed with the search for each
|
||||||
|
@ -343,7 +238,7 @@ export class SearchAutocomplete {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
enableAutocomplete() {
|
enableDropdown() {
|
||||||
this.setScrollFade();
|
this.setScrollFade();
|
||||||
|
|
||||||
// No need to enable anything if user is not logged in
|
// No need to enable anything if user is not logged in
|
||||||
|
@ -360,7 +255,7 @@ export class SearchAutocomplete {
|
||||||
}
|
}
|
||||||
|
|
||||||
onSearchInputChange() {
|
onSearchInputChange() {
|
||||||
this.enableAutocomplete();
|
this.enableDropdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
onSearchInputKeyUp(e) {
|
onSearchInputKeyUp(e) {
|
||||||
|
@ -369,7 +264,7 @@ export class SearchAutocomplete {
|
||||||
this.restoreOriginalState();
|
this.restoreOriginalState();
|
||||||
break;
|
break;
|
||||||
case KEYCODE.ENTER:
|
case KEYCODE.ENTER:
|
||||||
this.disableAutocomplete();
|
this.disableDropdown();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
@ -422,7 +317,7 @@ export class SearchAutocomplete {
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
disableAutocomplete() {
|
disableDropdown() {
|
||||||
if (!this.searchInput.hasClass('js-autocomplete-disabled') && this.dropdown.hasClass('show')) {
|
if (!this.searchInput.hasClass('js-autocomplete-disabled') && this.dropdown.hasClass('show')) {
|
||||||
this.searchInput.addClass('js-autocomplete-disabled');
|
this.searchInput.addClass('js-autocomplete-disabled');
|
||||||
this.dropdownToggle.dropdown('toggle');
|
this.dropdownToggle.dropdown('toggle');
|
||||||
|
@ -438,16 +333,8 @@ export class SearchAutocomplete {
|
||||||
onClick(item, $el, e) {
|
onClick(item, $el, e) {
|
||||||
if (window.location.pathname.indexOf(item.url) !== -1) {
|
if (window.location.pathname.indexOf(item.url) !== -1) {
|
||||||
if (!e.metaKey) e.preventDefault();
|
if (!e.metaKey) e.preventDefault();
|
||||||
/* eslint-disable-next-line @gitlab/require-i18n-strings */
|
|
||||||
if (item.category === 'Projects') {
|
|
||||||
this.projectInputEl.val(item.id);
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line @gitlab/require-i18n-strings
|
|
||||||
if (item.category === 'Groups') {
|
|
||||||
this.groupInputEl.val(item.id);
|
|
||||||
}
|
|
||||||
$el.removeClass('is-active');
|
$el.removeClass('is-active');
|
||||||
this.disableAutocomplete();
|
this.disableDropdown();
|
||||||
return this.searchInput.val('').focus();
|
return this.searchInput.val('').focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -456,20 +343,58 @@ export class SearchAutocomplete {
|
||||||
this.searchInput.data('glDropdown').highlightRowAtIndex(null, 0);
|
this.searchInput.data('glDropdown').highlightRowAtIndex(null, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
getAvatar(item) {
|
getCategoryContents() {
|
||||||
if (!Object.hasOwnProperty.call(item, 'avatar_url')) {
|
const userName = gon.current_username;
|
||||||
return false;
|
const { projectOptions, groupOptions, dashboardOptions } = gl;
|
||||||
|
|
||||||
|
// Get options
|
||||||
|
let options;
|
||||||
|
if (isInProjectPage() && projectOptions) {
|
||||||
|
options = projectOptions[getProjectSlug()];
|
||||||
|
} else if (isInGroupsPage() && groupOptions) {
|
||||||
|
options = groupOptions[getGroupSlug()];
|
||||||
|
} else if (dashboardOptions) {
|
||||||
|
options = dashboardOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { label, id } = item;
|
const { issuesPath, mrPath, name, issuesDisabled } = options;
|
||||||
const avatarUrl = item.avatar_url;
|
const baseItems = [];
|
||||||
const avatar = avatarUrl
|
|
||||||
? `<img class="search-item-avatar" src="${avatarUrl}" />`
|
|
||||||
: `<div class="s16 avatar identicon ${getIdenticonBackgroundClass(id)}">${getIdenticonTitle(
|
|
||||||
escape(label),
|
|
||||||
)}</div>`;
|
|
||||||
|
|
||||||
return avatar;
|
if (name) {
|
||||||
|
baseItems.push({
|
||||||
|
type: 'header',
|
||||||
|
content: `${name}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const issueItems = [
|
||||||
|
{
|
||||||
|
text: s__('SearchAutocomplete|Issues assigned to me'),
|
||||||
|
url: `${issuesPath}/?assignee_username=${userName}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: s__("SearchAutocomplete|Issues I've created"),
|
||||||
|
url: `${issuesPath}/?author_username=${userName}`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const mergeRequestItems = [
|
||||||
|
{
|
||||||
|
text: s__('SearchAutocomplete|Merge requests assigned to me'),
|
||||||
|
url: `${mrPath}/?assignee_username=${userName}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: s__("SearchAutocomplete|Merge requests I've created"),
|
||||||
|
url: `${mrPath}/?author_username=${userName}`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let items;
|
||||||
|
if (issuesDisabled) {
|
||||||
|
items = baseItems.concat(mergeRequestItems);
|
||||||
|
} else {
|
||||||
|
items = baseItems.concat(...issueItems, ...mergeRequestItems);
|
||||||
|
}
|
||||||
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
isScrolledUp() {
|
isScrolledUp() {
|
||||||
|
@ -495,6 +420,6 @@ export class SearchAutocomplete {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function initSearchAutocomplete(opts) {
|
export default function initGlobalSearchInput(opts) {
|
||||||
return new SearchAutocomplete(opts);
|
return new GlobalSearchInput(opts);
|
||||||
}
|
}
|
|
@ -32,7 +32,7 @@ import initFrequentItemDropdowns from './frequent_items';
|
||||||
import initBreadcrumbs from './breadcrumb';
|
import initBreadcrumbs from './breadcrumb';
|
||||||
import initUsagePingConsent from './usage_ping_consent';
|
import initUsagePingConsent from './usage_ping_consent';
|
||||||
import initPerformanceBar from './performance_bar';
|
import initPerformanceBar from './performance_bar';
|
||||||
import initSearchAutocomplete from './search_autocomplete';
|
import initGlobalSearchInput from './global_search_input';
|
||||||
import GlFieldErrors from './gl_field_errors';
|
import GlFieldErrors from './gl_field_errors';
|
||||||
import initUserPopovers from './user_popovers';
|
import initUserPopovers from './user_popovers';
|
||||||
import initBroadcastNotifications from './broadcast_notification';
|
import initBroadcastNotifications from './broadcast_notification';
|
||||||
|
@ -110,7 +110,7 @@ function deferredInitialisation() {
|
||||||
initFrequentItemDropdowns();
|
initFrequentItemDropdowns();
|
||||||
initPersistentUserCallouts();
|
initPersistentUserCallouts();
|
||||||
|
|
||||||
if (document.querySelector('.search')) initSearchAutocomplete();
|
if (document.querySelector('.search')) initGlobalSearchInput();
|
||||||
|
|
||||||
addSelectOnFocusBehaviour('.js-select-on-focus');
|
addSelectOnFocusBehaviour('.js-select-on-focus');
|
||||||
|
|
||||||
|
|
|
@ -51,21 +51,6 @@ class SearchController < ApplicationController
|
||||||
render json: { count: count }
|
render json: { count: count }
|
||||||
end
|
end
|
||||||
|
|
||||||
# rubocop: disable CodeReuse/ActiveRecord
|
|
||||||
def autocomplete
|
|
||||||
term = params[:term]
|
|
||||||
|
|
||||||
if params[:project_id].present?
|
|
||||||
@project = Project.find_by(id: params[:project_id])
|
|
||||||
@project = nil unless can?(current_user, :read_project, @project)
|
|
||||||
end
|
|
||||||
|
|
||||||
@ref = params[:project_ref] if params[:project_ref].present?
|
|
||||||
|
|
||||||
render json: search_autocomplete_opts(term).to_json
|
|
||||||
end
|
|
||||||
# rubocop: enable CodeReuse/ActiveRecord
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def preload_method
|
def preload_method
|
||||||
|
|
|
@ -3,28 +3,6 @@
|
||||||
module SearchHelper
|
module SearchHelper
|
||||||
SEARCH_PERMITTED_PARAMS = [:search, :scope, :project_id, :group_id, :repository_ref, :snippets].freeze
|
SEARCH_PERMITTED_PARAMS = [:search, :scope, :project_id, :group_id, :repository_ref, :snippets].freeze
|
||||||
|
|
||||||
def search_autocomplete_opts(term)
|
|
||||||
return unless current_user
|
|
||||||
|
|
||||||
resources_results = [
|
|
||||||
groups_autocomplete(term),
|
|
||||||
projects_autocomplete(term)
|
|
||||||
].flatten
|
|
||||||
|
|
||||||
search_pattern = Regexp.new(Regexp.escape(term), "i")
|
|
||||||
|
|
||||||
generic_results = project_autocomplete + default_autocomplete + help_autocomplete
|
|
||||||
generic_results.concat(default_autocomplete_admin) if current_user.admin?
|
|
||||||
generic_results.select! { |result| result[:label] =~ search_pattern }
|
|
||||||
|
|
||||||
[
|
|
||||||
resources_results,
|
|
||||||
generic_results
|
|
||||||
].flatten.uniq do |item|
|
|
||||||
item[:label]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def search_entries_info(collection, scope, term)
|
def search_entries_info(collection, scope, term)
|
||||||
return if collection.to_a.empty?
|
return if collection.to_a.empty?
|
||||||
|
|
||||||
|
@ -95,91 +73,6 @@ module SearchHelper
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# Autocomplete results for various settings pages
|
|
||||||
def default_autocomplete
|
|
||||||
[
|
|
||||||
{ category: "Settings", label: _("User settings"), url: profile_path },
|
|
||||||
{ category: "Settings", label: _("SSH Keys"), url: profile_keys_path },
|
|
||||||
{ category: "Settings", label: _("Dashboard"), url: root_path }
|
|
||||||
]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Autocomplete results for settings pages, for admins
|
|
||||||
def default_autocomplete_admin
|
|
||||||
[
|
|
||||||
{ category: "Settings", label: _("Admin Section"), url: admin_root_path }
|
|
||||||
]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Autocomplete results for internal help pages
|
|
||||||
def help_autocomplete
|
|
||||||
[
|
|
||||||
{ category: "Help", label: _("API Help"), url: help_page_path("api/README") },
|
|
||||||
{ category: "Help", label: _("Markdown Help"), url: help_page_path("user/markdown") },
|
|
||||||
{ category: "Help", label: _("Permissions Help"), url: help_page_path("user/permissions") },
|
|
||||||
{ category: "Help", label: _("Public Access Help"), url: help_page_path("public_access/public_access") },
|
|
||||||
{ category: "Help", label: _("Rake Tasks Help"), url: help_page_path("raketasks/README") },
|
|
||||||
{ category: "Help", label: _("SSH Keys Help"), url: help_page_path("ssh/README") },
|
|
||||||
{ category: "Help", label: _("System Hooks Help"), url: help_page_path("system_hooks/system_hooks") },
|
|
||||||
{ category: "Help", label: _("Webhooks Help"), url: help_page_path("user/project/integrations/webhooks") },
|
|
||||||
{ category: "Help", label: _("Workflow Help"), url: help_page_path("workflow/README") }
|
|
||||||
]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Autocomplete results for the current project, if it's defined
|
|
||||||
def project_autocomplete
|
|
||||||
if @project && @project.repository.root_ref
|
|
||||||
ref = @ref || @project.repository.root_ref
|
|
||||||
|
|
||||||
[
|
|
||||||
{ category: "In this project", label: _("Files"), url: project_tree_path(@project, ref) },
|
|
||||||
{ category: "In this project", label: _("Commits"), url: project_commits_path(@project, ref) },
|
|
||||||
{ category: "In this project", label: _("Network"), url: project_network_path(@project, ref) },
|
|
||||||
{ category: "In this project", label: _("Graph"), url: project_graph_path(@project, ref) },
|
|
||||||
{ category: "In this project", label: _("Issues"), url: project_issues_path(@project) },
|
|
||||||
{ category: "In this project", label: _("Merge Requests"), url: project_merge_requests_path(@project) },
|
|
||||||
{ category: "In this project", label: _("Milestones"), url: project_milestones_path(@project) },
|
|
||||||
{ category: "In this project", label: _("Snippets"), url: project_snippets_path(@project) },
|
|
||||||
{ category: "In this project", label: _("Members"), url: project_project_members_path(@project) },
|
|
||||||
{ category: "In this project", label: _("Wiki"), url: project_wikis_path(@project) }
|
|
||||||
]
|
|
||||||
else
|
|
||||||
[]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Autocomplete results for the current user's groups
|
|
||||||
# rubocop: disable CodeReuse/ActiveRecord
|
|
||||||
def groups_autocomplete(term, limit = 5)
|
|
||||||
current_user.authorized_groups.order_id_desc.search(term).limit(limit).map do |group|
|
|
||||||
{
|
|
||||||
category: "Groups",
|
|
||||||
id: group.id,
|
|
||||||
label: "#{search_result_sanitize(group.full_name)}",
|
|
||||||
url: group_path(group),
|
|
||||||
avatar_url: group.avatar_url || ''
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
# rubocop: enable CodeReuse/ActiveRecord
|
|
||||||
|
|
||||||
# Autocomplete results for the current user's projects
|
|
||||||
# rubocop: disable CodeReuse/ActiveRecord
|
|
||||||
def projects_autocomplete(term, limit = 5)
|
|
||||||
current_user.authorized_projects.order_id_desc.search_by_title(term)
|
|
||||||
.sorted_by_stars_desc.non_archived.limit(limit).map do |p|
|
|
||||||
{
|
|
||||||
category: "Projects",
|
|
||||||
id: p.id,
|
|
||||||
value: "#{search_result_sanitize(p.name)}",
|
|
||||||
label: "#{search_result_sanitize(p.full_name)}",
|
|
||||||
url: project_path(p),
|
|
||||||
avatar_url: p.avatar_url || ''
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
# rubocop: enable CodeReuse/ActiveRecord
|
|
||||||
|
|
||||||
def search_result_sanitize(str)
|
def search_result_sanitize(str)
|
||||||
Sanitize.clean(str)
|
Sanitize.clean(str)
|
||||||
end
|
end
|
||||||
|
|
|
@ -149,7 +149,9 @@ class Event < ApplicationRecord
|
||||||
def visible_to_user?(user = nil)
|
def visible_to_user?(user = nil)
|
||||||
return false unless capability.present?
|
return false unless capability.present?
|
||||||
|
|
||||||
Ability.allowed?(user, capability, permission_object)
|
capability.all? do |rule|
|
||||||
|
Ability.allowed?(user, rule, permission_object)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def resource_parent
|
def resource_parent
|
||||||
|
@ -361,34 +363,30 @@ class Event < ApplicationRecord
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
# rubocop:disable Metrics/CyclomaticComplexity
|
|
||||||
# rubocop:disable Metrics/PerceivedComplexity
|
|
||||||
#
|
|
||||||
# TODO Refactor this method so we no longer need to disable the above cops
|
|
||||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/216879.
|
|
||||||
def capability
|
def capability
|
||||||
@capability ||= begin
|
@capability ||= begin
|
||||||
if push_action? || commit_note?
|
capabilities.flat_map do |ability, syms|
|
||||||
:download_code
|
if syms.any? { |sym| send(sym) } # rubocop: disable GitlabSecurity/PublicSend
|
||||||
elsif membership_changed? || created_project_action?
|
[ability]
|
||||||
:read_project
|
else
|
||||||
elsif issue? || issue_note?
|
[]
|
||||||
:read_issue
|
end
|
||||||
elsif merge_request? || merge_request_note?
|
end
|
||||||
:read_merge_request
|
end
|
||||||
elsif personal_snippet_note? || project_snippet_note?
|
end
|
||||||
:read_snippet
|
|
||||||
elsif milestone?
|
def capabilities
|
||||||
:read_milestone
|
{
|
||||||
elsif wiki_page?
|
download_code: %i[push_action? commit_note?],
|
||||||
:read_wiki
|
read_project: %i[membership_changed? created_project_action?],
|
||||||
elsif design_note? || design?
|
read_issue: %i[issue? issue_note?],
|
||||||
:read_design
|
read_merge_request: %i[merge_request? merge_request_note?],
|
||||||
end
|
read_snippet: %i[personal_snippet_note? project_snippet_note?],
|
||||||
end
|
read_milestone: %i[milestone?],
|
||||||
|
read_wiki: %i[wiki_page?],
|
||||||
|
read_design: %i[design_note? design?]
|
||||||
|
}
|
||||||
end
|
end
|
||||||
# rubocop:enable Metrics/CyclomaticComplexity
|
|
||||||
# rubocop:enable Metrics/PerceivedComplexity
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
|
|
@ -6,19 +6,18 @@ module AutoMerge
|
||||||
include MergeRequests::AssignsMergeParams
|
include MergeRequests::AssignsMergeParams
|
||||||
|
|
||||||
def execute(merge_request)
|
def execute(merge_request)
|
||||||
assign_allowed_merge_params(merge_request, params.merge(auto_merge_strategy: strategy))
|
ActiveRecord::Base.transaction do
|
||||||
|
register_auto_merge_parameters!(merge_request)
|
||||||
merge_request.auto_merge_enabled = true
|
yield if block_given?
|
||||||
merge_request.merge_user = current_user
|
end
|
||||||
|
|
||||||
return :failed unless merge_request.save
|
|
||||||
|
|
||||||
yield if block_given?
|
|
||||||
|
|
||||||
# Notify the event that auto merge is enabled or merge param is updated
|
# Notify the event that auto merge is enabled or merge param is updated
|
||||||
AutoMergeProcessWorker.perform_async(merge_request.id)
|
AutoMergeProcessWorker.perform_async(merge_request.id)
|
||||||
|
|
||||||
strategy.to_sym
|
strategy.to_sym
|
||||||
|
rescue => e
|
||||||
|
track_exception(e, merge_request)
|
||||||
|
:failed
|
||||||
end
|
end
|
||||||
|
|
||||||
def update(merge_request)
|
def update(merge_request)
|
||||||
|
@ -30,23 +29,27 @@ module AutoMerge
|
||||||
end
|
end
|
||||||
|
|
||||||
def cancel(merge_request)
|
def cancel(merge_request)
|
||||||
if clear_auto_merge_parameters(merge_request)
|
ActiveRecord::Base.transaction do
|
||||||
|
clear_auto_merge_parameters!(merge_request)
|
||||||
yield if block_given?
|
yield if block_given?
|
||||||
|
|
||||||
success
|
|
||||||
else
|
|
||||||
error("Can't cancel the automatic merge", 406)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
success
|
||||||
|
rescue => e
|
||||||
|
track_exception(e, merge_request)
|
||||||
|
error("Can't cancel the automatic merge", 406)
|
||||||
end
|
end
|
||||||
|
|
||||||
def abort(merge_request, reason)
|
def abort(merge_request, reason)
|
||||||
if clear_auto_merge_parameters(merge_request)
|
ActiveRecord::Base.transaction do
|
||||||
|
clear_auto_merge_parameters!(merge_request)
|
||||||
yield if block_given?
|
yield if block_given?
|
||||||
|
|
||||||
success
|
|
||||||
else
|
|
||||||
error("Can't abort the automatic merge", 406)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
success
|
||||||
|
rescue => e
|
||||||
|
track_exception(e, merge_request)
|
||||||
|
error("Can't abort the automatic merge", 406)
|
||||||
end
|
end
|
||||||
|
|
||||||
def available_for?(merge_request)
|
def available_for?(merge_request)
|
||||||
|
@ -65,7 +68,14 @@ module AutoMerge
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def clear_auto_merge_parameters(merge_request)
|
def register_auto_merge_parameters!(merge_request)
|
||||||
|
assign_allowed_merge_params(merge_request, params.merge(auto_merge_strategy: strategy))
|
||||||
|
merge_request.auto_merge_enabled = true
|
||||||
|
merge_request.merge_user = current_user
|
||||||
|
merge_request.save!
|
||||||
|
end
|
||||||
|
|
||||||
|
def clear_auto_merge_parameters!(merge_request)
|
||||||
merge_request.auto_merge_enabled = false
|
merge_request.auto_merge_enabled = false
|
||||||
merge_request.merge_user = nil
|
merge_request.merge_user = nil
|
||||||
|
|
||||||
|
@ -76,7 +86,11 @@ module AutoMerge
|
||||||
'auto_merge_strategy'
|
'auto_merge_strategy'
|
||||||
)
|
)
|
||||||
|
|
||||||
merge_request.save
|
merge_request.save!
|
||||||
|
end
|
||||||
|
|
||||||
|
def track_exception(error, merge_request)
|
||||||
|
Gitlab::ErrorTracking.track_exception(error, merge_request_id: merge_request&.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -150,7 +150,7 @@ module Projects
|
||||||
|
|
||||||
if @project.save
|
if @project.save
|
||||||
unless @project.gitlab_project_import?
|
unless @project.gitlab_project_import?
|
||||||
create_services_from_active_templates(@project)
|
create_services_from_active_instances_or_templates(@project)
|
||||||
@project.create_labels
|
@project.create_labels
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -175,15 +175,6 @@ module Projects
|
||||||
@project
|
@project
|
||||||
end
|
end
|
||||||
|
|
||||||
# rubocop: disable CodeReuse/ActiveRecord
|
|
||||||
def create_services_from_active_templates(project)
|
|
||||||
Service.where(template: true, active: true).each do |template|
|
|
||||||
service = Service.build_from_integration(project.id, template)
|
|
||||||
service.save!
|
|
||||||
end
|
|
||||||
end
|
|
||||||
# rubocop: enable CodeReuse/ActiveRecord
|
|
||||||
|
|
||||||
def create_prometheus_service
|
def create_prometheus_service
|
||||||
service = @project.find_or_initialize_service(::PrometheusService.to_param)
|
service = @project.find_or_initialize_service(::PrometheusService.to_param)
|
||||||
|
|
||||||
|
@ -225,6 +216,15 @@ module Projects
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
# rubocop: disable CodeReuse/ActiveRecord
|
||||||
|
def create_services_from_active_instances_or_templates(project)
|
||||||
|
Service.active.where(instance: true).or(Service.active.where(template: true)).group_by(&:type).each do |type, records|
|
||||||
|
service = records.find(&:instance?) || records.find(&:template?)
|
||||||
|
Service.build_from_integration(project.id, service).save!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
# rubocop: enable CodeReuse/ActiveRecord
|
||||||
|
|
||||||
def project_namespace
|
def project_namespace
|
||||||
@project_namespace ||= Namespace.find_by_id(@params[:namespace_id]) || current_user.namespace
|
@project_namespace ||= Namespace.find_by_id(@params[:namespace_id]) || current_user.namespace
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
= form_tag search_path, method: :get, class: 'form-inline' do |f|
|
= form_tag search_path, method: :get, class: 'form-inline' do |f|
|
||||||
.search-input-container
|
.search-input-container
|
||||||
.search-input-wrap
|
.search-input-wrap
|
||||||
.dropdown{ data: { url: search_autocomplete_path } }
|
.dropdown
|
||||||
= search_field_tag 'search', nil, placeholder: _('Search or jump to…'),
|
= search_field_tag 'search', nil, placeholder: _('Search or jump to…'),
|
||||||
class: 'search-input dropdown-menu-toggle no-outline js-search-dashboard-options',
|
class: 'search-input dropdown-menu-toggle no-outline js-search-dashboard-options',
|
||||||
spellcheck: false,
|
spellcheck: false,
|
||||||
|
@ -37,6 +37,3 @@
|
||||||
-# workaround for non-JS feature specs, see spec/support/helpers/search_helpers.rb
|
-# workaround for non-JS feature specs, see spec/support/helpers/search_helpers.rb
|
||||||
- if ENV['RAILS_ENV'] == 'test'
|
- if ENV['RAILS_ENV'] == 'test'
|
||||||
%noscript= button_tag 'Search'
|
%noscript= button_tag 'Search'
|
||||||
.search-autocomplete-opts.hide{ :'data-autocomplete-path' => search_autocomplete_path,
|
|
||||||
:'data-autocomplete-project-id' => search_context.project.try(:id),
|
|
||||||
:'data-autocomplete-project-ref' => search_context.ref }
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Rename Add Designs button
|
||||||
|
merge_request: 33491
|
||||||
|
author:
|
||||||
|
type: changed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Remove all search autocomplete for groups/projects/other
|
||||||
|
merge_request: 31187
|
||||||
|
author:
|
||||||
|
type: removed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Wrap auto merge parameters update in database transaction
|
||||||
|
merge_request: 33471
|
||||||
|
author:
|
||||||
|
type: fixed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Update GitLab Workhorse to v8.34.0
|
||||||
|
merge_request: 33543
|
||||||
|
author:
|
||||||
|
type: fixed
|
|
@ -43,6 +43,7 @@
|
||||||
- digital_experience_management
|
- digital_experience_management
|
||||||
- disaster_recovery
|
- disaster_recovery
|
||||||
- dynamic_application_security_testing
|
- dynamic_application_security_testing
|
||||||
|
- editor_extension
|
||||||
- epics
|
- epics
|
||||||
- error_tracking
|
- error_tracking
|
||||||
- feature_flags
|
- feature_flags
|
||||||
|
@ -52,6 +53,7 @@
|
||||||
- geo_replication
|
- geo_replication
|
||||||
- git_lfs
|
- git_lfs
|
||||||
- gitaly
|
- gitaly
|
||||||
|
- gitlab_docs
|
||||||
- gitlab_handbook
|
- gitlab_handbook
|
||||||
- gitter
|
- gitter
|
||||||
- global_search
|
- global_search
|
||||||
|
@ -82,6 +84,7 @@
|
||||||
- pages
|
- pages
|
||||||
- pki_management
|
- pki_management
|
||||||
- planning_analytics
|
- planning_analytics
|
||||||
|
- product_analytics
|
||||||
- quality_management
|
- quality_management
|
||||||
- release_evidence
|
- release_evidence
|
||||||
- release_orchestration
|
- release_orchestration
|
||||||
|
@ -100,7 +103,6 @@
|
||||||
- source_code_management
|
- source_code_management
|
||||||
- static_application_security_testing
|
- static_application_security_testing
|
||||||
- static_site_editor
|
- static_site_editor
|
||||||
- status_page
|
|
||||||
- subgroups
|
- subgroups
|
||||||
- templates
|
- templates
|
||||||
- time_tracking
|
- time_tracking
|
||||||
|
|
|
@ -148,6 +148,7 @@ if Gitlab::Metrics.enabled? && !Rails.env.test? && !(Rails.env.development? && d
|
||||||
config.middleware.use(Gitlab::Metrics::RackMiddleware)
|
config.middleware.use(Gitlab::Metrics::RackMiddleware)
|
||||||
config.middleware.use(Gitlab::Middleware::RailsQueueDuration)
|
config.middleware.use(Gitlab::Middleware::RailsQueueDuration)
|
||||||
config.middleware.use(Gitlab::Metrics::RedisRackMiddleware)
|
config.middleware.use(Gitlab::Metrics::RedisRackMiddleware)
|
||||||
|
config.middleware.use(Gitlab::Metrics::ElasticsearchRackMiddleware)
|
||||||
end
|
end
|
||||||
|
|
||||||
Sidekiq.configure_server do |config|
|
Sidekiq.configure_server do |config|
|
||||||
|
|
|
@ -58,7 +58,6 @@ Rails.application.routes.draw do
|
||||||
|
|
||||||
# Search
|
# Search
|
||||||
get 'search' => 'search#show'
|
get 'search' => 'search#show'
|
||||||
get 'search/autocomplete' => 'search#autocomplete', as: :search_autocomplete
|
|
||||||
get 'search/count' => 'search#count', as: :search_count
|
get 'search/count' => 'search#count', as: :search_count
|
||||||
|
|
||||||
# JSON Web Token
|
# JSON Web Token
|
||||||
|
|
|
@ -94,6 +94,8 @@ The following metrics are available:
|
||||||
| `http_request_duration_seconds` | Histogram | 9.4 | HTTP response time from rack middleware | `method`, `status` |
|
| `http_request_duration_seconds` | Histogram | 9.4 | HTTP response time from rack middleware | `method`, `status` |
|
||||||
| `http_redis_requests_duration_seconds` | Histogram | 13.1 | Redis requests duration during web transactions | `controller`, `action` |
|
| `http_redis_requests_duration_seconds` | Histogram | 13.1 | Redis requests duration during web transactions | `controller`, `action` |
|
||||||
| `http_redis_requests_total` | Counter | 13.1 | Redis requests count during web transactions | `controller`, `action` |
|
| `http_redis_requests_total` | Counter | 13.1 | Redis requests count during web transactions | `controller`, `action` |
|
||||||
|
| `http_elasticsearch_requests_duration_seconds` **(STARTER)** | Histogram | 13.1 | Elasticsearch requests duration during web transactions | `controller`, `action` |
|
||||||
|
| `http_elasticsearch_requests_total` **(STARTER)** | Counter | 13.1 | Elasticsearch requests count during web transactions | `controller`, `action` |
|
||||||
| `pipelines_created_total` | Counter | 9.4 | Counter of pipelines created | |
|
| `pipelines_created_total` | Counter | 9.4 | Counter of pipelines created | |
|
||||||
| `rack_uncaught_errors_total` | Counter | 9.4 | Rack connections handling uncaught errors count | |
|
| `rack_uncaught_errors_total` | Counter | 9.4 | Rack connections handling uncaught errors count | |
|
||||||
| `user_session_logins_total` | Counter | 9.4 | Counter of how many users have logged in | |
|
| `user_session_logins_total` | Counter | 9.4 | Counter of how many users have logged in | |
|
||||||
|
|
|
@ -60,8 +60,10 @@ the following documents:
|
||||||
- [GitLab CI/CD basic workflow](introduction/index.md#basic-cicd-workflow).
|
- [GitLab CI/CD basic workflow](introduction/index.md#basic-cicd-workflow).
|
||||||
- [Step-by-step guide for writing `.gitlab-ci.yml` for the first time](../user/project/pages/getting_started_part_four.md).
|
- [Step-by-step guide for writing `.gitlab-ci.yml` for the first time](../user/project/pages/getting_started_part_four.md).
|
||||||
|
|
||||||
If you're coming over from Jenkins, you can also check out our handy [reference](jenkins/index.md)
|
If you're migrating from another CI/CD tool, check out our handy references:
|
||||||
for converting your pipelines.
|
|
||||||
|
- [Migrating from CircleCI](migration/circleci.md)
|
||||||
|
- [Migrating from Jenkins](jenkins/index.md)
|
||||||
|
|
||||||
You can also get started by using one of the
|
You can also get started by using one of the
|
||||||
[`.gitlab-ci.yml` templates](https://gitlab.com/gitlab-org/gitlab-foss/tree/master/lib/gitlab/ci/templates)
|
[`.gitlab-ci.yml` templates](https://gitlab.com/gitlab-org/gitlab-foss/tree/master/lib/gitlab/ci/templates)
|
||||||
|
|
|
@ -0,0 +1,332 @@
|
||||||
|
---
|
||||||
|
comments: false
|
||||||
|
type: index, howto
|
||||||
|
---
|
||||||
|
|
||||||
|
# Migrating from CircleCI
|
||||||
|
|
||||||
|
If you are currently using CircleCI, you can migrate your CI/CD pipelines to [GitLab CI/CD](../introduction/index.md),
|
||||||
|
and start making use of all its powerful features. Check out our
|
||||||
|
[CircleCI vs GitLab](https://about.gitlab.com/devops-tools/circle-ci-vs-gitlab.html)
|
||||||
|
comparison to see what's different.
|
||||||
|
|
||||||
|
We have collected several resources that you may find useful before starting to migrate.
|
||||||
|
|
||||||
|
The [Quick Start Guide](../quick_start/README.md) is a good overview of how GitLab CI/CD works. You may also be interested in [Auto DevOps](../../topics/autodevops/index.md) which can be used to build, test, and deploy your applications with little to no configuration needed at all.
|
||||||
|
|
||||||
|
For advanced CI/CD teams, [custom project templates](../../user/admin_area/custom_project_templates.md) can enable the reuse of pipeline configurations.
|
||||||
|
|
||||||
|
If you have questions that are not answered here, the [GitLab community forum](https://forum.gitlab.com/) can be a great resource.
|
||||||
|
|
||||||
|
## `config.yml` vs `gitlab-ci.yml`
|
||||||
|
|
||||||
|
CircleCI's `config.yml` configuration file defines scripts, jobs, and workflows (known as "stages" in GitLab). In GitLab, a similar approach is used with a `.gitlab-ci.yml` file in the root directory of your repository.
|
||||||
|
|
||||||
|
### Jobs
|
||||||
|
|
||||||
|
In CircleCI, jobs are a collection of steps to perform a specific task. In GitLab, [jobs](../yaml/README.md#introduction) are also a fundamental element in the configuration file. The `checkout` parameter is not necessary in GitLab CI/CD as the repository is automatically fetched.
|
||||||
|
|
||||||
|
CircleCI example job definition:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
jobs:
|
||||||
|
job1:
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run: "execute-script-for-job1"
|
||||||
|
```
|
||||||
|
|
||||||
|
Example of the same job definition in GitLab CI/CD:
|
||||||
|
|
||||||
|
``` yaml
|
||||||
|
job1:
|
||||||
|
script: "execute-script-for-job1"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker image definition
|
||||||
|
|
||||||
|
CircleCI defines images at the job level, which is also supported by GitLab CI/CD. Additionally, GitLab CI/CD supports setting this globally to be used by all jobs that don't have `image` defined.
|
||||||
|
|
||||||
|
CircleCI example image definition:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
jobs:
|
||||||
|
job1:
|
||||||
|
docker:
|
||||||
|
- image: ruby:2.6
|
||||||
|
```
|
||||||
|
|
||||||
|
Example of the same image definition in GitLab CI/CD:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
job1:
|
||||||
|
image: ruby:2.6
|
||||||
|
```
|
||||||
|
|
||||||
|
### Workflows
|
||||||
|
|
||||||
|
CircleCI determines the run order for jobs with `workflows`. This is also used to determine concurrent, sequential, scheduled, or manual runs. The equivalent function in GitLab CI/CD is called [stages](../yaml/README.md#stages). Jobs on the same stage run in parallel, and only run after previous stages complete. Execution of the next stage is skipped when a job fails by default, but this can be allowed to continue even [after a failed job](../yaml/README.md#allow_failure).
|
||||||
|
|
||||||
|
See [the Pipeline Architecture Overview](../pipelines/pipeline_architectures.md) for guidance on different types of pipelines that you can use. Pipelines can be tailored to meet your needs, such as for a large complex project or a monorepo with independent defined components.
|
||||||
|
|
||||||
|
#### Parallel and sequential job execution
|
||||||
|
|
||||||
|
The following examples show how jobs can run in parallel, or sequentially:
|
||||||
|
|
||||||
|
1. `job1` and `job2` run in parallel (in the `build` stage for GitLab CI/CD).
|
||||||
|
1. `job3` runs only after `job1` and `job2` complete successfully (in the `test` stage).
|
||||||
|
1. `job4` runs only after `job3` completes successfully (in the `deploy` stage).
|
||||||
|
|
||||||
|
CircleCI example with `workflows`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: 2
|
||||||
|
jobs:
|
||||||
|
job1:
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run: make build dependencies
|
||||||
|
job2:
|
||||||
|
steps:
|
||||||
|
- run: make build artifacts
|
||||||
|
job3:
|
||||||
|
steps:
|
||||||
|
- run: make test
|
||||||
|
job4:
|
||||||
|
steps:
|
||||||
|
- run: make deploy
|
||||||
|
|
||||||
|
workflows:
|
||||||
|
version: 2
|
||||||
|
jobs:
|
||||||
|
- job1
|
||||||
|
- job2
|
||||||
|
- job3:
|
||||||
|
requires:
|
||||||
|
- job1
|
||||||
|
- job2
|
||||||
|
- job4:
|
||||||
|
requires:
|
||||||
|
- job3
|
||||||
|
```
|
||||||
|
|
||||||
|
Example of the same workflow as `stages` in GitLab CI/CD:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
stages:
|
||||||
|
- build
|
||||||
|
- test
|
||||||
|
- deploy
|
||||||
|
|
||||||
|
job 1:
|
||||||
|
stage: build
|
||||||
|
script: make build dependencies
|
||||||
|
|
||||||
|
job 2:
|
||||||
|
stage: build
|
||||||
|
script: make build artifacts
|
||||||
|
|
||||||
|
job3:
|
||||||
|
stage: test
|
||||||
|
script: make test
|
||||||
|
|
||||||
|
job4:
|
||||||
|
stage: deploy
|
||||||
|
script: make deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Scheduled run
|
||||||
|
|
||||||
|
GitLab CI/CD has an easy to use UI to [schedule pipelines](../pipelines/schedules.md). Also, [rules](../yaml/README.md#rules) can be used to determine if jobs should be included or excluded from a scheduled pipeline.
|
||||||
|
|
||||||
|
CircleCI example of a scheduled workflow:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
commit-workflow:
|
||||||
|
jobs:
|
||||||
|
- build
|
||||||
|
scheduled-workflow:
|
||||||
|
triggers:
|
||||||
|
- schedule:
|
||||||
|
cron: "0 1 * * *"
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only: try-schedule-workflow
|
||||||
|
jobs:
|
||||||
|
- build
|
||||||
|
```
|
||||||
|
|
||||||
|
Example of the same scheduled pipeline using [`rules`](../yaml/README.md#rules) in GitLab CI/CD:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
job1:
|
||||||
|
script:
|
||||||
|
- make build
|
||||||
|
rules:
|
||||||
|
- if: '$CI_PIPELINE_SOURCE == "schedule" && $CI_COMMIT_REF_NAME == "try-schedule-workflow"'
|
||||||
|
```
|
||||||
|
|
||||||
|
After the pipeline configuration is saved, you configure the cron schedule in the [GitLab UI](../pipelines/schedules.md#configuring-pipeline-schedules), and can enable or disable schedules in the UI as well.
|
||||||
|
|
||||||
|
#### Manual run
|
||||||
|
|
||||||
|
CircleCI example of a manual workflow:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
release-branch-workflow:
|
||||||
|
jobs:
|
||||||
|
- build
|
||||||
|
- testing:
|
||||||
|
requires:
|
||||||
|
- build
|
||||||
|
- deploy:
|
||||||
|
type: approval
|
||||||
|
requires:
|
||||||
|
- testing
|
||||||
|
```
|
||||||
|
|
||||||
|
Example of the same workflow using [`when: manual`](../yaml/README.md#whenmanual) in GitLab CI/CD:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
deploy_prod:
|
||||||
|
stage: deploy
|
||||||
|
script:
|
||||||
|
- echo "Deploy to production server"
|
||||||
|
when: manual
|
||||||
|
```
|
||||||
|
|
||||||
|
### Filter job by branch
|
||||||
|
|
||||||
|
[Rules](../yaml/README.md#rules) are a mechanism to determine if the job will or will not run for a specific branch.
|
||||||
|
|
||||||
|
CircleCI example of a job filtered by branch:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
- /rc-.*/
|
||||||
|
```
|
||||||
|
|
||||||
|
Example of the same workflow using `rules` in GitLab CI/CD:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
deploy_prod:
|
||||||
|
stage: deploy
|
||||||
|
script:
|
||||||
|
- echo "Deploy to production server"
|
||||||
|
rules:
|
||||||
|
- if: '$CI_COMMIT_BRANCH == "master"'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Caching
|
||||||
|
|
||||||
|
GitLab provides a caching mechanism to speed up build times for your jobs by reusing previously downloaded dependencies. It's important to know the different between [cache and artifacts](../caching/index.md#cache-vs-artifacts) to make the best use of these features.
|
||||||
|
|
||||||
|
CircleCI example of a job using a cache:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
jobs:
|
||||||
|
job1:
|
||||||
|
steps:
|
||||||
|
- restore_cache:
|
||||||
|
key: source-v1-< .Revision >
|
||||||
|
- checkout
|
||||||
|
- run: npm install
|
||||||
|
- save_cache:
|
||||||
|
key: source-v1-< .Revision >
|
||||||
|
paths:
|
||||||
|
- "node_modules"
|
||||||
|
```
|
||||||
|
|
||||||
|
Example of the same pipeline using `cache` in GitLab CI/CD:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
image: node:latest
|
||||||
|
|
||||||
|
# Cache modules in between jobs
|
||||||
|
cache:
|
||||||
|
key: $CI_COMMIT_REF_SLUG
|
||||||
|
paths:
|
||||||
|
- .npm/
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- npm ci --cache .npm --prefer-offline
|
||||||
|
|
||||||
|
test_async:
|
||||||
|
script:
|
||||||
|
- node ./specs/start.js ./specs/async.spec.js
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contexts and variables
|
||||||
|
|
||||||
|
CircleCI provides [Contexts](https://circleci.com/docs/2.0/contexts/) to securely pass environment variables across project pipelines. In GitLab, a [Group](../../user/group/index.md) can be created to assemble related projects together. At the group level, [variables](../variables/README.md#group-level-environment-variables) can be stored outside the individual projects, and securely passed into pipelines across multiple projects.
|
||||||
|
|
||||||
|
## Orbs
|
||||||
|
|
||||||
|
There are two GitLab issues open addressing CircleCI Orbs and how GitLab can achieve similar functionality.
|
||||||
|
|
||||||
|
- <https://gitlab.com/gitlab-com/Product/-/issues/1151>
|
||||||
|
- <https://gitlab.com/gitlab-org/gitlab/-/issues/195173>
|
||||||
|
|
||||||
|
## Build environments
|
||||||
|
|
||||||
|
CircleCI offers `executors` as the underlying technology to run a specific job. In GitLab, this is done by [Runners](https://docs.gitlab.com/runner/).
|
||||||
|
|
||||||
|
The following environments are supported:
|
||||||
|
|
||||||
|
Self-Managed Runners:
|
||||||
|
|
||||||
|
- Linux
|
||||||
|
- Windows
|
||||||
|
- macOS
|
||||||
|
|
||||||
|
GitLab.com Shared Runners:
|
||||||
|
|
||||||
|
- Linux
|
||||||
|
- Windows
|
||||||
|
- [Planned: macOS](https://gitlab.com/gitlab-com/gl-infra/infrastructure/-/issues/5720)
|
||||||
|
|
||||||
|
### Machine and specific build environments
|
||||||
|
|
||||||
|
[Tags](../yaml/README.md#tags) can be used to run jobs on different platforms, by telling GitLab which Runners should run the jobs.
|
||||||
|
|
||||||
|
CircleCI example of a job running on a specific environment:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
jobs:
|
||||||
|
ubuntuJob:
|
||||||
|
machine:
|
||||||
|
image: ubuntu-1604:201903-01
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run: echo "Hello, $USER!"
|
||||||
|
osxJob:
|
||||||
|
macos:
|
||||||
|
xcode: 11.3.0
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run: echo "Hello, $USER!"
|
||||||
|
```
|
||||||
|
|
||||||
|
Example of the same job using `tags` in GitLab CI/CD:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
windows job:
|
||||||
|
stage:
|
||||||
|
- build
|
||||||
|
tags:
|
||||||
|
- windows
|
||||||
|
script:
|
||||||
|
- echo Hello, %USERNAME%!
|
||||||
|
|
||||||
|
osx job:
|
||||||
|
stage:
|
||||||
|
- build
|
||||||
|
tags:
|
||||||
|
- osx
|
||||||
|
script:
|
||||||
|
- echo "Hello, $USER!"
|
||||||
|
```
|
|
@ -22,6 +22,11 @@ This guide will help you get started with Git through the command line and can b
|
||||||
for Git commands in the future. If you're only looking for a quick reference of Git commands, you
|
for Git commands in the future. If you're only looking for a quick reference of Git commands, you
|
||||||
can download GitLab's [Git Cheat Sheet](https://about.gitlab.com/images/press/git-cheat-sheet.pdf).
|
can download GitLab's [Git Cheat Sheet](https://about.gitlab.com/images/press/git-cheat-sheet.pdf).
|
||||||
|
|
||||||
|
> For more information about the advantages of working with Git and GitLab:
|
||||||
|
>
|
||||||
|
> - Watch the [GitLab Source Code Management Walkthrough](https://www.youtube.com/watch?v=wTQ3aXJswtM) video.
|
||||||
|
> - Learn how GitLab became the backbone of [Worldline](https://about.gitlab.com/customers/worldline/)’s development environment.
|
||||||
|
|
||||||
TIP: **Tip:**
|
TIP: **Tip:**
|
||||||
To help you visualize what you're doing locally, there are
|
To help you visualize what you're doing locally, there are
|
||||||
[Git GUI apps](https://git-scm.com/download/gui/) you can install.
|
[Git GUI apps](https://git-scm.com/download/gui/) you can install.
|
||||||
|
|
|
@ -26,6 +26,11 @@ This means that until Git automatically cleans detached commits (which cannot be
|
||||||
accessed by branch or tag) it will be possible to view them with `git reflog` command
|
accessed by branch or tag) it will be possible to view them with `git reflog` command
|
||||||
and access them with direct commit ID. Read more about _[redoing the undo](#redoing-the-undo)_ in the section below.
|
and access them with direct commit ID. Read more about _[redoing the undo](#redoing-the-undo)_ in the section below.
|
||||||
|
|
||||||
|
> For more information about working with Git and GitLab:
|
||||||
|
>
|
||||||
|
> - Learn why [North Western Mutual chose GitLab](https://youtu.be/kPNMyxKRRoM) for their Enterprise source code management.
|
||||||
|
> - Learn how to [get started with Git](https://about.gitlab.com/resources/whitepaper-moving-to-git/).
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
This guide is organized depending on the [stage of development](https://git-scm.com/book/en/v2/Git-Basics-Recording-Changes-to-the-Repository)
|
This guide is organized depending on the [stage of development](https://git-scm.com/book/en/v2/Git-Basics-Recording-Changes-to-the-Repository)
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module Metrics
|
||||||
|
# Rack middleware for tracking Elasticsearch metrics from Grape and Web requests.
|
||||||
|
class ElasticsearchRackMiddleware
|
||||||
|
HISTOGRAM_BUCKETS = [0.1, 0.25, 0.5, 1, 2.5, 5, 10, 60].freeze
|
||||||
|
|
||||||
|
def initialize(app)
|
||||||
|
@app = app
|
||||||
|
|
||||||
|
@requests_total_counter = Gitlab::Metrics.counter(:http_elasticsearch_requests_total,
|
||||||
|
'Amount of calls to Elasticsearch servers during web requests',
|
||||||
|
Gitlab::Metrics::Transaction::BASE_LABELS)
|
||||||
|
@requests_duration_histogram = Gitlab::Metrics.histogram(:http_elasticsearch_requests_duration_seconds,
|
||||||
|
'Query time for Elasticsearch servers during web requests',
|
||||||
|
Gitlab::Metrics::Transaction::BASE_LABELS,
|
||||||
|
HISTOGRAM_BUCKETS)
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(env)
|
||||||
|
transaction = Gitlab::Metrics.current_transaction
|
||||||
|
|
||||||
|
@app.call(env)
|
||||||
|
ensure
|
||||||
|
record_metrics(transaction)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def record_metrics(transaction)
|
||||||
|
labels = transaction.labels
|
||||||
|
query_time = ::Gitlab::Instrumentation::ElasticsearchTransport.query_time
|
||||||
|
request_count = ::Gitlab::Instrumentation::ElasticsearchTransport.get_request_count
|
||||||
|
|
||||||
|
@requests_total_counter.increment(labels, request_count)
|
||||||
|
@requests_duration_histogram.observe(labels, query_time)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -6,6 +6,14 @@ module Gitlab
|
||||||
class RedisRackMiddleware
|
class RedisRackMiddleware
|
||||||
def initialize(app)
|
def initialize(app)
|
||||||
@app = app
|
@app = app
|
||||||
|
|
||||||
|
@requests_total_counter = Gitlab::Metrics.counter(:http_redis_requests_total,
|
||||||
|
'Amount of calls to Redis servers during web requests',
|
||||||
|
Gitlab::Metrics::Transaction::BASE_LABELS)
|
||||||
|
@requests_duration_histogram = Gitlab::Metrics.histogram(:http_redis_requests_duration_seconds,
|
||||||
|
'Query time for Redis servers during web requests',
|
||||||
|
Gitlab::Metrics::Transaction::BASE_LABELS,
|
||||||
|
Gitlab::Instrumentation::Redis::QUERY_TIME_BUCKETS)
|
||||||
end
|
end
|
||||||
|
|
||||||
def call(env)
|
def call(env)
|
||||||
|
@ -13,7 +21,7 @@ module Gitlab
|
||||||
|
|
||||||
@app.call(env)
|
@app.call(env)
|
||||||
ensure
|
ensure
|
||||||
record_metrics(transaction) if transaction
|
record_metrics(transaction)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -23,14 +31,8 @@ module Gitlab
|
||||||
query_time = Gitlab::Instrumentation::Redis.query_time
|
query_time = Gitlab::Instrumentation::Redis.query_time
|
||||||
request_count = Gitlab::Instrumentation::Redis.get_request_count
|
request_count = Gitlab::Instrumentation::Redis.get_request_count
|
||||||
|
|
||||||
Gitlab::Metrics.counter(:http_redis_requests_total,
|
@requests_total_counter.increment(labels, request_count)
|
||||||
'Amount of calls to Redis servers during web requests',
|
@requests_duration_histogram.observe(labels, query_time)
|
||||||
Gitlab::Metrics::Transaction::BASE_LABELS).increment(labels, request_count)
|
|
||||||
|
|
||||||
Gitlab::Metrics.histogram(:http_redis_requests_duration_seconds,
|
|
||||||
'Query time for Redis servers during web requests',
|
|
||||||
Gitlab::Metrics::Transaction::BASE_LABELS,
|
|
||||||
Gitlab::Instrumentation::Redis::QUERY_TIME_BUCKETS).observe(labels, query_time)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -79,6 +79,7 @@ module Gitlab
|
||||||
config[:'gitaly-ruby'] = { dir: File.join(gitaly_dir, 'ruby') } if gitaly_ruby
|
config[:'gitaly-ruby'] = { dir: File.join(gitaly_dir, 'ruby') } if gitaly_ruby
|
||||||
config[:'gitlab-shell'] = { dir: Gitlab.config.gitlab_shell.path }
|
config[:'gitlab-shell'] = { dir: Gitlab.config.gitlab_shell.path }
|
||||||
config[:bin_dir] = Gitlab.config.gitaly.client_path
|
config[:bin_dir] = Gitlab.config.gitaly.client_path
|
||||||
|
config[:gitlab] = { url: Gitlab.config.gitlab.url }
|
||||||
|
|
||||||
TomlRB.dump(config)
|
TomlRB.dump(config)
|
||||||
end
|
end
|
||||||
|
@ -97,7 +98,8 @@ module Gitlab
|
||||||
def configuration_toml(gitaly_dir, storage_paths)
|
def configuration_toml(gitaly_dir, storage_paths)
|
||||||
nodes = [{ storage: 'default', address: "unix:#{gitaly_dir}/gitaly.socket", primary: true, token: 'secret' }]
|
nodes = [{ storage: 'default', address: "unix:#{gitaly_dir}/gitaly.socket", primary: true, token: 'secret' }]
|
||||||
storages = [{ name: 'default', node: nodes }]
|
storages = [{ name: 'default', node: nodes }]
|
||||||
config = { socket_path: "#{gitaly_dir}/praefect.socket", memory_queue_enabled: true, virtual_storage: storages }
|
failover = { enabled: false }
|
||||||
|
config = { socket_path: "#{gitaly_dir}/praefect.socket", memory_queue_enabled: true, virtual_storage: storages, failover: failover }
|
||||||
config[:token] = 'secret' if Rails.env.test?
|
config[:token] = 'secret' if Rails.env.test?
|
||||||
|
|
||||||
TomlRB.dump(config)
|
TomlRB.dump(config)
|
||||||
|
|
|
@ -983,9 +983,6 @@ msgstr ""
|
||||||
msgid "ACTION REQUIRED: Something went wrong while obtaining the Let's Encrypt certificate for GitLab Pages domain '%{domain}'"
|
msgid "ACTION REQUIRED: Something went wrong while obtaining the Let's Encrypt certificate for GitLab Pages domain '%{domain}'"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "API Help"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "API Token"
|
msgid "API Token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -1441,9 +1438,6 @@ msgstr ""
|
||||||
msgid "Admin Overview"
|
msgid "Admin Overview"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Admin Section"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Admin mode already enabled"
|
msgid "Admin mode already enabled"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -7487,9 +7481,6 @@ msgstr ""
|
||||||
msgid "DesignManagement|%{filename} did not change."
|
msgid "DesignManagement|%{filename} did not change."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "DesignManagement|Add designs"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "DesignManagement|Adding a design with the same filename replaces the file in a new version."
|
msgid "DesignManagement|Adding a design with the same filename replaces the file in a new version."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -7574,6 +7565,9 @@ msgstr ""
|
||||||
msgid "DesignManagement|To enable design management, you'll need to %{requirements_link_start}meet the requirements%{requirements_link_end}. If you need help, reach out to our %{support_link_start}support team%{support_link_end} for assistance."
|
msgid "DesignManagement|To enable design management, you'll need to %{requirements_link_start}meet the requirements%{requirements_link_end}. If you need help, reach out to our %{support_link_start}support team%{support_link_end} for assistance."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "DesignManagement|Upload designs"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "DesignManagement|Upload skipped."
|
msgid "DesignManagement|Upload skipped."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -13336,9 +13330,6 @@ msgstr ""
|
||||||
msgid "Markdown"
|
msgid "Markdown"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Markdown Help"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Markdown enabled"
|
msgid "Markdown enabled"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -15654,9 +15645,6 @@ msgstr ""
|
||||||
msgid "Permissions"
|
msgid "Permissions"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Permissions Help"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Permissions, LFS, 2FA"
|
msgid "Permissions, LFS, 2FA"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -17685,9 +17673,6 @@ msgstr ""
|
||||||
msgid "Public - The project can be accessed without any authentication."
|
msgid "Public - The project can be accessed without any authentication."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Public Access Help"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Public deploy keys (%{deploy_keys_count})"
|
msgid "Public deploy keys (%{deploy_keys_count})"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -17817,9 +17802,6 @@ msgstr ""
|
||||||
msgid "README"
|
msgid "README"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Rake Tasks Help"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Raw blob request rate limit per minute"
|
msgid "Raw blob request rate limit per minute"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -18861,9 +18843,6 @@ msgstr ""
|
||||||
msgid "SSH Keys"
|
msgid "SSH Keys"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "SSH Keys Help"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "SSH host key fingerprints"
|
msgid "SSH host key fingerprints"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -21343,9 +21322,6 @@ msgstr ""
|
||||||
msgid "System Hooks"
|
msgid "System Hooks"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "System Hooks Help"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "System Info"
|
msgid "System Info"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -23975,9 +23951,6 @@ msgstr ""
|
||||||
msgid "User restrictions"
|
msgid "User restrictions"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "User settings"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "User was successfully created."
|
msgid "User was successfully created."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -24789,9 +24762,6 @@ msgstr ""
|
||||||
msgid "Webhooks"
|
msgid "Webhooks"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Webhooks Help"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
|
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -25058,9 +25028,6 @@ msgstr ""
|
||||||
msgid "Work in progress Limit"
|
msgid "Work in progress Limit"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Workflow Help"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Write"
|
msgid "Write"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rubocop/rspec/final_end_location'
|
|
||||||
require 'rubocop/rspec/blank_line_separation'
|
|
||||||
require 'rubocop/rspec/language'
|
|
||||||
|
|
||||||
module RuboCop
|
|
||||||
module Cop
|
|
||||||
module RSpec
|
|
||||||
# Checks if there is an empty line after shared example blocks.
|
|
||||||
#
|
|
||||||
# @example
|
|
||||||
# # bad
|
|
||||||
# RSpec.describe Foo do
|
|
||||||
# it_behaves_like 'do this first'
|
|
||||||
# it_behaves_like 'does this' do
|
|
||||||
# end
|
|
||||||
# it_behaves_like 'does that' do
|
|
||||||
# end
|
|
||||||
# it_behaves_like 'do some more'
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# # good
|
|
||||||
# RSpec.describe Foo do
|
|
||||||
# it_behaves_like 'do this first'
|
|
||||||
# it_behaves_like 'does this' do
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# it_behaves_like 'does that' do
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# it_behaves_like 'do some more'
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# # fair - it's ok to have non-separated without blocks
|
|
||||||
# RSpec.describe Foo do
|
|
||||||
# it_behaves_like 'do this first'
|
|
||||||
# it_behaves_like 'does this'
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
class EmptyLineAfterSharedExample < RuboCop::Cop::Cop
|
|
||||||
include RuboCop::RSpec::BlankLineSeparation
|
|
||||||
include RuboCop::RSpec::Language
|
|
||||||
|
|
||||||
MSG = 'Add an empty line after `%<example>s` block.'
|
|
||||||
|
|
||||||
def_node_matcher :shared_examples,
|
|
||||||
(SharedGroups::ALL + Includes::ALL).block_pattern
|
|
||||||
|
|
||||||
def on_block(node)
|
|
||||||
shared_examples(node) do
|
|
||||||
break if last_child?(node)
|
|
||||||
|
|
||||||
missing_separating_line(node) do |location|
|
|
||||||
add_offense(node,
|
|
||||||
location: location,
|
|
||||||
message: format(MSG, example: node.method_name))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -14,6 +14,7 @@ class GitalyTestBuild
|
||||||
def run
|
def run
|
||||||
abort 'gitaly build failed' unless system(env, 'make', chdir: tmp_tests_gitaly_dir)
|
abort 'gitaly build failed' unless system(env, 'make', chdir: tmp_tests_gitaly_dir)
|
||||||
|
|
||||||
|
ensure_gitlab_shell_secret!
|
||||||
check_gitaly_config!
|
check_gitaly_config!
|
||||||
|
|
||||||
# Starting gitaly further validates its configuration
|
# Starting gitaly further validates its configuration
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
# Please be careful when modifying this file. Your changes must work
|
# Please be careful when modifying this file. Your changes must work
|
||||||
# both for local development rspec runs, and in CI.
|
# both for local development rspec runs, and in CI.
|
||||||
|
|
||||||
|
require 'securerandom'
|
||||||
require 'socket'
|
require 'socket'
|
||||||
|
|
||||||
module GitalyTest
|
module GitalyTest
|
||||||
|
@ -11,10 +12,22 @@ module GitalyTest
|
||||||
File.expand_path('../tmp/tests/gitaly', __dir__)
|
File.expand_path('../tmp/tests/gitaly', __dir__)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def tmp_tests_gitlab_shell_dir
|
||||||
|
File.expand_path('../tmp/tests/gitlab-shell', __dir__)
|
||||||
|
end
|
||||||
|
|
||||||
|
def rails_gitlab_shell_secret
|
||||||
|
File.expand_path('../.gitlab_shell_secret', __dir__)
|
||||||
|
end
|
||||||
|
|
||||||
def gemfile
|
def gemfile
|
||||||
File.join(tmp_tests_gitaly_dir, 'ruby', 'Gemfile')
|
File.join(tmp_tests_gitaly_dir, 'ruby', 'Gemfile')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def gitlab_shell_secret_file
|
||||||
|
File.join(tmp_tests_gitlab_shell_dir, '.gitlab_shell_secret')
|
||||||
|
end
|
||||||
|
|
||||||
def env
|
def env
|
||||||
env_hash = {
|
env_hash = {
|
||||||
'HOME' => File.expand_path('tmp/tests'),
|
'HOME' => File.expand_path('tmp/tests'),
|
||||||
|
@ -70,6 +83,20 @@ module GitalyTest
|
||||||
pid
|
pid
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Taken from Gitlab::Shell.generate_and_link_secret_token
|
||||||
|
def ensure_gitlab_shell_secret!
|
||||||
|
secret_file = rails_gitlab_shell_secret
|
||||||
|
shell_link = gitlab_shell_secret_file
|
||||||
|
|
||||||
|
unless File.size?(secret_file)
|
||||||
|
File.write(secret_file, SecureRandom.hex(16))
|
||||||
|
end
|
||||||
|
|
||||||
|
unless File.exist?(shell_link)
|
||||||
|
FileUtils.ln_s(secret_file, shell_link)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def check_gitaly_config!
|
def check_gitaly_config!
|
||||||
puts "Checking gitaly-ruby Gemfile..."
|
puts "Checking gitaly-ruby Gemfile..."
|
||||||
|
|
||||||
|
|
|
@ -211,9 +211,4 @@ describe SearchController do
|
||||||
end.to raise_error(ActionController::ParameterMissing)
|
end.to raise_error(ActionController::ParameterMissing)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'GET #autocomplete' do
|
|
||||||
it_behaves_like 'when the user cannot read cross project', :autocomplete, { term: 'hello' }
|
|
||||||
it_behaves_like 'with external authorization service enabled', :autocomplete, { term: 'hello' }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,4 +13,6 @@ require 'active_support/all'
|
||||||
|
|
||||||
ActiveSupport::Dependencies.autoload_paths << 'lib'
|
ActiveSupport::Dependencies.autoload_paths << 'lib'
|
||||||
ActiveSupport::Dependencies.autoload_paths << 'ee/lib'
|
ActiveSupport::Dependencies.autoload_paths << 'ee/lib'
|
||||||
|
ActiveSupport::Dependencies.autoload_paths << 'tooling/lib'
|
||||||
|
|
||||||
ActiveSupport::XmlMini.backend = 'Nokogiri'
|
ActiveSupport::XmlMini.backend = 'Nokogiri'
|
||||||
|
|
|
@ -10,7 +10,7 @@ exports[`Design management upload button component renders inverted upload desig
|
||||||
variant="success"
|
variant="success"
|
||||||
>
|
>
|
||||||
|
|
||||||
Add designs
|
Upload designs
|
||||||
|
|
||||||
<!---->
|
<!---->
|
||||||
</gl-deprecated-button-stub>
|
</gl-deprecated-button-stub>
|
||||||
|
@ -34,7 +34,7 @@ exports[`Design management upload button component renders loading icon 1`] = `
|
||||||
variant="success"
|
variant="success"
|
||||||
>
|
>
|
||||||
|
|
||||||
Add designs
|
Upload designs
|
||||||
|
|
||||||
<gl-loading-icon-stub
|
<gl-loading-icon-stub
|
||||||
class="ml-1"
|
class="ml-1"
|
||||||
|
@ -63,7 +63,7 @@ exports[`Design management upload button component renders upload design button
|
||||||
variant="success"
|
variant="success"
|
||||||
>
|
>
|
||||||
|
|
||||||
Add designs
|
Upload designs
|
||||||
|
|
||||||
<!---->
|
<!---->
|
||||||
</gl-deprecated-button-stub>
|
</gl-deprecated-button-stub>
|
||||||
|
|
|
@ -8,99 +8,6 @@ describe SearchHelper do
|
||||||
str
|
str
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'search_autocomplete_opts' do
|
|
||||||
context "with no current user" do
|
|
||||||
before do
|
|
||||||
allow(self).to receive(:current_user).and_return(nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns nil" do
|
|
||||||
expect(search_autocomplete_opts("q")).to be_nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "with a standard user" do
|
|
||||||
let(:user) { create(:user) }
|
|
||||||
|
|
||||||
before do
|
|
||||||
allow(self).to receive(:current_user).and_return(user)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "includes Help sections" do
|
|
||||||
expect(search_autocomplete_opts("hel").size).to eq(9)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "includes default sections" do
|
|
||||||
expect(search_autocomplete_opts("dash").size).to eq(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "does not include admin sections" do
|
|
||||||
expect(search_autocomplete_opts("admin").size).to eq(0)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "does not allow regular expression in search term" do
|
|
||||||
expect(search_autocomplete_opts("(webhooks|api)").size).to eq(0)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "includes the user's groups" do
|
|
||||||
create(:group).add_owner(user)
|
|
||||||
expect(search_autocomplete_opts("gro").size).to eq(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "includes nested group" do
|
|
||||||
create(:group, :nested, name: 'foo').add_owner(user)
|
|
||||||
expect(search_autocomplete_opts('foo').size).to eq(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "includes the user's projects" do
|
|
||||||
project = create(:project, namespace: create(:namespace, owner: user))
|
|
||||||
expect(search_autocomplete_opts(project.name).size).to eq(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "includes the required project attrs" do
|
|
||||||
project = create(:project, namespace: create(:namespace, owner: user))
|
|
||||||
result = search_autocomplete_opts(project.name).first
|
|
||||||
|
|
||||||
expect(result.keys).to match_array(%i[category id value label url avatar_url])
|
|
||||||
end
|
|
||||||
|
|
||||||
it "includes the required group attrs" do
|
|
||||||
create(:group).add_owner(user)
|
|
||||||
result = search_autocomplete_opts("gro").first
|
|
||||||
|
|
||||||
expect(result.keys).to match_array(%i[category id label url avatar_url])
|
|
||||||
end
|
|
||||||
|
|
||||||
it "does not include the public group" do
|
|
||||||
group = create(:group)
|
|
||||||
expect(search_autocomplete_opts(group.name).size).to eq(0)
|
|
||||||
end
|
|
||||||
|
|
||||||
context "with a current project" do
|
|
||||||
before do
|
|
||||||
@project = create(:project, :repository)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "includes project-specific sections" do
|
|
||||||
expect(search_autocomplete_opts("Files").size).to eq(1)
|
|
||||||
expect(search_autocomplete_opts("Commits").size).to eq(1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with an admin user' do
|
|
||||||
let(:admin) { create(:admin) }
|
|
||||||
|
|
||||||
before do
|
|
||||||
allow(self).to receive(:current_user).and_return(admin)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "includes admin sections" do
|
|
||||||
expect(search_autocomplete_opts("admin").size).to eq(1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'search_entries_info' do
|
describe 'search_entries_info' do
|
||||||
using RSpec::Parameterized::TableSyntax
|
using RSpec::Parameterized::TableSyntax
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
|
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import '~/gl_dropdown';
|
import '~/gl_dropdown';
|
||||||
import initSearchAutocomplete from '~/search_autocomplete';
|
import initGlobalSearchInput from '~/global_search_input';
|
||||||
import '~/lib/utils/common_utils';
|
import '~/lib/utils/common_utils';
|
||||||
|
|
||||||
describe('Search autocomplete dropdown', () => {
|
describe('Global search input dropdown', () => {
|
||||||
let widget = null;
|
let widget = null;
|
||||||
|
|
||||||
const userName = 'root';
|
const userName = 'root';
|
||||||
|
@ -112,15 +112,15 @@ describe('Search autocomplete dropdown', () => {
|
||||||
expect(list.find(mrsIHaveCreatedLink).text()).toBe("Merge requests I've created");
|
expect(list.find(mrsIHaveCreatedLink).text()).toBe("Merge requests I've created");
|
||||||
};
|
};
|
||||||
|
|
||||||
preloadFixtures('static/search_autocomplete.html');
|
preloadFixtures('static/global_search_input.html');
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
loadFixtures('static/search_autocomplete.html');
|
loadFixtures('static/global_search_input.html');
|
||||||
|
|
||||||
window.gon = {};
|
window.gon = {};
|
||||||
window.gon.current_user_id = userId;
|
window.gon.current_user_id = userId;
|
||||||
window.gon.current_username = userName;
|
window.gon.current_username = userName;
|
||||||
|
|
||||||
return (widget = initSearchAutocomplete());
|
return (widget = initGlobalSearchInput());
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
|
@ -189,25 +189,25 @@ describe('Search autocomplete dropdown', () => {
|
||||||
expect(submitSpy).not.toHaveBeenTriggered();
|
expect(submitSpy).not.toHaveBeenTriggered();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('disableAutocomplete', function() {
|
describe('disableDropdown', function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
widget.enableAutocomplete();
|
widget.enableDropdown();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should close the Dropdown', function() {
|
it('should close the Dropdown', function() {
|
||||||
const toggleSpy = spyOn(widget.dropdownToggle, 'dropdown');
|
const toggleSpy = spyOn(widget.dropdownToggle, 'dropdown');
|
||||||
|
|
||||||
widget.dropdown.addClass('show');
|
widget.dropdown.addClass('show');
|
||||||
widget.disableAutocomplete();
|
widget.disableDropdown();
|
||||||
|
|
||||||
expect(toggleSpy).toHaveBeenCalledWith('toggle');
|
expect(toggleSpy).toHaveBeenCalledWith('toggle');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('enableAutocomplete', function() {
|
describe('enableDropdown', function() {
|
||||||
it('should open the Dropdown', function() {
|
it('should open the Dropdown', function() {
|
||||||
const toggleSpy = spyOn(widget.dropdownToggle, 'dropdown');
|
const toggleSpy = spyOn(widget.dropdownToggle, 'dropdown');
|
||||||
widget.enableAutocomplete();
|
widget.enableDropdown();
|
||||||
|
|
||||||
expect(toggleSpy).toHaveBeenCalledWith('toggle');
|
expect(toggleSpy).toHaveBeenCalledWith('toggle');
|
||||||
});
|
});
|
|
@ -0,0 +1,57 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Gitlab::Metrics::ElasticsearchRackMiddleware do
|
||||||
|
let(:app) { double(:app, call: 'app call result') }
|
||||||
|
let(:middleware) { described_class.new(app) }
|
||||||
|
let(:env) { {} }
|
||||||
|
let(:transaction) { Gitlab::Metrics::WebTransaction.new(env) }
|
||||||
|
|
||||||
|
describe '#call' do
|
||||||
|
let(:counter) { instance_double(Prometheus::Client::Counter, increment: nil) }
|
||||||
|
let(:histogram) { instance_double(Prometheus::Client::Histogram, observe: nil) }
|
||||||
|
let(:elasticsearch_query_time) { 0.1 }
|
||||||
|
let(:elasticsearch_requests_count) { 2 }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(Gitlab::Instrumentation::ElasticsearchTransport).to receive(:query_time) { elasticsearch_query_time }
|
||||||
|
allow(Gitlab::Instrumentation::ElasticsearchTransport).to receive(:get_request_count) { elasticsearch_requests_count }
|
||||||
|
|
||||||
|
allow(Gitlab::Metrics).to receive(:counter)
|
||||||
|
.with(:http_elasticsearch_requests_total,
|
||||||
|
an_instance_of(String),
|
||||||
|
Gitlab::Metrics::Transaction::BASE_LABELS)
|
||||||
|
.and_return(counter)
|
||||||
|
|
||||||
|
allow(Gitlab::Metrics).to receive(:histogram)
|
||||||
|
.with(:http_elasticsearch_requests_duration_seconds,
|
||||||
|
an_instance_of(String),
|
||||||
|
Gitlab::Metrics::Transaction::BASE_LABELS,
|
||||||
|
described_class::HISTOGRAM_BUCKETS)
|
||||||
|
.and_return(histogram)
|
||||||
|
|
||||||
|
allow(Gitlab::Metrics).to receive(:current_transaction).and_return(transaction)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'calls the app' do
|
||||||
|
expect(middleware.call(env)).to eq('app call result')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'records elasticsearch metrics' do
|
||||||
|
expect(counter).to receive(:increment).with(transaction.labels, elasticsearch_requests_count)
|
||||||
|
expect(histogram).to receive(:observe).with(transaction.labels, elasticsearch_query_time)
|
||||||
|
|
||||||
|
middleware.call(env)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'records elasticsearch metrics if an error is raised' do
|
||||||
|
expect(counter).to receive(:increment).with(transaction.labels, elasticsearch_requests_count)
|
||||||
|
expect(histogram).to receive(:observe).with(transaction.labels, elasticsearch_query_time)
|
||||||
|
|
||||||
|
allow(app).to receive(:call).with(env).and_raise(StandardError)
|
||||||
|
|
||||||
|
expect { middleware.call(env) }.to raise_error(StandardError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -13,68 +13,49 @@ describe Gitlab::Metrics::RedisRackMiddleware do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#call' do
|
describe '#call' do
|
||||||
context 'when metrics are disabled' do
|
let(:counter) { double(Prometheus::Client::Counter, increment: nil) }
|
||||||
before do
|
let(:histogram) { double(Prometheus::Client::Histogram, observe: nil) }
|
||||||
allow(Gitlab::Metrics).to receive(:current_transaction).and_return(nil)
|
let(:redis_query_time) { 0.1 }
|
||||||
end
|
let(:redis_requests_count) { 2 }
|
||||||
|
|
||||||
it 'calls the app' do
|
before do
|
||||||
expect(middleware.call(env)).to eq('wub wub')
|
allow(Gitlab::Instrumentation::Redis).to receive(:query_time) { redis_query_time }
|
||||||
end
|
allow(Gitlab::Instrumentation::Redis).to receive(:get_request_count) { redis_requests_count }
|
||||||
|
|
||||||
it 'does not record metrics' do
|
allow(Gitlab::Metrics).to receive(:counter)
|
||||||
expect(Gitlab::Metrics).not_to receive(:counter)
|
.with(:http_redis_requests_total,
|
||||||
expect(Gitlab::Metrics).not_to receive(:histogram)
|
an_instance_of(String),
|
||||||
|
Gitlab::Metrics::Transaction::BASE_LABELS)
|
||||||
|
.and_return(counter)
|
||||||
|
|
||||||
middleware.call(env)
|
allow(Gitlab::Metrics).to receive(:histogram)
|
||||||
end
|
.with(:http_redis_requests_duration_seconds,
|
||||||
|
an_instance_of(String),
|
||||||
|
Gitlab::Metrics::Transaction::BASE_LABELS,
|
||||||
|
Gitlab::Instrumentation::Redis::QUERY_TIME_BUCKETS)
|
||||||
|
.and_return(histogram)
|
||||||
|
|
||||||
|
allow(Gitlab::Metrics).to receive(:current_transaction).and_return(transaction)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when metrics are enabled' do
|
it 'calls the app' do
|
||||||
let(:counter) { double(Prometheus::Client::Counter, increment: nil) }
|
expect(middleware.call(env)).to eq('wub wub')
|
||||||
let(:histogram) { double(Prometheus::Client::Histogram, observe: nil) }
|
end
|
||||||
let(:redis_query_time) { 0.1 }
|
|
||||||
let(:redis_requests_count) { 2 }
|
|
||||||
|
|
||||||
before do
|
it 'records redis metrics' do
|
||||||
allow(Gitlab::Instrumentation::Redis).to receive(:query_time) { redis_query_time }
|
expect(counter).to receive(:increment).with(transaction.labels, redis_requests_count)
|
||||||
allow(Gitlab::Instrumentation::Redis).to receive(:get_request_count) { redis_requests_count }
|
expect(histogram).to receive(:observe).with(transaction.labels, redis_query_time)
|
||||||
|
|
||||||
allow(Gitlab::Metrics).to receive(:counter)
|
middleware.call(env)
|
||||||
.with(:http_redis_requests_total,
|
end
|
||||||
an_instance_of(String),
|
|
||||||
Gitlab::Metrics::Transaction::BASE_LABELS)
|
|
||||||
.and_return(counter)
|
|
||||||
|
|
||||||
allow(Gitlab::Metrics).to receive(:histogram)
|
it 'records redis metrics if an error is raised' do
|
||||||
.with(:http_redis_requests_duration_seconds,
|
expect(counter).to receive(:increment).with(transaction.labels, redis_requests_count)
|
||||||
an_instance_of(String),
|
expect(histogram).to receive(:observe).with(transaction.labels, redis_query_time)
|
||||||
Gitlab::Metrics::Transaction::BASE_LABELS,
|
|
||||||
Gitlab::Instrumentation::Redis::QUERY_TIME_BUCKETS)
|
|
||||||
.and_return(histogram)
|
|
||||||
|
|
||||||
allow(Gitlab::Metrics).to receive(:current_transaction).and_return(transaction)
|
allow(app).to receive(:call).with(env).and_raise(StandardError)
|
||||||
end
|
|
||||||
|
|
||||||
it 'calls the app' do
|
expect { middleware.call(env) }.to raise_error(StandardError)
|
||||||
expect(middleware.call(env)).to eq('wub wub')
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'records redis metrics' do
|
|
||||||
expect(counter).to receive(:increment).with(transaction.labels, redis_requests_count)
|
|
||||||
expect(histogram).to receive(:observe).with(transaction.labels, redis_query_time)
|
|
||||||
|
|
||||||
middleware.call(env)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'records redis metrics if an error is raised' do
|
|
||||||
expect(counter).to receive(:increment).with(transaction.labels, redis_requests_count)
|
|
||||||
expect(histogram).to receive(:observe).with(transaction.labels, redis_query_time)
|
|
||||||
|
|
||||||
allow(app).to receive(:call).with(env).and_raise(StandardError)
|
|
||||||
|
|
||||||
expect { middleware.call(env) }.to raise_error(StandardError)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,86 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'spec_helper'
|
|
||||||
|
|
||||||
require_relative '../../../../rubocop/cop/rspec/empty_line_after_shared_example'
|
|
||||||
|
|
||||||
describe RuboCop::Cop::RSpec::EmptyLineAfterSharedExample do
|
|
||||||
subject(:cop) { described_class.new }
|
|
||||||
|
|
||||||
it 'flags a missing empty line after `it_behaves_like` block' do
|
|
||||||
expect_offense(<<-RUBY)
|
|
||||||
RSpec.describe Foo do
|
|
||||||
it_behaves_like 'does this' do
|
|
||||||
end
|
|
||||||
^^^ Add an empty line after `it_behaves_like` block.
|
|
||||||
it_behaves_like 'does that' do
|
|
||||||
end
|
|
||||||
end
|
|
||||||
RUBY
|
|
||||||
|
|
||||||
expect_correction(<<-RUBY)
|
|
||||||
RSpec.describe Foo do
|
|
||||||
it_behaves_like 'does this' do
|
|
||||||
end
|
|
||||||
|
|
||||||
it_behaves_like 'does that' do
|
|
||||||
end
|
|
||||||
end
|
|
||||||
RUBY
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'ignores one-line shared examples before shared example blocks' do
|
|
||||||
expect_no_offenses(<<-RUBY)
|
|
||||||
RSpec.describe Foo do
|
|
||||||
it_behaves_like 'does this'
|
|
||||||
it_behaves_like 'does that' do
|
|
||||||
end
|
|
||||||
end
|
|
||||||
RUBY
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'flags a missing empty line after `shared_examples`' do
|
|
||||||
expect_offense(<<-RUBY)
|
|
||||||
RSpec.context 'foo' do
|
|
||||||
shared_examples do
|
|
||||||
end
|
|
||||||
^^^ Add an empty line after `shared_examples` block.
|
|
||||||
shared_examples 'something gets done' do
|
|
||||||
end
|
|
||||||
end
|
|
||||||
RUBY
|
|
||||||
|
|
||||||
expect_correction(<<-RUBY)
|
|
||||||
RSpec.context 'foo' do
|
|
||||||
shared_examples do
|
|
||||||
end
|
|
||||||
|
|
||||||
shared_examples 'something gets done' do
|
|
||||||
end
|
|
||||||
end
|
|
||||||
RUBY
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'ignores consecutive one-liners' do
|
|
||||||
expect_no_offenses(<<-RUBY)
|
|
||||||
RSpec.describe Foo do
|
|
||||||
it_behaves_like 'do this'
|
|
||||||
it_behaves_like 'do that'
|
|
||||||
end
|
|
||||||
RUBY
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'flags mixed one-line and multi-line shared examples' do
|
|
||||||
expect_offense(<<-RUBY)
|
|
||||||
RSpec.context 'foo' do
|
|
||||||
it_behaves_like 'do this'
|
|
||||||
it_behaves_like 'do that'
|
|
||||||
it_behaves_like 'does this' do
|
|
||||||
end
|
|
||||||
^^^ Add an empty line after `it_behaves_like` block.
|
|
||||||
it_behaves_like 'do this'
|
|
||||||
it_behaves_like 'do that'
|
|
||||||
end
|
|
||||||
RUBY
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -82,9 +82,9 @@ describe AutoMerge::BaseService do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when failed to save' do
|
context 'when failed to save merge request' do
|
||||||
before do
|
before do
|
||||||
allow(merge_request).to receive(:save) { false }
|
allow(merge_request).to receive(:save!) { raise ActiveRecord::RecordInvalid.new }
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not yield block' do
|
it 'does not yield block' do
|
||||||
|
@ -94,6 +94,39 @@ describe AutoMerge::BaseService do
|
||||||
it 'returns failed' do
|
it 'returns failed' do
|
||||||
is_expected.to eq(:failed)
|
is_expected.to eq(:failed)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'tracks the exception' do
|
||||||
|
expect(Gitlab::ErrorTracking)
|
||||||
|
.to receive(:track_exception).with(kind_of(ActiveRecord::RecordInvalid),
|
||||||
|
merge_request_id: merge_request.id)
|
||||||
|
|
||||||
|
subject
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when exception happens in yield block' do
|
||||||
|
def execute_with_error_in_yield
|
||||||
|
service.execute(merge_request) { raise 'Something went wrong' }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns failed status' do
|
||||||
|
expect(execute_with_error_in_yield).to eq(:failed)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'rollback the transaction' do
|
||||||
|
execute_with_error_in_yield
|
||||||
|
|
||||||
|
merge_request.reload
|
||||||
|
expect(merge_request).not_to be_auto_merge_enabled
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'tracks the exception' do
|
||||||
|
expect(Gitlab::ErrorTracking)
|
||||||
|
.to receive(:track_exception).with(kind_of(RuntimeError),
|
||||||
|
merge_request_id: merge_request.id)
|
||||||
|
|
||||||
|
execute_with_error_in_yield
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -162,7 +195,7 @@ describe AutoMerge::BaseService do
|
||||||
|
|
||||||
context 'when failed to save' do
|
context 'when failed to save' do
|
||||||
before do
|
before do
|
||||||
allow(merge_request).to receive(:save) { false }
|
allow(merge_request).to receive(:save!) { raise ActiveRecord::RecordInvalid.new }
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not yield block' do
|
it 'does not yield block' do
|
||||||
|
@ -178,9 +211,9 @@ describe AutoMerge::BaseService do
|
||||||
|
|
||||||
it_behaves_like 'Canceled or Dropped'
|
it_behaves_like 'Canceled or Dropped'
|
||||||
|
|
||||||
context 'when failed to save' do
|
context 'when failed to save merge request' do
|
||||||
before do
|
before do
|
||||||
allow(merge_request).to receive(:save) { false }
|
allow(merge_request).to receive(:save!) { raise ActiveRecord::RecordInvalid.new }
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns error status' do
|
it 'returns error status' do
|
||||||
|
@ -188,6 +221,33 @@ describe AutoMerge::BaseService do
|
||||||
expect(subject[:message]).to eq("Can't cancel the automatic merge")
|
expect(subject[:message]).to eq("Can't cancel the automatic merge")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when exception happens in yield block' do
|
||||||
|
def cancel_with_error_in_yield
|
||||||
|
service.cancel(merge_request) { raise 'Something went wrong' }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns error' do
|
||||||
|
result = cancel_with_error_in_yield
|
||||||
|
expect(result[:status]).to eq(:error)
|
||||||
|
expect(result[:message]).to eq("Can't cancel the automatic merge")
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'rollback the transaction' do
|
||||||
|
cancel_with_error_in_yield
|
||||||
|
|
||||||
|
merge_request.reload
|
||||||
|
expect(merge_request).to be_auto_merge_enabled
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'tracks the exception' do
|
||||||
|
expect(Gitlab::ErrorTracking)
|
||||||
|
.to receive(:track_exception).with(kind_of(RuntimeError),
|
||||||
|
merge_request_id: merge_request.id)
|
||||||
|
|
||||||
|
cancel_with_error_in_yield
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#abort' do
|
describe '#abort' do
|
||||||
|
@ -200,7 +260,7 @@ describe AutoMerge::BaseService do
|
||||||
|
|
||||||
context 'when failed to save' do
|
context 'when failed to save' do
|
||||||
before do
|
before do
|
||||||
allow(merge_request).to receive(:save) { false }
|
allow(merge_request).to receive(:save!) { raise ActiveRecord::RecordInvalid.new }
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns error status' do
|
it 'returns error status' do
|
||||||
|
@ -208,5 +268,32 @@ describe AutoMerge::BaseService do
|
||||||
expect(subject[:message]).to eq("Can't abort the automatic merge")
|
expect(subject[:message]).to eq("Can't abort the automatic merge")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when exception happens in yield block' do
|
||||||
|
def abort_with_error_in_yield
|
||||||
|
service.abort(merge_request, reason) { raise 'Something went wrong' }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns error' do
|
||||||
|
result = abort_with_error_in_yield
|
||||||
|
expect(result[:status]).to eq(:error)
|
||||||
|
expect(result[:message]).to eq("Can't abort the automatic merge")
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'rollback the transaction' do
|
||||||
|
abort_with_error_in_yield
|
||||||
|
|
||||||
|
merge_request.reload
|
||||||
|
expect(merge_request).to be_auto_merge_enabled
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'tracks the exception' do
|
||||||
|
expect(Gitlab::ErrorTracking)
|
||||||
|
.to receive(:track_exception).with(kind_of(RuntimeError),
|
||||||
|
merge_request_id: merge_request.id)
|
||||||
|
|
||||||
|
abort_with_error_in_yield
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -339,29 +339,40 @@ describe Projects::CreateService, '#execute' do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when there is an active service template' do
|
describe 'create service for the project' do
|
||||||
before do
|
subject(:project) { create_project(user, opts) }
|
||||||
create(:prometheus_service, project: nil, template: true, active: true)
|
|
||||||
|
context 'when there is an active instance-level and an active template integration' do
|
||||||
|
before do
|
||||||
|
create(:prometheus_service, :instance, api_url: 'https://prometheus.instance.com/')
|
||||||
|
create(:prometheus_service, :template, api_url: 'https://prometheus.template.com/')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates a service from the instance-level integration' do
|
||||||
|
expect(project.services.count).to eq(1)
|
||||||
|
expect(project.services.first.api_url).to eq('https://prometheus.instance.com/')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'creates a service from this template' do
|
context 'when there is an active service template' do
|
||||||
project = create_project(user, opts)
|
before do
|
||||||
|
create(:prometheus_service, :template, active: true)
|
||||||
|
end
|
||||||
|
|
||||||
expect(project.services.count).to eq 1
|
it 'creates a service from the template' do
|
||||||
expect(project.errors).to be_empty
|
expect(project.services.count).to eq(1)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
context 'when a bad service template is created' do
|
context 'when there is an invalid integration' do
|
||||||
it 'sets service to be inactive' do
|
before do
|
||||||
opts[:import_url] = 'http://www.gitlab.com/gitlab-org/gitlab-foss'
|
create(:service, :template, type: 'DroneCiService', active: true)
|
||||||
create(:service, type: 'DroneCiService', project: nil, template: true, active: true)
|
end
|
||||||
|
|
||||||
project = create_project(user, opts)
|
it 'creates an inactive service' do
|
||||||
service = project.services.first
|
expect(project).to be_persisted
|
||||||
|
expect(project.services.first.active).to be false
|
||||||
expect(project).to be_persisted
|
end
|
||||||
expect(service.active).to be false
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'fast_spec_helper'
|
||||||
|
|
||||||
|
describe Tooling::TestFileFinder do
|
||||||
|
subject { Tooling::TestFileFinder.new(file) }
|
||||||
|
|
||||||
|
describe '#test_files' do
|
||||||
|
context 'when given non .rb files' do
|
||||||
|
let(:file) { 'app/assets/images/emoji.png' }
|
||||||
|
|
||||||
|
it 'does not return a test file' do
|
||||||
|
expect(subject.test_files).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when given file in app/' do
|
||||||
|
let(:file) { 'app/finders/admin/projects_finder.rb' }
|
||||||
|
|
||||||
|
it 'returns the matching app spec file' do
|
||||||
|
expect(subject.test_files).to contain_exactly('spec/finders/admin/projects_finder_spec.rb')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when given file in lib/' do
|
||||||
|
let(:file) { 'lib/banzai/color_parser.rb' }
|
||||||
|
|
||||||
|
it 'returns the matching app spec file' do
|
||||||
|
expect(subject.test_files).to contain_exactly('spec/lib/banzai/color_parser_spec.rb')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when given a file in tooling/' do
|
||||||
|
let(:file) { 'tooling/lib/quality/test_file_finder.rb' }
|
||||||
|
|
||||||
|
it 'returns the matching tooling test' do
|
||||||
|
expect(subject.test_files).to contain_exactly('spec/tooling/lib/quality/test_file_finder_spec.rb')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when given a test file' do
|
||||||
|
let(:file) { 'spec/lib/banzai/color_parser_spec.rb' }
|
||||||
|
|
||||||
|
it 'returns the matching test file itself' do
|
||||||
|
expect(subject.test_files).to contain_exactly('spec/lib/banzai/color_parser_spec.rb')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when given an app file in ee/' do
|
||||||
|
let(:file) { 'ee/app/models/analytics/cycle_analytics/group_level.rb' }
|
||||||
|
|
||||||
|
it 'returns the matching ee/ test file' do
|
||||||
|
expect(subject.test_files).to contain_exactly('ee/spec/models/analytics/cycle_analytics/group_level_spec.rb')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when given a module file in ee/' do
|
||||||
|
let(:file) { 'ee/app/models/ee/user.rb' }
|
||||||
|
|
||||||
|
it 'returns the matching ee/ module test file and the ee/ model test file' do
|
||||||
|
test_files = ['ee/spec/models/ee/user_spec.rb', 'spec/app/models/user_spec.rb']
|
||||||
|
expect(subject.test_files).to contain_exactly(*test_files)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when given a lib file in ee/' do
|
||||||
|
let(:file) { 'ee/lib/flipper_session.rb' }
|
||||||
|
|
||||||
|
it 'returns the matching ee/ lib test file' do
|
||||||
|
expect(subject.test_files).to contain_exactly('ee/spec/lib/flipper_session_spec.rb')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when given a test file in ee/' do
|
||||||
|
let(:file) { 'ee/spec/models/container_registry/event_spec.rb' }
|
||||||
|
|
||||||
|
it 'returns the test file itself' do
|
||||||
|
expect(subject.test_files).to contain_exactly('ee/spec/models/container_registry/event_spec.rb')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when given a module test file in ee/' do
|
||||||
|
let(:file) { 'ee/spec/models/ee/appearance_spec.rb' }
|
||||||
|
|
||||||
|
it 'returns the matching module test file itself and the corresponding spec model test file' do
|
||||||
|
test_files = ['ee/spec/models/ee/appearance_spec.rb', 'spec/models/appearance_spec.rb']
|
||||||
|
expect(subject.test_files).to contain_exactly(*test_files)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with foss_test_only: true' do
|
||||||
|
subject { Tooling::TestFileFinder.new(file, foss_test_only: true) }
|
||||||
|
|
||||||
|
context 'when given a module file in ee/' do
|
||||||
|
let(:file) { 'ee/app/models/ee/user.rb' }
|
||||||
|
|
||||||
|
it 'returns only the corresponding spec model test file in foss' do
|
||||||
|
expect(subject.test_files).to contain_exactly('spec/app/models/user_spec.rb')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when given an app file in ee/' do
|
||||||
|
let(:file) { 'ee/app/models/approval.rb' }
|
||||||
|
|
||||||
|
it 'returns no test file in foss' do
|
||||||
|
expect(subject.test_files).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,29 @@
|
||||||
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative '../../lib/gitlab/popen'
|
||||||
|
require_relative '../lib/tooling/test_file_finder'
|
||||||
|
|
||||||
|
require 'gitlab'
|
||||||
|
|
||||||
|
gitlab_token = ENV.fetch('DANGER_GITLAB_API_TOKEN', '')
|
||||||
|
|
||||||
|
Gitlab.configure do |config|
|
||||||
|
config.endpoint = 'https://gitlab.com/api/v4'
|
||||||
|
config.private_token = gitlab_token
|
||||||
|
end
|
||||||
|
|
||||||
|
output_file = ARGV.shift
|
||||||
|
|
||||||
|
mr_project_path = ENV.fetch('CI_MERGE_REQUEST_PROJECT_PATH')
|
||||||
|
mr_iid = ENV.fetch('CI_MERGE_REQUEST_IID')
|
||||||
|
|
||||||
|
mr_changes = Gitlab.merge_request_changes(mr_project_path, mr_iid)
|
||||||
|
changed_files = mr_changes.changes.map { |change| change['new_path'] }
|
||||||
|
|
||||||
|
tests_to_run = changed_files.flat_map do |file|
|
||||||
|
test_files = Tooling::TestFileFinder.new(file, foss_test_only: true).test_files
|
||||||
|
test_files.select { |f| File.exist?(f) }
|
||||||
|
end
|
||||||
|
|
||||||
|
File.write(output_file, tests_to_run.uniq.join(' '))
|
|
@ -0,0 +1,78 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'ostruct'
|
||||||
|
require 'set'
|
||||||
|
|
||||||
|
module Tooling
|
||||||
|
class TestFileFinder
|
||||||
|
RUBY_EXTENSION = '.rb'
|
||||||
|
EE_PREFIX = 'ee/'
|
||||||
|
|
||||||
|
def initialize(file, foss_test_only: false)
|
||||||
|
@file = file
|
||||||
|
@foss_test_only = foss_test_only
|
||||||
|
@result = Set.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_files
|
||||||
|
contexts = [ee_context, foss_context]
|
||||||
|
contexts.flat_map do |context|
|
||||||
|
match_test_files_for(context)
|
||||||
|
end
|
||||||
|
|
||||||
|
result.to_a
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
attr_reader :file, :foss_test_only, :result
|
||||||
|
|
||||||
|
def ee_context
|
||||||
|
OpenStruct.new.tap do |ee|
|
||||||
|
ee.app = %r{^#{EE_PREFIX}app/(.+)\.rb$} unless foss_test_only
|
||||||
|
ee.lib = %r{^#{EE_PREFIX}lib/(.+)\.rb$} unless foss_test_only
|
||||||
|
ee.spec = %r{^#{EE_PREFIX}spec/(.+)_spec.rb$} unless foss_test_only
|
||||||
|
ee.spec_dir = "#{EE_PREFIX}spec" unless foss_test_only
|
||||||
|
ee.ee_modules = %r{^#{EE_PREFIX}(?!spec)(.*\/)ee/(.+)\.rb$}
|
||||||
|
ee.ee_module_spec = %r{^#{EE_PREFIX}spec/(.*\/)ee/(.+)\.rb$}
|
||||||
|
ee.foss_spec_dir = 'spec'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def foss_context
|
||||||
|
OpenStruct.new.tap do |foss|
|
||||||
|
foss.app = %r{^app/(.+)\.rb$}
|
||||||
|
foss.lib = %r{^lib/(.+)\.rb$}
|
||||||
|
foss.tooling = %r{^(tooling/lib/.+)\.rb$}
|
||||||
|
foss.spec = %r{^spec/(.+)_spec.rb$}
|
||||||
|
foss.spec_dir = 'spec'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def match_test_files_for(context)
|
||||||
|
if (match = context.app&.match(file))
|
||||||
|
result << "#{context.spec_dir}/#{match[1]}_spec.rb"
|
||||||
|
end
|
||||||
|
|
||||||
|
if (match = context.lib&.match(file))
|
||||||
|
result << "#{context.spec_dir}/lib/#{match[1]}_spec.rb"
|
||||||
|
end
|
||||||
|
|
||||||
|
if (match = context.tooling&.match(file))
|
||||||
|
result << "#{context.spec_dir}/#{match[1]}_spec.rb"
|
||||||
|
end
|
||||||
|
|
||||||
|
if context.spec&.match(file)
|
||||||
|
result << file
|
||||||
|
end
|
||||||
|
|
||||||
|
if (match = context.ee_modules&.match(file))
|
||||||
|
result << "#{context.foss_spec_dir}/#{match[1]}#{match[2]}_spec.rb"
|
||||||
|
end
|
||||||
|
|
||||||
|
if (match = context.ee_module_spec&.match(file))
|
||||||
|
result << "#{context.foss_spec_dir}/#{match[1]}#{match[2]}.rb"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue