From 8c2d06cba79ff8965a4de9467e05e80d7c7f449e Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Fri, 29 Jul 2022 15:12:25 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .../Navigation - Left Sidebar Proposals.md | 2 +- GITLAB_PAGES_VERSION | 2 +- .../components/bubble_menus/formatting.vue | 2 + .../extensions/reference_definition.js | 29 + .../content_editor/extensions/sourcemap.js | 2 + .../services/create_content_editor.js | 2 + .../services/markdown_serializer.js | 20 + .../services/remark_markdown_deserializer.js | 12 +- .../services/serialization_helpers.js | 17 +- app/assets/javascripts/lib/gfm/index.js | 10 + .../pipeline_schedules/index/index.js | 41 + .../components/take_ownership_modal.vue | 52 + .../projects/project_transfered_event.rb | 25 + app/helpers/commits_helper.rb | 7 +- app/models/blob.rb | 4 +- app/services/projects/transfer_service.rb | 14 + .../protected_branches/base_service.rb | 4 + .../protected_branches/cache_service.rb | 47 + .../protected_branches/create_service.rb | 2 + .../protected_branches/destroy_service.rb | 2 +- .../protected_branches/update_service.rb | 2 + .../_runner_registrars_form.html.haml | 2 +- .../application_settings/_usage.html.haml | 4 +- app/views/layouts/group.html.haml | 2 +- app/views/layouts/profile.html.haml | 1 + app/views/layouts/project.html.haml | 2 +- .../_pipeline_schedule.html.haml | 2 +- .../pipeline_schedules/index.html.haml | 2 + config/metrics/aggregates/common.yml | 10 - config/webpack.vendor.config.js | 1 - doc/administration/audit_events.md | 2 +- .../auth/ldap/ldap_synchronization.md | 2 +- doc/administration/compliance.md | 2 +- doc/api/groups.md | 8 +- doc/integration/saml.md | 2 +- doc/security/two_factor_authentication.md | 2 +- doc/subscriptions/bronze_starter.md | 10 +- .../admin_area/settings/usage_statistics.md | 2 +- doc/user/group/access_and_permissions.md | 244 +++- doc/user/group/index.md | 245 +--- doc/user/permissions.md | 6 +- doc/user/project/members/index.md | 4 +- .../members/share_project_with_groups.md | 2 +- doc/user/project/repository/push_rules.md | 2 +- glfm_specification/example_snapshots/html.yml | 93 +- .../example_snapshots/prosemirror_json.yml | 1187 ++++++++++++++++- lib/api/helpers.rb | 4 +- lib/gitlab/diff/file.rb | 8 +- lib/gitlab/memory/reports_daemon.rb | 23 +- .../usage_data_counters/hll_redis_counter.rb | 6 +- .../known_events/common.yml | 20 - lib/gitlab/utils/batch_loader.rb | 23 + locale/gitlab.pot | 18 + package.json | 1 - .../projects/commit_controller_spec.rb | 16 + .../projects/compare_controller_spec.rb | 19 +- .../projects/project_transfered_event_spec.rb | 46 + .../projects/pipeline_schedules_spec.rb | 7 +- .../remark_markdown_processing_spec.js | 30 + .../render_html_and_json_for_all_examples.js | 2 + .../services/markdown_serializer_spec.js | 36 + spec/frontend/lib/gfm/index_spec.js | 64 +- .../components/take_ownership_modal_spec.js | 54 + .../content_editor_integration_spec.js | 47 +- spec/helpers/commits_helper_spec.rb | 18 +- spec/lib/gitlab/auth/o_auth/user_spec.rb | 1 + spec/lib/gitlab/memory/reports_daemon_spec.rb | 1 - .../hll_redis_counter_spec.rb | 25 +- spec/lib/gitlab/utils/batch_loader_spec.rb | 82 ++ .../api/ci/runner/jobs_artifacts_spec.rb | 6 + .../projects/transfer_service_spec.rb | 27 + .../protected_branches/cache_service_spec.rb | 81 ++ .../protected_branches/create_service_spec.rb | 8 + .../destroy_service_spec.rb | 8 + .../protected_branches/update_service_spec.rb | 8 + .../_pipeline_schedule.html.haml_spec.rb | 4 +- yarn.lock | 60 +- 77 files changed, 2434 insertions(+), 456 deletions(-) create mode 100644 app/assets/javascripts/content_editor/extensions/reference_definition.js create mode 100644 app/assets/javascripts/pipeline_schedules/components/take_ownership_modal.vue create mode 100644 app/events/projects/project_transfered_event.rb create mode 100644 app/services/protected_branches/cache_service.rb create mode 100644 lib/gitlab/utils/batch_loader.rb create mode 100644 spec/events/projects/project_transfered_event_spec.rb create mode 100644 spec/frontend/pipeline_schedules/components/take_ownership_modal_spec.js create mode 100644 spec/lib/gitlab/utils/batch_loader_spec.rb create mode 100644 spec/services/protected_branches/cache_service_spec.rb diff --git a/.gitlab/issue_templates/Navigation - Left Sidebar Proposals.md b/.gitlab/issue_templates/Navigation - Left Sidebar Proposals.md index e9e510da11e..3939fca44f1 100644 --- a/.gitlab/issue_templates/Navigation - Left Sidebar Proposals.md +++ b/.gitlab/issue_templates/Navigation - Left Sidebar Proposals.md @@ -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. diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION index 30e298c7494..76d05362056 100644 --- a/GITLAB_PAGES_VERSION +++ b/GITLAB_PAGES_VERSION @@ -1 +1 @@ -1.61.1 +1.62.0 diff --git a/app/assets/javascripts/content_editor/components/bubble_menus/formatting.vue b/app/assets/javascripts/content_editor/components/bubble_menus/formatting.vue index f0726ff3e63..8caf12eac5f 100644 --- a/app/assets/javascripts/content_editor/components/bubble_menus/formatting.vue +++ b/app/assets/javascripts/content_editor/components/bubble_menus/formatting.vue @@ -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)); diff --git a/app/assets/javascripts/content_editor/extensions/reference_definition.js b/app/assets/javascripts/content_editor/extensions/reference_definition.js new file mode 100644 index 00000000000..e2762fe9fd9 --- /dev/null +++ b/app/assets/javascripts/content_editor/extensions/reference_definition.js @@ -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]; + }, +}); diff --git a/app/assets/javascripts/content_editor/extensions/sourcemap.js b/app/assets/javascripts/content_editor/extensions/sourcemap.js index 618f17b1c5e..fdac852e55c 100644 --- a/app/assets/javascripts/content_editor/extensions/sourcemap.js +++ b/app/assets/javascripts/content_editor/extensions/sourcemap.js @@ -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, diff --git a/app/assets/javascripts/content_editor/services/create_content_editor.js b/app/assets/javascripts/content_editor/services/create_content_editor.js index 795355abdab..7a289df94ea 100644 --- a/app/assets/javascripts/content_editor/services/create_content_editor.js +++ b/app/assets/javascripts/content_editor/services/create_content_editor.js @@ -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, diff --git a/app/assets/javascripts/content_editor/services/markdown_serializer.js b/app/assets/javascripts/content_editor/services/markdown_serializer.js index c1c7af6b1af..1511f122a15 100644 --- a/app/assets/javascripts/content_editor/services/markdown_serializer.js +++ b/app/assets/javascripts/content_editor/services/markdown_serializer.js @@ -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); diff --git a/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js b/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js index 8e2c066e011..e38be672063 100644 --- a/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js +++ b/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js @@ -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 }; diff --git a/app/assets/javascripts/content_editor/services/serialization_helpers.js b/app/assets/javascripts/content_editor/services/serialization_helpers.js index 7d5e718b41c..97bad375aea 100644 --- a/app/assets/javascripts/content_editor/services/serialization_helpers.js +++ b/app/assets/javascripts/content_editor/services/serialization_helpers.js @@ -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); } }; } diff --git a/app/assets/javascripts/lib/gfm/index.js b/app/assets/javascripts/lib/gfm/index.js index 92118c8929f..05534401a2a 100644 --- a/app/assets/javascripts/lib/gfm/index.js +++ b/app/assets/javascripts/lib/gfm/index.js @@ -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 = [] }) => { diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js b/app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js index cd4bc35e74e..9513f42d9c9 100644 --- a/app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js +++ b/app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js @@ -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(); diff --git a/app/assets/javascripts/pipeline_schedules/components/take_ownership_modal.vue b/app/assets/javascripts/pipeline_schedules/components/take_ownership_modal.vue new file mode 100644 index 00000000000..7ded3945a32 --- /dev/null +++ b/app/assets/javascripts/pipeline_schedules/components/take_ownership_modal.vue @@ -0,0 +1,52 @@ + + diff --git a/app/events/projects/project_transfered_event.rb b/app/events/projects/project_transfered_event.rb new file mode 100644 index 00000000000..14cc53daabb --- /dev/null +++ b/app/events/projects/project_transfered_event.rb @@ -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 diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index 33b771eef69..d574aa51ec4 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -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 diff --git a/app/models/blob.rb b/app/models/blob.rb index a12d856dc36..20d7c230aa2 100644 --- a/app/models/blob.rb +++ b/app/models/blob.rb @@ -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 diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index 666227951c6..3cb5a564ba5 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -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 diff --git a/app/services/protected_branches/base_service.rb b/app/services/protected_branches/base_service.rb index f48e02ab4b5..d26c1b148bf 100644 --- a/app/services/protected_branches/base_service.rb +++ b/app/services/protected_branches/base_service.rb @@ -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 diff --git a/app/services/protected_branches/cache_service.rb b/app/services/protected_branches/cache_service.rb new file mode 100644 index 00000000000..fed5dca2581 --- /dev/null +++ b/app/services/protected_branches/cache_service.rb @@ -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 diff --git a/app/services/protected_branches/create_service.rb b/app/services/protected_branches/create_service.rb index dada449989a..903addf7afc 100644 --- a/app/services/protected_branches/create_service.rb +++ b/app/services/protected_branches/create_service.rb @@ -7,6 +7,8 @@ module ProtectedBranches save_protected_branch + refresh_cache + protected_branch end diff --git a/app/services/protected_branches/destroy_service.rb b/app/services/protected_branches/destroy_service.rb index 47332ace417..01d3b68314f 100644 --- a/app/services/protected_branches/destroy_service.rb +++ b/app/services/protected_branches/destroy_service.rb @@ -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 diff --git a/app/services/protected_branches/update_service.rb b/app/services/protected_branches/update_service.rb index 1e70f2d9793..c155e0022f5 100644 --- a/app/services/protected_branches/update_service.rb +++ b/app/services/protected_branches/update_service.rb @@ -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 diff --git a/app/views/admin/application_settings/_runner_registrars_form.html.haml b/app/views/admin/application_settings/_runner_registrars_form.html.haml index 1d6051a06ea..7781db29bab 100644 --- a/app/views/admin/application_settings/_runner_registrars_form.html.haml +++ b/app/views/admin/application_settings/_runner_registrars_form.html.haml @@ -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 diff --git a/app/views/admin/application_settings/_usage.html.haml b/app/views/admin/application_settings/_usage.html.haml index c9ed2309cec..7326a63f8c2 100644 --- a/app/views/admin/application_settings/_usage.html.haml +++ b/app/views/admin/application_settings/_usage.html.haml @@ -3,7 +3,7 @@ - link_end = ''.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 } diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml index 655375e4d18..67809cbc608 100644 --- a/app/views/layouts/group.html.haml +++ b/app/views/layouts/group.html.haml @@ -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 diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml index 43ec2ceff99..1ec839ef642 100644 --- a/app/views/layouts/profile.html.haml +++ b/app/views/layouts/profile.html.haml @@ -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" diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml index 614f247bb1c..9503e874fd0 100644 --- a/app/views/layouts/project.html.haml +++ b/app/views/layouts/project.html.haml @@ -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 diff --git a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml index edcd44563f7..c36c3ae5adf 100644 --- a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml +++ b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml @@ -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 diff --git a/app/views/projects/pipeline_schedules/index.html.haml b/app/views/projects/pipeline_schedules/index.html.haml index a56e8f7f5c7..661cf465081 100644 --- a/app/views/projects/pipeline_schedules/index.html.haml +++ b/app/views/projects/pipeline_schedules/index.html.haml @@ -18,3 +18,5 @@ - else .card.bg-light.gl-mt-3 .nothing-here-block= _("No schedules") + +#pipeline-take-ownership-modal diff --git a/config/metrics/aggregates/common.yml b/config/metrics/aggregates/common.yml index 6396187a26a..b8f2d179741 100644 --- a/config/metrics/aggregates/common.yml +++ b/config/metrics/aggregates/common.yml @@ -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 diff --git a/config/webpack.vendor.config.js b/config/webpack.vendor.config.js index ff38497a05e..903607679a4 100644 --- a/config/webpack.vendor.config.js +++ b/config/webpack.vendor.config.js @@ -41,7 +41,6 @@ module.exports = { 'three', 'select2', 'moment-mini', - 'aws-sdk', 'dompurify', 'bootstrap/dist/js/bootstrap.js', 'sortablejs/modular/sortable.esm.js', diff --git a/doc/administration/audit_events.md b/doc/administration/audit_events.md index 8bcd78391cb..3282bb5195d 100644 --- a/doc/administration/audit_events.md +++ b/doc/administration/audit_events.md @@ -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. diff --git a/doc/administration/auth/ldap/ldap_synchronization.md b/doc/administration/auth/ldap/ldap_synchronization.md index b0ada1c11dd..62706a9e3b9 100644 --- a/doc/administration/auth/ldap/ldap_synchronization.md +++ b/doc/administration/auth/ldap/ldap_synchronization.md @@ -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 diff --git a/doc/administration/compliance.md b/doc/administration/compliance.md index 869de929eb8..8c7f8bf766d 100644 --- a/doc/administration/compliance.md +++ b/doc/administration/compliance.md @@ -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 diff --git a/doc/api/groups.md b/doc/api/groups.md index 6e6149e935b..bafb6ed3e7e 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -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. diff --git a/doc/integration/saml.md b/doc/integration/saml.md index 97ff2a7df80..8a900975a3a 100644 --- a/doc/integration/saml.md +++ b/doc/integration/saml.md @@ -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). diff --git a/doc/security/two_factor_authentication.md b/doc/security/two_factor_authentication.md index 5907860f5cc..008aef69691 100644 --- a/doc/security/two_factor_authentication.md +++ b/doc/security/two_factor_authentication.md @@ -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. diff --git a/doc/subscriptions/bronze_starter.md b/doc/subscriptions/bronze_starter.md index 2649249f8ec..62e045a7593 100644 --- a/doc/subscriptions/bronze_starter.md +++ b/doc/subscriptions/bronze_starter.md @@ -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: diff --git a/doc/user/admin_area/settings/usage_statistics.md b/doc/user/admin_area/settings/usage_statistics.md index 65712a9a85c..afb937494e0 100644 --- a/doc/user/admin_area/settings/usage_statistics.md +++ b/doc/user/admin_area/settings/usage_statistics.md @@ -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. diff --git a/doc/user/group/access_and_permissions.md b/doc/user/group/access_and_permissions.md index 1ed5f4b2eb7..03b638f8902 100644 --- a/doc/user/group/access_and_permissions.md +++ b/doc/user/group/access_and_permissions.md @@ -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 `` 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 `` 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: + + + +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**. + + + +### 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. diff --git a/doc/user/group/index.md b/doc/user/group/index.md index 420e06c3678..3e2672e725d 100644 --- a/doc/user/group/index.md +++ b/doc/user/group/index.md @@ -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: - - - -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**. - - - -### 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 `` 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 `` 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. diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 33c1584cb72..07b78dc7788 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -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 diff --git a/doc/user/project/members/index.md b/doc/user/project/members/index.md index 2f12a963754..2fccf8bbe5c 100644 --- a/doc/user/project/members/index.md +++ b/doc/user/project/members/index.md @@ -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 diff --git a/doc/user/project/members/share_project_with_groups.md b/doc/user/project/members/share_project_with_groups.md index bb8ae6ae5d2..cf03cc122f2 100644 --- a/doc/user/project/members/share_project_with_groups.md +++ b/doc/user/project/members/share_project_with_groups.md @@ -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). diff --git a/doc/user/project/repository/push_rules.md b/doc/user/project/repository/push_rules.md index 592aff85434..46a9585604e 100644 --- a/doc/user/project/repository/push_rules.md +++ b/doc/user/project/repository/push_rules.md @@ -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). diff --git a/glfm_specification/example_snapshots/html.yml b/glfm_specification/example_snapshots/html.yml index 834dd49a934..105a0d26409 100644 --- a/glfm_specification/example_snapshots/html.yml +++ b/glfm_specification/example_snapshots/html.yml @@ -2144,6 +2144,7 @@ static: |-

foo

wysiwyg: |- +
[foo]: /url "title"

foo

04_07__leaf_blocks__link_reference_definitions__002: canonical: | @@ -2151,6 +2152,7 @@ static: |-

foo

wysiwyg: |- +
[foo]: /url "the title"

foo

04_07__leaf_blocks__link_reference_definitions__003: canonical: | @@ -2158,6 +2160,7 @@ static: |-

Foo*bar]

wysiwyg: |- +
[foo*bar\]]: my_(url) "title (with parens)"

Foo*bar]

04_07__leaf_blocks__link_reference_definitions__004: canonical: | @@ -2165,6 +2168,7 @@ static: |-

Foo bar

wysiwyg: |- +
[foo bar]: my url "title"

Foo bar

04_07__leaf_blocks__link_reference_definitions__005: canonical: | @@ -2180,6 +2184,11 @@ line2 ">foo

wysiwyg: |- +
[foo]: /url "
+    title
+    line1
+    line2
+    "

foo

wysiwyg: |- +
[foo]: /url

foo

04_07__leaf_blocks__link_reference_definitions__008: canonical: | @@ -2221,6 +2231,7 @@ static: |-

foo

wysiwyg: |- +
[foo]: 

foo

04_07__leaf_blocks__link_reference_definitions__010: canonical: | @@ -2238,6 +2249,7 @@ static: |-

foo

wysiwyg: |- +
[foo]: /url\bar*baz "foo"bar\baz"

foo

04_07__leaf_blocks__link_reference_definitions__012: canonical: | @@ -2246,6 +2258,7 @@

foo

wysiwyg: |-

foo

+
[foo]: url
04_07__leaf_blocks__link_reference_definitions__013: canonical: |

foo

@@ -2253,12 +2266,15 @@

foo

wysiwyg: |-

foo

+
[foo]: first
+
[foo]: second
04_07__leaf_blocks__link_reference_definitions__014: canonical: |

Foo

static: |-

Foo

wysiwyg: |- +
[foo]: /url

Foo

04_07__leaf_blocks__link_reference_definitions__015: canonical: | @@ -2266,18 +2282,20 @@ static: |-

αγω

wysiwyg: |- +
[αγω]: /φου

αγω

04_07__leaf_blocks__link_reference_definitions__016: canonical: "" static: "" wysiwyg: |- -

+
[foo]: /url
04_07__leaf_blocks__link_reference_definitions__017: canonical: |

bar

static: |-

bar

wysiwyg: |- +
[foo]: /url

bar

04_07__leaf_blocks__link_reference_definitions__018: canonical: | @@ -2292,6 +2310,7 @@ static: |-

"title" ok

wysiwyg: |- +
[foo]: /url

"title" ok

04_07__leaf_blocks__link_reference_definitions__020: canonical: | @@ -2349,6 +2368,7 @@ wysiwyg: |-

Foo

+
[foo]: /url

bar

04_07__leaf_blocks__link_reference_definitions__024: canonical: | @@ -2359,6 +2379,7 @@ bar

foo

wysiwyg: |- +
[foo]: /url

bar

foo

04_07__leaf_blocks__link_reference_definitions__025: @@ -2369,6 +2390,7 @@

=== foo

wysiwyg: |- +
[foo]: /url

=== foo

04_07__leaf_blocks__link_reference_definitions__026: @@ -2381,6 +2403,9 @@ bar, baz

wysiwyg: |- +
[foo]: /foo-url "foo"
+
[bar]: /bar-url "bar"
+
[baz]: /baz-url

foo, bar, baz

@@ -2395,12 +2420,12 @@ wysiwyg: |-

foo

-

+
[foo]: /url
04_07__leaf_blocks__link_reference_definitions__028: canonical: "" static: "" wysiwyg: |- -

+
[foo]: /url
04_08__leaf_blocks__paragraphs__001: canonical: |

aaa

@@ -4543,7 +4568,7 @@ wysiwyg: |- -
  • a

  • b

  • d

+
  • a

  • b

    [ref]: /url
  • d

05_04__container_blocks__lists__018: canonical: |
    @@ -4880,6 +4905,7 @@

    foo

    wysiwyg: |-

    foo

    +
    [foo]: /bar* "ti*tle"
    06_02__inlines__backslash_escapes__013: canonical: |
    foo
    @@ -4969,6 +4995,7 @@
         

    foo

    wysiwyg: |-

    foo

    +
    [foo]: /föö "föö"
    06_03__inlines__entity_and_numeric_character_references__010: canonical: |
    foo
    @@ -6475,6 +6502,7 @@
         

    foo

    wysiwyg: |-

    foo

    +
    [bar]: /url "title"
    06_07__inlines__links__044: canonical: |

    link [foo [bar]]

    @@ -6482,6 +6510,7 @@

    link [foo [bar]]

    wysiwyg: |-

    link [foo [bar]]

    +
    [ref]: /uri
    06_07__inlines__links__045: canonical: |

    link [bar

    @@ -6489,6 +6518,7 @@

    link [bar

    wysiwyg: |-

    link [bar

    +
    [ref]: /uri
    06_07__inlines__links__046: canonical: |

    link foo bar #

    @@ -6496,6 +6526,7 @@

    link foo bar #

    wysiwyg: |-

    link foo bar#

    +
    [ref]: /uri
    06_07__inlines__links__047: canonical: |

    moon

    @@ -6503,6 +6534,7 @@

    moon

    wysiwyg: |-

    moon

    +
    [ref]: /uri
    06_07__inlines__links__048: canonical: |

    [foo bar]ref

    @@ -6510,6 +6542,7 @@

    [foo bar]ref

    wysiwyg: |-

    [foo bar]ref

    +
    [ref]: /uri
    06_07__inlines__links__049: canonical: |

    [foo bar baz]ref

    @@ -6517,6 +6550,7 @@

    [foo bar baz]ref

    wysiwyg: |-

    [foo bar baz]ref

    +
    [ref]: /uri
    06_07__inlines__links__050: canonical: |

    *foo*

    @@ -6524,6 +6558,7 @@

    *foo*

    wysiwyg: |-

    *foo*

    +
    [ref]: /uri
    06_07__inlines__links__051: canonical: |

    foo *bar

    @@ -6531,6 +6566,7 @@

    foo *bar

    wysiwyg: |-

    foo *bar

    +
    [ref]: /uri
    06_07__inlines__links__052: canonical: |

    [foo

    @@ -6538,6 +6574,7 @@

    [foo

    wysiwyg: |-

    [foo

    +
    [ref]: /uri
    06_07__inlines__links__053: canonical: |

    [foo][ref]

    @@ -6545,6 +6582,7 @@

    [foo][ref]

    wysiwyg: |-

    [foo][ref]

    +
    [ref]: /uri
    06_07__inlines__links__054: canonical: |

    [foohttp://example.com/?search=][ref]

    @@ -6552,6 +6590,7 @@

    [foohttp://example.com/?search=][ref]

    wysiwyg: |-

    [foohttp://example.com/?search=][ref]

    +
    [ref]: /uri
    06_07__inlines__links__055: canonical: |

    foo

    @@ -6559,6 +6598,7 @@

    foo

    wysiwyg: |-

    foo

    +
    [bar]: /url "title"
    06_07__inlines__links__056: canonical: |

    Толпой is a Russian word.

    @@ -6566,12 +6606,14 @@

    Толпой is a Russian word.

    wysiwyg: |-

    Толпой is a Russian word.

    +
    [толпой]: /url
    06_07__inlines__links__057: canonical: |

    Baz

    static: |-

    Baz

    wysiwyg: |- +
    [foo bar]: /url

    Baz

    06_07__inlines__links__058: canonical: | @@ -6580,6 +6622,7 @@

    [foo] bar

    wysiwyg: |-

    [foo] bar

    +
    [bar]: /url "title"
    06_07__inlines__links__059: canonical: |

    [foo] @@ -6590,12 +6633,15 @@ wysiwyg: |-

    [foo] bar

    +
    [bar]: /url "title"
    06_07__inlines__links__060: canonical: |

    bar

    static: |-

    bar

    wysiwyg: |- +
    [foo]: /url1
    +
    [foo]: /url2

    bar

    06_07__inlines__links__061: canonical: | @@ -6604,6 +6650,7 @@

    [bar][foo!]

    wysiwyg: |-

    [bar][foo!]

    +
    [foo!]: /url
    06_07__inlines__links__062: canonical: |

    [foo][ref[]

    @@ -6641,12 +6688,14 @@

    foo

    wysiwyg: |-

    foo

    +
    [ref\[]: /uri
    06_07__inlines__links__066: canonical: |

    bar\

    static: |-

    bar\

    wysiwyg: |- +
    [bar\\]: /uri

    bar\

    06_07__inlines__links__067: canonical: | @@ -6681,6 +6730,7 @@

    foo

    wysiwyg: |-

    foo

    +
    [foo]: /url "title"
    06_07__inlines__links__070: canonical: |

    foo bar

    @@ -6688,6 +6738,7 @@

    foo bar

    wysiwyg: |-

    foo bar

    +
    [*foo* bar]: /url "title"
    06_07__inlines__links__071: canonical: |

    Foo

    @@ -6695,6 +6746,7 @@

    Foo

    wysiwyg: |-

    Foo

    +
    [foo]: /url "title"
    06_07__inlines__links__072: canonical: |

    foo @@ -6705,6 +6757,7 @@ wysiwyg: |-

    foo []

    +
    [foo]: /url "title"
    06_07__inlines__links__073: canonical: |

    foo

    @@ -6712,6 +6765,7 @@

    foo

    wysiwyg: |-

    foo

    +
    [foo]: /url "title"
    06_07__inlines__links__074: canonical: |

    foo bar

    @@ -6719,6 +6773,7 @@

    foo bar

    wysiwyg: |-

    foo bar

    +
    [*foo* bar]: /url "title"
    06_07__inlines__links__075: canonical: |

    [foo bar]

    @@ -6726,6 +6781,7 @@

    [foo bar]

    wysiwyg: |-

    [foo bar]

    +
    [*foo* bar]: /url "title"
    06_07__inlines__links__076: canonical: |

    [[bar foo

    @@ -6733,6 +6789,7 @@

    [[bar foo

    wysiwyg: |-

    [[bar foo

    +
    [foo]: /url
    06_07__inlines__links__077: canonical: |

    Foo

    @@ -6740,6 +6797,7 @@

    Foo

    wysiwyg: |-

    Foo

    +
    [foo]: /url "title"
    06_07__inlines__links__078: canonical: |

    foo bar

    @@ -6747,6 +6805,7 @@

    foo bar

    wysiwyg: |-

    foo bar

    +
    [foo]: /url
    06_07__inlines__links__079: canonical: |

    [foo]

    @@ -6754,12 +6813,14 @@

    [foo]

    wysiwyg: |-

    [foo]

    +
    [foo]: /url "title"
    06_07__inlines__links__080: canonical: |

    *foo*

    static: |-

    *foo*

    wysiwyg: |- +
    [foo*]: /url

    *foo*

    06_07__inlines__links__081: canonical: | @@ -6768,6 +6829,8 @@

    foo

    wysiwyg: |-

    foo

    +
    [foo]: /url1
    +
    [bar]: /url2
    06_07__inlines__links__082: canonical: |

    foo

    @@ -6775,6 +6838,7 @@

    foo

    wysiwyg: |-

    foo

    +
    [foo]: /url1
    06_07__inlines__links__083: canonical: |

    foo

    @@ -6782,6 +6846,7 @@

    foo

    wysiwyg: |-

    foo

    +
    [foo]: /url1
    06_07__inlines__links__084: canonical: |

    foo(not a link)

    @@ -6789,6 +6854,7 @@

    foo(not a link)

    wysiwyg: |-

    foo(not a link)

    +
    [foo]: /url1
    06_07__inlines__links__085: canonical: |

    [foo]bar

    @@ -6796,6 +6862,7 @@

    [foo]bar

    wysiwyg: |-

    [foo]bar

    +
    [baz]: /url
    06_07__inlines__links__086: canonical: |

    foobaz

    @@ -6803,6 +6870,8 @@

    foobaz

    wysiwyg: |-

    foobaz

    +
    [baz]: /url1
    +
    [bar]: /url2
    06_07__inlines__links__087: canonical: |

    [foo]bar

    @@ -6810,6 +6879,8 @@

    [foo]bar

    wysiwyg: |-

    [foo]bar

    +
    [baz]: /url1
    +
    [foo]: /url2
    06_08__inlines__images__001: canonical: |

    foo

    @@ -6824,6 +6895,7 @@

    foo bar

    wysiwyg: |-

    foo bar

    +
    [foo *bar*]: train.jpg "train & tracks"
    06_08__inlines__images__003: canonical: |

    foo bar

    @@ -6845,6 +6917,7 @@

    foo bar

    wysiwyg: |-

    foo bar

    +
    [foo *bar*]: train.jpg "train & tracks"
    06_08__inlines__images__006: canonical: |

    foo bar

    @@ -6852,6 +6925,7 @@

    foo bar

    wysiwyg: |-

    foo bar

    +
    [foobar]: train.jpg "train & tracks"
    06_08__inlines__images__007: canonical: |

    foo

    @@ -6887,6 +6961,7 @@

    foo

    wysiwyg: |-

    foo

    +
    [bar]: /url
    06_08__inlines__images__012: canonical: |

    foo

    @@ -6894,6 +6969,7 @@

    foo

    wysiwyg: |-

    foo

    +
    [bar]: /url
    06_08__inlines__images__013: canonical: |

    foo

    @@ -6901,6 +6977,7 @@

    foo

    wysiwyg: |-

    foo

    +
    [foo]: /url "title"
    06_08__inlines__images__014: canonical: |

    foo bar

    @@ -6908,6 +6985,7 @@

    foo bar

    wysiwyg: |-

    foo bar

    +
    [*foo* bar]: /url "title"
    06_08__inlines__images__015: canonical: |

    Foo

    @@ -6915,6 +6993,7 @@

    Foo

    wysiwyg: |-

    Foo

    +
    [foo]: /url "title"
    06_08__inlines__images__016: canonical: |

    foo @@ -6925,6 +7004,7 @@ wysiwyg: |-

    foo []

    +
    [foo]: /url "title"
    06_08__inlines__images__017: canonical: |

    foo

    @@ -6932,6 +7012,7 @@

    foo

    wysiwyg: |-

    foo

    +
    [foo]: /url "title"
    06_08__inlines__images__018: canonical: |

    foo bar

    @@ -6939,6 +7020,7 @@

    foo bar

    wysiwyg: |-

    foo bar

    +
    [*foo* bar]: /url "title"
    06_08__inlines__images__019: canonical: |

    ![[foo]]

    @@ -6956,6 +7038,7 @@

    Foo

    wysiwyg: |-

    Foo

    +
    [foo]: /url "title"
    06_08__inlines__images__021: canonical: |

    ![foo]

    @@ -6963,6 +7046,7 @@

    ![foo]

    wysiwyg: |-

    ![foo]

    +
    [foo]: /url "title"
    06_08__inlines__images__022: canonical: |

    !foo

    @@ -6970,6 +7054,7 @@

    !foo

    wysiwyg: |-

    !foo

    +
    [foo]: /url "title"
    06_09__inlines__autolinks__001: canonical: |

    http://foo.bar.baz

    diff --git a/glfm_specification/example_snapshots/prosemirror_json.yml b/glfm_specification/example_snapshots/prosemirror_json.yml index f770d341c42..7cb4757b368 100644 --- a/glfm_specification/example_snapshots/prosemirror_json.yml +++ b/glfm_specification/example_snapshots/prosemirror_json.yml @@ -3883,6 +3883,20 @@ { "type": "doc", "content": [ + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url", + "title": "title" + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url \"title\"" + } + ] + }, { "type": "paragraph", "content": [ @@ -3910,6 +3924,20 @@ { "type": "doc", "content": [ + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url", + "title": "the title" + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url \"the title\"" + } + ] + }, { "type": "paragraph", "content": [ @@ -3937,6 +3965,20 @@ { "type": "doc", "content": [ + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo*bar\\]", + "url": "my_(url)", + "title": "title (with parens)" + }, + "content": [ + { + "type": "text", + "text": "[foo*bar\\]]: my_(url) \"title (with parens)\"" + } + ] + }, { "type": "paragraph", "content": [ @@ -3964,6 +4006,20 @@ { "type": "doc", "content": [ + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo bar", + "url": "my url", + "title": "title" + }, + "content": [ + { + "type": "text", + "text": "[foo bar]: my url \"title\"" + } + ] + }, { "type": "paragraph", "content": [ @@ -3991,6 +4047,20 @@ { "type": "doc", "content": [ + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url", + "title": "\ntitle\nline1\nline2\n" + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url \"\ntitle\nline1\nline2\n\"" + } + ] + }, { "type": "paragraph", "content": [ @@ -4051,6 +4121,20 @@ { "type": "doc", "content": [ + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url" + } + ] + }, { "type": "paragraph", "content": [ @@ -4102,6 +4186,20 @@ { "type": "doc", "content": [ + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[foo]: " + } + ] + }, { "type": "paragraph", "content": [ @@ -4153,6 +4251,20 @@ { "type": "doc", "content": [ + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url\\bar*baz", + "title": "foo\"bar\\baz" + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url\\bar*baz \"foo\"bar\\baz\"" + } + ] + }, { "type": "paragraph", "content": [ @@ -4200,6 +4312,20 @@ "text": "foo" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "url", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[foo]: url" + } + ] } ] } @@ -4227,6 +4353,34 @@ "text": "foo" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "first", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[foo]: first" + } + ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "second", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[foo]: second" + } + ] } ] } @@ -4234,6 +4388,20 @@ { "type": "doc", "content": [ + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url" + } + ] + }, { "type": "paragraph", "content": [ @@ -4261,6 +4429,20 @@ { "type": "doc", "content": [ + { + "type": "referenceDefinition", + "attrs": { + "identifier": "αγω", + "url": "/φου", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[αγω]: /φου" + } + ] + }, { "type": "paragraph", "content": [ @@ -4289,7 +4471,18 @@ "type": "doc", "content": [ { - "type": "paragraph" + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url" + } + ] } ] } @@ -4297,6 +4490,20 @@ { "type": "doc", "content": [ + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url" + } + ] + }, { "type": "paragraph", "content": [ @@ -4327,6 +4534,20 @@ { "type": "doc", "content": [ + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url" + } + ] + }, { "type": "paragraph", "content": [ @@ -4446,6 +4667,20 @@ } ] }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url" + } + ] + }, { "type": "blockquote", "attrs": { @@ -4469,6 +4704,20 @@ { "type": "doc", "content": [ + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url" + } + ] + }, { "type": "heading", "attrs": { @@ -4508,6 +4757,20 @@ { "type": "doc", "content": [ + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url" + } + ] + }, { "type": "paragraph", "content": [ @@ -4539,6 +4802,48 @@ { "type": "doc", "content": [ + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/foo-url", + "title": "foo" + }, + "content": [ + { + "type": "text", + "text": "[foo]: /foo-url \"foo\"" + } + ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "bar", + "url": "/bar-url", + "title": "bar" + }, + "content": [ + { + "type": "text", + "text": "[bar]: /bar-url \"bar\"" + } + ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "baz", + "url": "/baz-url", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[baz]: /baz-url" + } + ] + }, { "type": "paragraph", "content": [ @@ -4634,7 +4939,18 @@ }, "content": [ { - "type": "paragraph" + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url" + } + ] } ] } @@ -4645,7 +4961,18 @@ "type": "doc", "content": [ { - "type": "paragraph" + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url" + } + ] } ] } @@ -9572,6 +9899,20 @@ "text": "b" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "ref", + "url": "/url", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[ref]: /url" + } + ] } ] }, @@ -10416,6 +10757,20 @@ "text": "foo" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/bar*", + "title": "ti*tle" + }, + "content": [ + { + "type": "text", + "text": "[foo]: /bar* \"ti*tle\"" + } + ] } ] } @@ -10588,6 +10943,20 @@ "text": "foo" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/föö", + "title": "föö" + }, + "content": [ + { + "type": "text", + "text": "[foo]: /föö \"föö\"" + } + ] } ] } @@ -15592,6 +15961,20 @@ "text": "foo" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "bar", + "url": "/url", + "title": "title" + }, + "content": [ + { + "type": "text", + "text": "[bar]: /url \"title\"" + } + ] } ] } @@ -15619,6 +16002,20 @@ "text": "link [foo [bar]]" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "ref", + "url": "/uri", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[ref]: /uri" + } + ] } ] } @@ -15646,6 +16043,20 @@ "text": "link [bar" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "ref", + "url": "/uri", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[ref]: /uri" + } + ] } ] } @@ -15739,6 +16150,20 @@ "text": "#" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "ref", + "url": "/uri", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[ref]: /uri" + } + ] } ] } @@ -15772,6 +16197,20 @@ ] } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "ref", + "url": "/uri", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[ref]: /uri" + } + ] } ] } @@ -15823,6 +16262,20 @@ "text": "ref" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "ref", + "url": "/uri", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[ref]: /uri" + } + ] } ] } @@ -15886,6 +16339,20 @@ "text": "ref" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "ref", + "url": "/uri", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[ref]: /uri" + } + ] } ] } @@ -15917,6 +16384,20 @@ "text": "foo*" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "ref", + "url": "/uri", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[ref]: /uri" + } + ] } ] } @@ -15944,6 +16425,20 @@ "text": "foo *bar" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "ref", + "url": "/uri", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[ref]: /uri" + } + ] } ] } @@ -15959,6 +16454,20 @@ "text": "[foo " } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "ref", + "url": "/uri", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[ref]: /uri" + } + ] } ] } @@ -15983,6 +16492,20 @@ "text": "][ref]" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "ref", + "url": "/uri", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[ref]: /uri" + } + ] } ] } @@ -16014,6 +16537,20 @@ "text": "http://example.com/?search=][ref]" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "ref", + "url": "/uri", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[ref]: /uri" + } + ] } ] } @@ -16041,6 +16578,20 @@ "text": "foo" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "bar", + "url": "/url", + "title": "title" + }, + "content": [ + { + "type": "text", + "text": "[bar]: /url \"title\"" + } + ] } ] } @@ -16072,6 +16623,20 @@ "text": " is a Russian word." } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "толпой", + "url": "/url", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[толпой]: /url" + } + ] } ] } @@ -16079,6 +16644,20 @@ { "type": "doc", "content": [ + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo bar", + "url": "/url", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[foo bar]: /url" + } + ] + }, { "type": "paragraph", "content": [ @@ -16130,6 +16709,20 @@ "text": "bar" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "bar", + "url": "/url", + "title": "title" + }, + "content": [ + { + "type": "text", + "text": "[bar]: /url \"title\"" + } + ] } ] } @@ -16161,6 +16754,20 @@ "text": "bar" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "bar", + "url": "/url", + "title": "title" + }, + "content": [ + { + "type": "text", + "text": "[bar]: /url \"title\"" + } + ] } ] } @@ -16168,6 +16775,34 @@ { "type": "doc", "content": [ + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url1", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url1" + } + ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url2", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url2" + } + ] + }, { "type": "paragraph", "content": [ @@ -16203,6 +16838,20 @@ "text": "[bar][foo!]" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo!", + "url": "/url", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[foo!]: /url" + } + ] } ] } @@ -16302,6 +16951,20 @@ "text": "foo" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "ref\\[", + "url": "/uri", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[ref\\[]: /uri" + } + ] } ] } @@ -16309,6 +16972,20 @@ { "type": "doc", "content": [ + { + "type": "referenceDefinition", + "attrs": { + "identifier": "bar\\\\", + "url": "/uri", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[bar\\\\]: /uri" + } + ] + }, { "type": "paragraph", "content": [ @@ -16404,6 +17081,20 @@ "text": "foo" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url", + "title": "title" + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url \"title\"" + } + ] } ] } @@ -16450,6 +17141,20 @@ "text": " bar" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "*foo* bar", + "url": "/url", + "title": "title" + }, + "content": [ + { + "type": "text", + "text": "[*foo* bar]: /url \"title\"" + } + ] } ] } @@ -16477,6 +17182,20 @@ "text": "Foo" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url", + "title": "title" + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url \"title\"" + } + ] } ] } @@ -16508,6 +17227,20 @@ "text": "\n[]" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url", + "title": "title" + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url \"title\"" + } + ] } ] } @@ -16535,6 +17268,20 @@ "text": "foo" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url", + "title": "title" + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url \"title\"" + } + ] } ] } @@ -16581,6 +17328,20 @@ "text": " bar" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "*foo* bar", + "url": "/url", + "title": "title" + }, + "content": [ + { + "type": "text", + "text": "[*foo* bar]: /url \"title\"" + } + ] } ] } @@ -16635,6 +17396,20 @@ "text": "]" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "*foo* bar", + "url": "/url", + "title": "title" + }, + "content": [ + { + "type": "text", + "text": "[*foo* bar]: /url \"title\"" + } + ] } ] } @@ -16666,6 +17441,20 @@ "text": "foo" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url" + } + ] } ] } @@ -16693,6 +17482,20 @@ "text": "Foo" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url", + "title": "title" + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url \"title\"" + } + ] } ] } @@ -16724,6 +17527,20 @@ "text": " bar" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url" + } + ] } ] } @@ -16739,6 +17556,20 @@ "text": "[foo]" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url", + "title": "title" + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url \"title\"" + } + ] } ] } @@ -16746,6 +17577,20 @@ { "type": "doc", "content": [ + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo*", + "url": "/url", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[foo*]: /url" + } + ] + }, { "type": "paragraph", "content": [ @@ -16797,6 +17642,34 @@ "text": "foo" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url1", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url1" + } + ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "bar", + "url": "/url2", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[bar]: /url2" + } + ] } ] } @@ -16824,6 +17697,20 @@ "text": "foo" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url1", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url1" + } + ] } ] } @@ -16851,6 +17738,20 @@ "text": "foo" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url1", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url1" + } + ] } ] } @@ -16882,6 +17783,20 @@ "text": "(not a link)" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url1", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url1" + } + ] } ] } @@ -16913,6 +17828,20 @@ "text": "bar" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "baz", + "url": "/url", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[baz]: /url" + } + ] } ] } @@ -16956,6 +17885,34 @@ "text": "baz" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "baz", + "url": "/url1", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[baz]: /url1" + } + ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "bar", + "url": "/url2", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[bar]: /url2" + } + ] } ] } @@ -16987,6 +17944,34 @@ "text": "bar" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "baz", + "url": "/url1", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[baz]: /url1" + } + ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url2", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url2" + } + ] } ] } @@ -17029,6 +18014,20 @@ } } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo *bar*", + "url": "train.jpg", + "title": "train & tracks" + }, + "content": [ + { + "type": "text", + "text": "[foo *bar*]: train.jpg \"train & tracks\"" + } + ] } ] } @@ -17092,6 +18091,20 @@ } } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo *bar*", + "url": "train.jpg", + "title": "train & tracks" + }, + "content": [ + { + "type": "text", + "text": "[foo *bar*]: train.jpg \"train & tracks\"" + } + ] } ] } @@ -17113,6 +18126,20 @@ } } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foobar", + "url": "train.jpg", + "title": "train & tracks" + }, + "content": [ + { + "type": "text", + "text": "[foobar]: train.jpg \"train & tracks\"" + } + ] } ] } @@ -17222,6 +18249,20 @@ } } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "bar", + "url": "/url", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[bar]: /url" + } + ] } ] } @@ -17243,6 +18284,20 @@ } } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "bar", + "url": "/url", + "title": null + }, + "content": [ + { + "type": "text", + "text": "[bar]: /url" + } + ] } ] } @@ -17264,6 +18319,20 @@ } } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url", + "title": "title" + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url \"title\"" + } + ] } ] } @@ -17285,6 +18354,20 @@ } } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "*foo* bar", + "url": "/url", + "title": "title" + }, + "content": [ + { + "type": "text", + "text": "[*foo* bar]: /url \"title\"" + } + ] } ] } @@ -17306,6 +18389,20 @@ } } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url", + "title": "title" + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url \"title\"" + } + ] } ] } @@ -17331,6 +18428,20 @@ "text": "\n[]" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url", + "title": "title" + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url \"title\"" + } + ] } ] } @@ -17352,6 +18463,20 @@ } } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url", + "title": "title" + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url \"title\"" + } + ] } ] } @@ -17373,6 +18498,20 @@ } } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "*foo* bar", + "url": "/url", + "title": "title" + }, + "content": [ + { + "type": "text", + "text": "[*foo* bar]: /url \"title\"" + } + ] } ] } @@ -17418,6 +18557,20 @@ } } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url", + "title": "title" + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url \"title\"" + } + ] } ] } @@ -17433,6 +18586,20 @@ "text": "![foo]" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url", + "title": "title" + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url \"title\"" + } + ] } ] } @@ -17464,6 +18631,20 @@ "text": "foo" } ] + }, + { + "type": "referenceDefinition", + "attrs": { + "identifier": "foo", + "url": "/url", + "title": "title" + }, + "content": [ + { + "type": "text", + "text": "[foo]: /url \"title\"" + } + ] } ] } diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 0bc643b40a3..a71a1cc40e6 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -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 diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index 8c55652da43..5583c896803 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -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? diff --git a/lib/gitlab/memory/reports_daemon.rb b/lib/gitlab/memory/reports_daemon.rb index 779c19b3a1e..8788bee27a6 100644 --- a/lib/gitlab/memory/reports_daemon.rb +++ b/lib/gitlab/memory/reports_daemon.rb @@ -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 diff --git a/lib/gitlab/usage_data_counters/hll_redis_counter.rb b/lib/gitlab/usage_data_counters/hll_redis_counter.rb index 40581bda81b..4647b19554d 100644 --- a/lib/gitlab/usage_data_counters/hll_redis_counter.rb +++ b/lib/gitlab/usage_data_counters/hll_redis_counter.rb @@ -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] 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 diff --git a/lib/gitlab/usage_data_counters/known_events/common.yml b/lib/gitlab/usage_data_counters/known_events/common.yml index 88c9f44c165..d647573ec9f 100644 --- a/lib/gitlab/usage_data_counters/known_events/common.yml +++ b/lib/gitlab/usage_data_counters/known_events/common.yml @@ -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 diff --git a/lib/gitlab/utils/batch_loader.rb b/lib/gitlab/utils/batch_loader.rb new file mode 100644 index 00000000000..67ade0633e2 --- /dev/null +++ b/lib/gitlab/utils/batch_loader.rb @@ -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 diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 958d0a7489f..4dc897d3ddf 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -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 "" diff --git a/package.json b/package.json index 4ba8caee76e..d30b3f803c9 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb index 59b2168c41a..edb07bbdce6 100644 --- a/spec/controllers/projects/commit_controller_spec.rb +++ b/spec/controllers/projects/commit_controller_spec.rb @@ -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) diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb index e6e0307d0ca..6ed6f7017e3 100644 --- a/spec/controllers/projects/compare_controller_spec.rb +++ b/spec/controllers/projects/compare_controller_spec.rb @@ -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 diff --git a/spec/events/projects/project_transfered_event_spec.rb b/spec/events/projects/project_transfered_event_spec.rb new file mode 100644 index 00000000000..51c8bf4765f --- /dev/null +++ b/spec/events/projects/project_transfered_event_spec.rb @@ -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 diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb index 8cf6d5bd29b..0711a30e974 100644 --- a/spec/features/projects/pipeline_schedules_spec.rb +++ b/spec/features/projects/pipeline_schedules_spec.rb @@ -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') diff --git a/spec/frontend/content_editor/remark_markdown_processing_spec.js b/spec/frontend/content_editor/remark_markdown_processing_spec.js index 48adceaab58..ddf49a4c18e 100644 --- a/spec/frontend/content_editor/remark_markdown_processing_spec.js +++ b/spec/frontend/content_editor/remark_markdown_processing_spec.js @@ -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); diff --git a/spec/frontend/content_editor/render_html_and_json_for_all_examples.js b/spec/frontend/content_editor/render_html_and_json_for_all_examples.js index 116a26cf7d5..782158810dd 100644 --- a/spec/frontend/content_editor/render_html_and_json_for_all_examples.js +++ b/spec/frontend/content_editor/render_html_and_json_for_all_examples.js @@ -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, diff --git a/spec/frontend/content_editor/services/markdown_serializer_spec.js b/spec/frontend/content_editor/services/markdown_serializer_spec.js index 29f1f219971..422eb3f311b 100644 --- a/spec/frontend/content_editor/services/markdown_serializer_spec.js +++ b/spec/frontend/content_editor/services/markdown_serializer_spec.js @@ -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(); }; diff --git a/spec/frontend/lib/gfm/index_spec.js b/spec/frontend/lib/gfm/index_spec.js index b722315d63a..624744b9a92 100644 --- a/spec/frontend/lib/gfm/index_spec.js +++ b/spec/frontend/lib/gfm/index_spec.js @@ -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"', + }, + ], + }), + ); + }); }); }); }); diff --git a/spec/frontend/pipeline_schedules/components/take_ownership_modal_spec.js b/spec/frontend/pipeline_schedules/components/take_ownership_modal_spec.js new file mode 100644 index 00000000000..23bfdef13c6 --- /dev/null +++ b/spec/frontend/pipeline_schedules/components/take_ownership_modal_spec.js @@ -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(); + }); +}); diff --git a/spec/frontend_integration/content_editor/content_editor_integration_spec.js b/spec/frontend_integration/content_editor/content_editor_integration_spec.js index 89b8d8d6d94..4d400a383e3 100644 --- a/spec/frontend_integration/content_editor/content_editor_integration_spec.js +++ b/spec/frontend_integration/content_editor/content_editor_integration_spec.js @@ -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(` -

    - This reference tag is a mix of letters and numbers. 2 -

    -
    -
      -
    1. -

      This is another footnote.

      -
    2. -
    -
    + 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'); + }); }); }); diff --git a/spec/helpers/commits_helper_spec.rb b/spec/helpers/commits_helper_spec.rb index b5b572e9719..b27954de0d4 100644 --- a/spec/helpers/commits_helper_spec.rb +++ b/spec/helpers/commits_helper_spec.rb @@ -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 diff --git a/spec/lib/gitlab/auth/o_auth/user_spec.rb b/spec/lib/gitlab/auth/o_auth/user_spec.rb index 5f5e7f211f8..b160f322fb8 100644 --- a/spec/lib/gitlab/auth/o_auth/user_spec.rb +++ b/spec/lib/gitlab/auth/o_auth/user_spec.rb @@ -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'] } diff --git a/spec/lib/gitlab/memory/reports_daemon_spec.rb b/spec/lib/gitlab/memory/reports_daemon_spec.rb index 75334834c5b..6af317f2771 100644 --- a/spec/lib/gitlab/memory/reports_daemon_spec.rb +++ b/spec/lib/gitlab/memory/reports_daemon_spec.rb @@ -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', diff --git a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb index 54d49b432f4..d45ee0113e8 100644 --- a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb +++ b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb @@ -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 diff --git a/spec/lib/gitlab/utils/batch_loader_spec.rb b/spec/lib/gitlab/utils/batch_loader_spec.rb new file mode 100644 index 00000000000..c1f6d6df07a --- /dev/null +++ b/spec/lib/gitlab/utils/batch_loader_spec.rb @@ -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 diff --git a/spec/requests/api/ci/runner/jobs_artifacts_spec.rb b/spec/requests/api/ci/runner/jobs_artifacts_spec.rb index 5767fa4326e..cd8c3dd2806 100644 --- a/spec/requests/api/ci/runner/jobs_artifacts_spec.rb +++ b/spec/requests/api/ci/runner/jobs_artifacts_spec.rb @@ -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) diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index ecf9f92d74f..49073931c02 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -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) } diff --git a/spec/services/protected_branches/cache_service_spec.rb b/spec/services/protected_branches/cache_service_spec.rb new file mode 100644 index 00000000000..4c05d6dcbd9 --- /dev/null +++ b/spec/services/protected_branches/cache_service_spec.rb @@ -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 diff --git a/spec/services/protected_branches/create_service_spec.rb b/spec/services/protected_branches/create_service_spec.rb index 3ac42d41377..5a371b68f0c 100644 --- a/spec/services/protected_branches/create_service_spec.rb +++ b/spec/services/protected_branches/create_service_spec.rb @@ -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) { 'foobar<\b>' } diff --git a/spec/services/protected_branches/destroy_service_spec.rb b/spec/services/protected_branches/destroy_service_spec.rb index 4e55c72f312..492b91dbb8b 100644 --- a/spec/services/protected_branches/destroy_service_spec.rb +++ b/spec/services/protected_branches/destroy_service_spec.rb @@ -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) diff --git a/spec/services/protected_branches/update_service_spec.rb b/spec/services/protected_branches/update_service_spec.rb index 4405af35c37..ee61382bd89 100644 --- a/spec/services/protected_branches/update_service_spec.rb +++ b/spec/services/protected_branches/update_service_spec.rb @@ -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) { 'foobar<\b>' } let(:result) { service.execute(protected_branch) } diff --git a/spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb b/spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb index e650e183bc8..37c9908af1d 100644 --- a/spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb +++ b/spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb @@ -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 diff --git a/yarn.lock b/yarn.lock index ca755429f19..4d7aae26d5b 100644 --- a/yarn.lock +++ b/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"