Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
7fd99ae2a4
commit
8c2d06cba7
|
@ -6,7 +6,7 @@
|
|||
|
||||
### Checklist
|
||||
|
||||
- [ ] If your proposal includes changes to the top-level menu items within the left sidebar, engage the [Foundations Product Design Manager](https://about.gitlab.com/handbook/product/categories/#foundations-group) for approval. The Foundations DRI will work with UX partners in product design, research, and technical writing, as applicable.
|
||||
- [ ] If your proposal includes changes to the menu items within the left sidebar, engage the [Foundations Product Manager](https://about.gitlab.com/handbook/product/categories/#foundations-group) for approval. The Foundations DRI will work with UX partners in product design, research, and technical writing, as applicable.
|
||||
- [ ] Follow the [product development workflow](https://about.gitlab.com/handbook/product-development-flow/#validation-phase-2-problem-validation) validation process to ensure you are solving a well understood problem and that the proposed change is understandable and non-disruptive to users. Navigation-specific research is strongly encouraged.
|
||||
- [ ] Engage the [Foundations](https://about.gitlab.com/handbook/product/categories/#foundations-group) team to ensure your proposal is in alignment with holistic changes happening to the left side bar.
|
||||
- [ ] Consider whether you need to communicate the change somehow, or if you will have an interim period in the UI where your nav item will live in more than one place.
|
||||
|
|
|
@ -1 +1 @@
|
|||
1.61.1
|
||||
1.62.0
|
||||
|
|
|
@ -10,6 +10,7 @@ import Code from '../../extensions/code';
|
|||
import CodeBlockHighlight from '../../extensions/code_block_highlight';
|
||||
import Diagram from '../../extensions/diagram';
|
||||
import Frontmatter from '../../extensions/frontmatter';
|
||||
import ReferenceDefinition from '../../extensions/reference_definition';
|
||||
import ToolbarButton from '../toolbar_button.vue';
|
||||
|
||||
export default {
|
||||
|
@ -35,6 +36,7 @@ export default {
|
|||
Image.name,
|
||||
Audio.name,
|
||||
Video.name,
|
||||
ReferenceDefinition.name,
|
||||
];
|
||||
|
||||
return !exclude.some((type) => editor.isActive(type));
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
import { Node } from '@tiptap/core';
|
||||
|
||||
export default Node.create({
|
||||
name: 'referenceDefinition',
|
||||
|
||||
group: 'block',
|
||||
|
||||
content: 'text*',
|
||||
|
||||
marks: '',
|
||||
|
||||
addAttributes() {
|
||||
return {
|
||||
identifier: {
|
||||
default: null,
|
||||
},
|
||||
url: {
|
||||
default: null,
|
||||
},
|
||||
title: {
|
||||
default: null,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
renderHTML() {
|
||||
return ['pre', {}, 0];
|
||||
},
|
||||
});
|
|
@ -16,6 +16,7 @@ import Link from './link';
|
|||
import ListItem from './list_item';
|
||||
import OrderedList from './ordered_list';
|
||||
import Paragraph from './paragraph';
|
||||
import ReferenceDefinition from './reference_definition';
|
||||
import Strike from './strike';
|
||||
import TaskList from './task_list';
|
||||
import TaskItem from './task_item';
|
||||
|
@ -45,6 +46,7 @@ export default Extension.create({
|
|||
ListItem.name,
|
||||
OrderedList.name,
|
||||
Paragraph.name,
|
||||
ReferenceDefinition.name,
|
||||
Strike.name,
|
||||
TaskList.name,
|
||||
TaskItem.name,
|
||||
|
|
|
@ -42,6 +42,7 @@ import OrderedList from '../extensions/ordered_list';
|
|||
import Paragraph from '../extensions/paragraph';
|
||||
import PasteMarkdown from '../extensions/paste_markdown';
|
||||
import Reference from '../extensions/reference';
|
||||
import ReferenceDefinition from '../extensions/reference_definition';
|
||||
import Sourcemap from '../extensions/sourcemap';
|
||||
import Strike from '../extensions/strike';
|
||||
import Subscript from '../extensions/subscript';
|
||||
|
@ -128,6 +129,7 @@ export const createContentEditor = ({
|
|||
Paragraph,
|
||||
PasteMarkdown,
|
||||
Reference,
|
||||
ReferenceDefinition,
|
||||
Sourcemap,
|
||||
Strike,
|
||||
Subscript,
|
||||
|
|
|
@ -33,6 +33,7 @@ import MathInline from '../extensions/math_inline';
|
|||
import OrderedList from '../extensions/ordered_list';
|
||||
import Paragraph from '../extensions/paragraph';
|
||||
import Reference from '../extensions/reference';
|
||||
import ReferenceDefinition from '../extensions/reference_definition';
|
||||
import Strike from '../extensions/strike';
|
||||
import Subscript from '../extensions/subscript';
|
||||
import Superscript from '../extensions/superscript';
|
||||
|
@ -177,6 +178,25 @@ const defaultSerializerConfig = {
|
|||
[Reference.name]: (state, node) => {
|
||||
state.write(node.attrs.originalText || node.attrs.text);
|
||||
},
|
||||
[ReferenceDefinition.name]: preserveUnchanged({
|
||||
render: (state, node, parent, index, same, sourceMarkdown) => {
|
||||
const nextSibling = parent.maybeChild(index + 1);
|
||||
|
||||
state.text(same ? sourceMarkdown : node.textContent, false);
|
||||
|
||||
/**
|
||||
* Do not insert a blank line between reference definitions
|
||||
* because it isn’t necessary and a more compact text format
|
||||
* is preferred.
|
||||
*/
|
||||
if (!nextSibling || nextSibling.type.name !== ReferenceDefinition.name) {
|
||||
state.closeBlock(node);
|
||||
} else {
|
||||
state.ensureNewLine();
|
||||
}
|
||||
},
|
||||
overwriteSourcePreservationStrategy: true,
|
||||
}),
|
||||
[TableOfContents.name]: (state, node) => {
|
||||
state.write('[[_TOC_]]');
|
||||
state.closeBlock(node);
|
||||
|
|
|
@ -170,6 +170,16 @@ const factorySpecs = {
|
|||
type: 'ignore',
|
||||
selector: (hastNode) => hastNode.type === 'comment',
|
||||
},
|
||||
|
||||
referenceDefinition: {
|
||||
type: 'block',
|
||||
selector: 'referencedefinition',
|
||||
getAttrs: (hastNode) => ({
|
||||
title: hastNode.properties.title,
|
||||
url: hastNode.properties.url,
|
||||
identifier: hastNode.properties.identifier,
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
export default () => {
|
||||
|
@ -185,7 +195,7 @@ export default () => {
|
|||
wrappableTags,
|
||||
markdown,
|
||||
}),
|
||||
skipRendering: ['footnoteReference', 'footnoteDefinition', 'code'],
|
||||
skipRendering: ['footnoteReference', 'footnoteDefinition', 'code', 'definition'],
|
||||
});
|
||||
|
||||
return { document };
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { uniq, isString, omit } from 'lodash';
|
||||
import { uniq, isString, omit, isFunction } from 'lodash';
|
||||
|
||||
const defaultAttrs = {
|
||||
td: { colspan: 1, rowspan: 1, colwidth: null },
|
||||
|
@ -327,16 +327,25 @@ export function renderCodeBlock(state, node) {
|
|||
state.closeBlock(node);
|
||||
}
|
||||
|
||||
export function preserveUnchanged(render) {
|
||||
const expandPreserveUnchangedConfig = (configOrRender) =>
|
||||
isFunction(configOrRender)
|
||||
? { render: configOrRender, overwriteSourcePreservationStrategy: false }
|
||||
: configOrRender;
|
||||
|
||||
export function preserveUnchanged(configOrRender) {
|
||||
return (state, node, parent, index) => {
|
||||
const { render, overwriteSourcePreservationStrategy } = expandPreserveUnchangedConfig(
|
||||
configOrRender,
|
||||
);
|
||||
|
||||
const { sourceMarkdown } = node.attrs;
|
||||
const same = state.options.changeTracker.get(node);
|
||||
|
||||
if (same) {
|
||||
if (same && !overwriteSourcePreservationStrategy) {
|
||||
state.write(sourceMarkdown);
|
||||
state.closeBlock(node);
|
||||
} else {
|
||||
render(state, node, parent, index);
|
||||
render(state, node, parent, index, same, sourceMarkdown);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -19,6 +19,16 @@ const skipRenderingHandlers = {
|
|||
h(node.position, 'codeBlock', { language: node.lang, meta: node.meta }, [
|
||||
{ type: 'text', value: node.value },
|
||||
]),
|
||||
definition: (h, node) => {
|
||||
const title = node.title ? ` "${node.title}"` : '';
|
||||
|
||||
return h(
|
||||
node.position,
|
||||
'referenceDefinition',
|
||||
{ identifier: node.identifier, url: node.url, title: node.title },
|
||||
[{ type: 'text', value: `[${node.identifier}]: ${node.url}${title}` }],
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
const createParser = ({ skipRendering = [] }) => {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import Vue from 'vue';
|
||||
import { BV_SHOW_MODAL } from '~/lib/utils/constants';
|
||||
import PipelineSchedulesTakeOwnershipModal from '~/pipeline_schedules/components/take_ownership_modal.vue';
|
||||
import PipelineSchedulesCallout from '../shared/components/pipeline_schedules_callout.vue';
|
||||
|
||||
function initPipelineSchedules() {
|
||||
|
@ -23,4 +25,43 @@ function initPipelineSchedules() {
|
|||
});
|
||||
}
|
||||
|
||||
function initTakeownershipModal() {
|
||||
const modalId = 'pipeline-take-ownership-modal';
|
||||
const buttonSelector = 'js-take-ownership-button';
|
||||
const el = document.getElementById(modalId);
|
||||
const takeOwnershipButtons = document.querySelectorAll(`.${buttonSelector}`);
|
||||
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el,
|
||||
data() {
|
||||
return {
|
||||
url: '',
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
takeOwnershipButtons.forEach((button) => {
|
||||
button.addEventListener('click', () => {
|
||||
const { url } = button.dataset;
|
||||
|
||||
this.url = url;
|
||||
this.$root.$emit(BV_SHOW_MODAL, modalId, `.${buttonSelector}`);
|
||||
});
|
||||
});
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement(PipelineSchedulesTakeOwnershipModal, {
|
||||
props: {
|
||||
ownershipUrl: this.url,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
initPipelineSchedules();
|
||||
initTakeownershipModal();
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
<script>
|
||||
import { GlModal } from '@gitlab/ui';
|
||||
import { __, s__ } from '~/locale';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlModal,
|
||||
},
|
||||
props: {
|
||||
ownershipUrl: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
modalId: 'pipeline-take-ownership-modal',
|
||||
i18n: {
|
||||
takeOwnership: s__('PipelineSchedules|Take ownership'),
|
||||
ownershipMessage: s__(
|
||||
'PipelineSchedules|Only the owner of a pipeline schedule can make changes to it. Do you want to take ownership of this schedule?',
|
||||
),
|
||||
cancelLabel: __('Cancel'),
|
||||
},
|
||||
computed: {
|
||||
actionCancel() {
|
||||
return { text: this.$options.i18n.cancelLabel };
|
||||
},
|
||||
actionPrimary() {
|
||||
return {
|
||||
text: this.$options.i18n.takeOwnership,
|
||||
attributes: [
|
||||
{
|
||||
variant: 'confirm',
|
||||
category: 'primary',
|
||||
href: this.ownershipUrl,
|
||||
'data-method': 'post',
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<gl-modal
|
||||
:modal-id="$options.modalId"
|
||||
:action-primary="actionPrimary"
|
||||
:action-cancel="actionCancel"
|
||||
:title="$options.i18n.takeOwnership"
|
||||
>
|
||||
<p>{{ $options.i18n.ownershipMessage }}</p>
|
||||
</gl-modal>
|
||||
</template>
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Projects
|
||||
class ProjectTransferedEvent < ::Gitlab::EventStore::Event
|
||||
def schema
|
||||
{
|
||||
'type' => 'object',
|
||||
'properties' => {
|
||||
'project_id' => { 'type' => 'integer' },
|
||||
'old_namespace_id' => { 'type' => 'integer' },
|
||||
'old_root_namespace_id' => { 'type' => 'integer' },
|
||||
'new_namespace_id' => { 'type' => 'integer' },
|
||||
'new_root_namespace_id' => { 'type' => 'integer' }
|
||||
},
|
||||
'required' => %w[
|
||||
project_id
|
||||
old_namespace_id
|
||||
old_root_namespace_id
|
||||
new_namespace_id
|
||||
new_root_namespace_id
|
||||
]
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -137,7 +137,12 @@ module CommitsHelper
|
|||
|
||||
def conditionally_paginate_diff_files(diffs, paginate:, page:, per:)
|
||||
if paginate
|
||||
Kaminari.paginate_array(diffs.diff_files.to_a).page(page).per(per)
|
||||
diff_files = diffs.diff_files.to_a
|
||||
Gitlab::Utils::BatchLoader.clear_key([:repository_blobs, diffs.project.repository])
|
||||
|
||||
Kaminari.paginate_array(diff_files).page(page).per(per).tap do |diff_files|
|
||||
diff_files.each(&:add_blobs_to_batch_loader)
|
||||
end
|
||||
else
|
||||
diffs.diff_files
|
||||
end
|
||||
|
|
|
@ -93,8 +93,8 @@ class Blob < SimpleDelegator
|
|||
end
|
||||
|
||||
def self.lazy(repository, commit_id, path, blob_size_limit: Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE)
|
||||
BatchLoader.for([commit_id, path]).batch(key: repository) do |items, loader, args|
|
||||
args[:key].blobs_at(items, blob_size_limit: blob_size_limit).each do |blob|
|
||||
BatchLoader.for([commit_id, path]).batch(key: [:repository_blobs, repository]) do |items, loader, args|
|
||||
args[:key].last.blobs_at(items, blob_size_limit: blob_size_limit).each do |blob|
|
||||
loader.call([blob.commit_id, blob.path], blob) if blob
|
||||
end
|
||||
end
|
||||
|
|
|
@ -121,6 +121,8 @@ module Projects
|
|||
# Overridden in EE
|
||||
def post_update_hooks(project)
|
||||
ensure_personal_project_owner_membership(project)
|
||||
|
||||
publish_event
|
||||
end
|
||||
|
||||
# Overridden in EE
|
||||
|
@ -268,6 +270,18 @@ module Projects
|
|||
|
||||
CustomerRelations::IssueContact.delete_for_project(project.id)
|
||||
end
|
||||
|
||||
def publish_event
|
||||
event = ::Projects::ProjectTransferedEvent.new(data: {
|
||||
project_id: project.id,
|
||||
old_namespace_id: old_namespace.id,
|
||||
old_root_namespace_id: old_namespace.root_ancestor.id,
|
||||
new_namespace_id: new_namespace.id,
|
||||
new_root_namespace_id: new_namespace.root_ancestor.id
|
||||
})
|
||||
|
||||
Gitlab::EventStore.publish(event)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -13,5 +13,9 @@ module ProtectedBranches
|
|||
def after_execute(*)
|
||||
# overridden in EE::ProtectedBranches module
|
||||
end
|
||||
|
||||
def refresh_cache
|
||||
CacheService.new(@project, @current_user, @params).refresh
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ProtectedBranches
|
||||
class CacheService < ProtectedBranches::BaseService
|
||||
CACHE_ROOT_KEY = 'cache:gitlab:protected_branch'
|
||||
TTL_UNSET = -1
|
||||
CACHE_EXPIRE_IN = 1.day
|
||||
CACHE_LIMIT = 1000
|
||||
|
||||
def fetch(ref_name)
|
||||
record = OpenSSL::Digest::SHA256.hexdigest(ref_name)
|
||||
|
||||
Gitlab::Redis::Cache.with do |redis|
|
||||
cached_result = redis.hget(redis_key, record)
|
||||
|
||||
break Gitlab::Redis::Boolean.decode(cached_result) unless cached_result.nil?
|
||||
|
||||
value = yield
|
||||
|
||||
redis.hset(redis_key, record, Gitlab::Redis::Boolean.encode(value))
|
||||
|
||||
# We don't want to extend cache expiration time
|
||||
if redis.ttl(redis_key) == TTL_UNSET
|
||||
redis.expire(redis_key, CACHE_EXPIRE_IN)
|
||||
end
|
||||
|
||||
# If the cache record has too many elements, then something went wrong and
|
||||
# it's better to drop the cache key.
|
||||
if redis.hlen(redis_key) > CACHE_LIMIT
|
||||
redis.unlink(redis_key)
|
||||
end
|
||||
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
def refresh
|
||||
Gitlab::Redis::Cache.with { |redis| redis.unlink(redis_key) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def redis_key
|
||||
@redis_key ||= [CACHE_ROOT_KEY, @project.id].join(':')
|
||||
end
|
||||
end
|
||||
end
|
|
@ -7,6 +7,8 @@ module ProtectedBranches
|
|||
|
||||
save_protected_branch
|
||||
|
||||
refresh_cache
|
||||
|
||||
protected_branch
|
||||
end
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ module ProtectedBranches
|
|||
def execute(protected_branch)
|
||||
raise Gitlab::Access::AccessDeniedError unless can?(current_user, :destroy_protected_branch, protected_branch)
|
||||
|
||||
protected_branch.destroy
|
||||
protected_branch.destroy.tap { refresh_cache }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,6 +10,8 @@ module ProtectedBranches
|
|||
|
||||
if protected_branch.update(params)
|
||||
after_execute(protected_branch: protected_branch, old_merge_access_levels: old_merge_access_levels, old_push_access_levels: old_push_access_levels)
|
||||
|
||||
refresh_cache
|
||||
end
|
||||
|
||||
protected_branch
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
= gitlab_ui_form_for @application_setting, url: ci_cd_admin_application_settings_path(anchor: 'js-runner-settings'), html: { class: 'fieldset-form' } do |f|
|
||||
= form_errors(@application_setting)
|
||||
= form_errors(@application_setting, pajamas_alert: true)
|
||||
|
||||
%fieldset
|
||||
.gl-form-group
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
- link_end = '</a>'.html_safe
|
||||
|
||||
= gitlab_ui_form_for @application_setting, url: metrics_and_profiling_admin_application_settings_path(anchor: 'js-usage-settings'), html: { class: 'fieldset-form' } do |f|
|
||||
= form_errors(@application_setting)
|
||||
= form_errors(@application_setting, pajamas_alert: true)
|
||||
|
||||
%fieldset
|
||||
.form-group
|
||||
|
@ -38,7 +38,7 @@
|
|||
%p.gl-mb-3= s_('AdminSettings|Registration Features include:')
|
||||
- email_from_gitlab_path = help_page_path('user/admin_area/email_from_gitlab')
|
||||
- repo_size_limit_path = help_page_path('user/admin_area/settings/account_and_limit_settings', anchor: 'repository-size-limit')
|
||||
- restrict_ip_path = help_page_path('user/group/index', anchor: 'restrict-group-access-by-ip-address')
|
||||
- restrict_ip_path = help_page_path('user/group/access_and_permissions', anchor: 'restrict-group-access-by-ip-address')
|
||||
- email_from_gitlab_link = link_start % { url: email_from_gitlab_path }
|
||||
- repo_size_limit_link = link_start % { url: repo_size_limit_path }
|
||||
- restrict_ip_link = link_start % { url: restrict_ip_path }
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
- content_for :flash_message do
|
||||
= render "layouts/header/storage_enforcement_banner", context: @group
|
||||
= dispensable_render_if_exists "shared/namespace_storage_limit_alert"
|
||||
= dispensable_render_if_exists "shared/namespace_storage_limit_alert", context: @group
|
||||
|
||||
- content_for :page_specific_javascripts do
|
||||
- if current_user
|
||||
|
|
|
@ -8,5 +8,6 @@
|
|||
|
||||
- content_for :flash_message do
|
||||
= render "layouts/header/storage_enforcement_banner", context: current_user.namespace
|
||||
= dispensable_render_if_exists "shared/namespace_storage_limit_alert", context: current_user.namespace
|
||||
|
||||
= render template: "layouts/application"
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
- content_for :flash_message do
|
||||
= render "layouts/header/storage_enforcement_banner", context: @project
|
||||
= dispensable_render_if_exists "shared/namespace_storage_limit_alert"
|
||||
= dispensable_render_if_exists "shared/namespace_storage_limit_alert", context: @project
|
||||
|
||||
- content_for :project_javascripts do
|
||||
- project = @target_project || @project
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
= link_to play_pipeline_schedule_path(pipeline_schedule), method: :post, title: _('Play'), class: 'btn gl-button btn-default btn-icon' do
|
||||
= sprite_icon('play')
|
||||
- if can?(current_user, :take_ownership_pipeline_schedule, pipeline_schedule)
|
||||
= link_to take_ownership_pipeline_schedule_path(pipeline_schedule), method: :post, title: s_('PipelineSchedules|Take ownership'), class: 'btn gl-button btn-default' do
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-take-ownership-button has-tooltip', title: s_('PipelineSchedule|Take ownership to edit'), data: { url: take_ownership_pipeline_schedule_path(pipeline_schedule) } }) do
|
||||
= s_('PipelineSchedules|Take ownership')
|
||||
- if can?(current_user, :update_pipeline_schedule, pipeline_schedule)
|
||||
= link_to edit_pipeline_schedule_path(pipeline_schedule), title: _('Edit'), class: 'btn gl-button btn-default btn-icon' do
|
||||
|
|
|
@ -18,3 +18,5 @@
|
|||
- else
|
||||
.card.bg-light.gl-mt-3
|
||||
.nothing-here-block= _("No schedules")
|
||||
|
||||
#pipeline-take-ownership-modal
|
||||
|
|
|
@ -17,16 +17,6 @@
|
|||
# Corresponding feature flag should have `default_enabled` attribute set to `false`.
|
||||
# This attribute is OPTIONAL and can be omitted, when `feature_flag` is missing no feature flag will be checked.
|
||||
---
|
||||
- name: compliance_features_track_unique_visits_union
|
||||
operator: OR
|
||||
source: redis
|
||||
time_frame: [7d, 28d]
|
||||
events:
|
||||
- 'g_compliance_audit_events'
|
||||
- 'g_compliance_dashboard'
|
||||
- 'i_compliance_audit_events'
|
||||
- 'a_compliance_audit_events_api'
|
||||
- 'i_compliance_credential_inventory'
|
||||
- name: incident_management_alerts_total_unique_counts
|
||||
operator: OR
|
||||
source: redis
|
||||
|
|
|
@ -41,7 +41,6 @@ module.exports = {
|
|||
'three',
|
||||
'select2',
|
||||
'moment-mini',
|
||||
'aws-sdk',
|
||||
'dompurify',
|
||||
'bootstrap/dist/js/bootstrap.js',
|
||||
'sortablejs/modular/sortable.esm.js',
|
||||
|
|
|
@ -115,7 +115,7 @@ From there, you can see the following actions:
|
|||
- Instance administrator started or stopped impersonation of a group member. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/300961) in GitLab 14.8.
|
||||
- Group deploy token was successfully created, revoked, or deleted. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/353452) in GitLab 14.9.
|
||||
- Failed attempt to create a group deploy token. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/353452) in GitLab 14.9.
|
||||
- [IP restrictions](../user/group/index.md#group-access-restriction-by-ip-address) changed. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/358986) in GitLab 15.0.
|
||||
- [IP restrictions](../user/group/access_and_permissions.md#restrict-group-access-by-ip-address) changed. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/358986) in GitLab 15.0.
|
||||
- Changes to push rules. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/227629) in GitLab 15.0.
|
||||
- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/356152) in GitLab 15.1, changes to the following merge request approvals settings:
|
||||
- Prevent approval by author.
|
||||
|
|
|
@ -128,7 +128,7 @@ To take advantage of group sync, group Owners or users with the [Maintainer role
|
|||
### Add group links
|
||||
|
||||
For information on adding group links by using CNs and filters, refer to the
|
||||
[GitLab groups documentation](../../../user/group/index.md#manage-group-memberships-via-ldap).
|
||||
[GitLab groups documentation](../../../user/group/access_and_permissions.md#manage-group-memberships-via-ldap).
|
||||
|
||||
### Administrator sync
|
||||
|
||||
|
|
|
@ -97,7 +97,7 @@ These features can also help with compliance requirements:
|
|||
projects): Search dependencies for their licenses. This lets you determine if
|
||||
the licenses of your project's dependencies are compatible with your project's
|
||||
license.
|
||||
- [**Lock project membership to group**](../user/group/index.md#prevent-members-from-being-added-to-projects-in-a-group)
|
||||
- [**Lock project membership to group**](../user/group/access_and_permissions.md#prevent-members-from-being-added-to-projects-in-a-group)
|
||||
(for groups): Group owners can prevent new members from being added to projects
|
||||
within a group.
|
||||
- [**LDAP group sync**](auth/ldap/ldap_synchronization.md#group-sync) (for
|
||||
|
|
|
@ -926,7 +926,7 @@ PUT /groups/:id
|
|||
| `emails_disabled` | boolean | no | Disable email notifications. |
|
||||
| `lfs_enabled` | boolean | no | Enable/disable Large File Storage (LFS) for the projects in this group. |
|
||||
| `mentions_disabled` | boolean | no | Disable the capability of a group from getting mentioned. |
|
||||
| `prevent_sharing_groups_outside_hierarchy` | boolean | no | See [Prevent group sharing outside the group hierarchy](../user/group/index.md#prevent-group-sharing-outside-the-group-hierarchy). This attribute is only available on top-level groups. [Introduced in GitLab 14.1](https://gitlab.com/gitlab-org/gitlab/-/issues/333721) |
|
||||
| `prevent_sharing_groups_outside_hierarchy` | boolean | no | See [Prevent group sharing outside the group hierarchy](../user/group/access_and_permissions.md#prevent-group-sharing-outside-the-group-hierarchy). This attribute is only available on top-level groups. [Introduced in GitLab 14.1](https://gitlab.com/gitlab-org/gitlab/-/issues/333721) |
|
||||
| `project_creation_level` | string | no | Determine if developers can create projects in the group. Can be `noone` (No one), `maintainer` (users with the Maintainer role), or `developer` (users with the Developer or Maintainer role). |
|
||||
| `request_access_enabled` | boolean | no | Allow users to request member access. |
|
||||
| `require_two_factor_authentication` | boolean | no | Require all users in this group to setup Two-factor authentication. |
|
||||
|
@ -1481,7 +1481,7 @@ DELETE /groups/:id/share/:group_id
|
|||
|
||||
### Get group push rules **(PREMIUM)**
|
||||
|
||||
Get the [push rules](../user/group/index.md#group-push-rules) of a group.
|
||||
Get the [push rules](../user/group/access_and_permissions.md#group-push-rules) of a group.
|
||||
|
||||
Only available to group owners and administrators.
|
||||
|
||||
|
@ -1524,7 +1524,7 @@ the `commit_committer_check` and `reject_unsigned_commits` parameters:
|
|||
|
||||
### Add group push rule **(PREMIUM)**
|
||||
|
||||
Adds [push rules](../user/group/index.md#group-push-rules) to the specified group.
|
||||
Adds [push rules](../user/group/access_and_permissions.md#group-push-rules) to the specified group.
|
||||
|
||||
Only available to group owners and administrators.
|
||||
|
||||
|
@ -1618,7 +1618,7 @@ Response:
|
|||
|
||||
### Delete group push rule **(PREMIUM)**
|
||||
|
||||
Deletes the [push rules](../user/group/index.md#group-push-rules) of a group.
|
||||
Deletes the [push rules](../user/group/access_and_permissions.md#group-push-rules) of a group.
|
||||
|
||||
Only available to group owners and administrators.
|
||||
|
||||
|
|
|
@ -756,7 +756,7 @@ Group SAML on a self-managed instance is limited when compared to the recommende
|
|||
[instance-wide SAML](../user/group/saml_sso/index.md). The recommended solution allows you to take advantage of:
|
||||
|
||||
- [LDAP compatibility](../administration/auth/ldap/index.md).
|
||||
- [LDAP Group Sync](../user/group/index.md#manage-group-memberships-via-ldap).
|
||||
- [LDAP Group Sync](../user/group/access_and_permissions.md#manage-group-memberships-via-ldap).
|
||||
- [Required groups](#required-groups).
|
||||
- [Administrator groups](#administrator-groups).
|
||||
- [Auditor groups](#auditor-groups).
|
||||
|
|
|
@ -71,7 +71,7 @@ The following are important notes about 2FA:
|
|||
2FA for the project. For example, if project *P* belongs to 2FA-enabled group *A* and
|
||||
is shared with 2FA-disabled group *B*, members of group *B* can access project *P*
|
||||
without 2FA. To ensure this scenario doesn't occur,
|
||||
[prevent sharing of projects](../user/group/index.md#prevent-a-project-from-being-shared-with-groups)
|
||||
[prevent sharing of projects](../user/group/access_and_permissions.md#prevent-a-project-from-being-shared-with-groups)
|
||||
for the 2FA-enabled group.
|
||||
- If you add additional members to a project within a group or subgroup that has
|
||||
2FA enabled, 2FA is **not** required for those individually added members.
|
||||
|
|
|
@ -23,11 +23,11 @@ the tiers are no longer mentioned in GitLab documentation:
|
|||
- [Setting a default template for merge requests and issues](../user/project/description_templates.md#set-a-default-template-for-merge-requests-and-issues)
|
||||
- [Email from GitLab](../user/admin_area/email_from_gitlab.md)
|
||||
- Groups:
|
||||
- [Creating group memberships via CN](../user/group/index.md#create-group-links-via-cn)
|
||||
- [Group push rules](../user/group/index.md#group-push-rules)
|
||||
- [Managing group memberships via LDAP](../user/group/index.md#manage-group-memberships-via-ldap)
|
||||
- [Member locking](../user/group/index.md#prevent-members-from-being-added-to-projects-in-a-group)
|
||||
- [Overriding user permissions](../user/group/index.md#override-user-permissions)
|
||||
- [Creating group memberships via CN](../user/group/access_and_permissions.md#create-group-links-via-cn)
|
||||
- [Group push rules](../user/group/access_and_permissions.md#group-push-rules)
|
||||
- [Managing group memberships via LDAP](../user/group/access_and_permissions.md#manage-group-memberships-via-ldap)
|
||||
- [Member locking](../user/group/access_and_permissions.md#prevent-members-from-being-added-to-projects-in-a-group)
|
||||
- [Overriding user permissions](../user/group/access_and_permissions.md#override-user-permissions)
|
||||
- [User contribution analytics](../user/group/contribution_analytics/index.md)
|
||||
- [Kerberos integration](../integration/kerberos.md)
|
||||
- Issue boards:
|
||||
|
|
|
@ -48,7 +48,7 @@ tier. Users can continue to access the features in a paid tier without sharing u
|
|||
### Features available in 14.4 and later
|
||||
|
||||
- [Repository size limit](../settings/account_and_limit_settings.md#repository-size-limit).
|
||||
- [Group access restriction by IP address](../../group/index.md#group-access-restriction-by-ip-address).
|
||||
- [Group access restriction by IP address](../../group/access_and_permissions.md#restrict-group-access-by-ip-address).
|
||||
|
||||
NOTE:
|
||||
Registration is not yet required for participation, but may be added in a future milestone.
|
||||
|
|
|
@ -6,6 +6,246 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
# Group access and permissions
|
||||
|
||||
Use Groups to manage one or more related projects at the same time.
|
||||
Configure your groups to control group permissions and access.
|
||||
|
||||
For instructions on how to configure access and permissions for groups, see [Groups](index.md).
|
||||
## Group push rules **(PREMIUM)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/34370) in GitLab 12.8.
|
||||
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/224129) in GitLab 13.4.
|
||||
|
||||
Group push rules allow group maintainers to set
|
||||
[push rules](../project/repository/push_rules.md) for newly created projects in the specific group.
|
||||
|
||||
To configure push rules for a group:
|
||||
|
||||
1. Go to the groups's **Push Rules** page.
|
||||
1. Select the settings you want.
|
||||
1. Select **Save Push Rules**.
|
||||
|
||||
The group's new subgroups have push rules set for them based on either:
|
||||
|
||||
- The closest parent group with push rules defined.
|
||||
- Push rules set at the instance level, if no parent groups have push rules defined.
|
||||
|
||||
## Restrict group access by IP address **(PREMIUM)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/1985) in GitLab 12.0.
|
||||
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/215410) from GitLab Ultimate to GitLab Premium in 13.1.
|
||||
|
||||
To ensure only people from your organization can access particular
|
||||
resources, you can restrict access to groups by IP address. This group-level setting
|
||||
applies to:
|
||||
|
||||
- The GitLab UI, including subgroups, projects, and issues.
|
||||
- [In GitLab 12.3 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/12874), the API.
|
||||
|
||||
### Security implications
|
||||
|
||||
You should consider some security implications before configuring IP address restrictions.
|
||||
|
||||
- Restricting HTTP traffic on GitLab.com with IP address restrictions causes SSH requests (including Git operations over
|
||||
SSH) to fail. For more information, see [the relevant issue](https://gitlab.com/gitlab-org/gitlab/-/issues/271673).
|
||||
- Administrators and group owners can access group settings from any IP address, regardless of IP restriction. However:
|
||||
- Groups owners cannot access projects belonging to the group when accessing from a disallowed IP address.
|
||||
- Administrators can access projects belonging to the group when accessing from a disallowed IP address.
|
||||
Access to projects includes cloning code from them.
|
||||
- Users can still see group and project names and hierarchies. Only the following are restricted:
|
||||
- [Groups](../../api/groups.md), including all [group resources](../../api/api_resources.md#group-resources).
|
||||
- [Project](../../api/projects.md), including all [project resources](../../api/api_resources.md#project-resources).
|
||||
- When you register a runner, it is not bound by the IP restrictions. When the runner requests a new job or an update to
|
||||
a job's state, it is also not bound by the IP restrictions. But when the running CI/CD job sends Git requests from a
|
||||
restricted IP address, the IP restriction prevents code from being cloned.
|
||||
- Users may still see some events from the IP restricted groups and projects on their dashboard. Activity may include
|
||||
push, merge, issue, or comment events.
|
||||
|
||||
### Restrict group access by IP address
|
||||
|
||||
To restrict group access by IP address:
|
||||
|
||||
1. Go to the group's **Settings > General** page.
|
||||
1. Expand the **Permissions and group features** section.
|
||||
1. In the **Allow access to the following IP addresses** field, enter IPv4 or IPv6 address ranges in CIDR notation.
|
||||
1. Select **Save changes**.
|
||||
|
||||
In self-managed installations of GitLab 15.1 and later, you can also configure
|
||||
[globally-allowed IP address ranges](../admin_area/settings/visibility_and_access_controls.md#configure-globally-allowed-ip-address-ranges)
|
||||
at the group level.
|
||||
|
||||
## Restrict group access by domain **(PREMIUM)**
|
||||
|
||||
> - Support for specifying multiple email domains [added](https://gitlab.com/gitlab-org/gitlab/-/issues/33143) in GitLab 13.1.
|
||||
> - Support for restricting access to projects in the group [added](https://gitlab.com/gitlab-org/gitlab/-/issues/14004) in GitLab 14.1.2.
|
||||
|
||||
You can prevent users with email addresses in specific domains from being added to a group and its projects.
|
||||
|
||||
To restrict group access by domain:
|
||||
|
||||
1. Go to the group's **Settings > General** page.
|
||||
1. Expand the **Permissions and group features** section.
|
||||
1. In the **Restrict membership by email** field, enter the domain names.
|
||||
1. Select **Save changes**.
|
||||
|
||||
Any time you attempt to add a new user, the user's [primary email](../profile/index.md#change-your-primary-email) is compared against this list.
|
||||
Only users with a [primary email](../profile/index.md#change-your-primary-email) that matches any of the configured email domain restrictions
|
||||
can be added to the group.
|
||||
|
||||
The most popular public email domains cannot be restricted, such as:
|
||||
|
||||
- `gmail.com`, `yahoo.com`, `aol.com`, `icloud.com`
|
||||
- `hotmail.com`, `hotmail.co.uk`, `hotmail.fr`
|
||||
- `msn.com`, `live.com`, `outlook.com`
|
||||
|
||||
## Prevent group sharing outside the group hierarchy
|
||||
|
||||
You can configure a top-level group so its subgroups and projects
|
||||
cannot invite other groups outside of the top-level group's hierarchy.
|
||||
This option is only available for top-level groups.
|
||||
|
||||
For example, in the following group and project hierarchy:
|
||||
|
||||
- **Animals > Dogs > Dog Project**
|
||||
- **Animals > Cats**
|
||||
- **Plants > Trees**
|
||||
|
||||
If you prevent group sharing outside the hierarchy for the **Animals** group:
|
||||
|
||||
- **Dogs** can invite the group **Cats**.
|
||||
- **Dogs** cannot invite the group **Trees**.
|
||||
- **Dog Project** can invite the group **Cats**.
|
||||
- **Dog Project** cannot invite the group **Trees**.
|
||||
|
||||
To prevent sharing outside of the group's hierarchy:
|
||||
|
||||
1. On the top bar, select **Menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Settings > General**.
|
||||
1. Expand **Permissions and group features**.
|
||||
1. Select **Prevent members from sending invitations to groups outside of `<group_name>` and its subgroups**.
|
||||
1. Select **Save changes**.
|
||||
|
||||
## Prevent a project from being shared with groups
|
||||
|
||||
Prevent projects in a group from [sharing
|
||||
a project with another group](../project/members/share_project_with_groups.md) to enable tighter control over project access.
|
||||
|
||||
To prevent a project from being shared with other groups:
|
||||
|
||||
1. Go to the group's **Settings > General** page.
|
||||
1. Expand the **Permissions and group features** section.
|
||||
1. Select **Prevent sharing a project in `<group_name>` with other groups**.
|
||||
1. Select **Save changes**.
|
||||
|
||||
This setting applies to all subgroups unless overridden by a group owner. Groups already
|
||||
added to a project lose access when the setting is enabled.
|
||||
|
||||
## Prevent users from requesting access to a group
|
||||
|
||||
As a group owner, you can prevent non-members from requesting access to
|
||||
your group.
|
||||
|
||||
1. On the top bar, select **Menu > Groups**.
|
||||
1. Select **Your Groups**.
|
||||
1. Find the group and select it.
|
||||
1. From the left menu, select **Settings > General**.
|
||||
1. Expand the **Permissions and group features** section.
|
||||
1. Clear the **Allow users to request access** checkbox.
|
||||
1. Select **Save changes**.
|
||||
|
||||
## Prevent project forking outside group **(PREMIUM)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216987) in GitLab 13.3.
|
||||
|
||||
By default, projects in a group can be forked.
|
||||
Optionally, on [GitLab Premium](https://about.gitlab.com/pricing/) or higher tiers,
|
||||
you can prevent the projects in a group from being forked outside of the current top-level group.
|
||||
|
||||
This setting will be removed from the SAML setting page, and migrated to the
|
||||
group settings page. In the interim period, both of these settings are taken into consideration.
|
||||
If even one is set to `true`, then the group does not allow outside forks.
|
||||
|
||||
To prevent projects from being forked outside the group:
|
||||
|
||||
1. Go to the top-level group's **Settings > General** page.
|
||||
1. Expand the **Permissions and group features** section.
|
||||
1. Check **Prevent project forking outside current group**.
|
||||
1. Select **Save changes**.
|
||||
|
||||
Existing forks are not removed.
|
||||
|
||||
## Prevent members from being added to projects in a group **(PREMIUM)**
|
||||
|
||||
As a group owner, you can prevent any new project membership for all
|
||||
projects in a group, allowing tighter control over project membership.
|
||||
|
||||
For example, if you want to lock the group for an [Audit Event](../../administration/audit_events.md),
|
||||
you can guarantee that project membership cannot be modified during the audit.
|
||||
|
||||
You can still invite groups or to add members to groups, implicitly giving members access to projects in the **locked** group.
|
||||
|
||||
The setting does not cascade. Projects in subgroups observe the subgroup configuration, ignoring the parent group.
|
||||
|
||||
To prevent members from being added to projects in a group:
|
||||
|
||||
1. Go to the group's **Settings > General** page.
|
||||
1. Expand the **Permissions and group features** section.
|
||||
1. Under **Membership**, select **Prevent adding new members to projects within this group**.
|
||||
1. Select **Save changes**.
|
||||
|
||||
All users who previously had permissions can no longer add members to a group.
|
||||
API requests to add a new user to a project are not possible.
|
||||
|
||||
## Manage group memberships via LDAP **(PREMIUM SELF)**
|
||||
|
||||
Group syncing allows LDAP groups to be mapped to GitLab groups. This provides more control over per-group user management. To configure group syncing, edit the `group_base` **DN** (`'OU=Global Groups,OU=GitLab INT,DC=GitLab,DC=org'`). This **OU** contains all groups that are associated with GitLab groups.
|
||||
|
||||
Group links can be created by using either a CN or a filter. To create these group links, go to the group's **Settings > LDAP Synchronization** page. After configuring the link, it may take more than an hour for the users to sync with the GitLab group.
|
||||
|
||||
For more information on the administration of LDAP and group sync, refer to the [main LDAP documentation](../../administration/auth/ldap/ldap_synchronization.md#group-sync).
|
||||
|
||||
NOTE:
|
||||
When you add LDAP synchronization, if an LDAP user is a group member and they are not part of the LDAP group, they are removed from the group.
|
||||
|
||||
### Create group links via CN **(PREMIUM SELF)**
|
||||
|
||||
To create group links via CN:
|
||||
|
||||
<!-- vale gitlab.Spelling = NO -->
|
||||
|
||||
1. Select the **LDAP Server** for the link.
|
||||
1. As the **Sync method**, select `LDAP Group cn`.
|
||||
1. In the **LDAP Group cn** field, begin typing the CN of the group. There is a dropdown list with matching CNs in the configured `group_base`. Select your CN from this list.
|
||||
1. In the **LDAP Access** section, select the [permission level](../permissions.md) for users synced in this group.
|
||||
1. Select **Add Synchronization**.
|
||||
|
||||
<!-- vale gitlab.Spelling = YES -->
|
||||
|
||||
### Create group links via filter **(PREMIUM SELF)**
|
||||
|
||||
To create group links via filter:
|
||||
|
||||
1. Select the **LDAP Server** for the link.
|
||||
1. As the **Sync method**, select `LDAP user filter`.
|
||||
1. Input your filter in the **LDAP User filter** box. Follow the [documentation on user filters](../../administration/auth/ldap/index.md#set-up-ldap-user-filter).
|
||||
1. In the **LDAP Access** section, select the [permission level](../permissions.md) for users synced in this group.
|
||||
1. Select **Add Synchronization**.
|
||||
|
||||
### Override user permissions **(PREMIUM SELF)**
|
||||
|
||||
LDAP user permissions can be manually overridden by an administrator. To override a user's permissions:
|
||||
|
||||
1. Go to your group's **Group information > Members** page.
|
||||
1. In the row for the user you are editing, select the pencil (**{pencil}**) icon.
|
||||
1. Select **Edit permissions** in the modal.
|
||||
|
||||
Now you can edit the user's permissions from the **Members** page.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Verify if access is blocked by IP restriction
|
||||
|
||||
If a user sees a 404 when they would normally expect access, and the problem is limited to a specific group, search the `auth.log` rails log for one or more of the following:
|
||||
|
||||
- `json.message`: `'Attempting to access IP restricted group'`
|
||||
- `json.allowed`: `false`
|
||||
|
||||
In viewing the log entries, compare the `remote.ip` with the list of
|
||||
[allowed IPs](#restrict-group-access-by-ip-address) for the group.
|
||||
|
|
|
@ -48,19 +48,6 @@ For example, consider a user named Alex:
|
|||
| Alex creates a group for their team with the group name `alex-team`. The group and its projects are available at: `https://gitlab.example.com/alex-team`. | The namespace in this case is `alex-team`. |
|
||||
| Alex creates a subgroup of `alex-team` with the subgroup name `marketing`. The subgroup and its projects are available at: `https://gitlab.example.com/alex-team/marketing`. | The namespace in this case is `alex-team/marketing`. |
|
||||
|
||||
## Prevent users from requesting access to a group
|
||||
|
||||
As a group owner, you can prevent non-members from requesting access to
|
||||
your group.
|
||||
|
||||
1. On the top bar, select **Menu > Groups**.
|
||||
1. Select **Your Groups**.
|
||||
1. Find the group and select it.
|
||||
1. From the left menu, select **Settings > General**.
|
||||
1. Expand the **Permissions and group features** section.
|
||||
1. Clear the **Allow users to request access** checkbox.
|
||||
1. Select **Save changes**.
|
||||
|
||||
## Mention a group in an issue or merge request
|
||||
|
||||
When you mention a group in a comment, every member of the group gets a to-do item
|
||||
|
@ -73,115 +60,6 @@ added to their To-do list.
|
|||
|
||||
A to-do item is created for all the group and subgroup members.
|
||||
|
||||
## Manage group memberships via LDAP **(PREMIUM SELF)**
|
||||
|
||||
Group syncing allows LDAP groups to be mapped to GitLab groups. This provides more control over per-group user management. To configure group syncing, edit the `group_base` **DN** (`'OU=Global Groups,OU=GitLab INT,DC=GitLab,DC=org'`). This **OU** contains all groups that are associated with GitLab groups.
|
||||
|
||||
Group links can be created by using either a CN or a filter. To create these group links, go to the group's **Settings > LDAP Synchronization** page. After configuring the link, it may take more than an hour for the users to sync with the GitLab group.
|
||||
|
||||
For more information on the administration of LDAP and group sync, refer to the [main LDAP documentation](../../administration/auth/ldap/ldap_synchronization.md#group-sync).
|
||||
|
||||
NOTE:
|
||||
When you add LDAP synchronization, if an LDAP user is a group member and they are not part of the LDAP group, they are removed from the group.
|
||||
|
||||
### Create group links via CN **(PREMIUM SELF)**
|
||||
|
||||
To create group links via CN:
|
||||
|
||||
<!-- vale gitlab.Spelling = NO -->
|
||||
|
||||
1. Select the **LDAP Server** for the link.
|
||||
1. As the **Sync method**, select `LDAP Group cn`.
|
||||
1. In the **LDAP Group cn** field, begin typing the CN of the group. There is a dropdown list with matching CNs in the configured `group_base`. Select your CN from this list.
|
||||
1. In the **LDAP Access** section, select the [permission level](../permissions.md) for users synced in this group.
|
||||
1. Select **Add Synchronization**.
|
||||
|
||||
<!-- vale gitlab.Spelling = YES -->
|
||||
|
||||
### Create group links via filter **(PREMIUM SELF)**
|
||||
|
||||
To create group links via filter:
|
||||
|
||||
1. Select the **LDAP Server** for the link.
|
||||
1. As the **Sync method**, select `LDAP user filter`.
|
||||
1. Input your filter in the **LDAP User filter** box. Follow the [documentation on user filters](../../administration/auth/ldap/index.md#set-up-ldap-user-filter).
|
||||
1. In the **LDAP Access** section, select the [permission level](../permissions.md) for users synced in this group.
|
||||
1. Select **Add Synchronization**.
|
||||
|
||||
### Override user permissions **(PREMIUM SELF)**
|
||||
|
||||
LDAP user permissions can be manually overridden by an administrator. To override a user's permissions:
|
||||
|
||||
1. Go to your group's **Group information > Members** page.
|
||||
1. In the row for the user you are editing, select the pencil (**{pencil}**) icon.
|
||||
1. Select **Edit permissions** in the modal.
|
||||
|
||||
Now you can edit the user's permissions from the **Members** page.
|
||||
|
||||
## Prevent group sharing outside the group hierarchy
|
||||
|
||||
You can configure a top-level group so its subgroups and projects
|
||||
cannot invite other groups outside of the top-level group's hierarchy.
|
||||
This option is only available for top-level groups.
|
||||
|
||||
For example, in the following group and project hierarchy:
|
||||
|
||||
- **Animals > Dogs > Dog Project**
|
||||
- **Animals > Cats**
|
||||
- **Plants > Trees**
|
||||
|
||||
If you prevent group sharing outside the hierarchy for the **Animals** group:
|
||||
|
||||
- **Dogs** can invite the group **Cats**.
|
||||
- **Dogs** cannot invite the group **Trees**.
|
||||
- **Dog Project** can invite the group **Cats**.
|
||||
- **Dog Project** cannot invite the group **Trees**.
|
||||
|
||||
To prevent sharing outside of the group's hierarchy:
|
||||
|
||||
1. On the top bar, select **Menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Settings > General**.
|
||||
1. Expand **Permissions and group features**.
|
||||
1. Select **Prevent members from sending invitations to groups outside of `<group_name>` and its subgroups**.
|
||||
1. Select **Save changes**.
|
||||
|
||||
## Prevent a project from being shared with groups
|
||||
|
||||
Prevent projects in a group from [sharing
|
||||
a project with another group](../project/members/share_project_with_groups.md) to enable tighter control over project access.
|
||||
|
||||
To prevent a project from being shared with other groups:
|
||||
|
||||
1. Go to the group's **Settings > General** page.
|
||||
1. Expand the **Permissions and group features** section.
|
||||
1. Select **Prevent sharing a project in `<group_name>` with other groups**.
|
||||
1. Select **Save changes**.
|
||||
|
||||
This setting applies to all subgroups unless overridden by a group owner. Groups already
|
||||
added to a project lose access when the setting is enabled.
|
||||
|
||||
## Prevent members from being added to projects in a group **(PREMIUM)**
|
||||
|
||||
As a group owner, you can prevent any new project membership for all
|
||||
projects in a group, allowing tighter control over project membership.
|
||||
|
||||
For example, if you want to lock the group for an [Audit Event](../../administration/audit_events.md),
|
||||
you can guarantee that project membership cannot be modified during the audit.
|
||||
|
||||
You can still invite groups or to add members to groups, implicitly giving members access to projects in the **locked** group.
|
||||
|
||||
The setting does not cascade. Projects in subgroups observe the subgroup configuration, ignoring the parent group.
|
||||
|
||||
To prevent members from being added to projects in a group:
|
||||
|
||||
1. Go to the group's **Settings > General** page.
|
||||
1. Expand the **Permissions and group features** section.
|
||||
1. Under **Membership**, select **Prevent adding new members to projects within this group**.
|
||||
1. Select **Save changes**.
|
||||
|
||||
All users who previously had permissions can no longer add members to a group.
|
||||
API requests to add a new user to a project are not possible.
|
||||
|
||||
## Export members as CSV **(PREMIUM)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/287940) in GitLab 14.2.
|
||||
|
@ -193,115 +71,6 @@ You can export a list of members in a group or subgroup as a CSV.
|
|||
1. Select **Export as CSV**.
|
||||
1. After the CSV file has been generated, it is emailed as an attachment to the user that requested it.
|
||||
|
||||
## Group access restriction by IP address **(PREMIUM)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/1985) in GitLab 12.0.
|
||||
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/215410) from GitLab Ultimate to GitLab Premium in 13.1.
|
||||
|
||||
To ensure only people from your organization can access particular
|
||||
resources, you can restrict access to groups by IP address. This group-level setting
|
||||
applies to:
|
||||
|
||||
- The GitLab UI, including subgroups, projects, and issues.
|
||||
- [In GitLab 12.3 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/12874), the API.
|
||||
|
||||
### Security implications
|
||||
|
||||
You should consider some security implications before configuring IP address restrictions.
|
||||
|
||||
- Restricting HTTP traffic on GitLab.com with IP address restrictions causes SSH requests (including Git operations over
|
||||
SSH) to fail. For more information, see [the relevant issue](https://gitlab.com/gitlab-org/gitlab/-/issues/271673).
|
||||
- Administrators and group owners can access group settings from any IP address, regardless of IP restriction. However:
|
||||
- Groups owners cannot access projects belonging to the group when accessing from a disallowed IP address.
|
||||
- Administrators can access projects belonging to the group when accessing from a disallowed IP address.
|
||||
Access to projects includes cloning code from them.
|
||||
- Users can still see group and project names and hierarchies. Only the following are restricted:
|
||||
- [Groups](../../api/groups.md), including all [group resources](../../api/api_resources.md#group-resources).
|
||||
- [Project](../../api/projects.md), including all [project resources](../../api/api_resources.md#project-resources).
|
||||
- When you register a runner, it is not bound by the IP restrictions. When the runner requests a new job or an update to
|
||||
a job's state, it is also not bound by the IP restrictions. But when the running CI/CD job sends Git requests from a
|
||||
restricted IP address, the IP restriction prevents code from being cloned.
|
||||
- Users may still see some events from the IP restricted groups and projects on their dashboard. Activity may include
|
||||
push, merge, issue, or comment events.
|
||||
|
||||
### Restrict group access by IP address
|
||||
|
||||
To restrict group access by IP address:
|
||||
|
||||
1. Go to the group's **Settings > General** page.
|
||||
1. Expand the **Permissions and group features** section.
|
||||
1. In the **Allow access to the following IP addresses** field, enter IPv4 or IPv6 address ranges in CIDR notation.
|
||||
1. Select **Save changes**.
|
||||
|
||||
In self-managed installations of GitLab 15.1 and later, you can also configure
|
||||
[globally-allowed IP address ranges](../admin_area/settings/visibility_and_access_controls.md#configure-globally-allowed-ip-address-ranges)
|
||||
at the group level.
|
||||
|
||||
## Restrict group access by domain **(PREMIUM)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7297) in GitLab 12.2.
|
||||
> - Support for specifying multiple email domains [added](https://gitlab.com/gitlab-org/gitlab/-/issues/33143) in GitLab 13.1.
|
||||
> - Support for restricting access to projects in the group [added](https://gitlab.com/gitlab-org/gitlab/-/issues/14004) in GitLab 14.1.2.
|
||||
|
||||
You can prevent users with email addresses in specific domains from being added to a group and its projects.
|
||||
|
||||
To restrict group access by domain:
|
||||
|
||||
1. Go to the group's **Settings > General** page.
|
||||
1. Expand the **Permissions and group features** section.
|
||||
1. In the **Restrict membership by email** field, enter the domain names.
|
||||
1. Select **Save changes**.
|
||||
|
||||
Any time you attempt to add a new user, the user's [primary email](../profile/index.md#change-your-primary-email) is compared against this list.
|
||||
Only users with a [primary email](../profile/index.md#change-your-primary-email) that matches any of the configured email domain restrictions
|
||||
can be added to the group.
|
||||
|
||||
The most popular public email domains cannot be restricted, such as:
|
||||
|
||||
- `gmail.com`, `yahoo.com`, `aol.com`, `icloud.com`
|
||||
- `hotmail.com`, `hotmail.co.uk`, `hotmail.fr`
|
||||
- `msn.com`, `live.com`, `outlook.com`
|
||||
|
||||
## Prevent project forking outside group **(PREMIUM)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216987) in GitLab 13.3.
|
||||
|
||||
By default, projects in a group can be forked.
|
||||
Optionally, on [GitLab Premium](https://about.gitlab.com/pricing/) or higher tiers,
|
||||
you can prevent the projects in a group from being forked outside of the current top-level group.
|
||||
|
||||
This setting will be removed from the SAML setting page, and migrated to the
|
||||
group settings page. In the interim period, both of these settings are taken into consideration.
|
||||
If even one is set to `true`, then the group does not allow outside forks.
|
||||
|
||||
To prevent projects from being forked outside the group:
|
||||
|
||||
1. Go to the top-level group's **Settings > General** page.
|
||||
1. Expand the **Permissions and group features** section.
|
||||
1. Check **Prevent project forking outside current group**.
|
||||
1. Select **Save changes**.
|
||||
|
||||
Existing forks are not removed.
|
||||
|
||||
## Group push rules **(PREMIUM)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/34370) in GitLab 12.8.
|
||||
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/224129) in GitLab 13.4.
|
||||
|
||||
Group push rules allow group maintainers to set
|
||||
[push rules](../project/repository/push_rules.md) for newly created projects in the specific group.
|
||||
|
||||
To configure push rules for a group:
|
||||
|
||||
1. Go to the groups's **Push Rules** page.
|
||||
1. Select the settings you want.
|
||||
1. Select **Save Push Rules**.
|
||||
|
||||
The group's new subgroups have push rules set for them based on either:
|
||||
|
||||
- The closest parent group with push rules defined.
|
||||
- Push rules set at the instance level, if no parent groups have push rules defined.
|
||||
|
||||
## Related topics
|
||||
|
||||
- [Group wikis](../project/wiki/index.md)
|
||||
|
@ -323,20 +92,8 @@ The group's new subgroups have push rules set for them based on either:
|
|||
- [Integrations](../admin_area/settings/project_integration_management.md).
|
||||
- [Transfer a project into a group](../project/settings/index.md#transfer-a-project-to-another-namespace).
|
||||
- [Share a project with a group](../project/members/share_project_with_groups.md): Give all group members access to the project at once.
|
||||
- [Lock the sharing with group feature](#prevent-a-project-from-being-shared-with-groups).
|
||||
- [Lock the sharing with group feature](access_and_permissions.md#prevent-a-project-from-being-shared-with-groups).
|
||||
- [Enforce two-factor authentication (2FA)](../../security/two_factor_authentication.md#enforce-2fa-for-all-users-in-a-group): Enforce 2FA
|
||||
for all group members.
|
||||
- Namespaces [API](../../api/namespaces.md) and [Rake tasks](../../raketasks/index.md).
|
||||
- [Control access and visibility](../admin_area/settings/visibility_and_access_controls.md).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Verify if access is blocked by IP restriction
|
||||
|
||||
If a user sees a 404 when they would normally expect access, and the problem is limited to a specific group, search the `auth.log` rails log for one or more of the following:
|
||||
|
||||
- `json.message`: `'Attempting to access IP restricted group'`
|
||||
- `json.allowed`: `false`
|
||||
|
||||
In viewing the log entries, compare the `remote.ip` with the list of
|
||||
[allowed IPs](#group-access-restriction-by-ip-address) for the group.
|
||||
|
|
|
@ -217,7 +217,7 @@ The following table lists project permissions available for each role:
|
|||
4. If the [branch is protected](project/protected_branches.md), this depends on the access Developers and Maintainers are given.
|
||||
5. Guest users can access GitLab [**Releases**](project/releases/index.md) for downloading assets but are not allowed to download the source code nor see [repository information like commits and release evidence](project/releases/index.md#view-a-release-and-download-assets).
|
||||
6. Actions are limited only to records owned (referenced) by user.
|
||||
7. When [Share Group Lock](group/index.md#prevent-a-project-from-being-shared-with-groups) is enabled the project can't be shared with other groups. It does not affect group with group sharing.
|
||||
7. When [Share Group Lock](group/access_and_permissions.md#prevent-a-project-from-being-shared-with-groups) is enabled the project can't be shared with other groups. It does not affect group with group sharing.
|
||||
8. For information on eligible approvers for merge requests, see
|
||||
[Eligible approvers](project/merge_requests/approvals/rules.md#eligible-approvers).
|
||||
9. Applies only to comments on [Design Management](project/issues/design_management.md) designs.
|
||||
|
@ -412,7 +412,7 @@ The following table lists group permissions available for each role:
|
|||
| Delete [group wiki](project/wiki/group.md) pages | | | ✓ | ✓ | ✓ |
|
||||
| Edit [epic](group/epics/index.md) comments (posted by any user) | | | | ✓ (2) | ✓ (2) |
|
||||
| List group deploy tokens | | | | ✓ | ✓ |
|
||||
| Manage [group push rules](group/index.md#group-push-rules) | | | | ✓ | ✓ |
|
||||
| Manage [group push rules](group/access_and_permissions.md#group-push-rules) | | | | ✓ | ✓ |
|
||||
| View/manage group-level Kubernetes cluster | | | | ✓ | ✓ |
|
||||
| Create and manage compliance frameworks | | | | | ✓ |
|
||||
| Create/Delete group deploy tokens | | | | | ✓ |
|
||||
|
@ -600,7 +600,7 @@ for more information.
|
|||
## LDAP users permissions
|
||||
|
||||
LDAP user permissions can be manually overridden by an administrator.
|
||||
Read through the documentation on [LDAP users permissions](group/index.md#manage-group-memberships-via-ldap) to learn more.
|
||||
Read through the documentation on [LDAP users permissions](group/access_and_permissions.md#manage-group-memberships-via-ldap) to learn more.
|
||||
|
||||
## Project aliases
|
||||
|
||||
|
|
|
@ -91,7 +91,7 @@ Each user's access is based on:
|
|||
Prerequisite:
|
||||
|
||||
- You must have the Maintainer or Owner role.
|
||||
- Sharing the project with other groups must not be [prevented](../../group/index.md#prevent-a-project-from-being-shared-with-groups).
|
||||
- Sharing the project with other groups must not be [prevented](../../group/access_and_permissions.md#prevent-a-project-from-being-shared-with-groups).
|
||||
|
||||
To add groups to a project:
|
||||
|
||||
|
@ -173,7 +173,7 @@ To remove a member from a project:
|
|||
user has not forked the private repository or created webhooks. Existing forks continue to receive
|
||||
changes from the upstream project, and webhooks continue to receive updates. You may also want to configure your project
|
||||
to prevent projects in a group
|
||||
[from being forked outside their group](../../group/index.md#prevent-project-forking-outside-group).
|
||||
[from being forked outside their group](../../group/access_and_permissions.md#prevent-project-forking-outside-group).
|
||||
1. Select **Remove member**.
|
||||
|
||||
## Filter and sort members
|
||||
|
|
|
@ -81,4 +81,4 @@ It is possible to prevent projects in a group from [sharing
|
|||
a project with another group](../members/share_project_with_groups.md).
|
||||
This allows for tighter control over project access.
|
||||
|
||||
Learn more about [Share with group lock](../../group/index.md#prevent-a-project-from-being-shared-with-groups).
|
||||
Learn more about [Share with group lock](../../group/access_and_permissions.md#prevent-a-project-from-being-shared-with-groups).
|
||||
|
|
|
@ -25,7 +25,7 @@ For custom push rules use [server hooks](../../../administration/server_hooks.md
|
|||
## Enable global push rules
|
||||
|
||||
You can create push rules for all new projects to inherit, but they can be overridden
|
||||
at the project level or the [group level](../../group/index.md#group-push-rules).
|
||||
at the project level or the [group level](../../group/access_and_permissions.md#group-push-rules).
|
||||
All projects created after you configure global push rules inherit this
|
||||
configuration. However, each existing project must be updated manually, using the
|
||||
process described in [Override global push rules per project](#override-global-push-rules-per-project).
|
||||
|
|
|
@ -2144,6 +2144,7 @@
|
|||
static: |-
|
||||
<p data-sourcepos="3:1-3:5" dir="auto"><a href="/url" title="title">foo</a></p>
|
||||
wysiwyg: |-
|
||||
<pre>[foo]: /url "title"</pre>
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title">foo</a></p>
|
||||
04_07__leaf_blocks__link_reference_definitions__002:
|
||||
canonical: |
|
||||
|
@ -2151,6 +2152,7 @@
|
|||
static: |-
|
||||
<p data-sourcepos="5:1-5:5" dir="auto"><a href="/url" title="the title">foo</a></p>
|
||||
wysiwyg: |-
|
||||
<pre>[foo]: /url "the title"</pre>
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="the title">foo</a></p>
|
||||
04_07__leaf_blocks__link_reference_definitions__003:
|
||||
canonical: |
|
||||
|
@ -2158,6 +2160,7 @@
|
|||
static: |-
|
||||
<p data-sourcepos="3:1-3:11" dir="auto"><a href="my_(url)" title="title (with parens)">Foo*bar]</a></p>
|
||||
wysiwyg: |-
|
||||
<pre>[foo*bar\]]: my_(url) "title (with parens)"</pre>
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="my_(url)" title="title (with parens)">Foo*bar]</a></p>
|
||||
04_07__leaf_blocks__link_reference_definitions__004:
|
||||
canonical: |
|
||||
|
@ -2165,6 +2168,7 @@
|
|||
static: |-
|
||||
<p data-sourcepos="5:1-5:9" dir="auto"><a href="my%20url" title="title">Foo bar</a></p>
|
||||
wysiwyg: |-
|
||||
<pre>[foo bar]: my url "title"</pre>
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="my%20url" title="title">Foo bar</a></p>
|
||||
04_07__leaf_blocks__link_reference_definitions__005:
|
||||
canonical: |
|
||||
|
@ -2180,6 +2184,11 @@
|
|||
line2
|
||||
">foo</a></p>
|
||||
wysiwyg: |-
|
||||
<pre>[foo]: /url "
|
||||
title
|
||||
line1
|
||||
line2
|
||||
"</pre>
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="
|
||||
title
|
||||
line1
|
||||
|
@ -2204,6 +2213,7 @@
|
|||
static: |-
|
||||
<p data-sourcepos="4:1-4:5" dir="auto"><a href="/url">foo</a></p>
|
||||
wysiwyg: |-
|
||||
<pre>[foo]: /url</pre>
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url">foo</a></p>
|
||||
04_07__leaf_blocks__link_reference_definitions__008:
|
||||
canonical: |
|
||||
|
@ -2221,6 +2231,7 @@
|
|||
static: |-
|
||||
<p data-sourcepos="3:1-3:5" dir="auto"><a href="">foo</a></p>
|
||||
wysiwyg: |-
|
||||
<pre>[foo]: </pre>
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="">foo</a></p>
|
||||
04_07__leaf_blocks__link_reference_definitions__010:
|
||||
canonical: |
|
||||
|
@ -2238,6 +2249,7 @@
|
|||
static: |-
|
||||
<p data-sourcepos="3:1-3:5" dir="auto"><a href="/url%5Cbar*baz" title='foo"bar\baz'>foo</a></p>
|
||||
wysiwyg: |-
|
||||
<pre>[foo]: /url\bar*baz "foo"bar\baz"</pre>
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url%5Cbar*baz" title="foo"bar\baz">foo</a></p>
|
||||
04_07__leaf_blocks__link_reference_definitions__012:
|
||||
canonical: |
|
||||
|
@ -2246,6 +2258,7 @@
|
|||
<p data-sourcepos="1:1-1:5" dir="auto"><a href="url">foo</a></p>
|
||||
wysiwyg: |-
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="url">foo</a></p>
|
||||
<pre>[foo]: url</pre>
|
||||
04_07__leaf_blocks__link_reference_definitions__013:
|
||||
canonical: |
|
||||
<p><a href="first">foo</a></p>
|
||||
|
@ -2253,12 +2266,15 @@
|
|||
<p data-sourcepos="1:1-1:5" dir="auto"><a href="first">foo</a></p>
|
||||
wysiwyg: |-
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="first">foo</a></p>
|
||||
<pre>[foo]: first</pre>
|
||||
<pre>[foo]: second</pre>
|
||||
04_07__leaf_blocks__link_reference_definitions__014:
|
||||
canonical: |
|
||||
<p><a href="/url">Foo</a></p>
|
||||
static: |-
|
||||
<p data-sourcepos="3:1-3:5" dir="auto"><a href="/url">Foo</a></p>
|
||||
wysiwyg: |-
|
||||
<pre>[foo]: /url</pre>
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url">Foo</a></p>
|
||||
04_07__leaf_blocks__link_reference_definitions__015:
|
||||
canonical: |
|
||||
|
@ -2266,18 +2282,20 @@
|
|||
static: |-
|
||||
<p data-sourcepos="3:1-3:8" dir="auto"><a href="/%CF%86%CE%BF%CF%85">αγω</a></p>
|
||||
wysiwyg: |-
|
||||
<pre>[αγω]: /φου</pre>
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/%CF%86%CE%BF%CF%85">αγω</a></p>
|
||||
04_07__leaf_blocks__link_reference_definitions__016:
|
||||
canonical: ""
|
||||
static: ""
|
||||
wysiwyg: |-
|
||||
<p></p>
|
||||
<pre>[foo]: /url</pre>
|
||||
04_07__leaf_blocks__link_reference_definitions__017:
|
||||
canonical: |
|
||||
<p>bar</p>
|
||||
static: |-
|
||||
<p data-sourcepos="1:1-4:3" dir="auto">bar</p>
|
||||
wysiwyg: |-
|
||||
<pre>[foo]: /url</pre>
|
||||
<p>bar</p>
|
||||
04_07__leaf_blocks__link_reference_definitions__018:
|
||||
canonical: |
|
||||
|
@ -2292,6 +2310,7 @@
|
|||
static: |-
|
||||
<p data-sourcepos="1:1-2:10" dir="auto">"title" ok</p>
|
||||
wysiwyg: |-
|
||||
<pre>[foo]: /url</pre>
|
||||
<p>"title" ok</p>
|
||||
04_07__leaf_blocks__link_reference_definitions__020:
|
||||
canonical: |
|
||||
|
@ -2349,6 +2368,7 @@
|
|||
</blockquote>
|
||||
wysiwyg: |-
|
||||
<h1><a target="_blank" rel="noopener noreferrer nofollow" href="/url">Foo</a></h1>
|
||||
<pre>[foo]: /url</pre>
|
||||
<blockquote multiline="false"><p>bar</p></blockquote>
|
||||
04_07__leaf_blocks__link_reference_definitions__024:
|
||||
canonical: |
|
||||
|
@ -2359,6 +2379,7 @@
|
|||
<a id="user-content-bar" class="anchor" href="#bar" aria-hidden="true"></a>bar</h1>
|
||||
<p data-sourcepos="4:1-4:5" dir="auto"><a href="/url">foo</a></p>
|
||||
wysiwyg: |-
|
||||
<pre>[foo]: /url</pre>
|
||||
<h1>bar</h1>
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url">foo</a></p>
|
||||
04_07__leaf_blocks__link_reference_definitions__025:
|
||||
|
@ -2369,6 +2390,7 @@
|
|||
<p data-sourcepos="1:1-3:5" dir="auto">===
|
||||
<a href="/url">foo</a></p>
|
||||
wysiwyg: |-
|
||||
<pre>[foo]: /url</pre>
|
||||
<p>===
|
||||
<a target="_blank" rel="noopener noreferrer nofollow" href="/url">foo</a></p>
|
||||
04_07__leaf_blocks__link_reference_definitions__026:
|
||||
|
@ -2381,6 +2403,9 @@
|
|||
<a href="/bar-url" title="bar">bar</a>,
|
||||
<a href="/baz-url">baz</a></p>
|
||||
wysiwyg: |-
|
||||
<pre>[foo]: /foo-url "foo"</pre>
|
||||
<pre>[bar]: /bar-url "bar"</pre>
|
||||
<pre>[baz]: /baz-url</pre>
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/foo-url" title="foo">foo</a>,
|
||||
<a target="_blank" rel="noopener noreferrer nofollow" href="/bar-url" title="bar">bar</a>,
|
||||
<a target="_blank" rel="noopener noreferrer nofollow" href="/baz-url">baz</a></p>
|
||||
|
@ -2395,12 +2420,12 @@
|
|||
</blockquote>
|
||||
wysiwyg: |-
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url">foo</a></p>
|
||||
<blockquote multiline="false"><p></p></blockquote>
|
||||
<blockquote multiline="false"><pre>[foo]: /url</pre></blockquote>
|
||||
04_07__leaf_blocks__link_reference_definitions__028:
|
||||
canonical: ""
|
||||
static: ""
|
||||
wysiwyg: |-
|
||||
<p></p>
|
||||
<pre>[foo]: /url</pre>
|
||||
04_08__leaf_blocks__paragraphs__001:
|
||||
canonical: |
|
||||
<p>aaa</p>
|
||||
|
@ -4543,7 +4568,7 @@
|
|||
</li>
|
||||
</ul>
|
||||
wysiwyg: |-
|
||||
<ul bullet="*"><li><p>a</p></li><li><p>b</p></li><li><p>d</p></li></ul>
|
||||
<ul bullet="*"><li><p>a</p></li><li><p>b</p><pre>[ref]: /url</pre></li><li><p>d</p></li></ul>
|
||||
05_04__container_blocks__lists__018:
|
||||
canonical: |
|
||||
<ul>
|
||||
|
@ -4880,6 +4905,7 @@
|
|||
<p data-sourcepos="1:1-1:5" dir="auto"><a href="/bar*" title="ti*tle">foo</a></p>
|
||||
wysiwyg: |-
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/bar*" title="ti*tle">foo</a></p>
|
||||
<pre>[foo]: /bar* "ti*tle"</pre>
|
||||
06_02__inlines__backslash_escapes__013:
|
||||
canonical: |
|
||||
<pre><code class="language-foo+bar">foo
|
||||
|
@ -4969,6 +4995,7 @@
|
|||
<p data-sourcepos="1:1-1:5" dir="auto"><a href="/f%C3%B6%C3%B6" title="föö">foo</a></p>
|
||||
wysiwyg: |-
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/f%C3%B6%C3%B6" title="föö">foo</a></p>
|
||||
<pre>[foo]: /föö "föö"</pre>
|
||||
06_03__inlines__entity_and_numeric_character_references__010:
|
||||
canonical: |
|
||||
<pre><code class="language-föö">foo
|
||||
|
@ -6475,6 +6502,7 @@
|
|||
<p data-sourcepos="1:1-1:10" dir="auto"><a href="/url" title="title">foo</a></p>
|
||||
wysiwyg: |-
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title">foo</a></p>
|
||||
<pre>[bar]: /url "title"</pre>
|
||||
06_07__inlines__links__044:
|
||||
canonical: |
|
||||
<p><a href="/uri">link [foo [bar]]</a></p>
|
||||
|
@ -6482,6 +6510,7 @@
|
|||
<p data-sourcepos="1:1-1:23" dir="auto"><a href="/uri">link [foo [bar]]</a></p>
|
||||
wysiwyg: |-
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/uri">link [foo [bar]]</a></p>
|
||||
<pre>[ref]: /uri</pre>
|
||||
06_07__inlines__links__045:
|
||||
canonical: |
|
||||
<p><a href="/uri">link [bar</a></p>
|
||||
|
@ -6489,6 +6518,7 @@
|
|||
<p data-sourcepos="1:1-1:17" dir="auto"><a href="/uri">link [bar</a></p>
|
||||
wysiwyg: |-
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/uri">link [bar</a></p>
|
||||
<pre>[ref]: /uri</pre>
|
||||
06_07__inlines__links__046:
|
||||
canonical: |
|
||||
<p><a href="/uri">link <em>foo <strong>bar</strong> <code>#</code></em></a></p>
|
||||
|
@ -6496,6 +6526,7 @@
|
|||
<p data-sourcepos="1:1-1:29" dir="auto"><a href="/uri">link <em>foo <strong>bar</strong> <code>#</code></em></a></p>
|
||||
wysiwyg: |-
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/uri">link <em>foo </em><strong><em>bar<code>#</code></em></strong></a></p>
|
||||
<pre>[ref]: /uri</pre>
|
||||
06_07__inlines__links__047:
|
||||
canonical: |
|
||||
<p><a href="/uri"><img src="moon.jpg" alt="moon" /></a></p>
|
||||
|
@ -6503,6 +6534,7 @@
|
|||
<p data-sourcepos="1:1-1:24" dir="auto"><a href="/uri"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="moon" decoding="async" class="lazy" data-src="moon.jpg"></a></p>
|
||||
wysiwyg: |-
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/uri"><img src="moon.jpg" alt="moon"></a></p>
|
||||
<pre>[ref]: /uri</pre>
|
||||
06_07__inlines__links__048:
|
||||
canonical: |
|
||||
<p>[foo <a href="/uri">bar</a>]<a href="/uri">ref</a></p>
|
||||
|
@ -6510,6 +6542,7 @@
|
|||
<p data-sourcepos="1:1-1:22" dir="auto">[foo <a href="/uri">bar</a>]<a href="/uri">ref</a></p>
|
||||
wysiwyg: |-
|
||||
<p>[foo <a target="_blank" rel="noopener noreferrer nofollow" href="/uri">bar</a>]<a target="_blank" rel="noopener noreferrer nofollow" href="/uri">ref</a></p>
|
||||
<pre>[ref]: /uri</pre>
|
||||
06_07__inlines__links__049:
|
||||
canonical: |
|
||||
<p>[foo <em>bar <a href="/uri">baz</a></em>]<a href="/uri">ref</a></p>
|
||||
|
@ -6517,6 +6550,7 @@
|
|||
<p data-sourcepos="1:1-1:27" dir="auto">[foo <em>bar <a href="/uri">baz</a></em>]<a href="/uri">ref</a></p>
|
||||
wysiwyg: |-
|
||||
<p>[foo <em>bar </em><a target="_blank" rel="noopener noreferrer nofollow" href="/uri"><em>baz</em></a>]<a target="_blank" rel="noopener noreferrer nofollow" href="/uri">ref</a></p>
|
||||
<pre>[ref]: /uri</pre>
|
||||
06_07__inlines__links__050:
|
||||
canonical: |
|
||||
<p>*<a href="/uri">foo*</a></p>
|
||||
|
@ -6524,6 +6558,7 @@
|
|||
<p data-sourcepos="1:1-1:12" dir="auto">*<a href="/uri">foo*</a></p>
|
||||
wysiwyg: |-
|
||||
<p>*<a target="_blank" rel="noopener noreferrer nofollow" href="/uri">foo*</a></p>
|
||||
<pre>[ref]: /uri</pre>
|
||||
06_07__inlines__links__051:
|
||||
canonical: |
|
||||
<p><a href="/uri">foo *bar</a></p>
|
||||
|
@ -6531,6 +6566,7 @@
|
|||
<p data-sourcepos="1:1-1:15" dir="auto"><a href="/uri">foo *bar</a></p>
|
||||
wysiwyg: |-
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/uri">foo *bar</a></p>
|
||||
<pre>[ref]: /uri</pre>
|
||||
06_07__inlines__links__052:
|
||||
canonical: |
|
||||
<p>[foo <bar attr="][ref]"></p>
|
||||
|
@ -6538,6 +6574,7 @@
|
|||
<p data-sourcepos="1:1-1:24" dir="auto">[foo </p>
|
||||
wysiwyg: |-
|
||||
<p>[foo </p>
|
||||
<pre>[ref]: /uri</pre>
|
||||
06_07__inlines__links__053:
|
||||
canonical: |
|
||||
<p>[foo<code>][ref]</code></p>
|
||||
|
@ -6545,6 +6582,7 @@
|
|||
<p data-sourcepos="1:1-1:12" dir="auto">[foo<code>][ref]</code></p>
|
||||
wysiwyg: |-
|
||||
<p>[foo<code>][ref]</code></p>
|
||||
<pre>[ref]: /uri</pre>
|
||||
06_07__inlines__links__054:
|
||||
canonical: |
|
||||
<p>[foo<a href="http://example.com/?search=%5D%5Bref%5D">http://example.com/?search=][ref]</a></p>
|
||||
|
@ -6552,6 +6590,7 @@
|
|||
<p data-sourcepos="1:1-1:39" dir="auto">[foo<a href="http://example.com/?search=%5D%5Bref%5D" rel="nofollow noreferrer noopener" target="_blank">http://example.com/?search=][ref]</a></p>
|
||||
wysiwyg: |-
|
||||
<p>[foo<a target="_blank" rel="noopener noreferrer nofollow" href="http://example.com/?search=%5D%5Bref%5D">http://example.com/?search=][ref]</a></p>
|
||||
<pre>[ref]: /uri</pre>
|
||||
06_07__inlines__links__055:
|
||||
canonical: |
|
||||
<p><a href="/url" title="title">foo</a></p>
|
||||
|
@ -6559,6 +6598,7 @@
|
|||
<p data-sourcepos="1:1-1:10" dir="auto"><a href="/url" title="title">foo</a></p>
|
||||
wysiwyg: |-
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title">foo</a></p>
|
||||
<pre>[bar]: /url "title"</pre>
|
||||
06_07__inlines__links__056:
|
||||
canonical: |
|
||||
<p><a href="/url">Толпой</a> is a Russian word.</p>
|
||||
|
@ -6566,12 +6606,14 @@
|
|||
<p data-sourcepos="1:1-1:47" dir="auto"><a href="/url">Толпой</a> is a Russian word.</p>
|
||||
wysiwyg: |-
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url">Толпой</a> is a Russian word.</p>
|
||||
<pre>[толпой]: /url</pre>
|
||||
06_07__inlines__links__057:
|
||||
canonical: |
|
||||
<p><a href="/url">Baz</a></p>
|
||||
static: |-
|
||||
<p data-sourcepos="4:1-4:14" dir="auto"><a href="/url">Baz</a></p>
|
||||
wysiwyg: |-
|
||||
<pre>[foo bar]: /url</pre>
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url">Baz</a></p>
|
||||
06_07__inlines__links__058:
|
||||
canonical: |
|
||||
|
@ -6580,6 +6622,7 @@
|
|||
<p data-sourcepos="1:1-1:11" dir="auto">[foo] <a href="/url" title="title">bar</a></p>
|
||||
wysiwyg: |-
|
||||
<p>[foo] <a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title">bar</a></p>
|
||||
<pre>[bar]: /url "title"</pre>
|
||||
06_07__inlines__links__059:
|
||||
canonical: |
|
||||
<p>[foo]
|
||||
|
@ -6590,12 +6633,15 @@
|
|||
wysiwyg: |-
|
||||
<p>[foo]
|
||||
<a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title">bar</a></p>
|
||||
<pre>[bar]: /url "title"</pre>
|
||||
06_07__inlines__links__060:
|
||||
canonical: |
|
||||
<p><a href="/url1">bar</a></p>
|
||||
static: |-
|
||||
<p data-sourcepos="5:1-5:10" dir="auto"><a href="/url1">bar</a></p>
|
||||
wysiwyg: |-
|
||||
<pre>[foo]: /url1</pre>
|
||||
<pre>[foo]: /url2</pre>
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url1">bar</a></p>
|
||||
06_07__inlines__links__061:
|
||||
canonical: |
|
||||
|
@ -6604,6 +6650,7 @@
|
|||
<p data-sourcepos="1:1-1:32" dir="auto">[bar][foo<span>!</span>]</p>
|
||||
wysiwyg: |-
|
||||
<p>[bar][foo!]</p>
|
||||
<pre>[foo!]: /url</pre>
|
||||
06_07__inlines__links__062:
|
||||
canonical: |
|
||||
<p>[foo][ref[]</p>
|
||||
|
@ -6641,12 +6688,14 @@
|
|||
<p data-sourcepos="1:1-1:12" dir="auto"><a href="/uri">foo</a></p>
|
||||
wysiwyg: |-
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/uri">foo</a></p>
|
||||
<pre>[ref\[]: /uri</pre>
|
||||
06_07__inlines__links__066:
|
||||
canonical: |
|
||||
<p><a href="/uri">bar\</a></p>
|
||||
static: |-
|
||||
<p data-sourcepos="3:1-3:7" dir="auto"><a href="/uri">bar\</a></p>
|
||||
wysiwyg: |-
|
||||
<pre>[bar\\]: /uri</pre>
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/uri">bar\</a></p>
|
||||
06_07__inlines__links__067:
|
||||
canonical: |
|
||||
|
@ -6681,6 +6730,7 @@
|
|||
<p data-sourcepos="1:1-1:7" dir="auto"><a href="/url" title="title">foo</a></p>
|
||||
wysiwyg: |-
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title">foo</a></p>
|
||||
<pre>[foo]: /url "title"</pre>
|
||||
06_07__inlines__links__070:
|
||||
canonical: |
|
||||
<p><a href="/url" title="title"><em>foo</em> bar</a></p>
|
||||
|
@ -6688,6 +6738,7 @@
|
|||
<p data-sourcepos="1:1-1:13" dir="auto"><a href="/url" title="title"><em>foo</em> bar</a></p>
|
||||
wysiwyg: |-
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title"><em>foo</em> bar</a></p>
|
||||
<pre>[*foo* bar]: /url "title"</pre>
|
||||
06_07__inlines__links__071:
|
||||
canonical: |
|
||||
<p><a href="/url" title="title">Foo</a></p>
|
||||
|
@ -6695,6 +6746,7 @@
|
|||
<p data-sourcepos="1:1-1:7" dir="auto"><a href="/url" title="title">Foo</a></p>
|
||||
wysiwyg: |-
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title">Foo</a></p>
|
||||
<pre>[foo]: /url "title"</pre>
|
||||
06_07__inlines__links__072:
|
||||
canonical: |
|
||||
<p><a href="/url" title="title">foo</a>
|
||||
|
@ -6705,6 +6757,7 @@
|
|||
wysiwyg: |-
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title">foo</a>
|
||||
[]</p>
|
||||
<pre>[foo]: /url "title"</pre>
|
||||
06_07__inlines__links__073:
|
||||
canonical: |
|
||||
<p><a href="/url" title="title">foo</a></p>
|
||||
|
@ -6712,6 +6765,7 @@
|
|||
<p data-sourcepos="1:1-1:5" dir="auto"><a href="/url" title="title">foo</a></p>
|
||||
wysiwyg: |-
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title">foo</a></p>
|
||||
<pre>[foo]: /url "title"</pre>
|
||||
06_07__inlines__links__074:
|
||||
canonical: |
|
||||
<p><a href="/url" title="title"><em>foo</em> bar</a></p>
|
||||
|
@ -6719,6 +6773,7 @@
|
|||
<p data-sourcepos="1:1-1:11" dir="auto"><a href="/url" title="title"><em>foo</em> bar</a></p>
|
||||
wysiwyg: |-
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title"><em>foo</em> bar</a></p>
|
||||
<pre>[*foo* bar]: /url "title"</pre>
|
||||
06_07__inlines__links__075:
|
||||
canonical: |
|
||||
<p>[<a href="/url" title="title"><em>foo</em> bar</a>]</p>
|
||||
|
@ -6726,6 +6781,7 @@
|
|||
<p data-sourcepos="1:1-1:13" dir="auto">[<a href="/url" title="title"><em>foo</em> bar</a>]</p>
|
||||
wysiwyg: |-
|
||||
<p>[<a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title"><em>foo</em> bar</a>]</p>
|
||||
<pre>[*foo* bar]: /url "title"</pre>
|
||||
06_07__inlines__links__076:
|
||||
canonical: |
|
||||
<p>[[bar <a href="/url">foo</a></p>
|
||||
|
@ -6733,6 +6789,7 @@
|
|||
<p data-sourcepos="1:1-1:11" dir="auto">[[bar <a href="/url">foo</a></p>
|
||||
wysiwyg: |-
|
||||
<p>[[bar <a target="_blank" rel="noopener noreferrer nofollow" href="/url">foo</a></p>
|
||||
<pre>[foo]: /url</pre>
|
||||
06_07__inlines__links__077:
|
||||
canonical: |
|
||||
<p><a href="/url" title="title">Foo</a></p>
|
||||
|
@ -6740,6 +6797,7 @@
|
|||
<p data-sourcepos="1:1-1:5" dir="auto"><a href="/url" title="title">Foo</a></p>
|
||||
wysiwyg: |-
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title">Foo</a></p>
|
||||
<pre>[foo]: /url "title"</pre>
|
||||
06_07__inlines__links__078:
|
||||
canonical: |
|
||||
<p><a href="/url">foo</a> bar</p>
|
||||
|
@ -6747,6 +6805,7 @@
|
|||
<p data-sourcepos="1:1-1:9" dir="auto"><a href="/url">foo</a> bar</p>
|
||||
wysiwyg: |-
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url">foo</a> bar</p>
|
||||
<pre>[foo]: /url</pre>
|
||||
06_07__inlines__links__079:
|
||||
canonical: |
|
||||
<p>[foo]</p>
|
||||
|
@ -6754,12 +6813,14 @@
|
|||
<p data-sourcepos="1:1-1:6" dir="auto">[foo]</p>
|
||||
wysiwyg: |-
|
||||
<p>[foo]</p>
|
||||
<pre>[foo]: /url "title"</pre>
|
||||
06_07__inlines__links__080:
|
||||
canonical: |
|
||||
<p>*<a href="/url">foo*</a></p>
|
||||
static: |-
|
||||
<p data-sourcepos="3:1-3:7" dir="auto">*<a href="/url">foo*</a></p>
|
||||
wysiwyg: |-
|
||||
<pre>[foo*]: /url</pre>
|
||||
<p>*<a target="_blank" rel="noopener noreferrer nofollow" href="/url">foo*</a></p>
|
||||
06_07__inlines__links__081:
|
||||
canonical: |
|
||||
|
@ -6768,6 +6829,8 @@
|
|||
<p data-sourcepos="1:1-1:10" dir="auto"><a href="/url2">foo</a></p>
|
||||
wysiwyg: |-
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url2">foo</a></p>
|
||||
<pre>[foo]: /url1</pre>
|
||||
<pre>[bar]: /url2</pre>
|
||||
06_07__inlines__links__082:
|
||||
canonical: |
|
||||
<p><a href="/url1">foo</a></p>
|
||||
|
@ -6775,6 +6838,7 @@
|
|||
<p data-sourcepos="1:1-1:7" dir="auto"><a href="/url1">foo</a></p>
|
||||
wysiwyg: |-
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url1">foo</a></p>
|
||||
<pre>[foo]: /url1</pre>
|
||||
06_07__inlines__links__083:
|
||||
canonical: |
|
||||
<p><a href="">foo</a></p>
|
||||
|
@ -6782,6 +6846,7 @@
|
|||
<p data-sourcepos="1:1-1:7" dir="auto"><a href="">foo</a></p>
|
||||
wysiwyg: |-
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="">foo</a></p>
|
||||
<pre>[foo]: /url1</pre>
|
||||
06_07__inlines__links__084:
|
||||
canonical: |
|
||||
<p><a href="/url1">foo</a>(not a link)</p>
|
||||
|
@ -6789,6 +6854,7 @@
|
|||
<p data-sourcepos="1:1-1:17" dir="auto"><a href="/url1">foo</a>(not a link)</p>
|
||||
wysiwyg: |-
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url1">foo</a>(not a link)</p>
|
||||
<pre>[foo]: /url1</pre>
|
||||
06_07__inlines__links__085:
|
||||
canonical: |
|
||||
<p>[foo]<a href="/url">bar</a></p>
|
||||
|
@ -6796,6 +6862,7 @@
|
|||
<p data-sourcepos="1:1-1:15" dir="auto">[foo]<a href="/url">bar</a></p>
|
||||
wysiwyg: |-
|
||||
<p>[foo]<a target="_blank" rel="noopener noreferrer nofollow" href="/url">bar</a></p>
|
||||
<pre>[baz]: /url</pre>
|
||||
06_07__inlines__links__086:
|
||||
canonical: |
|
||||
<p><a href="/url2">foo</a><a href="/url1">baz</a></p>
|
||||
|
@ -6803,6 +6870,8 @@
|
|||
<p data-sourcepos="1:1-1:15" dir="auto"><a href="/url2">foo</a><a href="/url1">baz</a></p>
|
||||
wysiwyg: |-
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url2">foo</a><a target="_blank" rel="noopener noreferrer nofollow" href="/url1">baz</a></p>
|
||||
<pre>[baz]: /url1</pre>
|
||||
<pre>[bar]: /url2</pre>
|
||||
06_07__inlines__links__087:
|
||||
canonical: |
|
||||
<p>[foo]<a href="/url1">bar</a></p>
|
||||
|
@ -6810,6 +6879,8 @@
|
|||
<p data-sourcepos="1:1-1:15" dir="auto">[foo]<a href="/url1">bar</a></p>
|
||||
wysiwyg: |-
|
||||
<p>[foo]<a target="_blank" rel="noopener noreferrer nofollow" href="/url1">bar</a></p>
|
||||
<pre>[baz]: /url1</pre>
|
||||
<pre>[foo]: /url2</pre>
|
||||
06_08__inlines__images__001:
|
||||
canonical: |
|
||||
<p><img src="/url" alt="foo" title="title" /></p>
|
||||
|
@ -6824,6 +6895,7 @@
|
|||
<p data-sourcepos="1:1-1:12" dir="auto"><a class="no-attachment-icon" href="train.jpg" target="_blank" rel="noopener noreferrer"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="foo bar" title="train & tracks" decoding="async" class="lazy" data-src="train.jpg"></a></p>
|
||||
wysiwyg: |-
|
||||
<p><img src="train.jpg" alt="foo bar" title="train & tracks"></p>
|
||||
<pre>[foo *bar*]: train.jpg "train & tracks"</pre>
|
||||
06_08__inlines__images__003:
|
||||
canonical: |
|
||||
<p><img src="/url2" alt="foo bar" /></p>
|
||||
|
@ -6845,6 +6917,7 @@
|
|||
<p data-sourcepos="1:1-1:14" dir="auto"><a class="no-attachment-icon" href="train.jpg" target="_blank" rel="noopener noreferrer"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="foo bar" title="train & tracks" decoding="async" class="lazy" data-src="train.jpg"></a></p>
|
||||
wysiwyg: |-
|
||||
<p><img src="train.jpg" alt="foo bar" title="train & tracks"></p>
|
||||
<pre>[foo *bar*]: train.jpg "train & tracks"</pre>
|
||||
06_08__inlines__images__006:
|
||||
canonical: |
|
||||
<p><img src="train.jpg" alt="foo bar" title="train & tracks" /></p>
|
||||
|
@ -6852,6 +6925,7 @@
|
|||
<p data-sourcepos="1:1-1:20" dir="auto"><a class="no-attachment-icon" href="train.jpg" target="_blank" rel="noopener noreferrer"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="foo bar" title="train & tracks" decoding="async" class="lazy" data-src="train.jpg"></a></p>
|
||||
wysiwyg: |-
|
||||
<p><img src="train.jpg" alt="foo bar" title="train & tracks"></p>
|
||||
<pre>[foobar]: train.jpg "train & tracks"</pre>
|
||||
06_08__inlines__images__007:
|
||||
canonical: |
|
||||
<p><img src="train.jpg" alt="foo" /></p>
|
||||
|
@ -6887,6 +6961,7 @@
|
|||
<p data-sourcepos="1:1-1:11" dir="auto"><a class="no-attachment-icon" href="/url" target="_blank" rel="noopener noreferrer"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="foo" decoding="async" class="lazy" data-src="/url"></a></p>
|
||||
wysiwyg: |-
|
||||
<p><img src="/url" alt="foo"></p>
|
||||
<pre>[bar]: /url</pre>
|
||||
06_08__inlines__images__012:
|
||||
canonical: |
|
||||
<p><img src="/url" alt="foo" /></p>
|
||||
|
@ -6894,6 +6969,7 @@
|
|||
<p data-sourcepos="1:1-1:11" dir="auto"><a class="no-attachment-icon" href="/url" target="_blank" rel="noopener noreferrer"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="foo" decoding="async" class="lazy" data-src="/url"></a></p>
|
||||
wysiwyg: |-
|
||||
<p><img src="/url" alt="foo"></p>
|
||||
<pre>[bar]: /url</pre>
|
||||
06_08__inlines__images__013:
|
||||
canonical: |
|
||||
<p><img src="/url" alt="foo" title="title" /></p>
|
||||
|
@ -6901,6 +6977,7 @@
|
|||
<p data-sourcepos="1:1-1:8" dir="auto"><a class="no-attachment-icon" href="/url" target="_blank" rel="noopener noreferrer"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="foo" title="title" decoding="async" class="lazy" data-src="/url"></a></p>
|
||||
wysiwyg: |-
|
||||
<p><img src="/url" alt="foo" title="title"></p>
|
||||
<pre>[foo]: /url "title"</pre>
|
||||
06_08__inlines__images__014:
|
||||
canonical: |
|
||||
<p><img src="/url" alt="foo bar" title="title" /></p>
|
||||
|
@ -6908,6 +6985,7 @@
|
|||
<p data-sourcepos="1:1-1:14" dir="auto"><a class="no-attachment-icon" href="/url" target="_blank" rel="noopener noreferrer"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="foo bar" title="title" decoding="async" class="lazy" data-src="/url"></a></p>
|
||||
wysiwyg: |-
|
||||
<p><img src="/url" alt="foo bar" title="title"></p>
|
||||
<pre>[*foo* bar]: /url "title"</pre>
|
||||
06_08__inlines__images__015:
|
||||
canonical: |
|
||||
<p><img src="/url" alt="Foo" title="title" /></p>
|
||||
|
@ -6915,6 +6993,7 @@
|
|||
<p data-sourcepos="1:1-1:8" dir="auto"><a class="no-attachment-icon" href="/url" target="_blank" rel="noopener noreferrer"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Foo" title="title" decoding="async" class="lazy" data-src="/url"></a></p>
|
||||
wysiwyg: |-
|
||||
<p><img src="/url" alt="Foo" title="title"></p>
|
||||
<pre>[foo]: /url "title"</pre>
|
||||
06_08__inlines__images__016:
|
||||
canonical: |
|
||||
<p><img src="/url" alt="foo" title="title" />
|
||||
|
@ -6925,6 +7004,7 @@
|
|||
wysiwyg: |-
|
||||
<p><img src="/url" alt="foo" title="title">
|
||||
[]</p>
|
||||
<pre>[foo]: /url "title"</pre>
|
||||
06_08__inlines__images__017:
|
||||
canonical: |
|
||||
<p><img src="/url" alt="foo" title="title" /></p>
|
||||
|
@ -6932,6 +7012,7 @@
|
|||
<p data-sourcepos="1:1-1:6" dir="auto"><a class="no-attachment-icon" href="/url" target="_blank" rel="noopener noreferrer"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="foo" title="title" decoding="async" class="lazy" data-src="/url"></a></p>
|
||||
wysiwyg: |-
|
||||
<p><img src="/url" alt="foo" title="title"></p>
|
||||
<pre>[foo]: /url "title"</pre>
|
||||
06_08__inlines__images__018:
|
||||
canonical: |
|
||||
<p><img src="/url" alt="foo bar" title="title" /></p>
|
||||
|
@ -6939,6 +7020,7 @@
|
|||
<p data-sourcepos="1:1-1:12" dir="auto"><a class="no-attachment-icon" href="/url" target="_blank" rel="noopener noreferrer"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="foo bar" title="title" decoding="async" class="lazy" data-src="/url"></a></p>
|
||||
wysiwyg: |-
|
||||
<p><img src="/url" alt="foo bar" title="title"></p>
|
||||
<pre>[*foo* bar]: /url "title"</pre>
|
||||
06_08__inlines__images__019:
|
||||
canonical: |
|
||||
<p>![[foo]]</p>
|
||||
|
@ -6956,6 +7038,7 @@
|
|||
<p data-sourcepos="1:1-1:6" dir="auto"><a class="no-attachment-icon" href="/url" target="_blank" rel="noopener noreferrer"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Foo" title="title" decoding="async" class="lazy" data-src="/url"></a></p>
|
||||
wysiwyg: |-
|
||||
<p><img src="/url" alt="Foo" title="title"></p>
|
||||
<pre>[foo]: /url "title"</pre>
|
||||
06_08__inlines__images__021:
|
||||
canonical: |
|
||||
<p>![foo]</p>
|
||||
|
@ -6963,6 +7046,7 @@
|
|||
<p data-sourcepos="1:1-1:7" dir="auto">![foo]</p>
|
||||
wysiwyg: |-
|
||||
<p>![foo]</p>
|
||||
<pre>[foo]: /url "title"</pre>
|
||||
06_08__inlines__images__022:
|
||||
canonical: |
|
||||
<p>!<a href="/url" title="title">foo</a></p>
|
||||
|
@ -6970,6 +7054,7 @@
|
|||
<p data-sourcepos="1:1-1:27" dir="auto"><span>!</span><a href="/url" title="title">foo</a></p>
|
||||
wysiwyg: |-
|
||||
<p>!<a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title">foo</a></p>
|
||||
<pre>[foo]: /url "title"</pre>
|
||||
06_09__inlines__autolinks__001:
|
||||
canonical: |
|
||||
<p><a href="http://foo.bar.baz">http://foo.bar.baz</a></p>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -584,12 +584,12 @@ module API
|
|||
end
|
||||
end
|
||||
|
||||
def log_artifact_size(file)
|
||||
def log_artifact_file_size(file)
|
||||
Gitlab::ApplicationContext.push(artifact: file.model)
|
||||
end
|
||||
|
||||
def present_artifacts_file!(file, **args)
|
||||
log_artifact_size(file) if file
|
||||
log_artifact_file_size(file) if file
|
||||
|
||||
present_carrierwave_file!(file, **args)
|
||||
end
|
||||
|
|
|
@ -41,8 +41,7 @@ module Gitlab
|
|||
@unfolded = false
|
||||
|
||||
# Ensure items are collected in the the batch
|
||||
new_blob_lazy
|
||||
old_blob_lazy
|
||||
add_blobs_to_batch_loader
|
||||
end
|
||||
|
||||
def use_semantic_ipynb_diff?
|
||||
|
@ -382,6 +381,11 @@ module Gitlab
|
|||
file_path.ends_with?('.ipynb')
|
||||
end
|
||||
|
||||
def add_blobs_to_batch_loader
|
||||
new_blob_lazy
|
||||
old_blob_lazy
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def diffable_by_attribute?
|
||||
|
|
|
@ -36,12 +36,16 @@ module Gitlab
|
|||
sleep interval_with_jitter
|
||||
|
||||
reports.select(&:active?).each do |report|
|
||||
tms = Benchmark.measure do
|
||||
report.run
|
||||
end
|
||||
start_monotonic_time = Gitlab::Metrics::System.monotonic_time
|
||||
start_thread_cpu_time = Gitlab::Metrics::System.thread_cpu_time
|
||||
|
||||
log_report(report_label(report), tms)
|
||||
@report_duration_counter.increment({ report: report_label(report) }, tms.real)
|
||||
report.run
|
||||
|
||||
cpu_s = Gitlab::Metrics::System.thread_cpu_duration(start_thread_cpu_time)
|
||||
duration_s = Gitlab::Metrics::System.monotonic_time - start_monotonic_time
|
||||
|
||||
log_report(label: report_label(report), cpu_s: cpu_s, duration_s: duration_s)
|
||||
@report_duration_counter.increment({ report: report_label(report) }, duration_s)
|
||||
|
||||
sleep sleep_between_reports_s
|
||||
end
|
||||
|
@ -58,15 +62,14 @@ module Gitlab
|
|||
sleep_s + rand(sleep_max_delta_s)
|
||||
end
|
||||
|
||||
def log_report(report_label, tms)
|
||||
def log_report(label:, duration_s:, cpu_s:)
|
||||
Gitlab::AppLogger.info(
|
||||
message: 'finished',
|
||||
pid: $$,
|
||||
worker_id: worker_id,
|
||||
perf_report: report_label,
|
||||
duration_s: tms.real.round(2),
|
||||
cpu_s: tms.utime.round(2),
|
||||
sys_cpu_s: tms.stime.round(2)
|
||||
perf_report: label,
|
||||
duration_s: duration_s.round(2),
|
||||
cpu_s: cpu_s.round(2)
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -114,6 +114,10 @@ module Gitlab
|
|||
@categories ||= known_events.map { |event| event[:category] }.uniq
|
||||
end
|
||||
|
||||
def categories_collected_from_metrics_definitions
|
||||
CATEGORIES_COLLECTED_FROM_METRICS_DEFINITIONS
|
||||
end
|
||||
|
||||
# @param category [String] the category name
|
||||
# @return [Array<String>] list of event names for given category
|
||||
def events_for_category(category)
|
||||
|
@ -164,7 +168,7 @@ module Gitlab
|
|||
|
||||
def categories_pending_migration
|
||||
if ::Feature.enabled?(:use_redis_hll_instrumentation_classes)
|
||||
(categories - CATEGORIES_COLLECTED_FROM_METRICS_DEFINITIONS)
|
||||
(categories - categories_collected_from_metrics_definitions)
|
||||
else
|
||||
categories
|
||||
end
|
||||
|
|
|
@ -1,25 +1,5 @@
|
|||
---
|
||||
# Compliance category
|
||||
- name: g_compliance_dashboard
|
||||
redis_slot: compliance
|
||||
category: compliance
|
||||
aggregation: weekly
|
||||
- name: g_compliance_audit_events
|
||||
category: compliance
|
||||
redis_slot: compliance
|
||||
aggregation: weekly
|
||||
- name: i_compliance_audit_events
|
||||
category: compliance
|
||||
redis_slot: compliance
|
||||
aggregation: weekly
|
||||
- name: i_compliance_credential_inventory
|
||||
category: compliance
|
||||
redis_slot: compliance
|
||||
aggregation: weekly
|
||||
- name: a_compliance_audit_events_api
|
||||
category: compliance
|
||||
redis_slot: compliance
|
||||
aggregation: weekly
|
||||
- name: g_edit_by_web_ide
|
||||
category: ide_edit
|
||||
redis_slot: edit
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Utils
|
||||
module BatchLoader
|
||||
# Clears batched items under the specified batch key
|
||||
# https://github.com/exAspArk/batch-loader#batch-key
|
||||
def self.clear_key(batch_key)
|
||||
return if ::BatchLoader::Executor.current.nil?
|
||||
|
||||
items_to_clear = ::BatchLoader::Executor.current.items_by_block.select do |k, v|
|
||||
# The Hash key here is [source_location, batch_key], so we just check k[1]
|
||||
k[1] == batch_key
|
||||
end
|
||||
|
||||
items_to_clear.each do |k, v|
|
||||
::BatchLoader::Executor.current.items_by_block.delete(k)
|
||||
::BatchLoader::Executor.current.loaded_values_by_block.delete(k)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -11694,9 +11694,21 @@ msgstr ""
|
|||
msgid "DastConfig|Enable DAST to automatically test for vulnerabilities in your project's running application, website, or API, in the CI/CD pipeline. Configuration changes must be applied to your .gitlab-ci.yml file to take effect. For details of all configuration options, see the %{linkStart}GitLab DAST documentation%{linkEnd}."
|
||||
msgstr ""
|
||||
|
||||
msgid "DastConfig|Enabled"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastConfig|Generate code snippet"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastConfig|Last scan triggered %{runTimeAgo} in pipeline "
|
||||
msgstr ""
|
||||
|
||||
msgid "DastConfig|No previous scans found for this project"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastConfig|Not enabled"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|A passive scan monitors all HTTP messages (requests and responses) sent to the target. An active scan attacks the target to find potential vulnerabilities."
|
||||
msgstr ""
|
||||
|
||||
|
@ -28580,6 +28592,9 @@ msgstr ""
|
|||
msgid "PipelineSchedules|None"
|
||||
msgstr ""
|
||||
|
||||
msgid "PipelineSchedules|Only the owner of a pipeline schedule can make changes to it. Do you want to take ownership of this schedule?"
|
||||
msgstr ""
|
||||
|
||||
msgid "PipelineSchedules|Provide a short description for this pipeline"
|
||||
msgstr ""
|
||||
|
||||
|
@ -28592,6 +28607,9 @@ msgstr ""
|
|||
msgid "PipelineSchedules|Variables"
|
||||
msgstr ""
|
||||
|
||||
msgid "PipelineSchedule|Take ownership to edit"
|
||||
msgstr ""
|
||||
|
||||
msgid "PipelineSource|API"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -91,7 +91,6 @@
|
|||
"@tiptap/vue-2": "^2.0.0-beta.84",
|
||||
"apollo-upload-client": "15.0.0",
|
||||
"autosize": "^5.0.1",
|
||||
"aws-sdk": "^2.637.0",
|
||||
"axios": "^0.24.0",
|
||||
"babel-loader": "^8.2.5",
|
||||
"babel-plugin-lodash": "^3.3.4",
|
||||
|
|
|
@ -82,6 +82,22 @@ RSpec.describe Projects::CommitController do
|
|||
expect(response).to be_successful
|
||||
end
|
||||
|
||||
it 'only loads blobs in the current page' do
|
||||
stub_feature_flags(async_commit_diff_files: false)
|
||||
stub_const('Projects::CommitController::COMMIT_DIFFS_PER_PAGE', 1)
|
||||
|
||||
commit = project.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863')
|
||||
|
||||
expect_next_instance_of(Repository) do |repository|
|
||||
# This commit contains 3 changed files but we expect only the blobs for the first one to be loaded
|
||||
expect(repository).to receive(:blobs_at).with([[commit.id, '.gitignore']], anything).and_call_original
|
||||
end
|
||||
|
||||
go(id: commit.id)
|
||||
|
||||
expect(response).to be_ok
|
||||
end
|
||||
|
||||
shared_examples "export as" do |format|
|
||||
it "does generally work" do
|
||||
go(id: commit.id, format: format)
|
||||
|
|
|
@ -226,8 +226,8 @@ RSpec.describe Projects::CompareController do
|
|||
|
||||
context 'when page is valid' do
|
||||
let(:from_project_id) { nil }
|
||||
let(:from_ref) { '08f22f25' }
|
||||
let(:to_ref) { '66eceea0' }
|
||||
let(:from_ref) { '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9' }
|
||||
let(:to_ref) { '5937ac0a7beb003549fc5fd26fc247adbce4a52e' }
|
||||
let(:page) { 1 }
|
||||
|
||||
it 'shows the diff' do
|
||||
|
@ -237,6 +237,21 @@ RSpec.describe Projects::CompareController do
|
|||
expect(assigns(:diffs).diff_files.first).to be_present
|
||||
expect(assigns(:commits).length).to be >= 1
|
||||
end
|
||||
|
||||
it 'only loads blobs in the current page' do
|
||||
stub_const('Projects::CompareController::COMMIT_DIFFS_PER_PAGE', 1)
|
||||
|
||||
expect_next_instance_of(Repository) do |repository|
|
||||
# This comparison contains 4 changed files but we expect only the blobs for the first one to be loaded
|
||||
expect(repository).to receive(:blobs_at).with(
|
||||
contain_exactly([from_ref, '.gitmodules'], [to_ref, '.gitmodules']), anything
|
||||
).and_call_original
|
||||
end
|
||||
|
||||
show_request
|
||||
|
||||
expect(response).to be_successful
|
||||
end
|
||||
end
|
||||
|
||||
context 'when page is not valid' do
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Projects::ProjectTransferedEvent do
|
||||
where(:data, :valid) do
|
||||
valid_event = {
|
||||
project_id: 1,
|
||||
old_namespace_id: 2,
|
||||
old_root_namespace_id: 3,
|
||||
new_namespace_id: 4,
|
||||
new_root_namespace_id: 5
|
||||
}
|
||||
|
||||
# All combinations of missing keys
|
||||
with_missing_keys = 0.upto(valid_event.size - 1)
|
||||
.flat_map { |size| valid_event.keys.combination(size).to_a }
|
||||
.map { |keys| [valid_event.slice(*keys), false] }
|
||||
|
||||
[
|
||||
[valid_event, true],
|
||||
*with_missing_keys,
|
||||
[{ project_id: 'foo', namespace_id: 2 }, false],
|
||||
[{ project_id: 1, namespace_id: 'foo' }, false],
|
||||
[{ project_id: [], namespace_id: 2 }, false],
|
||||
[{ project_id: 1, namespace_id: [] }, false],
|
||||
[{ project_id: {}, namespace_id: 2 }, false],
|
||||
[{ project_id: 1, namespace_id: {} }, false],
|
||||
['foo', false],
|
||||
[123, false],
|
||||
[[], false]
|
||||
]
|
||||
end
|
||||
|
||||
with_them do
|
||||
it 'validates data' do
|
||||
constructor = -> { described_class.new(data: data) }
|
||||
|
||||
if valid
|
||||
expect { constructor.call }.not_to raise_error
|
||||
else
|
||||
expect { constructor.call }.to raise_error(Gitlab::EventStore::InvalidEvent)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -109,7 +109,12 @@ RSpec.describe 'Pipeline Schedules', :js do
|
|||
end
|
||||
|
||||
it 'changes ownership of the pipeline' do
|
||||
click_link 'Take ownership'
|
||||
click_button 'Take ownership'
|
||||
|
||||
page.within('#pipeline-take-ownership-modal') do
|
||||
click_link 'Take ownership'
|
||||
end
|
||||
|
||||
page.within('.pipeline-schedule-table-row') do
|
||||
expect(page).not_to have_content('No owner')
|
||||
expect(page).to have_link('Sidney Jones')
|
||||
|
|
|
@ -15,6 +15,7 @@ import Link from '~/content_editor/extensions/link';
|
|||
import ListItem from '~/content_editor/extensions/list_item';
|
||||
import OrderedList from '~/content_editor/extensions/ordered_list';
|
||||
import Paragraph from '~/content_editor/extensions/paragraph';
|
||||
import ReferenceDefinition from '~/content_editor/extensions/reference_definition';
|
||||
import Sourcemap from '~/content_editor/extensions/sourcemap';
|
||||
import Strike from '~/content_editor/extensions/strike';
|
||||
import Table from '~/content_editor/extensions/table';
|
||||
|
@ -45,6 +46,7 @@ const tiptapEditor = createTestEditor({
|
|||
Link,
|
||||
ListItem,
|
||||
OrderedList,
|
||||
ReferenceDefinition,
|
||||
Sourcemap,
|
||||
Strike,
|
||||
Table,
|
||||
|
@ -78,6 +80,7 @@ const {
|
|||
listItem,
|
||||
orderedList,
|
||||
pre,
|
||||
referenceDefinition,
|
||||
strike,
|
||||
table,
|
||||
tableRow,
|
||||
|
@ -105,6 +108,7 @@ const {
|
|||
listItem: { nodeType: ListItem.name },
|
||||
orderedList: { nodeType: OrderedList.name },
|
||||
paragraph: { nodeType: Paragraph.name },
|
||||
referenceDefinition: { nodeType: ReferenceDefinition.name },
|
||||
strike: { nodeType: Strike.name },
|
||||
table: { nodeType: Table.name },
|
||||
tableCell: { nodeType: TableCell.name },
|
||||
|
@ -1079,6 +1083,32 @@ _world_.
|
|||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
markdown: `
|
||||
[GitLab][gitlab-url]
|
||||
|
||||
[gitlab-url]: https://gitlab.com "GitLab"
|
||||
|
||||
`,
|
||||
expectedDoc: doc(
|
||||
paragraph(
|
||||
source('[GitLab][gitlab-url]'),
|
||||
link(
|
||||
{ ...source('[GitLab][gitlab-url]'), href: 'https://gitlab.com', title: 'GitLab' },
|
||||
'GitLab',
|
||||
),
|
||||
),
|
||||
referenceDefinition(
|
||||
{
|
||||
...source('[gitlab-url]: https://gitlab.com "GitLab"'),
|
||||
identifier: 'gitlab-url',
|
||||
url: 'https://gitlab.com',
|
||||
title: 'GitLab',
|
||||
},
|
||||
'[gitlab-url]: https://gitlab.com "GitLab"',
|
||||
),
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const runOnly = examples.find((example) => example.only === true);
|
||||
|
|
|
@ -26,6 +26,7 @@ import Italic from '~/content_editor/extensions/italic';
|
|||
import Link from '~/content_editor/extensions/link';
|
||||
import ListItem from '~/content_editor/extensions/list_item';
|
||||
import OrderedList from '~/content_editor/extensions/ordered_list';
|
||||
import ReferenceDefinition from '~/content_editor/extensions/reference_definition';
|
||||
import Strike from '~/content_editor/extensions/strike';
|
||||
import Table from '~/content_editor/extensions/table';
|
||||
import TableCell from '~/content_editor/extensions/table_cell';
|
||||
|
@ -63,6 +64,7 @@ const tiptapEditor = createTestEditor({
|
|||
Link,
|
||||
ListItem,
|
||||
OrderedList,
|
||||
ReferenceDefinition,
|
||||
Strike,
|
||||
Table,
|
||||
TableCell,
|
||||
|
|
|
@ -24,6 +24,7 @@ import Link from '~/content_editor/extensions/link';
|
|||
import ListItem from '~/content_editor/extensions/list_item';
|
||||
import OrderedList from '~/content_editor/extensions/ordered_list';
|
||||
import Paragraph from '~/content_editor/extensions/paragraph';
|
||||
import ReferenceDefinition from '~/content_editor/extensions/reference_definition';
|
||||
import Sourcemap from '~/content_editor/extensions/sourcemap';
|
||||
import Strike from '~/content_editor/extensions/strike';
|
||||
import Table from '~/content_editor/extensions/table';
|
||||
|
@ -63,6 +64,7 @@ const tiptapEditor = createTestEditor({
|
|||
Link,
|
||||
ListItem,
|
||||
OrderedList,
|
||||
ReferenceDefinition,
|
||||
Sourcemap,
|
||||
Strike,
|
||||
Table,
|
||||
|
@ -104,6 +106,7 @@ const {
|
|||
listItem,
|
||||
orderedList,
|
||||
paragraph,
|
||||
referenceDefinition,
|
||||
strike,
|
||||
table,
|
||||
tableCell,
|
||||
|
@ -139,6 +142,7 @@ const {
|
|||
listItem: { nodeType: ListItem.name },
|
||||
orderedList: { nodeType: OrderedList.name },
|
||||
paragraph: { nodeType: Paragraph.name },
|
||||
referenceDefinition: { nodeType: ReferenceDefinition.name },
|
||||
strike: { markType: Strike.name },
|
||||
table: { nodeType: Table.name },
|
||||
tableCell: { nodeType: TableCell.name },
|
||||
|
@ -1163,6 +1167,38 @@ Oranges are orange [^1]
|
|||
);
|
||||
});
|
||||
|
||||
it('correctly serializes reference definition', () => {
|
||||
expect(
|
||||
serialize(
|
||||
referenceDefinition('[gitlab]: https://gitlab.com'),
|
||||
referenceDefinition('[foobar]: foobar.com'),
|
||||
),
|
||||
).toBe(
|
||||
`
|
||||
[gitlab]: https://gitlab.com
|
||||
[foobar]: foobar.com`.trimLeft(),
|
||||
);
|
||||
});
|
||||
|
||||
it('correctly adds a space between a reference definition and a block content', () => {
|
||||
expect(
|
||||
serialize(
|
||||
paragraph('paragraph'),
|
||||
referenceDefinition('[gitlab]: https://gitlab.com'),
|
||||
referenceDefinition('[foobar]: foobar.com'),
|
||||
heading({ level: 2 }, 'heading'),
|
||||
),
|
||||
).toBe(
|
||||
`
|
||||
paragraph
|
||||
|
||||
[gitlab]: https://gitlab.com
|
||||
[foobar]: foobar.com
|
||||
|
||||
## heading`.trimLeft(),
|
||||
);
|
||||
});
|
||||
|
||||
const defaultEditAction = (initialContent) => {
|
||||
tiptapEditor.chain().setContent(initialContent.toJSON()).insertContent(' modified').run();
|
||||
};
|
||||
|
|
|
@ -96,28 +96,60 @@ describe('gfm', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when skipping the rendering of code blocks', () => {
|
||||
it('transforms code nodes into codeblock html tags', async () => {
|
||||
const result = await markdownToAST(
|
||||
`
|
||||
describe('when skipping the rendering of code blocks', () => {
|
||||
it('transforms code nodes into codeblock html tags', async () => {
|
||||
const result = await markdownToAST(
|
||||
`
|
||||
\`\`\`javascript
|
||||
console.log('Hola');
|
||||
\`\`\`\
|
||||
`,
|
||||
['code'],
|
||||
);
|
||||
['code'],
|
||||
);
|
||||
|
||||
expectInRoot(
|
||||
result,
|
||||
expect.objectContaining({
|
||||
tagName: 'codeblock',
|
||||
properties: {
|
||||
language: 'javascript',
|
||||
},
|
||||
}),
|
||||
);
|
||||
expectInRoot(
|
||||
result,
|
||||
expect.objectContaining({
|
||||
tagName: 'codeblock',
|
||||
properties: {
|
||||
language: 'javascript',
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when skipping the rendering of reference definitions', () => {
|
||||
it('transforms code nodes into codeblock html tags', async () => {
|
||||
const result = await markdownToAST(
|
||||
`
|
||||
[gitlab][gitlab]
|
||||
|
||||
[gitlab]: https://gitlab.com "GitLab"
|
||||
`,
|
||||
['definition'],
|
||||
);
|
||||
|
||||
expectInRoot(
|
||||
result,
|
||||
expect.objectContaining({
|
||||
type: 'element',
|
||||
tagName: 'referencedefinition',
|
||||
properties: {
|
||||
identifier: 'gitlab',
|
||||
title: 'GitLab',
|
||||
url: 'https://gitlab.com',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: 'text',
|
||||
value: '[gitlab]: https://gitlab.com "GitLab"',
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
import { GlModal } from '@gitlab/ui';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import TakeOwnershipModal from '~/pipeline_schedules/components/take_ownership_modal.vue';
|
||||
|
||||
describe('Take ownership modal', () => {
|
||||
let wrapper;
|
||||
const url = `/root/job-log-tester/-/pipeline_schedules/3/take_ownership`;
|
||||
|
||||
const createComponent = (props = {}) => {
|
||||
wrapper = shallowMountExtended(TakeOwnershipModal, {
|
||||
propsData: {
|
||||
ownershipUrl: url,
|
||||
...props,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const findModal = () => wrapper.findComponent(GlModal);
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('has a primary action set to a url and a post data-method', () => {
|
||||
const actionPrimary = findModal().props('actionPrimary');
|
||||
|
||||
expect(actionPrimary.attributes).toEqual(
|
||||
expect.objectContaining([
|
||||
{
|
||||
category: 'primary',
|
||||
variant: 'confirm',
|
||||
href: url,
|
||||
'data-method': 'post',
|
||||
},
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('shows a take ownership message', () => {
|
||||
expect(findModal().text()).toBe(
|
||||
'Only the owner of a pipeline schedule can make changes to it. Do you want to take ownership of this schedule?',
|
||||
);
|
||||
});
|
||||
|
||||
it('emits the cancel event when clicking on cancel', async () => {
|
||||
findModal().vm.$emit('cancel');
|
||||
|
||||
expect(findModal().emitted('cancel')).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -61,29 +61,38 @@ describe('content_editor', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('renders footnote ids alongside the footnote definition', async () => {
|
||||
buildWrapper();
|
||||
describe('when preserveUnchangedMarkdown feature flag is enabled', () => {
|
||||
beforeEach(() => {
|
||||
gon.features = { preserveUnchangedMarkdown: true };
|
||||
});
|
||||
afterEach(() => {
|
||||
gon.features = { preserveUnchangedMarkdown: false };
|
||||
});
|
||||
|
||||
renderMarkdown.mockResolvedValue(`
|
||||
<p data-sourcepos="3:1-3:56" dir="auto">
|
||||
This reference tag is a mix of letters and numbers. <sup class="footnote-ref"><a href="#fn-footnote-2717" id="fnref-footnote-2717" data-footnote-ref="">2</a></sup>
|
||||
</p>
|
||||
<section class="footnotes" data-footnotes>
|
||||
<ol>
|
||||
<li id="fn-footnote-2717">
|
||||
<p data-sourcepos="6:7-6:31">This is another footnote. <a href="#fnref-footnote-2717" aria-label="Back to content" class="footnote-backref" data-footnote-backref=""><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p>
|
||||
</li>
|
||||
</ol>
|
||||
</section>
|
||||
it('processes and renders footnote ids alongside the footnote definition', async () => {
|
||||
buildWrapper();
|
||||
|
||||
await contentEditorService.setSerializedContent(`
|
||||
This reference tag is a mix of letters and numbers [^footnote].
|
||||
|
||||
[^footnote]: This is another footnote.
|
||||
`);
|
||||
await nextTick();
|
||||
|
||||
await contentEditorService.setSerializedContent(`
|
||||
This reference tag is a mix of letters and numbers [^footnote].
|
||||
expect(wrapper.text()).toContain('footnote: This is another footnote');
|
||||
});
|
||||
|
||||
[^footnote]: This is another footnote.
|
||||
`);
|
||||
await nextTick();
|
||||
it('processes and displays reference definitions', async () => {
|
||||
buildWrapper();
|
||||
|
||||
expect(wrapper.text()).toContain('footnote: This is another footnote');
|
||||
await contentEditorService.setSerializedContent(`
|
||||
[GitLab][gitlab]
|
||||
|
||||
[gitlab]: https://gitlab.com
|
||||
`);
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.find('pre').text()).toContain('[gitlab]: https://gitlab.com');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -153,16 +153,24 @@ RSpec.describe CommitsHelper do
|
|||
end
|
||||
|
||||
describe "#conditionally_paginate_diff_files" do
|
||||
let(:diffs_collection) { instance_double(Gitlab::Diff::FileCollection::Commit, diff_files: diff_files) }
|
||||
let(:diff_files) { Gitlab::Git::DiffCollection.new(files) }
|
||||
let(:page) { nil }
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
|
||||
let(:diffs_collection) { instance_double(Gitlab::Diff::FileCollection::Commit, diff_files: decorated_diff_files, project: project) }
|
||||
let(:decorated_diff_files) do
|
||||
diffs.map do |diff|
|
||||
Gitlab::Diff::File.new(diff, repository: project.repository)
|
||||
end
|
||||
end
|
||||
|
||||
let(:diffs) { Gitlab::Git::DiffCollection.new(files) }
|
||||
let(:files) do
|
||||
Array.new(85).map do
|
||||
{ too_large: false, diff: "" }
|
||||
end
|
||||
end
|
||||
|
||||
let(:page) { nil }
|
||||
|
||||
subject { helper.conditionally_paginate_diff_files(diffs_collection, paginate: paginate, page: page, per: Projects::CommitController::COMMIT_DIFFS_PER_PAGE) }
|
||||
|
||||
before do
|
||||
|
@ -203,8 +211,8 @@ RSpec.describe CommitsHelper do
|
|||
context "pagination is disabled" do
|
||||
let(:paginate) { false }
|
||||
|
||||
it "returns a standard DiffCollection" do
|
||||
expect(subject).to be_a(Gitlab::Git::DiffCollection)
|
||||
it "returns the unpaginated collection" do
|
||||
expect(subject.size).to eq(85)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -727,6 +727,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
|
|||
context 'signup with linked omniauth and LDAP account' do
|
||||
before do
|
||||
stub_omniauth_config(auto_link_ldap_user: true)
|
||||
stub_ldap_setting(enabled: true)
|
||||
allow(ldap_user).to receive(:uid) { uid }
|
||||
allow(ldap_user).to receive(:username) { uid }
|
||||
allow(ldap_user).to receive(:email) { ['johndoe@example.com', 'john2@example.com'] }
|
||||
|
|
|
@ -32,7 +32,6 @@ RSpec.describe Gitlab::Memory::ReportsDaemon do
|
|||
hash_including(
|
||||
:duration_s,
|
||||
:cpu_s,
|
||||
:sys_cpu_s,
|
||||
message: 'finished',
|
||||
pid: Process.pid,
|
||||
worker_id: 'worker_1',
|
||||
|
|
|
@ -82,7 +82,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
|
|||
stub_feature_flags(use_redis_hll_instrumentation_classes: true)
|
||||
|
||||
expect(described_class.unique_events_data.keys)
|
||||
.not_to include(*described_class::CATEGORIES_COLLECTED_FROM_METRICS_DEFINITIONS)
|
||||
.not_to include(*described_class.categories_collected_from_metrics_definitions)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -91,18 +91,17 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
|
|||
stub_feature_flags(use_redis_hll_instrumentation_classes: false)
|
||||
|
||||
expect(described_class.unique_events_data.keys)
|
||||
.to include(*described_class::CATEGORIES_COLLECTED_FROM_METRICS_DEFINITIONS)
|
||||
.to include(*described_class.categories_collected_from_metrics_definitions)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.categories' do
|
||||
it 'gets all unique category names' do
|
||||
expect(described_class.categories).to contain_exactly(
|
||||
it 'gets CE unique category names' do
|
||||
expect(described_class.categories).to include(
|
||||
'deploy_token_packages',
|
||||
'user_packages',
|
||||
'compliance',
|
||||
'ecosystem',
|
||||
'analytics',
|
||||
'ide_edit',
|
||||
|
@ -483,7 +482,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
|
|||
describe '.weekly_redis_keys' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let(:weekly_event) { 'g_compliance_dashboard' }
|
||||
let(:weekly_event) { 'i_search_total' }
|
||||
let(:redis_event) { described_class.send(:event_for, weekly_event) }
|
||||
|
||||
subject(:weekly_redis_keys) { described_class.send(:weekly_redis_keys, events: [redis_event], start_date: DateTime.parse(start_date), end_date: DateTime.parse(end_date)) }
|
||||
|
@ -493,13 +492,13 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
|
|||
'2020-12-21' | '2020-12-20' | []
|
||||
'2020-12-21' | '2020-11-21' | []
|
||||
'2021-01-01' | '2020-12-28' | []
|
||||
'2020-12-21' | '2020-12-28' | ['g_{compliance}_dashboard-2020-52']
|
||||
'2020-12-21' | '2021-01-01' | ['g_{compliance}_dashboard-2020-52']
|
||||
'2020-12-27' | '2021-01-01' | ['g_{compliance}_dashboard-2020-52']
|
||||
'2020-12-26' | '2021-01-04' | ['g_{compliance}_dashboard-2020-52', 'g_{compliance}_dashboard-2020-53']
|
||||
'2020-12-26' | '2021-01-11' | ['g_{compliance}_dashboard-2020-52', 'g_{compliance}_dashboard-2020-53', 'g_{compliance}_dashboard-2021-01']
|
||||
'2020-12-26' | '2021-01-17' | ['g_{compliance}_dashboard-2020-52', 'g_{compliance}_dashboard-2020-53', 'g_{compliance}_dashboard-2021-01']
|
||||
'2020-12-26' | '2021-01-18' | ['g_{compliance}_dashboard-2020-52', 'g_{compliance}_dashboard-2020-53', 'g_{compliance}_dashboard-2021-01', 'g_{compliance}_dashboard-2021-02']
|
||||
'2020-12-21' | '2020-12-28' | ['i_{search}_total-2020-52']
|
||||
'2020-12-21' | '2021-01-01' | ['i_{search}_total-2020-52']
|
||||
'2020-12-27' | '2021-01-01' | ['i_{search}_total-2020-52']
|
||||
'2020-12-26' | '2021-01-04' | ['i_{search}_total-2020-52', 'i_{search}_total-2020-53']
|
||||
'2020-12-26' | '2021-01-11' | ['i_{search}_total-2020-52', 'i_{search}_total-2020-53', 'i_{search}_total-2021-01']
|
||||
'2020-12-26' | '2021-01-17' | ['i_{search}_total-2020-52', 'i_{search}_total-2020-53', 'i_{search}_total-2021-01']
|
||||
'2020-12-26' | '2021-01-18' | ['i_{search}_total-2020-52', 'i_{search}_total-2020-53', 'i_{search}_total-2021-01', 'i_{search}_total-2021-02']
|
||||
end
|
||||
|
||||
with_them do
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'fast_spec_helper'
|
||||
require 'batch-loader'
|
||||
|
||||
RSpec.describe Gitlab::Utils::BatchLoader do
|
||||
let(:stubbed_loader) do
|
||||
double( # rubocop:disable RSpec/VerifiedDoubles
|
||||
'Loader',
|
||||
load_lazy_method: [],
|
||||
load_lazy_method_same_batch_key: [],
|
||||
load_lazy_method_other_batch_key: []
|
||||
)
|
||||
end
|
||||
|
||||
let(:test_module) do
|
||||
Module.new do
|
||||
def self.lazy_method(id)
|
||||
BatchLoader.for(id).batch(key: :my_batch_name) do |ids, loader|
|
||||
stubbed_loader.load_lazy_method(ids)
|
||||
|
||||
ids.each { |id| loader.call(id, id) }
|
||||
end
|
||||
end
|
||||
|
||||
def self.lazy_method_same_batch_key(id)
|
||||
BatchLoader.for(id).batch(key: :my_batch_name) do |ids, loader|
|
||||
stubbed_loader.load_lazy_method_same_batch_key(ids)
|
||||
|
||||
ids.each { |id| loader.call(id, id) }
|
||||
end
|
||||
end
|
||||
|
||||
def self.lazy_method_other_batch_key(id)
|
||||
BatchLoader.for(id).batch(key: :other_batch_name) do |ids, loader|
|
||||
stubbed_loader.load_lazy_method_other_batch_key(ids)
|
||||
|
||||
ids.each { |id| loader.call(id, id) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
BatchLoader::Executor.clear_current
|
||||
allow(test_module).to receive(:stubbed_loader).and_return(stubbed_loader)
|
||||
end
|
||||
|
||||
describe '.clear_key' do
|
||||
it 'clears batched items which match the specified batch key' do
|
||||
test_module.lazy_method(1)
|
||||
test_module.lazy_method_same_batch_key(2)
|
||||
test_module.lazy_method_other_batch_key(3)
|
||||
|
||||
described_class.clear_key(:my_batch_name)
|
||||
|
||||
test_module.lazy_method(4).to_i
|
||||
test_module.lazy_method_same_batch_key(5).to_i
|
||||
test_module.lazy_method_other_batch_key(6).to_i
|
||||
|
||||
expect(stubbed_loader).to have_received(:load_lazy_method).with([4])
|
||||
expect(stubbed_loader).to have_received(:load_lazy_method_same_batch_key).with([5])
|
||||
expect(stubbed_loader).to have_received(:load_lazy_method_other_batch_key).with([3, 6])
|
||||
end
|
||||
|
||||
it 'clears loaded values which match the specified batch key' do
|
||||
test_module.lazy_method(1).to_i
|
||||
test_module.lazy_method_same_batch_key(2).to_i
|
||||
test_module.lazy_method_other_batch_key(3).to_i
|
||||
|
||||
described_class.clear_key(:my_batch_name)
|
||||
|
||||
test_module.lazy_method(1).to_i
|
||||
test_module.lazy_method_same_batch_key(2).to_i
|
||||
test_module.lazy_method_other_batch_key(3).to_i
|
||||
|
||||
expect(stubbed_loader).to have_received(:load_lazy_method).with([1]).twice
|
||||
expect(stubbed_loader).to have_received(:load_lazy_method_same_batch_key).with([2]).twice
|
||||
expect(stubbed_loader).to have_received(:load_lazy_method_other_batch_key).with([3])
|
||||
end
|
||||
end
|
||||
end
|
|
@ -844,7 +844,13 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
|
|||
'Content-Disposition' => %q(attachment; filename="ci_build_artifacts.zip"; filename*=UTF-8''ci_build_artifacts.zip) }
|
||||
end
|
||||
|
||||
before do
|
||||
allow(Gitlab::ApplicationContext).to receive(:push).and_call_original
|
||||
end
|
||||
|
||||
it 'downloads artifacts' do
|
||||
expect(Gitlab::ApplicationContext).to receive(:push).with(artifact: an_instance_of(Ci::JobArtifact)).once.and_call_original
|
||||
|
||||
download_artifact
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
|
|
@ -64,6 +64,28 @@ RSpec.describe Projects::TransferService do
|
|||
expect(project.namespace).to eq(group)
|
||||
end
|
||||
|
||||
context 'EventStore' do
|
||||
let(:group) do
|
||||
create(:group, :nested).tap { |g| g.add_owner(user) }
|
||||
end
|
||||
|
||||
let(:target) do
|
||||
create(:group, :nested).tap { |g| g.add_owner(user) }
|
||||
end
|
||||
|
||||
it 'publishes a ProjectTransferedEvent' do
|
||||
expect { execute_transfer }
|
||||
.to publish_event(Projects::ProjectTransferedEvent)
|
||||
.with(
|
||||
project_id: kind_of(Numeric),
|
||||
old_namespace_id: group.id,
|
||||
old_root_namespace_id: group.parent_id,
|
||||
new_namespace_id: target.id,
|
||||
new_root_namespace_id: target.parent_id
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when project has an associated project namespace' do
|
||||
it 'keeps project namespace in sync with project' do
|
||||
transfer_result = execute_transfer
|
||||
|
@ -299,6 +321,11 @@ RSpec.describe Projects::TransferService do
|
|||
)
|
||||
end
|
||||
|
||||
it 'does not publish a ProjectTransferedEvent' do
|
||||
expect { attempt_project_transfer }
|
||||
.not_to publish_event(Projects::ProjectTransferedEvent)
|
||||
end
|
||||
|
||||
context 'when project has pending builds', :sidekiq_inline do
|
||||
let!(:other_project) { create(:project) }
|
||||
let!(:pending_build) { create(:ci_pending_build, project: project.reload) }
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
# frozen_string_literal: true
|
||||
# rubocop:disable Style/RedundantFetchBlock
|
||||
#
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe ProtectedBranches::CacheService, :clean_gitlab_redis_cache do
|
||||
subject(:service) { described_class.new(project, user) }
|
||||
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:user) { project.first_owner }
|
||||
|
||||
let(:immediate_expiration) { 0 }
|
||||
|
||||
describe '#fetch' do
|
||||
it 'caches the value' do
|
||||
expect(service.fetch('main') { true }).to eq(true)
|
||||
expect(service.fetch('not-found') { false }).to eq(false)
|
||||
|
||||
# Uses cached values
|
||||
expect(service.fetch('main') { false }).to eq(true)
|
||||
expect(service.fetch('not-found') { true }).to eq(false)
|
||||
end
|
||||
|
||||
it 'sets expiry on the key' do
|
||||
stub_const("#{described_class.name}::CACHE_EXPIRE_IN", immediate_expiration)
|
||||
|
||||
expect(service.fetch('main') { true }).to eq(true)
|
||||
expect(service.fetch('not-found') { false }).to eq(false)
|
||||
|
||||
expect(service.fetch('main') { false }).to eq(false)
|
||||
expect(service.fetch('not-found') { true }).to eq(true)
|
||||
end
|
||||
|
||||
it 'does not set an expiry on the key after the hash is already created' do
|
||||
expect(service.fetch('main') { true }).to eq(true)
|
||||
|
||||
stub_const("#{described_class.name}::CACHE_EXPIRE_IN", immediate_expiration)
|
||||
|
||||
expect(service.fetch('not-found') { false }).to eq(false)
|
||||
|
||||
expect(service.fetch('main') { false }).to eq(true)
|
||||
expect(service.fetch('not-found') { true }).to eq(false)
|
||||
end
|
||||
|
||||
context 'when CACHE_LIMIT is exceeded' do
|
||||
before do
|
||||
stub_const("#{described_class.name}::CACHE_LIMIT", 2)
|
||||
end
|
||||
|
||||
it 'recreates cache' do
|
||||
expect(service.fetch('main') { true }).to eq(true)
|
||||
expect(service.fetch('not-found') { false }).to eq(false)
|
||||
|
||||
# Uses cached values
|
||||
expect(service.fetch('main') { false }).to eq(true)
|
||||
expect(service.fetch('not-found') { true }).to eq(false)
|
||||
|
||||
# Overflow
|
||||
expect(service.fetch('new-branch') { true }).to eq(true)
|
||||
|
||||
# Refreshes values
|
||||
expect(service.fetch('main') { false }).to eq(false)
|
||||
expect(service.fetch('not-found') { true }).to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#refresh' do
|
||||
it 'clears cached values' do
|
||||
expect(service.fetch('main') { true }).to eq(true)
|
||||
expect(service.fetch('not-found') { false }).to eq(false)
|
||||
|
||||
service.refresh
|
||||
|
||||
# Recreates cache
|
||||
expect(service.fetch('main') { false }).to eq(false)
|
||||
expect(service.fetch('not-found') { true }).to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
# rubocop:enable Style/RedundantFetchBlock
|
|
@ -24,6 +24,14 @@ RSpec.describe ProtectedBranches::CreateService do
|
|||
expect(project.protected_branches.last.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::MAINTAINER])
|
||||
end
|
||||
|
||||
it 'refreshes the cache' do
|
||||
expect_next_instance_of(ProtectedBranches::CacheService) do |cache_service|
|
||||
expect(cache_service).to receive(:refresh)
|
||||
end
|
||||
|
||||
service.execute
|
||||
end
|
||||
|
||||
context 'when protecting a branch with a name that contains HTML tags' do
|
||||
let(:name) { 'foo<b>bar<\b>' }
|
||||
|
||||
|
|
|
@ -16,6 +16,14 @@ RSpec.describe ProtectedBranches::DestroyService do
|
|||
expect(protected_branch).to be_destroyed
|
||||
end
|
||||
|
||||
it 'refreshes the cache' do
|
||||
expect_next_instance_of(ProtectedBranches::CacheService) do |cache_service|
|
||||
expect(cache_service).to receive(:refresh)
|
||||
end
|
||||
|
||||
service.execute(protected_branch)
|
||||
end
|
||||
|
||||
context 'when a policy restricts rule deletion' do
|
||||
before do
|
||||
policy = instance_double(ProtectedBranchPolicy, allowed?: false)
|
||||
|
|
|
@ -18,6 +18,14 @@ RSpec.describe ProtectedBranches::UpdateService do
|
|||
expect(result.reload.name).to eq(params[:name])
|
||||
end
|
||||
|
||||
it 'refreshes the cache' do
|
||||
expect_next_instance_of(ProtectedBranches::CacheService) do |cache_service|
|
||||
expect(cache_service).to receive(:refresh)
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
context 'when updating name of a protected branch to one that contains HTML tags' do
|
||||
let(:new_name) { 'foo<b>bar<\b>' }
|
||||
let(:result) { service.execute(protected_branch) }
|
||||
|
|
|
@ -28,7 +28,7 @@ RSpec.describe 'projects/pipeline_schedules/_pipeline_schedule' do
|
|||
it 'non-owner can take ownership of pipeline' do
|
||||
render
|
||||
|
||||
expect(rendered).to have_link('Take ownership')
|
||||
expect(rendered).to have_button('Take ownership')
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -42,7 +42,7 @@ RSpec.describe 'projects/pipeline_schedules/_pipeline_schedule' do
|
|||
it 'owner cannot take ownership of pipeline' do
|
||||
render
|
||||
|
||||
expect(rendered).not_to have_link('Take ownership')
|
||||
expect(rendered).not_to have_button('Take ownership')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
60
yarn.lock
60
yarn.lock
|
@ -2744,21 +2744,6 @@ autosize@^5.0.1:
|
|||
resolved "https://registry.yarnpkg.com/autosize/-/autosize-5.0.1.tgz#ed269b0fa9b7eb47627048a1bb3299e99e003a0f"
|
||||
integrity sha512-UIWUlE4TOVPNNj2jjrU39wI4hEYbneUypEqcyRmRFIx5CC2gNdg3rQr+Zh7/3h6egbBvm33TDQjNQKtj9Tk1HA==
|
||||
|
||||
aws-sdk@^2.637.0:
|
||||
version "2.637.0"
|
||||
resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.637.0.tgz#810e25e53acf2250d35fc74498f9d4492e154217"
|
||||
integrity sha512-e7EYX5rNtQyEaleQylUtLSNKXOmvOwfifQ4bYkfF80mFsVI3DSydczLHXrqPzXoEJaS/GI/9HqVnlQcPs6Q3ew==
|
||||
dependencies:
|
||||
buffer "4.9.1"
|
||||
events "1.1.1"
|
||||
ieee754 "1.1.13"
|
||||
jmespath "0.15.0"
|
||||
querystring "0.2.0"
|
||||
sax "1.2.1"
|
||||
url "0.10.3"
|
||||
uuid "3.3.2"
|
||||
xml2js "0.4.19"
|
||||
|
||||
axios-mock-adapter@^1.15.0:
|
||||
version "1.15.0"
|
||||
resolved "https://registry.yarnpkg.com/axios-mock-adapter/-/axios-mock-adapter-1.15.0.tgz#fbc06825d8302c95c3334d21023bba996255d45d"
|
||||
|
@ -3137,7 +3122,7 @@ buffer-xor@^1.0.3:
|
|||
resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
|
||||
integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=
|
||||
|
||||
buffer@4.9.1, buffer@^4.3.0:
|
||||
buffer@^4.3.0:
|
||||
version "4.9.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298"
|
||||
integrity sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=
|
||||
|
@ -5444,11 +5429,6 @@ eventemitter3@^4.0.0:
|
|||
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.0.tgz#d65176163887ee59f386d64c82610b696a4a74eb"
|
||||
integrity sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==
|
||||
|
||||
events@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
|
||||
integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=
|
||||
|
||||
events@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88"
|
||||
|
@ -6582,7 +6562,7 @@ icss-utils@^4.1.0:
|
|||
dependencies:
|
||||
postcss "^7.0.14"
|
||||
|
||||
ieee754@1.1.13, ieee754@^1.1.13, ieee754@^1.1.4:
|
||||
ieee754@^1.1.13, ieee754@^1.1.4:
|
||||
version "1.1.13"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
|
||||
integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==
|
||||
|
@ -7528,11 +7508,6 @@ jest@^26.5.2:
|
|||
import-local "^3.0.2"
|
||||
jest-cli "^26.5.2"
|
||||
|
||||
jmespath@0.15.0:
|
||||
version "0.15.0"
|
||||
resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217"
|
||||
integrity sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=
|
||||
|
||||
jquery.caret@^0.3.1:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/jquery.caret/-/jquery.caret-0.3.1.tgz#9c093318faf327eff322e826ca9f3241368bc7b8"
|
||||
|
@ -10598,11 +10573,6 @@ sass@^1.49.9:
|
|||
immutable "^4.0.0"
|
||||
source-map-js ">=0.6.2 <2.0.0"
|
||||
|
||||
sax@1.2.1, sax@>=0.6.0:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a"
|
||||
integrity sha1-e45lYZCyKOgaZq6nSEgNgozS03o=
|
||||
|
||||
saxes@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d"
|
||||
|
@ -12004,14 +11974,6 @@ url-loader@^4.1.1:
|
|||
mime-types "^2.1.27"
|
||||
schema-utils "^3.0.0"
|
||||
|
||||
url@0.10.3:
|
||||
version "0.10.3"
|
||||
resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64"
|
||||
integrity sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=
|
||||
dependencies:
|
||||
punycode "1.3.2"
|
||||
querystring "0.2.0"
|
||||
|
||||
url@^0.11.0:
|
||||
version "0.11.0"
|
||||
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
|
||||
|
@ -12049,11 +12011,6 @@ utils-merge@1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
||||
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
|
||||
|
||||
uuid@3.3.2:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
|
||||
integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==
|
||||
|
||||
uuid@8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.1.0.tgz#6f1536eb43249f473abc6bd58ff983da1ca30d8d"
|
||||
|
@ -12634,24 +12591,11 @@ xml-name-validator@^3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
|
||||
integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==
|
||||
|
||||
xml2js@0.4.19:
|
||||
version "0.4.19"
|
||||
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7"
|
||||
integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==
|
||||
dependencies:
|
||||
sax ">=0.6.0"
|
||||
xmlbuilder "~9.0.1"
|
||||
|
||||
xml@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5"
|
||||
integrity sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=
|
||||
|
||||
xmlbuilder@~9.0.1:
|
||||
version "9.0.7"
|
||||
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
|
||||
integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=
|
||||
|
||||
xmlchars@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
|
||||
|
|
Loading…
Reference in New Issue