diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-08 18:09:49 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-08 18:09:49 +0000 |
commit | 89b770bb38aef8c0b895454e940d8f55a3038527 (patch) | |
tree | 83d0d7966b207747091f7ba6d892184f1e33bbcb | |
parent | 3bc30c280c408f3f31c90961e0fc5809c6246137 (diff) | |
download | gitlab-ce-89b770bb38aef8c0b895454e940d8f55a3038527.tar.gz |
Add latest changes from gitlab-org/gitlab@master
128 files changed, 1960 insertions, 867 deletions
@@ -519,3 +519,5 @@ gem 'webauthn', '~> 2.3' # IPAddress utilities gem 'ipaddress', '~> 0.8.3' + +gem 'parslet', '~> 1.8' diff --git a/Gemfile.lock b/Gemfile.lock index 1a08c567d55..ab94f963d42 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1464,6 +1464,7 @@ DEPENDENCIES omniauth_openid_connect (~> 0.3.5) org-ruby (~> 0.9.12) parallel (~> 1.19) + parslet (~> 1.8) peek (~> 1.1) pg (~> 1.1) pg_query (~> 1.3.0) diff --git a/app/assets/javascripts/jobs/components/sidebar_detail_row.vue b/app/assets/javascripts/jobs/components/sidebar_detail_row.vue index 7d541f93bad..05567328660 100644 --- a/app/assets/javascripts/jobs/components/sidebar_detail_row.vue +++ b/app/assets/javascripts/jobs/components/sidebar_detail_row.vue @@ -34,12 +34,12 @@ export default { }; </script> <template> - <p class="build-detail-row"> - <span v-if="hasTitle" class="font-weight-bold">{{ title }}:</span> {{ value }} - <span v-if="hasHelpURL" class="help-button float-right"> - <gl-link :href="helpUrl" target="_blank" rel="noopener noreferrer nofollow"> - <gl-icon name="question-o" /> - </gl-link> - </span> + <p class="gl-display-flex gl-justify-content-space-between gl-mb-2"> + <span v-if="hasTitle" + ><b>{{ title }}:</b> {{ value }}</span + > + <gl-link v-if="hasHelpURL" :href="helpUrl" target="_blank"> + <gl-icon name="question-o" /> + </gl-link> </p> </template> diff --git a/app/assets/javascripts/projects/pipelines/charts/components/app.vue b/app/assets/javascripts/projects/pipelines/charts/components/app.vue index 7282ac85c70..4a8e1424fa8 100644 --- a/app/assets/javascripts/projects/pipelines/charts/components/app.vue +++ b/app/assets/javascripts/projects/pipelines/charts/components/app.vue @@ -20,18 +20,26 @@ export default { }, }, data() { - const [chart] = getParameterValues('chart') || charts; - const tab = charts.indexOf(chart); return { - chart, - selectedTab: tab >= 0 ? tab : 0, + selectedTab: 0, }; }, + created() { + this.selectTab(); + window.addEventListener('popstate', this.selectTab); + }, methods: { + selectTab() { + const [chart] = getParameterValues('chart') || charts; + const tab = charts.indexOf(chart); + this.selectedTab = tab >= 0 ? tab : 0; + }, onTabChange(index) { - this.selectedTab = index; - const path = mergeUrlParams({ chart: charts[index] }, window.location.pathname); - updateHistory({ url: path }); + if (index !== this.selectedTab) { + this.selectedTab = index; + const path = mergeUrlParams({ chart: charts[index] }, window.location.pathname); + updateHistory({ url: path, title: window.title }); + } }, }, }; diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 58f8cf09780..a62df1258af 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -982,7 +982,14 @@ $mr-widget-min-height: 69px; } .mini-pipeline-graph-dropdown-toggle, - .stage-cell .mini-pipeline-graph-dropdown-toggle svg { + .stage-cell .mini-pipeline-graph-dropdown-toggle svg, + // As the `mini-pipeline-item` mixin specificity is lower + // than the toggle of dropdown with 'variant="link"' we add + // classes ".gl-button.btn-link" to make it more specific. + // Once FF ci_mini_pipeline_gl_dropdown is removed, the `mini-pipeline-item` + // itself could increase its specificity to simplify this selector + button.gl-button.btn-link.mini-pipeline-graph-gl-dropdown-toggle, + .stage-cell button.gl-button.btn-link.mini-pipeline-graph-gl-dropdown-toggle svg { height: $ci-action-icon-size-lg; width: $ci-action-icon-size-lg; } diff --git a/app/controllers/projects/templates_controller.rb b/app/controllers/projects/templates_controller.rb index f4726638777..ab05c9694fd 100644 --- a/app/controllers/projects/templates_controller.rb +++ b/app/controllers/projects/templates_controller.rb @@ -24,10 +24,8 @@ class Projects::TemplatesController < Projects::ApplicationController end def names - templates = @template_type.dropdown_names(project) - respond_to do |format| - format.json { render json: templates } + format.json { render json: TemplateFinder.all_template_names_array(project, params[:template_type].to_s.pluralize) } end end diff --git a/app/finders/license_template_finder.rb b/app/finders/license_template_finder.rb index 4d68b963dc3..02ab6ec5659 100644 --- a/app/finders/license_template_finder.rb +++ b/app/finders/license_template_finder.rb @@ -28,6 +28,10 @@ class LicenseTemplateFinder end end + def template_names + ::Gitlab::Template::BaseTemplate.template_names_by_category(vendored_licenses) + end + private def vendored_licenses diff --git a/app/finders/template_finder.rb b/app/finders/template_finder.rb index dac7c526474..36f8d144908 100644 --- a/app/finders/template_finder.rb +++ b/app/finders/template_finder.rb @@ -21,6 +21,18 @@ class TemplateFinder new(type, project, params) end end + + def all_template_names(project, type) + return {} if !VENDORED_TEMPLATES.key?(type.to_s) && type.to_s != 'licenses' + + build(type, project).template_names + end + + # This is issues and merge requests description templates only. + # This will be removed once we introduce group level inherited templates + def all_template_names_array(project, type) + all_template_names(project, type).values.flatten.uniq + end end attr_reader :type, :project, :params @@ -43,6 +55,10 @@ class TemplateFinder vendored_templates.all(project) end end + + def template_names + vendored_templates.template_names(project) + end end TemplateFinder.prepend_if_ee('::EE::TemplateFinder') diff --git a/app/graphql/resolvers/ci/config_resolver.rb b/app/graphql/resolvers/ci/config_resolver.rb index 72d3ae30d73..852bb47e215 100644 --- a/app/graphql/resolvers/ci/config_resolver.rb +++ b/app/graphql/resolvers/ci/config_resolver.rb @@ -29,6 +29,12 @@ module Resolvers .new(project: project, current_user: context[:current_user]) .validate(content, dry_run: dry_run) + response(result).merge(merged_yaml: result.merged_yaml) + end + + private + + def response(result) if result.errors.empty? { status: :valid, @@ -43,8 +49,6 @@ module Resolvers end end - private - def make_jobs(config_jobs) config_jobs.map do |job| { diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 15ed241f7e4..3435dfcf317 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -194,40 +194,28 @@ module BlobHelper @ref_project ||= @target_project || @project end - def template_dropdown_names(items) - grouped = items.group_by(&:category) - categories = grouped.keys - - categories.each_with_object({}) do |category, hash| - hash[category] = grouped[category].map do |item| - { name: item.name, id: item.key } - end - end - end - private :template_dropdown_names - def licenses_for_select(project) - @licenses_for_select ||= template_dropdown_names(TemplateFinder.build(:licenses, project).execute) + @licenses_for_select ||= TemplateFinder.all_template_names(project, :licenses) end def gitignore_names(project) - @gitignore_names ||= template_dropdown_names(TemplateFinder.build(:gitignores, project).execute) + @gitignore_names ||= TemplateFinder.all_template_names(project, :gitignores) end def gitlab_ci_ymls(project) - @gitlab_ci_ymls ||= template_dropdown_names(TemplateFinder.build(:gitlab_ci_ymls, project).execute) + @gitlab_ci_ymls ||= TemplateFinder.all_template_names(project, :gitlab_ci_ymls) end def gitlab_ci_syntax_ymls(project) - @gitlab_ci_syntax_ymls ||= template_dropdown_names(TemplateFinder.build(:gitlab_ci_syntax_ymls, project).execute) + @gitlab_ci_syntax_ymls ||= TemplateFinder.all_template_names(project, :gitlab_ci_syntax_ymls) end def metrics_dashboard_ymls(project) - @metrics_dashboard_ymls ||= template_dropdown_names(TemplateFinder.build(:metrics_dashboard_ymls, project).execute) + @metrics_dashboard_ymls ||= TemplateFinder.all_template_names(project, :metrics_dashboard_ymls) end def dockerfile_names(project) - @dockerfile_names ||= template_dropdown_names(TemplateFinder.build(:dockerfiles, project).execute) + @dockerfile_names ||= TemplateFinder.all_template_names(project, :dockerfiles) end def blob_editor_paths(project) diff --git a/app/helpers/issuables_description_templates_helper.rb b/app/helpers/issuables_description_templates_helper.rb new file mode 100644 index 00000000000..34d02f3a99d --- /dev/null +++ b/app/helpers/issuables_description_templates_helper.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module IssuablesDescriptionTemplatesHelper + include Gitlab::Utils::StrongMemoize + include GitlabRoutingHelper + + def template_dropdown_tag(issuable, &block) + title = selected_template(issuable) || "Choose a template" + options = { + toggle_class: 'js-issuable-selector', + title: title, + filter: true, + placeholder: 'Filter', + footer_content: true, + data: { + data: issuable_templates(ref_project, issuable.to_ability_name), + field_name: 'issuable_template', + selected: selected_template(issuable), + project_id: ref_project.id, + project_path: ref_project.path, + namespace_path: ref_project.namespace.full_path + } + } + + dropdown_tag(title, options: options) do + capture(&block) + end + end + + def issuable_templates(project, issuable_type) + @template_types ||= {} + @template_types[project.id] ||= {} + @template_types[project.id][issuable_type] ||= TemplateFinder.all_template_names_array(project, issuable_type.pluralize) + end + + def issuable_templates_names(issuable) + issuable_templates(ref_project, issuable.to_ability_name).map { |template| template[:name] } + end + + def selected_template(issuable) + params[:issuable_template] if issuable_templates(ref_project, issuable.to_ability_name).any? { |template| template[:name] == params[:issuable_template] } + end + + def template_names_path(parent, issuable) + return '' unless parent.is_a?(Project) + + project_template_names_path(parent, template_type: issuable.to_ability_name) + end +end diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index a5f64837c02..41e9f61cf9f 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -2,6 +2,7 @@ module IssuablesHelper include GitlabRoutingHelper + include IssuablesDescriptionTemplatesHelper def sidebar_gutter_toggle_icon content_tag(:span, class: 'js-sidebar-toggle-container', data: { is_expanded: !sidebar_gutter_collapsed? }) do @@ -75,28 +76,6 @@ module IssuablesHelper .to_json end - def template_dropdown_tag(issuable, &block) - title = selected_template(issuable) || "Choose a template" - options = { - toggle_class: 'js-issuable-selector', - title: title, - filter: true, - placeholder: 'Filter', - footer_content: true, - data: { - data: issuable_templates(issuable), - field_name: 'issuable_template', - selected: selected_template(issuable), - project_path: ref_project.path, - namespace_path: ref_project.namespace.full_path - } - } - - dropdown_tag(title, options: options) do - capture(&block) - end - end - def users_dropdown_label(selected_users) case selected_users.length when 0 @@ -282,6 +261,7 @@ module IssuablesHelper { projectPath: ref_project.path, + projectId: ref_project.id, projectNamespace: ref_project.namespace.full_path } end @@ -358,24 +338,6 @@ module IssuablesHelper cookies[:collapsed_gutter] == 'true' end - def issuable_templates(issuable) - @issuable_templates ||= - case issuable - when Issue - ref_project.repository.issue_template_names - when MergeRequest - ref_project.repository.merge_request_template_names - end - end - - def issuable_templates_names(issuable) - issuable_templates(issuable).map { |template| template[:name] } - end - - def selected_template(issuable) - params[:issuable_template] if issuable_templates(issuable).any? { |template| template[:name] == params[:issuable_template] } - end - def issuable_todo_button_data(issuable, is_collapsed) { todo_text: _('Add a to do'), @@ -413,12 +375,6 @@ module IssuablesHelper end end - def template_names_path(parent, issuable) - return '' unless parent.is_a?(Project) - - project_template_names_path(parent, template_type: issuable.class.name.underscore) - end - def issuable_sidebar_options(issuable) { endpoint: "#{issuable[:issuable_json_path]}?serializer=sidebar_extras", diff --git a/app/models/project.rb b/app/models/project.rb index aad2e7d2259..ab70ed56913 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -117,7 +117,7 @@ class Project < ApplicationRecord use_fast_destroy :build_trace_chunks - after_destroy -> { run_after_commit { remove_pages } } + after_destroy -> { run_after_commit { legacy_remove_pages } } after_destroy :remove_exports after_validation :check_pending_delete @@ -1788,16 +1788,16 @@ class Project < ApplicationRecord .delete_all end - # TODO: what to do here when not using Legacy Storage? Do we still need to rename and delay removal? + # TODO: remove this method https://gitlab.com/gitlab-org/gitlab/-/issues/320775 # rubocop: disable CodeReuse/ServiceClass - def remove_pages + def legacy_remove_pages + return unless Feature.enabled?(:pages_update_legacy_storage, default_enabled: true) + # Projects with a missing namespace cannot have their pages removed return unless namespace mark_pages_as_not_deployed unless destroyed? - DestroyPagesDeploymentsWorker.perform_async(id) - # 1. We rename pages to temporary directory # 2. We wait 5 minutes, due to NFS caching # 3. We asynchronously remove pages with force diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 61e9773d49a..5f8b6b48d6c 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +# Accessible as Project#external_issue_tracker class JiraService < IssueTrackerService extend ::Gitlab::Utils::Override include Gitlab::Routing @@ -162,7 +163,7 @@ class JiraService < IssueTrackerService jira_request { client.Issue.find(issue_key) } end - def close_issue(entity, external_issue) + def close_issue(entity, external_issue, current_user) issue = find_issue(external_issue.iid) return if issue.nil? || has_resolution?(issue) || !jira_issue_transition_id.present? @@ -179,6 +180,7 @@ class JiraService < IssueTrackerService # if it is closed, so we don't have one comment for every commit. issue = find_issue(issue.key) if transition_issue(issue) add_issue_solved_comment(issue, commit_id, commit_url) if has_resolution?(issue) + log_usage(:close_issue, current_user) end def create_cross_reference_note(mentioned, noteable, author) @@ -214,7 +216,7 @@ class JiraService < IssueTrackerService } } - add_comment(data, jira_issue) + add_comment(data, jira_issue).tap { log_usage(:cross_reference, author) } end def valid_connection? @@ -275,6 +277,12 @@ class JiraService < IssueTrackerService end end + def log_usage(action, user) + key = "i_ecosystem_jira_service_#{action}" + + Gitlab::UsageDataCounters::HLLRedisCounter.track_event(key, values: user.id) + end + def add_issue_solved_comment(issue, commit_id, commit_url) link_title = "Solved by commit #{commit_id}." comment = "Issue solved with [#{commit_id}|#{commit_url}]." diff --git a/app/models/repository.rb b/app/models/repository.rb index ba13b67d101..06a13194e1a 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -43,7 +43,7 @@ class Repository changelog license_blob license_key gitignore gitlab_ci_yml branch_names tag_names branch_count tag_count avatar exists? root_ref merged_branch_names - has_visible_content? issue_template_names merge_request_template_names + has_visible_content? issue_template_names_by_category merge_request_template_names_by_category user_defined_metrics_dashboard_paths xcode_project? has_ambiguous_refs?).freeze # Methods that use cache_method but only memoize the value @@ -60,8 +60,8 @@ class Repository gitignore: :gitignore, gitlab_ci: :gitlab_ci_yml, avatar: :avatar, - issue_template: :issue_template_names, - merge_request_template: :merge_request_template_names, + issue_template: :issue_template_names_by_category, + merge_request_template: :merge_request_template_names_by_category, metrics_dashboard: :user_defined_metrics_dashboard_paths, xcode_config: :xcode_project? }.freeze @@ -572,15 +572,16 @@ class Repository end cache_method :avatar - def issue_template_names - Gitlab::Template::IssueTemplate.dropdown_names(project) + # store issue_template_names as hash + def issue_template_names_by_category + Gitlab::Template::IssueTemplate.repository_template_names(project) end - cache_method :issue_template_names, fallback: [] + cache_method :issue_template_names_by_category, fallback: {} - def merge_request_template_names - Gitlab::Template::MergeRequestTemplate.dropdown_names(project) + def merge_request_template_names_by_category + Gitlab::Template::MergeRequestTemplate.repository_template_names(project) end - cache_method :merge_request_template_names, fallback: [] + cache_method :merge_request_template_names_by_category, fallback: {} def user_defined_metrics_dashboard_paths Gitlab::Metrics::Dashboard::RepoDashboardFinder.list_dashboards(project) diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb index baf7974c45d..746f7d1f4c1 100644 --- a/app/services/issues/close_service.rb +++ b/app/services/issues/close_service.rb @@ -55,7 +55,7 @@ module Issues def close_external_issue(issue, closed_via) return unless project.external_issue_tracker&.support_close_issue? - project.external_issue_tracker.close_issue(closed_via, issue) + project.external_issue_tracker.close_issue(closed_via, issue, current_user) todo_service.close_issue(issue, current_user) end diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index 04b7fba207b..488c847dcbb 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -27,7 +27,11 @@ module Notes end note_saved = note.with_transaction_returning_status do - !only_commands && note.save + break false if only_commands + + note.save.tap do + update_discussions(note) + end end when_saved(note) if note_saved @@ -54,12 +58,17 @@ module Notes @quick_actions_service ||= QuickActionsService.new(project, current_user) end - def when_saved(note) + def update_discussions(note) + # Ensure that individual notes that are promoted into discussions are + # updated in a transaction with the note creation to avoid inconsistencies: + # https://gitlab.com/gitlab-org/gitlab/-/issues/301237 if note.part_of_discussion? && note.discussion.can_convert_to_discussion? note.discussion.convert_to_discussion!.save note.clear_memoization(:discussion) end + end + def when_saved(note) todo_service.new_note(note, current_user) clear_noteable_diffs_cache(note) Suggestions::CreateService.new(note).execute diff --git a/app/services/pages/delete_service.rb b/app/services/pages/delete_service.rb index fc5d01a93a1..3dc9254718e 100644 --- a/app/services/pages/delete_service.rb +++ b/app/services/pages/delete_service.rb @@ -3,7 +3,13 @@ module Pages class DeleteService < BaseService def execute - PagesRemoveWorker.perform_async(project.id) + project.mark_pages_as_not_deployed # prevents domain from updating config when deleted + project.pages_domains.delete_all + + DestroyPagesDeploymentsWorker.perform_async(project.id) + + # TODO: remove this call https://gitlab.com/gitlab-org/gitlab/-/issues/320775 + PagesRemoveWorker.perform_async(project.id) if Feature.enabled?(:pages_update_legacy_storage, default_enabled: true) end end end diff --git a/app/views/admin/applications/index.html.haml b/app/views/admin/applications/index.html.haml index c1c1c2a4cfe..7ac55157f65 100644 --- a/app/views/admin/applications/index.html.haml +++ b/app/views/admin/applications/index.html.haml @@ -1,28 +1,35 @@ - page_title _("Applications") %h3.page-title - System OAuth applications + = _('System OAuth applications') %p.light - System OAuth applications don't belong to any user and can only be managed by admins + = _('System OAuth applications don\'t belong to any user and can only be managed by admins') %hr -%p= link_to 'New application', new_admin_application_path, class: 'gl-button btn btn-success' -%table.table - %thead - %tr - %th Name - %th Callback URL - %th Clients - %th Trusted - %th Confidential - %th - %th - %tbody.oauth-applications - - @applications.each do |application| - %tr{ :id => "application_#{application.id}" } - %td= link_to application.name, admin_application_path(application) - %td= application.redirect_uri - %td= @application_counts[application.id].to_i - %td= application.trusted? ? 'Y': 'N' - %td= application.confidential? ? 'Y': 'N' - %td= link_to 'Edit', edit_admin_application_path(application), class: 'gl-button btn btn-link' - %td= render 'delete_form', application: application +%p= link_to _('New application'), new_admin_application_path, class: 'gl-button btn btn-success' +.table-responsive + %table.table + %thead + %tr + %th + = _('Name') + %th + = _('Callback URL') + %th + = _('Clients') + %th + = _('Trusted') + %th + = _('Confidential') + %th + %th + %tbody.oauth-applications + - @applications.each do |application| + %tr{ :id => "application_#{application.id}" } + %td= link_to application.name, admin_application_path(application) + %td= application.redirect_uri + %td= @application_counts[application.id].to_i + %td= application.trusted? ? _('Yes'): _('No') + %td= application.confidential? ? _('Yes'): _('No') + %td= link_to 'Edit', edit_admin_application_path(application), class: 'gl-button btn btn-link' + %td= render 'delete_form', application: application + = paginate @applications, theme: 'gitlab' diff --git a/app/views/shared/issuable/form/_template_selector.html.haml b/app/views/shared/issuable/form/_template_selector.html.haml index 24a235277fe..71598b2cd2b 100644 --- a/app/views/shared/issuable/form/_template_selector.html.haml +++ b/app/views/shared/issuable/form/_template_selector.html.haml @@ -1,6 +1,6 @@ - issuable = local_assigns.fetch(:issuable, nil) -- return unless issuable && issuable_templates(issuable).any? +- return unless issuable && issuable_templates(ref_project, issuable.to_ability_name).any? .issuable-form-select-holder.selectbox.form-group .js-issuable-selector-wrap{ data: { issuable_type: issuable.to_ability_name, qa_selector: 'template_dropdown' } } diff --git a/app/views/shared/issuable/form/_title.html.haml b/app/views/shared/issuable/form/_title.html.haml index 98c9f73fa3a..94d0c395fa6 100644 --- a/app/views/shared/issuable/form/_title.html.haml +++ b/app/views/shared/issuable/form/_title.html.haml @@ -1,7 +1,7 @@ - issuable = local_assigns.fetch(:issuable) - has_wip_commits = local_assigns.fetch(:has_wip_commits) - form = local_assigns.fetch(:form) -- no_issuable_templates = issuable_templates(issuable).empty? +- no_issuable_templates = issuable_templates(ref_project, issuable.to_ability_name).empty? - div_class = no_issuable_templates ? 'col-sm-10' : 'col-sm-7 col-lg-8' - toggle_wip_link_start = '<a href="" class="js-toggle-wip">' - toggle_wip_link_end = '</a>' diff --git a/app/workers/pages_remove_worker.rb b/app/workers/pages_remove_worker.rb index b83168fd7bd..67ea18545a7 100644 --- a/app/workers/pages_remove_worker.rb +++ b/app/workers/pages_remove_worker.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +# TODO: remove this worker https://gitlab.com/gitlab-org/gitlab/-/issues/320775 class PagesRemoveWorker # rubocop:disable Scalability/IdempotentWorker include ApplicationWorker @@ -11,7 +12,6 @@ class PagesRemoveWorker # rubocop:disable Scalability/IdempotentWorker project = Project.find_by_id(project_id) return unless project - project.remove_pages - project.pages_domains.delete_all + project.legacy_remove_pages end end diff --git a/changelogs/unreleased/fix-back-button-ci-cd-analytics.yml b/changelogs/unreleased/fix-back-button-ci-cd-analytics.yml new file mode 100644 index 00000000000..2a4c6ebd142 --- /dev/null +++ b/changelogs/unreleased/fix-back-button-ci-cd-analytics.yml @@ -0,0 +1,5 @@ +--- +title: Back Button now switches to last active analytics tab +merge_request: 53495 +author: +type: fixed diff --git a/changelogs/unreleased/improve-system-oauth-app-layout.yml b/changelogs/unreleased/improve-system-oauth-app-layout.yml new file mode 100644 index 00000000000..3561232e529 --- /dev/null +++ b/changelogs/unreleased/improve-system-oauth-app-layout.yml @@ -0,0 +1,5 @@ +--- +title: Make System OAuth app index table responsive and externalize text +merge_request: 50979 +author: Kev @KevSlashNull +type: fixed diff --git a/changelogs/unreleased/lm-add-merged-yaml.yml b/changelogs/unreleased/lm-add-merged-yaml.yml new file mode 100644 index 00000000000..518a6f0c4fe --- /dev/null +++ b/changelogs/unreleased/lm-add-merged-yaml.yml @@ -0,0 +1,5 @@ +--- +title: 'GraphQL: Add mergedYaml to CiConfigResolver response' +merge_request: 53081 +author: +type: changed diff --git a/changelogs/unreleased/pks-gitaly-force-primary-routing-with-hookenv.yml b/changelogs/unreleased/pks-gitaly-force-primary-routing-with-hookenv.yml new file mode 100644 index 00000000000..1f625c03985 --- /dev/null +++ b/changelogs/unreleased/pks-gitaly-force-primary-routing-with-hookenv.yml @@ -0,0 +1,5 @@ +--- +title: 'gitaly: Fix access checks with transactions and quarantine environments' +merge_request: 53449 +author: +type: fixed diff --git a/changelogs/unreleased/sh-fix-discussion-note-promotion-failure-handling.yml b/changelogs/unreleased/sh-fix-discussion-note-promotion-failure-handling.yml new file mode 100644 index 00000000000..dfabb8e3089 --- /dev/null +++ b/changelogs/unreleased/sh-fix-discussion-note-promotion-failure-handling.yml @@ -0,0 +1,5 @@ +--- +title: Ensure note is promoted to discussion within reply create transaction +merge_request: 53542 +author: +type: fixed diff --git a/config/feature_flags/development/usage_data_track_ecosystem_jira_service.yml b/config/feature_flags/development/usage_data_track_ecosystem_jira_service.yml new file mode 100644 index 00000000000..adf850aa8bc --- /dev/null +++ b/config/feature_flags/development/usage_data_track_ecosystem_jira_service.yml @@ -0,0 +1,8 @@ +--- +name: usage_data_track_ecosystem_jira_service +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/52110 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/300447 +milestone: '13.9' +type: development +group: group::ecosystem +default_enabled: false diff --git a/doc/administration/compliance.md b/doc/administration/compliance.md index b2c8d8982bd..f8675b599a6 100644 --- a/doc/administration/compliance.md +++ b/doc/administration/compliance.md @@ -13,15 +13,15 @@ documentation. The [security features](../security/README.md) in GitLab may also help you meet relevant compliance standards. -|Feature |GitLab tier |GitLab.com | Product level | +|Feature |GitLab tier |GitLab SaaS | Product level | | ---------| :--------: | :-------: | :-----------: | |**[Restrict SSH Keys](../security/ssh_keys_restrictions.md)**<br>Control the technology and key length of SSH keys used to access GitLab|Free+||Instance| |**[Granular user roles and flexible permissions](../user/permissions.md)**<br>Manage access and permissions with five different user roles and settings for external users. Set permissions according to people's role, rather than either read or write access to a repository. Don't share the source code with people that only need access to the issue tracker.|Free+|✓|Instance, Group, Project| |**[Enforce TOS acceptance](../user/admin_area/settings/terms.md)**<br>Enforce your users accepting new terms of service by blocking GitLab traffic.|Free+||Instance| -|**[Email all users of a project, group, or entire server](../tools/email.md)**<br>An admin can email groups of users based on project or group membership, or email everyone using the GitLab instance. This is great for scheduled maintenance or upgrades.|Starter+|||Instance -|**[Omnibus package supports log forwarding](https://docs.gitlab.com/omnibus/settings/logs.html#udp-log-forwarding)**<br>Forward your logs to a central system.|Starter+||Instance| -|**[Lock project membership to group](../user/group/index.md#member-lock)**<br>Group owners can prevent new members from being added to projects within a group.|Starter+|✓|Group| -|**[LDAP group sync](auth/ldap/index.md#group-sync)**<br>GitLab Enterprise Edition gives admins the ability to automatically sync groups and manage SSH keys, permissions, and authentication, so you can focus on building your product, not configuring your tools.|Starter+||Instance| +|**[Email all users of a project, group, or entire server](../tools/email.md)**<br>An admin can email groups of users based on project or group membership, or email everyone using the GitLab instance. This is great for scheduled maintenance or upgrades.|Premium+||Instance| +|**[Omnibus package supports log forwarding](https://docs.gitlab.com/omnibus/settings/logs.html#udp-log-forwarding)**<br>Forward your logs to a central system.|Premium+||Instance| +|**[Lock project membership to group](../user/group/index.md#member-lock)**<br>Group owners can prevent new members from being added to projects within a group.|Premium+|✓|Group| +|**[LDAP group sync](auth/ldap/index.md#group-sync)**<br>GitLab Enterprise Edition gives admins the ability to automatically sync groups and manage SSH keys, permissions, and authentication, so you can focus on building your product, not configuring your tools.|Premium+||Instance| |**[LDAP group sync filters](auth/ldap/index.md#group-sync)**<br>GitLab Enterprise Edition Premium gives more flexibility to synchronize with LDAP based on filters, meaning you can leverage LDAP attributes to map GitLab permissions.|Premium+||Instance| |**[Audit events](audit_events.md)**<br>To maintain the integrity of your code, GitLab Enterprise Edition Premium gives admins the ability to view any modifications made within the GitLab server in an advanced audit events system, so you can control, analyze, and track every change.|Premium+|✓|Instance, Group, Project| |**[Auditor users](auditor_users.md)**<br>Auditor users are users who are given read-only access to all projects, groups, and other resources on the GitLab instance.|Premium+||Instance| diff --git a/doc/administration/maintenance_mode/index.md b/doc/administration/maintenance_mode/index.md index c37eff53549..0df51a455e1 100644 --- a/doc/administration/maintenance_mode/index.md +++ b/doc/administration/maintenance_mode/index.md @@ -129,6 +129,10 @@ it is recommended that you disable all cron jobs except for those related to Geo You can monitor queues and disable jobs in **Admin Area > Monitoring > Background Jobs**. +### Incident management + +During Maintenance Mode, [Incident management](../../operations/incident_management/index.md) functions will be limited. The creation of [alerts](../../operations/incident_management/alerts.md) and [incidents](../../operations/incident_management/incidents.md#incident-creation) will be paused entirely. Notifications and paging on alerts and incidents will therefore be disabled. + ### Geo secondaries The maintenance mode setting will be propagated to the secondary as they sync up. diff --git a/doc/administration/terraform_state.md b/doc/administration/terraform_state.md index 5cc377a45fc..bdf1b8de910 100644 --- a/doc/administration/terraform_state.md +++ b/doc/administration/terraform_state.md @@ -4,7 +4,7 @@ group: Configure info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Terraform state administration (alpha) **(CORE)** +# Terraform state administration (alpha) **(FREE)** > [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2673) in GitLab 12.10. diff --git a/doc/api/group_clusters.md b/doc/api/group_clusters.md index c4044b2332b..69b54591d0a 100644 --- a/doc/api/group_clusters.md +++ b/doc/api/group_clusters.md @@ -4,7 +4,7 @@ group: Configure info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Group clusters API **(CORE)** +# Group clusters API **(FREE)** > [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/30213) in GitLab 12.1. diff --git a/doc/api/group_iterations.md b/doc/api/group_iterations.md index 715f40e8325..3d72e035383 100644 --- a/doc/api/group_iterations.md +++ b/doc/api/group_iterations.md @@ -4,9 +4,10 @@ group: Project Management info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Group iterations API **(STARTER)** +# Group iterations API **(PREMIUM)** -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/118742) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.5. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/118742) in GitLab 13.5. +> - Moved to GitLab Premium in 13.9. This page describes the group iterations API. There's a separate [project iterations API](iterations.md) page. diff --git a/doc/api/group_milestones.md b/doc/api/group_milestones.md index 2c56b2c501d..cf542a9da0b 100644 --- a/doc/api/group_milestones.md +++ b/doc/api/group_milestones.md @@ -159,9 +159,10 @@ Parameters: | `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user | | `milestone_id` | integer | yes | The ID of a group milestone | -## Get all burndown chart events for a single milestone **(STARTER)** +## Get all burndown chart events for a single milestone **(PREMIUM)** -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/4737) in GitLab 12.1 +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/4737) in GitLab 12.1 +> - Moved to GitLab Premium in 13.9. Get all burndown chart events for a single milestone. diff --git a/doc/api/groups.md b/doc/api/groups.md index 0aba679655b..0010e32cea4 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -670,7 +670,7 @@ Example response: } ``` -Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) also see +Users of [GitLab Premium or higher](https://about.gitlab.com/pricing/) also see the `shared_runners_minutes_limit` and `extra_shared_runners_minutes_limit` parameters: Additional response parameters: @@ -685,7 +685,7 @@ Additional response parameters: } ``` -Users on GitLab [Premium or higher](https://about.gitlab.com/pricing/) also see +Users of [GitLab Premium or higher](https://about.gitlab.com/pricing/) also see the `marked_for_deletion_on` attribute: ```json diff --git a/doc/api/instance_clusters.md b/doc/api/instance_clusters.md index 164830f2f34..9ae38de380e 100644 --- a/doc/api/instance_clusters.md +++ b/doc/api/instance_clusters.md @@ -4,7 +4,7 @@ group: Configure info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Instance clusters API **(CORE)** +# Instance clusters API **(FREE)** > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36001) in GitLab 13.2. diff --git a/doc/api/issues.md b/doc/api/issues.md index 1abb4fe3b4a..ab8dc8f590d 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -169,7 +169,7 @@ Example response: ] ``` -Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) can also see +Users of [GitLab Premium or higher](https://about.gitlab.com/pricing/) can also see the `weight` parameter: ```json @@ -183,7 +183,7 @@ the `weight` parameter: ] ``` -Users on GitLab [Ultimate](https://about.gitlab.com/pricing/) can also see +Users of [GitLab Ultimate](https://about.gitlab.com/pricing/) can also see the `health_status` parameter: ```json @@ -347,7 +347,7 @@ Example response: ] ``` -Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) can also see +Users of [GitLab Premium or higher](https://about.gitlab.com/pricing/) can also see the `weight` parameter: ```json @@ -361,7 +361,7 @@ the `weight` parameter: ] ``` -Users on GitLab [Ultimate](https://about.gitlab.com/pricing/) can also see +Users of [GitLab Ultimate](https://about.gitlab.com/pricing/) can also see the `health_status` parameter: ```json @@ -530,7 +530,7 @@ Example response: ] ``` -Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) can also see +Users of [GitLab Premium or higher](https://about.gitlab.com/pricing/) can also see the `weight` parameter: ```json @@ -544,7 +544,7 @@ the `weight` parameter: ] ``` -Users on GitLab [Ultimate](https://about.gitlab.com/pricing/) can also see +Users of [GitLab Ultimate](https://about.gitlab.com/pricing/) can also see the `health_status` parameter: ```json @@ -684,7 +684,7 @@ Example response: } ``` -Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) can also see +Users of [GitLab Premium or higher](https://about.gitlab.com/pricing/) can also see the `weight` parameter: ```json @@ -696,7 +696,7 @@ the `weight` parameter: } ``` -Users on GitLab [Ultimate](https://about.gitlab.com/pricing/) can also see +Users of [GitLab Ultimate](https://about.gitlab.com/pricing/) can also see the `epic` property: ```javascript @@ -833,7 +833,7 @@ Example response: } ``` -Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) can also see +Users of [GitLab Premium or higher](https://about.gitlab.com/pricing/) can also see the `weight` parameter: ```json @@ -845,7 +845,7 @@ the `weight` parameter: } ``` -Users on GitLab [Premium](https://about.gitlab.com/pricing/) can also see +Users of [GitLab Premium](https://about.gitlab.com/pricing/) can also see the `epic` property: ```javascript @@ -864,7 +864,7 @@ the `epic` property: } ``` -Users on GitLab [Ultimate](https://about.gitlab.com/pricing/) can also see the `health_status` +Users of [GitLab Ultimate](https://about.gitlab.com/pricing/) can also see the `health_status` property: ```json @@ -980,7 +980,7 @@ Example response: } ``` -Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) can also see +Users of [GitLab Premium or higher](https://about.gitlab.com/pricing/) can also see the `weight` parameter: ```json @@ -992,7 +992,7 @@ the `weight` parameter: } ``` -Users on GitLab [Ultimate](https://about.gitlab.com/pricing/) can also see +Users of [GitLab Ultimate](https://about.gitlab.com/pricing/) can also see the `health_status` parameter: ```json @@ -1133,7 +1133,7 @@ Example response: } ``` -Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) can also see +Users of [GitLab Premium or higher](https://about.gitlab.com/pricing/) can also see the `weight` parameter: ```json @@ -1145,7 +1145,7 @@ the `weight` parameter: } ``` -Users on GitLab [Ultimate](https://about.gitlab.com/pricing/) can also see +Users of [GitLab Ultimate](https://about.gitlab.com/pricing/) can also see the `health_status` parameter: ```json @@ -1298,7 +1298,7 @@ Example response: } ``` -Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) can also see +Users of [GitLab Premium or higher](https://about.gitlab.com/pricing/) can also see the `weight` parameter: ```json @@ -1310,7 +1310,7 @@ the `weight` parameter: } ``` -Users on GitLab [Ultimate](https://about.gitlab.com/pricing/) can also see +Users of [GitLab Ultimate](https://about.gitlab.com/pricing/) can also see the `health_status` parameter: ```json @@ -1421,7 +1421,7 @@ Example response: } ``` -Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) can also see +Users of [GitLab Premium or higher](https://about.gitlab.com/pricing/) can also see the `weight` parameter: ```json diff --git a/doc/api/iterations.md b/doc/api/iterations.md index ff6e8e2542f..be52ef0ea47 100644 --- a/doc/api/iterations.md +++ b/doc/api/iterations.md @@ -4,9 +4,10 @@ group: Project Management info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Project iterations API **(STARTER)** +# Project iterations API **(PREMIUM)** -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/118742) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.5. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/118742) in GitLab 13.5. +> - Moved to GitLab Premium in 13.9. This page describes the project iterations API. There's a separate [group iterations API](group_iterations.md) page. diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 63a705201dd..1a768037015 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -1183,7 +1183,7 @@ POST /projects/:id/merge_requests } ``` -Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) also see +Users of [GitLab Premium or higher](https://about.gitlab.com/pricing/) also see the `approvals_before_merge` parameter: ```json diff --git a/doc/api/milestones.md b/doc/api/milestones.md index 93b867c0286..15927ad852e 100644 --- a/doc/api/milestones.md +++ b/doc/api/milestones.md @@ -159,9 +159,10 @@ Parameters: - `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user - `milestone_id` (required) - The ID of a project milestone -## Get all burndown chart events for a single milestone **(STARTER)** +## Get all burndown chart events for a single milestone **(PREMIUM)** -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/4737) in GitLab 12.1 +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/4737) in GitLab 12.1 +> - Moved to GitLab Premium in 13.9. Gets all burndown chart events for a single milestone. diff --git a/doc/api/project_clusters.md b/doc/api/project_clusters.md index 01bff56b9f6..a431e754774 100644 --- a/doc/api/project_clusters.md +++ b/doc/api/project_clusters.md @@ -4,7 +4,7 @@ group: Configure info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Project clusters API **(CORE)** +# Project clusters API **(FREE)** > [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/23922) in GitLab 11.7. diff --git a/doc/api/projects.md b/doc/api/projects.md index f58830f699c..c22b00ab8a2 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -298,11 +298,11 @@ When the user is authenticated and `simple` is not set this returns something li ``` NOTE: -For users of GitLab [Premium or higher](https://about.gitlab.com/pricing/), +For users of [GitLab Premium or higher](https://about.gitlab.com/pricing/), the `marked_for_deletion_at` attribute has been deprecated, and is removed in API v5 in favor of the `marked_for_deletion_on` attribute. -Users of GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) +Users of [GitLab Premium or higher](https://about.gitlab.com/pricing/) can also see the `approvals_before_merge` parameter: ```json @@ -933,7 +933,7 @@ GET /projects/:id } ``` -Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) +Users of [GitLab Premium or higher](https://about.gitlab.com/pricing/) can also see the `approvals_before_merge` parameter: ```json @@ -1053,7 +1053,7 @@ POST /projects |-------------------------------------------------------------|---------|------------------------|-------------| | `allow_merge_on_skipped_pipeline` | boolean | **{dotted-circle}** No | Set whether or not merge requests can be merged with skipped jobs. | | `analytics_access_level` | string | no | One of `disabled`, `private` or `enabled` | -| `approvals_before_merge` **(STARTER)** | integer | **{dotted-circle}** No | How many approvers should approve merge requests by default. | +| `approvals_before_merge` **(PREMIUM)** | integer | **{dotted-circle}** No | How many approvers should approve merge requests by default. | | `auto_cancel_pending_pipelines` | string | **{dotted-circle}** No | Auto-cancel pending pipelines. This isn't a boolean, but enabled/disabled. | | `auto_devops_deploy_strategy` | string | **{dotted-circle}** No | Auto Deploy strategy (`continuous`, `manual` or `timed_incremental`). | | `auto_devops_enabled` | boolean | **{dotted-circle}** No | Enable Auto DevOps for this project. | @@ -1081,8 +1081,8 @@ POST /projects | `merge_method` | string | **{dotted-circle}** No | Set the [merge method](#project-merge-method) used. | | `merge_requests_access_level` | string | **{dotted-circle}** No | One of `disabled`, `private`, or `enabled`. | | `merge_requests_enabled` | boolean | **{dotted-circle}** No | _(Deprecated)_ Enable merge requests for this project. Use `merge_requests_access_level` instead. | -| `mirror_trigger_builds` **(STARTER)** | boolean | **{dotted-circle}** No | Pull mirroring triggers builds. | -| `mirror` **(STARTER)** | boolean | **{dotted-circle}** No | Enables pull mirroring in a project. | +| `mirror_trigger_builds` **(PREMIUM)** | boolean | **{dotted-circle}** No | Pull mirroring triggers builds. | +| `mirror` **(PREMIUM)** | boolean | **{dotted-circle}** No | Enables pull mirroring in a project. | | `name` | string | **{check-circle}** Yes (if path isn't provided) | The name of the new project. Equals path if not provided. | | `namespace_id` | integer | **{dotted-circle}** No | Namespace for the new project (defaults to the current user's namespace). | | `operations_access_level` | string | **{dotted-circle}** No | One of `disabled`, `private`, or `enabled`. | @@ -1127,7 +1127,7 @@ POST /projects/user/:user_id |-------------------------------------------------------------|---------|------------------------|-------------| | `allow_merge_on_skipped_pipeline` | boolean | **{dotted-circle}** No | Set whether or not merge requests can be merged with skipped jobs. | | `analytics_access_level` | string | no | One of `disabled`, `private` or `enabled` | -| `approvals_before_merge` **(STARTER)** | integer | **{dotted-circle}** No | How many approvers should approve merge requests by default. | +| `approvals_before_merge` **(PREMIUM)** | integer | **{dotted-circle}** No | How many approvers should approve merge requests by default. | | `auto_cancel_pending_pipelines` | string | **{dotted-circle}** No | Auto-cancel pending pipelines. This isn't a boolean, but enabled/disabled. | | `auto_devops_deploy_strategy` | string | **{dotted-circle}** No | Auto Deploy strategy (`continuous`, `manual` or `timed_incremental`). | | `auto_devops_enabled` | boolean | **{dotted-circle}** No | Enable Auto DevOps for this project. | @@ -1153,8 +1153,8 @@ POST /projects/user/:user_id | `merge_method` | string | **{dotted-circle}** No | Set the [merge method](#project-merge-method) used. | | `merge_requests_access_level` | string | **{dotted-circle}** No | One of `disabled`, `private`, or `enabled`. | | `merge_requests_enabled` | boolean | **{dotted-circle}** No | _(Deprecated)_ Enable merge requests for this project. Use `merge_requests_access_level` instead. | -| `mirror_trigger_builds` **(STARTER)** | boolean | **{dotted-circle}** No | Pull mirroring triggers builds. | -| `mirror` **(STARTER)** | boolean | **{dotted-circle}** No | Enables pull mirroring in a project. | +| `mirror_trigger_builds` **(PREMIUM)** | boolean | **{dotted-circle}** No | Pull mirroring triggers builds. | +| `mirror` **(PREMIUM)** | boolean | **{dotted-circle}** No | Enables pull mirroring in a project. | | `name` | string | **{check-circle}** Yes | The name of the new project. | | `namespace_id` | integer | **{dotted-circle}** No | Namespace for the new project (defaults to the current user's namespace). | | `operations_access_level` | string | **{dotted-circle}** No | One of `disabled`, `private`, or `enabled`. | @@ -1200,7 +1200,7 @@ PUT /projects/:id |-------------------------------------------------------------|----------------|------------------------|-------------| | `allow_merge_on_skipped_pipeline` | boolean | **{dotted-circle}** No | Set whether or not merge requests can be merged with skipped jobs. | | `analytics_access_level` | string | no | One of `disabled`, `private` or `enabled` | -| `approvals_before_merge` **(STARTER)** | integer | **{dotted-circle}** No | How many approvers should approve merge request by default. | +| `approvals_before_merge` **(PREMIUM)** | integer | **{dotted-circle}** No | How many approvers should approve merge request by default. | | `auto_cancel_pending_pipelines` | string | **{dotted-circle}** No | Auto-cancel pending pipelines. This isn't a boolean, but enabled/disabled. | | `auto_devops_deploy_strategy` | string | **{dotted-circle}** No | Auto Deploy strategy (`continuous`, `manual`, or `timed_incremental`). | | `auto_devops_enabled` | boolean | **{dotted-circle}** No | Enable Auto DevOps for this project. | @@ -1229,15 +1229,15 @@ PUT /projects/:id | `merge_method` | string | **{dotted-circle}** No | Set the [merge method](#project-merge-method) used. | | `merge_requests_access_level` | string | **{dotted-circle}** No | One of `disabled`, `private`, or `enabled`. | | `merge_requests_enabled` | boolean | **{dotted-circle}** No | _(Deprecated)_ Enable merge requests for this project. Use `merge_requests_access_level` instead. | -| `mirror_overwrites_diverged_branches` **(STARTER)** | boolean | **{dotted-circle}** No | Pull mirror overwrites diverged branches. | -| `mirror_trigger_builds` **(STARTER)** | boolean | **{dotted-circle}** No | Pull mirroring triggers builds. | -| `mirror_user_id` **(STARTER)** | integer | **{dotted-circle}** No | User responsible for all the activity surrounding a pull mirror event. _(admins only)_ | -| `mirror` **(STARTER)** | boolean | **{dotted-circle}** No | Enables pull mirroring in a project. | +| `mirror_overwrites_diverged_branches` **(PREMIUM)** | boolean | **{dotted-circle}** No | Pull mirror overwrites diverged branches. | +| `mirror_trigger_builds` **(PREMIUM)** | boolean | **{dotted-circle}** No | Pull mirroring triggers builds. | +| `mirror_user_id` **(PREMIUM)** | integer | **{dotted-circle}** No | User responsible for all the activity surrounding a pull mirror event. _(admins only)_ | +| `mirror` **(PREMIUM)** | boolean | **{dotted-circle}** No | Enables pull mirroring in a project. | | `name` | string | **{dotted-circle}** No | The name of the project. | | `operations_access_level` | string | **{dotted-circle}** No | One of `disabled`, `private`, or `enabled`. | | `only_allow_merge_if_all_discussions_are_resolved` | boolean | **{dotted-circle}** No | Set whether merge requests can only be merged when all the discussions are resolved. | | `only_allow_merge_if_pipeline_succeeds` | boolean | **{dotted-circle}** No | Set whether merge requests can only be merged with successful jobs. | -| `only_mirror_protected_branches` **(STARTER)** | boolean | **{dotted-circle}** No | Only mirror protected branches. | +| `only_mirror_protected_branches` **(PREMIUM)** | boolean | **{dotted-circle}** No | Only mirror protected branches. | | `packages_enabled` | boolean | **{dotted-circle}** No | Enable or disable packages repository feature. | | `pages_access_level` | string | **{dotted-circle}** No | One of `disabled`, `private`, `enabled`, or `public`. | | `requirements_access_level` | string | **{dotted-circle}** No | One of `disabled`, `private`, `enabled` or `public` | @@ -1877,7 +1877,7 @@ This endpoint: - Deletes a project including all associated resources (including issues and merge requests). - From [GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/issues/220382) on - [Premium](https://about.gitlab.com/pricing/) or higher tiers, group + [Premium or higher](https://about.gitlab.com/pricing/) tiers, group admins can [configure](../user/group/index.md#enabling-delayed-project-removal) projects within a group to be deleted after a delayed period. When enabled, actual deletion happens after the number of days specified in the @@ -2187,8 +2187,6 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/a ## Start the Housekeeping task for a project -> Introduced in GitLab 9.0. - ```plaintext POST /projects/:id/housekeeping ``` @@ -2197,9 +2195,9 @@ POST /projects/:id/housekeeping |-----------|----------------|------------------------|-------------| | `id` | integer/string | **{check-circle}** Yes | The ID of the project or NAMESPACE/PROJECT_NAME. | -## Push Rules **(STARTER)** +## Push Rules **(PREMIUM)** -### Get project push rules **(STARTER)** +### Get project push rules **(PREMIUM)** Get the [push rules](../push_rules/push_rules.md#enabling-push-rules) of a project. @@ -2231,7 +2229,7 @@ GET /projects/:id/push_rule } ``` -Users of GitLab [Premium or higher](https://about.gitlab.com/pricing/) +Users of [GitLab Premium or higher](https://about.gitlab.com/pricing/) can also see the `commit_committer_check` and `reject_unsigned_commits` parameters: @@ -2245,7 +2243,7 @@ parameters: } ``` -### Add project push rule **(STARTER)** +### Add project push rule **(PREMIUM)** Adds a push rule to a specified project. @@ -2268,7 +2266,7 @@ POST /projects/:id/push_rule | `prevent_secrets` | boolean | **{dotted-circle}** No | GitLab rejects any files that are likely to contain secrets. | | `reject_unsigned_commits` **(PREMIUM)** | boolean | **{dotted-circle}** No | Reject commit when it's not signed through GPG. | -### Edit project push rule **(STARTER)** +### Edit project push rule **(PREMIUM)** Edits a push rule for a specified project. @@ -2293,7 +2291,7 @@ PUT /projects/:id/push_rule ### Delete project push rule -> Introduced in [GitLab Starter](https://about.gitlab.com/pricing/) 9.0. +> - Moved to GitLab Premium in 13.9. Removes a push rule from a project. This is an idempotent method and can be called multiple times. Either the push rule is available or not. @@ -2440,9 +2438,10 @@ Read more in the [Project import/export](project_import_export.md) documentation Read more in the [Project members](members.md) documentation. -## Configure pull mirroring for a project **(STARTER)** +## Configure pull mirroring for a project **(PREMIUM)** -> Introduced in [GitLab Starter](https://about.gitlab.com/pricing/) 11.2. +> - Introduced in GitLab 11. +> - Moved to GitLab Premium in 13.9. Configure pull mirroring while [creating a new project](#create-project) or [updating an existing project](#edit-project) using the API if the remote repository is publicly accessible or via `username/password` authentication. In case your HTTP repository is not publicly accessible, you can add the authentication information to the URL: `https://username:password@gitlab.company.com/group/project.git`, where password is a [personal access token](../user/profile/personal_access_tokens.md) with the API scope enabled. @@ -2452,9 +2451,9 @@ The relevant API parameters to update are: - `mirror`: Enables pull mirroring on project when set to `true`. - `only_mirror_protected_branches`: Set to `true` for protected branches. -## Start the pull mirroring process for a Project **(STARTER)** +## Start the pull mirroring process for a Project **(PREMIUM)** -> Introduced in [GitLab Starter](https://about.gitlab.com/pricing/) 10.3. +> - Moved to GitLab Premium in 13.9. ```plaintext POST /projects/:id/mirror/pull diff --git a/doc/api/repositories.md b/doc/api/repositories.md index 6bbd4c56e40..25237c33383 100644 --- a/doc/api/repositories.md +++ b/doc/api/repositories.md @@ -5,7 +5,7 @@ info: "To determine the technical writer assigned to the Stage/Group associated type: reference, api --- -# Repositories API **(CORE)** +# Repositories API **(FREE)** ## List repository tree @@ -410,6 +410,7 @@ follows: - [{{ title }}]({{ commit.reference }})\ {% if author.contributor %} by {{ author.reference }}{% end %}\ {% if merge_request %} ([merge request]({{ merge_request.reference }})){% end %} + {% end %} {% end %} @@ -457,11 +458,40 @@ If a line ends in a backslash, the next newline is ignored. This allows you to wrap code across multiple lines, without introducing unnecessary newlines in the Markdown output. +Tags that use `{%` and `%}` (known as expression tags) consume the newline that +directly follows them, if any. This means that this: + +```plaintext +--- +{% if foo %} +bar +{% end %} +--- +``` + +Compiles into this: + +```plaintext +--- +bar +--- +``` + +Instead of this: + +```plaintext +--- + +bar + +--- +``` + You can specify a custom template in your configuration like so: ```yaml --- -template: > +template: | {% if categories %} {% each categories %} ### {{ title }} @@ -469,6 +499,7 @@ template: > {% each entries %} - [{{ title }}]({{ commit.reference }})\ {% if author.contributor %} by {{ author.reference }}{% end %} + {% end %} {% end %} @@ -477,6 +508,9 @@ template: > {% end %} ``` +Note that when specifying the template you should use `template: |` and not +`template: >`, as the latter doesn't preserve newlines in the template. + ### Template data At the top level, the following variable is available: diff --git a/doc/api/resource_iteration_events.md b/doc/api/resource_iteration_events.md index 4c351308ae1..91c7a1b07ea 100644 --- a/doc/api/resource_iteration_events.md +++ b/doc/api/resource_iteration_events.md @@ -4,10 +4,11 @@ group: Project Management info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Resource iteration events API **(STARTER)** +# Resource iteration events API **(PREMIUM)** -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/229463) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.4. -> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/229463) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.5. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/229463) in GitLab 13.4. +> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/229463) in GitLab 13.5. +> - Moved to GitLab Premium in 13.9. Resource iteration events keep track of what happens to GitLab [issues](../user/project/issues/). diff --git a/doc/api/users.md b/doc/api/users.md index 92f36079ae9..d0907008129 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -1,6 +1,6 @@ --- -stage: none -group: unassigned +stage: Manage +group: Access info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- @@ -171,7 +171,7 @@ GET /users ] ``` -Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) also see the `shared_runners_minutes_limit`, `extra_shared_runners_minutes_limit`, and `using_license_seat` parameters. +Users on GitLab [Premium or higher](https://about.gitlab.com/pricing/) also see the `shared_runners_minutes_limit`, `extra_shared_runners_minutes_limit`, and `using_license_seat` parameters. ```json [ @@ -340,7 +340,7 @@ Example Responses: NOTE: The `plan` and `trial` parameters are only available on GitLab Enterprise Edition. -Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) also see +Users on GitLab [Premium or higher](https://about.gitlab.com/pricing/) also see the `shared_runners_minutes_limit`, and `extra_shared_runners_minutes_limit` parameters. ```json diff --git a/doc/architecture/blueprints/gitlab_to_kubernetes_communication/index.md b/doc/architecture/blueprints/gitlab_to_kubernetes_communication/index.md index 28719926cdc..fb71707c146 100644 --- a/doc/architecture/blueprints/gitlab_to_kubernetes_communication/index.md +++ b/doc/architecture/blueprints/gitlab_to_kubernetes_communication/index.md @@ -6,7 +6,7 @@ comments: false description: 'GitLab to Kubernetes communication' --- -# GitLab to Kubernetes communication **(CORE)** +# GitLab to Kubernetes communication **(FREE)** The goal of this document is to define how GitLab can communicate with Kubernetes and in-cluster services through the GitLab Kubernetes Agent. diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md index 43fd0af6f39..5254bab593b 100644 --- a/doc/ci/runners/README.md +++ b/doc/ci/runners/README.md @@ -136,10 +136,16 @@ Shared runners are automatically disabled for a project: To disable shared runners for a group: 1. Go to the group's **Settings > CI/CD** and expand the **Runners** section. -1. In the **Shared runners** area, click **Enable shared runners for this group**. +1. In the **Shared runners** area, turn off the **Enable shared runners for this group** toggle. 1. Optionally, to allow shared runners to be enabled for individual projects or subgroups, click **Allow projects and subgroups to override the group setting**. +NOTE: +To re-enable the shared runners for a group, turn on the +**Enable shared runners for this group** toggle. +Then, an owner or maintainer must explicitly change this setting +for each project subgroup or project. + ### Group runners Use *Group runners* when you want all projects in a group diff --git a/doc/development/api_graphql_styleguide.md b/doc/development/api_graphql_styleguide.md index c0317a7e73e..32169db9048 100644 --- a/doc/development/api_graphql_styleguide.md +++ b/doc/development/api_graphql_styleguide.md @@ -67,6 +67,10 @@ Complexity is explained [on our client-facing API page](../api/graphql/index.md# Fields default to adding `1` to a query's complexity score, but developers can [specify a custom complexity](#field-complexity) when defining a field. +To estimate the complexity of a query, you can run the +[`gitlab:graphql:analyze`](rake_tasks.md#analyze-graphql-queries) +Rake task. + ### Request timeout Requests time out at 30 seconds. diff --git a/doc/development/auto_devops.md b/doc/development/auto_devops.md index 6b54de55738..eaf1d712f17 100644 --- a/doc/development/auto_devops.md +++ b/doc/development/auto_devops.md @@ -4,7 +4,7 @@ group: Configure info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Auto DevOps development guide **(CORE)** +# Auto DevOps development guide **(FREE)** This document provides a development guide for contributors to [Auto DevOps](../topics/autodevops/index.md). diff --git a/doc/development/kubernetes.md b/doc/development/kubernetes.md index 6976dda433e..5be2080eb64 100644 --- a/doc/development/kubernetes.md +++ b/doc/development/kubernetes.md @@ -4,7 +4,7 @@ group: Configure info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Kubernetes integration - development guidelines **(CORE)** +# Kubernetes integration - development guidelines **(FREE)** This document provides various guidelines when developing for the GitLab [Kubernetes integration](../user/project/clusters/index.md). diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md index 226d21b2ecd..cb1520826bb 100644 --- a/doc/development/rake_tasks.md +++ b/doc/development/rake_tasks.md @@ -261,6 +261,48 @@ bundle exec rake db:obsolete_ignored_columns Feel free to remove their definitions from their `ignored_columns` definitions. +## Validate GraphQL queries + +To check the validity of one or more of our front-end GraphQL queries, +run: + +```shell +# Validate all queries +bundle exec rake gitlab::graphql:validate +# Validate one query +bundle exec rake gitlab::graphql:validate[path/to/query.graphql] +# Validate a directory +bundle exec rake gitlab::graphql:validate[path/to/queries] +``` + +This prints out a report with an entry for each query, explaining why +each query is invalid if it fails to pass validation. + +We strip out `@client` fields during validation so it is important to mark +client fields with the `@client` directive to avoid false positives. + +## Analyze GraphQL queries + +Analogous to `ANALYZE` in SQL, we can run `gitlab:graphql:analyze` to +estimate the of the cost of running a query. + +Usage: + +```shell +# Analyze all queries +bundle exec rake gitlab::graphql:analyze +# Analyze one query +bundle exec rake gitlab::graphql:analyze[path/to/query.graphql] +# Analyze a directory +bundle exec rake gitlab::graphql:analyze[path/to/queries] +``` + +This prints out a report for each query, including the complexity +of the query if it is valid. + +The complexity depends on the arguments in some cases, so the reported +complexity is a best-effort assessment of the upper bound. + ## Update GraphQL documentation and schema definitions To generate GraphQL documentation based on the GitLab schema, run: diff --git a/doc/development/redis.md b/doc/development/redis.md index 0dca415b77c..9f90c5ee760 100644 --- a/doc/development/redis.md +++ b/doc/development/redis.md @@ -185,9 +185,8 @@ The Redis [`PFCOUNT`](https://redis.io/commands/pfcount), [`PFMERGE`](https://redis.io/commands/pfmergge) commands operate on HyperLogLogs, a data structure that allows estimating the number of unique elements with low memory usage. (In addition to the `PFCOUNT` documentation, -Thoughtbot's article on [HyperLogLogs in -Redis](https://thoughtbot.com/blog/hyperloglogs-in-redis) provides a good -background here.) +Thoughtbot's article on [HyperLogLogs in Redis](https://thoughtbot.com/blog/hyperloglogs-in-redis) +provides a good background here.) [`Gitlab::Redis::HLL`](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/redis/hll.rb) provides a convenient interface for adding and counting values in HyperLogLogs. diff --git a/doc/topics/autodevops/customize.md b/doc/topics/autodevops/customize.md index 23928d236e5..09186585b24 100644 --- a/doc/topics/autodevops/customize.md +++ b/doc/topics/autodevops/customize.md @@ -4,7 +4,7 @@ group: Configure info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Customizing Auto DevOps **(CORE)** +# Customizing Auto DevOps **(FREE)** While [Auto DevOps](index.md) provides great defaults to get you started, you can customize almost everything to fit your needs. Auto DevOps offers everything from custom diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md index 81a42f005fc..ca678fd417a 100644 --- a/doc/topics/autodevops/index.md +++ b/doc/topics/autodevops/index.md @@ -4,7 +4,7 @@ group: Configure info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Auto DevOps **(CORE)** +# Auto DevOps **(FREE)** > - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/37115) in GitLab 10.0. > - Generally available on GitLab 11.0. diff --git a/doc/topics/autodevops/quick_start_guide.md b/doc/topics/autodevops/quick_start_guide.md index ddec5bf9f4d..cd951e53bd1 100644 --- a/doc/topics/autodevops/quick_start_guide.md +++ b/doc/topics/autodevops/quick_start_guide.md @@ -4,7 +4,7 @@ group: Configure info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Getting started with Auto DevOps **(CORE)** +# Getting started with Auto DevOps **(FREE)** This step-by-step guide helps you use [Auto DevOps](index.md) to deploy a project hosted on GitLab.com to Google Kubernetes Engine. diff --git a/doc/topics/autodevops/requirements.md b/doc/topics/autodevops/requirements.md index e4e68c9fcb8..930938b7571 100644 --- a/doc/topics/autodevops/requirements.md +++ b/doc/topics/autodevops/requirements.md @@ -4,7 +4,7 @@ group: Configure info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Requirements for Auto DevOps **(CORE)** +# Requirements for Auto DevOps **(FREE)** You can set up Auto DevOps for [Kubernetes](#auto-devops-requirements-for-kubernetes), [Amazon Elastic Container Service (ECS)](#auto-devops-requirements-for-amazon-ecs), diff --git a/doc/topics/autodevops/stages.md b/doc/topics/autodevops/stages.md index 1295d8b3a52..e2be3d59f10 100644 --- a/doc/topics/autodevops/stages.md +++ b/doc/topics/autodevops/stages.md @@ -4,7 +4,7 @@ group: Configure info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Stages of Auto DevOps **(CORE)** +# Stages of Auto DevOps **(FREE)** The following sections describe the stages of [Auto DevOps](index.md). Read them carefully to understand how each one works. diff --git a/doc/topics/autodevops/upgrading_postgresql.md b/doc/topics/autodevops/upgrading_postgresql.md index a62bf540ce7..a0c4a41f90d 100644 --- a/doc/topics/autodevops/upgrading_postgresql.md +++ b/doc/topics/autodevops/upgrading_postgresql.md @@ -4,7 +4,7 @@ group: Configure info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Upgrading PostgreSQL for Auto DevOps **(CORE)** +# Upgrading PostgreSQL for Auto DevOps **(FREE)** Auto DevOps provides an [in-cluster PostgreSQL database](customize.md#postgresql-database-support) for your application. diff --git a/doc/user/clusters/applications.md b/doc/user/clusters/applications.md index 25c50af84e1..9127f5f697b 100644 --- a/doc/user/clusters/applications.md +++ b/doc/user/clusters/applications.md @@ -4,7 +4,7 @@ group: Configure info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# GitLab Managed Apps **(CORE)** +# GitLab Managed Apps **(FREE)** GitLab provides **GitLab Managed Apps** for various applications which can be added directly to your configured cluster. These diff --git a/doc/user/clusters/crossplane.md b/doc/user/clusters/crossplane.md index 2d185798317..bdf9d582b93 100644 --- a/doc/user/clusters/crossplane.md +++ b/doc/user/clusters/crossplane.md @@ -4,7 +4,7 @@ group: Configure info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Crossplane configuration **(CORE)** +# Crossplane configuration **(FREE)** After [installing](applications.md#crossplane) Crossplane, you must configure it for use. The process of configuring Crossplane includes: diff --git a/doc/user/clusters/management_project.md b/doc/user/clusters/management_project.md index b0aa402de78..ef1b3ce44f2 100644 --- a/doc/user/clusters/management_project.md +++ b/doc/user/clusters/management_project.md @@ -4,7 +4,7 @@ group: Configure info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Cluster management project **(CORE)** +# Cluster management project **(FREE)** WARNING: This is an _alpha_ feature, and it is subject to change at any time without diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md index d474392b52a..60fb9a7d6ea 100644 --- a/doc/user/gitlab_com/index.md +++ b/doc/user/gitlab_com/index.md @@ -130,7 +130,7 @@ GitLab.com is fronted by Cloudflare. For incoming connections to GitLab.com you For outgoing connections from CI/CD runners we are not providing static IP addresses. All our runners are deployed into Google Cloud Platform (GCP) - any IP based firewall can be configured by looking up all -[IP address ranges or CIDR blocks for GCP](https://cloud.google.com/compute/docs/faq#where_can_i_find_product_name_short_ip_ranges). +[IP address ranges or CIDR blocks for GCP](https://cloud.google.com/compute/docs/faq#find_ip_range). ## Hostname list diff --git a/doc/user/group/iterations/index.md b/doc/user/group/iterations/index.md index 65d3129a825..8e125a0cc6e 100644 --- a/doc/user/group/iterations/index.md +++ b/doc/user/group/iterations/index.md @@ -5,15 +5,16 @@ group: Project Management info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Iterations **(STARTER)** +# Iterations **(PREMIUM)** -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214713) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.1. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214713) in GitLab 13.1. > - It was deployed behind a feature flag, disabled by default. > - [Became enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/221047) on GitLab 13.2. > - It's enabled on GitLab.com. > - It's able to be enabled or disabled per-group. > - It's recommended for production use. -> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#disable-iterations). **(STARTER ONLY)** +> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#disable-iterations). **(PREMIUM ONLY)** +> - Moved to GitLab Premium in 13.9. Iterations are a way to track issues over a period of time. This allows teams to track velocity and volatility metrics. Iterations can be used with [milestones](../../project/milestones/index.md) @@ -50,7 +51,7 @@ To create an iteration: ## Edit an iteration -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/218277) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.2. +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/218277) in GitLab 13.2. NOTE: You need Developer [permissions](../../permissions.md) or higher to edit an iteration. @@ -59,14 +60,14 @@ To edit an iteration, click the three-dot menu (**{ellipsis_v}**) > **Edit itera ## Add an issue to an iteration -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216158) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.2. +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216158) in GitLab 13.2. To learn how to add an issue to an iteration, see the steps in [Managing issues](../../project/issues/managing_issues.md#add-an-issue-to-an-iteration). ## View an iteration report -> Viewing iteration reports in projects [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/222763) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.5. +> Viewing iteration reports in projects [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/222763) in GitLab 13.5. You can track the progress of an iteration by reviewing iteration reports. An iteration report displays a list of all the issues assigned to an iteration and their status. @@ -79,8 +80,8 @@ To view an iteration report, go to the iterations list page and click an iterati ### Iteration burndown and burnup charts -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/222750) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.5. -> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/269972) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.7. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/222750) in GitLab 13.5. +> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/269972) in GitLab 13.7. The iteration report includes [burndown and burnup charts](../../project/milestones/burndown_and_burnup_charts.md), similar to how they appear when viewing a [milestone](../../project/milestones/index.md). @@ -104,7 +105,7 @@ To group issues by label: You can also search for labels by typing in the search input. 1. Click or tap outside of the label dropdown. The page is now grouped by the selected labels. -## Disable iterations **(STARTER ONLY)** +## Disable iterations **(PREMIUM SELF)** GitLab Iterations feature is deployed with a feature flag that is **enabled by default**. [GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md) diff --git a/doc/user/infrastructure/index.md b/doc/user/infrastructure/index.md index bab61402909..6ba5e49d553 100644 --- a/doc/user/infrastructure/index.md +++ b/doc/user/infrastructure/index.md @@ -4,7 +4,7 @@ group: Configure info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Infrastructure as code with Terraform and GitLab **(CORE)** +# Infrastructure as code with Terraform and GitLab **(FREE)** ## Motivation diff --git a/doc/user/infrastructure/mr_integration.md b/doc/user/infrastructure/mr_integration.md index c9c29405ee9..927552b90be 100644 --- a/doc/user/infrastructure/mr_integration.md +++ b/doc/user/infrastructure/mr_integration.md @@ -4,7 +4,7 @@ group: Configure info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Terraform integration in Merge Requests **(CORE)** +# Terraform integration in Merge Requests **(FREE)** Collaborating around Infrastructure as Code (IaC) changes requires both code changes and expected infrastructure changes to be checked and approved. GitLab provides a solution to help collaboration around Terraform code changes and their expected effects using the Merge Request pages. This way users don't have to build custom tools or rely on 3rd party solutions to streamline their IaC workflows. diff --git a/doc/user/infrastructure/terraform_state.md b/doc/user/infrastructure/terraform_state.md index c1b09b2f03b..749ad35f56f 100644 --- a/doc/user/infrastructure/terraform_state.md +++ b/doc/user/infrastructure/terraform_state.md @@ -4,7 +4,7 @@ group: Configure info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# GitLab managed Terraform State **(CORE)** +# GitLab managed Terraform State **(FREE)** > [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2673) in GitLab 13.0. diff --git a/doc/user/project/clusters/add_eks_clusters.md b/doc/user/project/clusters/add_eks_clusters.md index 94aedb1cdab..eb6b8302667 100644 --- a/doc/user/project/clusters/add_eks_clusters.md +++ b/doc/user/project/clusters/add_eks_clusters.md @@ -4,7 +4,7 @@ group: Configure info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Adding EKS clusters **(CORE)** +# Adding EKS clusters **(FREE)** GitLab supports adding new and existing EKS clusters. diff --git a/doc/user/project/clusters/add_gke_clusters.md b/doc/user/project/clusters/add_gke_clusters.md index 421a59db92c..af3a17dc60c 100644 --- a/doc/user/project/clusters/add_gke_clusters.md +++ b/doc/user/project/clusters/add_gke_clusters.md @@ -4,7 +4,7 @@ group: Configure info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Adding GKE clusters **(CORE)** +# Adding GKE clusters **(FREE)** GitLab supports adding new and existing GKE clusters. diff --git a/doc/user/project/clusters/add_remove_clusters.md b/doc/user/project/clusters/add_remove_clusters.md index 7f4d25b7eca..0ff22b47e10 100644 --- a/doc/user/project/clusters/add_remove_clusters.md +++ b/doc/user/project/clusters/add_remove_clusters.md @@ -4,7 +4,7 @@ group: Configure info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Adding and removing Kubernetes clusters **(CORE)** +# Adding and removing Kubernetes clusters **(FREE)** GitLab offers integrated cluster creation for the following Kubernetes providers: diff --git a/doc/user/project/clusters/runbooks/index.md b/doc/user/project/clusters/runbooks/index.md index 18c8ba9626f..7f344349984 100644 --- a/doc/user/project/clusters/runbooks/index.md +++ b/doc/user/project/clusters/runbooks/index.md @@ -4,7 +4,7 @@ group: Configure info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Runbooks **(CORE)** +# Runbooks **(FREE)** Runbooks are a collection of documented procedures that explain how to carry out a particular process, be it starting, stopping, debugging, diff --git a/doc/user/project/clusters/serverless/aws.md b/doc/user/project/clusters/serverless/aws.md index 2d0ed3cd207..1da5a01f32c 100644 --- a/doc/user/project/clusters/serverless/aws.md +++ b/doc/user/project/clusters/serverless/aws.md @@ -4,7 +4,7 @@ group: Configure info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Deploying AWS Lambda function using GitLab CI/CD **(CORE)** +# Deploying AWS Lambda function using GitLab CI/CD **(FREE)** GitLab allows users to easily deploy AWS Lambda functions and create rich serverless applications. diff --git a/doc/user/project/clusters/serverless/index.md b/doc/user/project/clusters/serverless/index.md index 336813b5ba5..c7ed8d6d2a5 100644 --- a/doc/user/project/clusters/serverless/index.md +++ b/doc/user/project/clusters/serverless/index.md @@ -4,7 +4,7 @@ group: Configure info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Serverless **(CORE)** +# Serverless **(FREE)** > Introduced in GitLab 11.5. diff --git a/doc/user/project/description_templates.md b/doc/user/project/description_templates.md index a7ce48b6487..c56470ee07a 100644 --- a/doc/user/project/description_templates.md +++ b/doc/user/project/description_templates.md @@ -6,8 +6,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w # Description templates -> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/4981) in GitLab 8.11. - We all know that a properly submitted issue is more likely to be addressed in a timely manner by the developers of a project. @@ -87,10 +85,9 @@ For example: `https://gitlab.com/gitlab-org/gitlab/-/issues/new?issuable_templat ![Description templates](img/description_templates.png) -## Setting a default template for merge requests and issues **(STARTER)** +## Setting a default template for merge requests and issues **(PREMIUM)** -> - Templates for merge requests [introduced](https://gitlab.com/gitlab-org/gitlab/commit/7478ece8b48e80782b5465b96c79f85cc91d391b) in GitLab 6.9. -> - Templates for issues [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28) in GitLab 8.1. +> - Moved to GitLab Premium in 13.9. The visibility of issues or merge requests should be set to either "Everyone with access" or "Only Project Members" in your project's **Settings / Visibility, project features, permissions** section, otherwise the diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md index d2f73a88eae..e4f42b97b84 100644 --- a/doc/user/project/issue_board.md +++ b/doc/user/project/issue_board.md @@ -6,8 +6,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w # Issue Boards **(FREE)** -> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/5554) in [GitLab 8.11](https://about.gitlab.com/releases/2016/08/22/gitlab-8-11-released/#issue-board). - The GitLab Issue Board is a software project management tool used to plan, organize, and visualize a workflow for a feature or product release. It can be used as a [Kanban](https://en.wikipedia.org/wiki/Kanban_(development)) or a @@ -52,9 +50,8 @@ the Issue Board feature. ## Multiple issue boards -> - [Introduced](https://about.gitlab.com/releases/2016/10/22/gitlab-8-13-released/) in GitLab 8.13. -> - Multiple issue boards per project [moved](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/53811) to [GitLab Free](https://about.gitlab.com/pricing/) in GitLab 12.1. -> - Multiple issue boards per group are available in [GitLab Premium](https://about.gitlab.com/pricing/). +> - Multiple issue boards per project [moved](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/53811) to GitLab Free in 12.1. +> - Multiple issue boards per group are available in GitLab Premium. Multiple issue boards allow for more than one issue board for a given project **(FREE)** or group **(PREMIUM)**. This is great for large projects with more than one team or when a repository hosts the code of multiple products. @@ -230,10 +227,10 @@ and vice versa. GitLab issue boards are available on the GitLab Free tier, but some advanced functionality is present in [higher tiers only](https://about.gitlab.com/pricing/). -### Configurable issue boards **(STARTER)** +### Configurable issue boards **(PREMIUM)** -> - [Introduced](https://about.gitlab.com/releases/2017/11/22/gitlab-10-2-released/#issue-boards-configuration) in GitLab 10.2. > - Setting current iteration as scope [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/196804) in GitLab 13.8. +> - Moved to GitLab Premium in 13.9. An issue board can be associated with a [milestone](milestones/index.md#milestones), [labels](labels.md), assignee, weight, and current [iteration](../group/iterations/index.md), @@ -256,14 +253,15 @@ the Configurable Issue Board feature. ### Focus mode -> - [Introduced]((https://about.gitlab.com/releases/2017/04/22/gitlab-9-1-released/#issue-boards-focus-mode-ees-eep)) in [GitLab Starter](https://about.gitlab.com/pricing/) 9.1. -> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28597) to the Free tier of GitLab SaaS in 12.10. +> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28597) to GitLab Free SaaS in 12.10. > - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/212331) to GitLab Free self-managed in 13.0. To enable or disable focus mode, select the **Toggle focus mode** button (**{maximize}**) at the top right. In focus mode, the navigation UI is hidden, allowing you to focus on issues in the board. -### Sum of issue weights **(STARTER)** +### Sum of issue weights **(PREMIUM)** + +> Moved to GitLab Premium in 13.9. The top of each list indicates the sum of issue weights for the issues that belong to that list. This is useful when using boards for capacity allocation, @@ -273,9 +271,6 @@ especially in combination with [assignee lists](#assignee-lists). ### Group issue boards **(PREMIUM)** -> - One group issue board per group introduced in GitLab 10.6. -> - Multiple group issue boards [introduced](https://about.gitlab.com/releases/2017/09/22/gitlab-10-0-released/#group-issue-boards) in [GitLab Premium](https://about.gitlab.com/pricing/) 10.0. - Accessible at the group navigation level, a group issue board offers the same features as a project-level board. It can display issues from all projects in that group and its descendant subgroups. Similarly, you can only filter by group labels for these @@ -359,9 +354,10 @@ You can also [drag issues](#drag-issues-between-lists) to change their position ![Drag issues between swimlanes](img/epics_swimlanes_drag_and_drop.png) -## Work In Progress limits **(STARTER)** +## Work In Progress limits **(PREMIUM)** -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/11403) in GitLab 12.7 +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/11403) in GitLab 12.7 +> - Moved to GitLab Premium in 13.9. You can set a Work In Progress (WIP) limit for each issue list on an issue board. When a limit is set, the list's header shows the number of issues in the list and the soft limit of issues. @@ -546,8 +542,8 @@ to another list, the label changes and a system note is recorded. When dragging issues between lists, different behavior occurs depending on the source list and the target list. -| | To Open | To Closed | To label `B` list | To assignee `Bob` list | -|----------------------------|--------------------|--------------|------------------------------|---------------------------------------| +| | To Open | To Closed | To label `B` list | To assignee `Bob` list | +| ------------------------------ | ------------------ | ------------ | ---------------------------- | ------------------------------------- | | **From Open** | - | Issue closed | `B` added | `Bob` assigned | | **From Closed** | Issue reopened | - | Issue reopened<br/>`B` added | Issue reopened<br/>`Bob` assigned | | **From label `A` list** | `A` removed | Issue closed | `A` removed<br/>`B` added | `Bob` assigned | diff --git a/doc/user/project/issues/issue_data_and_actions.md b/doc/user/project/issues/issue_data_and_actions.md index 5218bb57bca..bc732a4afa5 100644 --- a/doc/user/project/issues/issue_data_and_actions.md +++ b/doc/user/project/issues/issue_data_and_actions.md @@ -23,13 +23,13 @@ The numbers in the image correspond to the following features: - **1.** [Issue actions](#issue-actions) - **2.** [To Do](#to-do) - **3.** [Assignee](#assignee) - - **3.1.** [Multiple Assignees **(STARTER)**](#multiple-assignees) -- **4.** [Epic **(PREMIUM)**](#epic) + - **3.1.** [Multiple Assignees](#multiple-assignees) +- **4.** [Epic](#epic) - **5.** [Milestone](#milestone) - **6.** [Time tracking](#time-tracking) - **7.** [Due date](#due-date) - **8.** [Labels](#labels) -- **9.** [Weight **(STARTER)**](#weight) +- **9.** [Weight](#weight) - **10.** [Confidentiality](#confidentiality) - **11.** [Lock issue](#lock-issue) - **12.** [Participants](#participants) @@ -86,7 +86,7 @@ An issue can be assigned to: - Yourself. - Another person. -- [Many people](#multiple-assignees). **(STARTER)** +- [Many people](#multiple-assignees). **(PREMIUM)** The assignees can be changed as often as needed. The idea is that the assignees are responsible for that issue until it's reassigned to someone else to take it from there. @@ -96,7 +96,7 @@ NOTE: If a user is not member of that project, it can only be assigned to them if they created the issue themselves. -#### Multiple Assignees **(STARTER)** +#### Multiple Assignees **(PREMIUM)** Often, multiple people work on the same issue together. This can be difficult to track in large teams where there is shared ownership of an issue. @@ -138,7 +138,7 @@ available to all projects in the group. If a label doesn't exist yet, you can create one by clicking **Edit** followed by **Create new label** in the dropdown menu. -### Weight **(STARTER)** +### Weight **(PREMIUM)** [Assign a weight](issue_weight.md) to an issue. Larger values are used to indicate more effort is required to complete the issue. Only @@ -194,7 +194,8 @@ The plain text title and description of the issue fill the top center of the iss The description fully supports [GitLab Flavored Markdown](../../markdown.md#gitlab-flavored-markdown-gfm), allowing many formatting options. -> [In GitLab 12.6](https://gitlab.com/gitlab-org/gitlab/-/issues/10103) and later, changes to an issue's description are listed in the [issue history](#issue-history). **(STARTER)** +[In GitLab 12.6](https://gitlab.com/gitlab-org/gitlab/-/issues/10103) and later, changes to an +issue's description are listed in the [issue history](#issue-history). **(PREMIUM)** ### Mentions diff --git a/doc/user/project/issues/issue_weight.md b/doc/user/project/issues/issue_weight.md index 4e2c8bfd7f1..b10debf9888 100644 --- a/doc/user/project/issues/issue_weight.md +++ b/doc/user/project/issues/issue_weight.md @@ -5,15 +5,15 @@ group: Project Management info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Issue weight **(STARTER)** +# Issue weight **(PREMIUM)** -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/76) in [GitLab Starter](https://about.gitlab.com/pricing/) 8.3. +> - Moved to GitLab Premium in 13.9. When you have a lot of issues, it can be hard to get an overview. By adding a weight to each issue, you can get a better idea of how much time, value or complexity a given issue has or costs. -You can set the weight of an issue during its creation, by simply changing the +You can set the weight of an issue during its creation, by changing the value in the dropdown menu. You can set it to a non-negative integer value from 0, 1, 2, and so on. (The database stores a 4-byte value, so the upper bound is essentially limitless). diff --git a/doc/user/project/issues/managing_issues.md b/doc/user/project/issues/managing_issues.md index 7ca0556dff4..f1739726cf8 100644 --- a/doc/user/project/issues/managing_issues.md +++ b/doc/user/project/issues/managing_issues.md @@ -287,9 +287,9 @@ editing it and clicking on the delete button. ## Promote an issue to an epic **(PREMIUM)** -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/3777) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.6. -> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/37081) to [GitLab Premium](https://about.gitlab.com/pricing/) in 12.8. -> - Promoting issues to epics via the UI [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/233974) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.6. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/3777) in GitLab Ultimate 11.6. +> - Moved to GitLab Premium in 12.8. +> - Promoting issues to epics via the UI [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/233974) in GitLab Premium 13.6. You can promote an issue to an epic in the immediate parent group. @@ -302,9 +302,10 @@ Alternatively, you can use the `/promote` [quick action](../quick_actions.md#qui Read more about promoting an issue to an epic on the [Manage epics page](../../group/epics/manage_epics.md#promote-an-issue-to-an-epic). -## Add an issue to an iteration **(STARTER)** +## Add an issue to an iteration **(PREMIUM)** -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216158) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.2. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216158) in GitLab 13.2. +> - Moved to GitLab Premium in 13.9. To add an issue to an [iteration](../../group/iterations/index.md): diff --git a/doc/user/project/issues/multiple_assignees_for_issues.md b/doc/user/project/issues/multiple_assignees_for_issues.md index bb9038062f7..189777d40e7 100644 --- a/doc/user/project/issues/multiple_assignees_for_issues.md +++ b/doc/user/project/issues/multiple_assignees_for_issues.md @@ -4,9 +4,9 @@ group: Project Management info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Multiple Assignees for Issues **(STARTER)** +# Multiple Assignees for Issues **(PREMIUM)** -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/1904) in [GitLab Starter 9.2](https://about.gitlab.com/releases/2017/05/22/gitlab-9-2-released/#multiple-assignees-for-issues). +> - Moved to GitLab Premium in 13.9. In large teams, where there is shared ownership of an issue, it can be difficult to track who is working on it, who already completed their contributions, who @@ -40,4 +40,4 @@ to assign the issue to. ![adding multiple assignees](img/multiple_assignees.gif) -An assignee can be easily removed by deselecting them from the same dropdown menu. +To remove an assignee, deselect them from the same dropdown menu. diff --git a/doc/user/project/milestones/burndown_and_burnup_charts.md b/doc/user/project/milestones/burndown_and_burnup_charts.md index 3e266d054be..7c22b271ec2 100644 --- a/doc/user/project/milestones/burndown_and_burnup_charts.md +++ b/doc/user/project/milestones/burndown_and_burnup_charts.md @@ -5,18 +5,17 @@ group: Project Management info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Burndown and burnup charts **(STARTER)** +# Burndown and burnup charts **(PREMIUM)** [Burndown](#burndown-charts) and [burnup](#burnup-charts) charts show the progress of completing a milestone. ![burndown and burnup chart](img/burndown_and_burnup_charts_v13_6.png) -## Burndown charts +## Burndown charts **(PREMIUM)** -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/1540) in [GitLab Starter](https://about.gitlab.com/pricing/) 9.1 for project milestones. -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/5354) in [GitLab Premium](https://about.gitlab.com/pricing/) 10.8 for group milestones. -> - [Added](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/6495) to [GitLab Starter](https://about.gitlab.com/pricing/) 11.2 for group milestones. -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/6903) [fixed burndown charts](#fixed-burndown-charts) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.6. +> - [Added](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/6495) to GitLab 11.2 for group milestones. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/6903) [fixed burndown charts](#fixed-burndown-charts) in GitLab 13.6. +> - Moved to GitLab Premium in 13.9. Burndown charts show the number of issues over the course of a milestone. @@ -101,10 +100,11 @@ Therefore, when the milestone start date is changed, the number of opened issues change. Reopened issues are considered as having been opened on the day after they were last closed. -## Burnup charts +## Burnup charts **(PREMIUM)** -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/6903) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.6. -> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/268350) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.7. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/6903) in GitLab 13.6. +> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/268350) in GitLab 13.7. +> - Moved to GitLab Premium in 13.9. Burnup charts show the assigned and completed work for a milestone. diff --git a/doc/user/project/milestones/index.md b/doc/user/project/milestones/index.md index d7ca2eae831..fe34dca4959 100644 --- a/doc/user/project/milestones/index.md +++ b/doc/user/project/milestones/index.md @@ -123,10 +123,16 @@ From the project and group issue/merge request list pages, you can [filter](../. ### Filtering in issue boards -- From [project issue boards](../issue_board.md), you can filter by both group milestones and project milestones in the [search and filter bar](../../search/index.md#issue-boards). -- From [group issue boards](../issue_board.md#group-issue-boards), you can filter by only group milestones in the [search and filter bar](../../search/index.md#issue-boards). **(PREMIUM)** -- From [project issue boards](../issue_board.md), you can filter by both group milestones and project milestones in the [issue board configuration](../issue_board.md#configurable-issue-boards). **(STARTER)** -- From [group issue boards](../issue_board.md#group-issue-boards) you can filter by only group milestones in the [issue board configuration](../issue_board.md#configurable-issue-boards). **(STARTER)** +From [project issue boards](../issue_board.md), you can filter by both group milestones and project +milestones in: + +- [Search and filter bar](../../search/index.md#issue-boards) +- [Issue board configuration](../issue_board.md#configurable-issue-boards) + +From [group issue boards](../issue_board.md#group-issue-boards), you can filter by only group milestones in: + +- [Search and filter bar](../../search/index.md#issue-boards) +- [Issue board configuration](../issue_board.md#configurable-issue-boards) ### Special milestone filters @@ -155,15 +161,17 @@ There are also tabs below these that show the following: - **Participants**: Shows all assignees of issues assigned to the milestone. - **Labels**: Shows all labels that are used in issues assigned to the milestone. -### Project Burndown Charts **(STARTER)** +### Project Burndown Charts -For project milestones in [GitLab Starter](https://about.gitlab.com/pricing/), a [burndown chart](burndown_and_burnup_charts.md) is in the milestone view, showing the progress of completing a milestone. +For project milestones, a [burndown chart](burndown_and_burnup_charts.md) is in the milestone view, +showing the progress of completing a milestone. ![burndown chart](img/burndown_chart_v13_6.png) -### Group Burndown Charts **(STARTER)** +### Group Burndown Charts -For group milestones in [GitLab Starter](https://about.gitlab.com/pricing/), a [burndown chart](burndown_and_burnup_charts.md) is in the milestone view, showing the progress of completing a milestone. +For group milestones, a [burndown chart](burndown_and_burnup_charts.md) is in the milestone view, +showing the progress of completing a milestone. ### Milestone sidebar diff --git a/doc/user/project/service_desk.md b/doc/user/project/service_desk.md index 7c66f078770..e3ff675a87e 100644 --- a/doc/user/project/service_desk.md +++ b/doc/user/project/service_desk.md @@ -6,9 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w # Service Desk **(FREE)** -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/149) in [GitLab Premium](https://about.gitlab.com/pricing/) 9.1. -> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/214839) to [GitLab Starter](https://about.gitlab.com/pricing/) in 13.0. -> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/215364) to [GitLab Free](https://about.gitlab.com/pricing/) in 13.2. +> - Moved to GitLab Free in 13.2. Service Desk is a module that allows your team to connect with any external party through email, without any external tools. @@ -89,9 +87,8 @@ Service Desk is now enabled for this project! You should be able to access it fr ### Using customized email templates -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/2460) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.7. -> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/214839) to [GitLab Starter](https://about.gitlab.com/pricing/) in 13.0. -> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/215364) to [GitLab Free](https://about.gitlab.com/pricing/) in 13.2. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/2460) in GitLab Premium 12.7. +> - Moved to GitLab Free in 13.2. An email is sent to the author when: @@ -159,7 +156,7 @@ To edit the custom email display name: ### Using custom email address -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/2201) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.0. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/2201) in GitLab Premium 13.0. > - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/284656) in GitLab 13.8. If the `service_desk_email` is configured, then you can create Service Desk diff --git a/lib/api/project_templates.rb b/lib/api/project_templates.rb index 19244ed697f..f8b5ac65323 100644 --- a/lib/api/project_templates.rb +++ b/lib/api/project_templates.rb @@ -26,9 +26,7 @@ module API use :pagination end get ':id/templates/:type' do - templates = TemplateFinder - .build(params[:type], user_project) - .execute + templates = TemplateFinder.all_template_names_array(user_project, params[:type]) present paginate(::Kaminari.paginate_array(templates)), with: Entities::TemplatesList end diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index 353f2ed1c25..d0e81a947d9 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -228,7 +228,7 @@ module API service.execute status(200) - rescue => ex + rescue Gitlab::Changelog::Error => ex render_api_error!("Failed to generate the changelog: #{ex.message}", 500) end end diff --git a/lib/gitlab/changelog/ast.rb b/lib/gitlab/changelog/ast.rb new file mode 100644 index 00000000000..2c787d396f5 --- /dev/null +++ b/lib/gitlab/changelog/ast.rb @@ -0,0 +1,157 @@ +# frozen_string_literal: true + +module Gitlab + module Changelog + # AST nodes to evaluate when rendering a template. + # + # Evaluating an AST is done by walking over the nodes and calling + # `evaluate`. This method takes two arguments: + # + # 1. An instance of `EvalState`, used for tracking data such as the number + # of nested loops. + # 2. An object used as the data for the current scope. This can be an Array, + # Hash, String, or something else. It's up to the AST node to determine + # what to do with it. + # + # While tree walking interpreters (such as implemented here) aren't usually + # the fastest type of interpreter, they are: + # + # 1. Fast enough for our use case + # 2. Easy to implement and maintain + # + # In addition, our AST interpreter doesn't allow for arbitrary code + # execution, unlike existing template engines such as Mustache + # (https://github.com/mustache/mustache/issues/244) or ERB. + # + # Our interpreter also takes care of limiting the number of nested loops. + # And unlike Liquid, our interpreter is much smaller and thus has a smaller + # attack surface. Liquid isn't without its share of issues, such as + # https://github.com/Shopify/liquid/pull/1071. + # + # We also evaluated using Handlebars using the project + # https://github.com/SmartBear/ruby-handlebars. Sadly, this implementation + # of Handlebars doesn't support control of whitespace + # (https://github.com/SmartBear/ruby-handlebars/issues/37), and the project + # didn't appear to be maintained that much. + # + # This doesn't mean these template engines aren't good, instead it means + # they won't work for our use case. For more information, refer to the + # comment https://gitlab.com/gitlab-org/gitlab/-/merge_requests/50063#note_469293322. + module AST + # An identifier in a selector. + Identifier = Struct.new(:name) do + def evaluate(state, data) + return data if name == 'it' + + data[name] if data.is_a?(Hash) + end + end + + # An integer used in a selector. + Integer = Struct.new(:value) do + def evaluate(state, data) + data[value] if data.is_a?(Array) + end + end + + # A selector used for loading a value. + Selector = Struct.new(:steps) do + def evaluate(state, data) + steps.reduce(data) do |current, step| + break if current.nil? + + step.evaluate(state, current) + end + end + end + + # A tag used for displaying a value in the output. + Variable = Struct.new(:selector) do + def evaluate(state, data) + selector.evaluate(state, data).to_s + end + end + + # A collection of zero or more expressions. + Expressions = Struct.new(:nodes) do + def evaluate(state, data) + nodes.map { |node| node.evaluate(state, data) }.join('') + end + end + + # A single text node. + Text = Struct.new(:text) do + def evaluate(*) + text + end + end + + # An `if` expression, with an optional `else` clause. + If = Struct.new(:condition, :true_body, :false_body) do + def evaluate(state, data) + result = + if truthy?(condition.evaluate(state, data)) + true_body.evaluate(state, data) + elsif false_body + false_body.evaluate(state, data) + end + + result.to_s + end + + def truthy?(value) + # We treat empty collections and such as false, removing the need for + # some sort of `if length(x) > 0` expression. + value.respond_to?(:empty?) ? !value.empty? : !!value + end + end + + # An `each` expression. + Each = Struct.new(:collection, :body) do + def evaluate(state, data) + values = collection.evaluate(state, data) + + return '' unless values.respond_to?(:each) + + # While unlikely to happen, it's possible users attempt to nest many + # loops in order to negatively impact the GitLab instance. To make + # this more difficult, we limit the number of nested loops a user can + # create. + state.enter_loop do + values.map { |value| body.evaluate(state, value) }.join('') + end + end + end + + # A class for transforming a raw Parslet AST into a more structured/easier + # to work with AST. + # + # For more information about Parslet transformations, refer to the + # documentation at http://kschiess.github.io/parslet/transform.html. + class Transformer < Parslet::Transform + rule(ident: simple(:name)) { Identifier.new(name.to_s) } + rule(int: simple(:name)) { Integer.new(name.to_i) } + rule(text: simple(:text)) { Text.new(text.to_s) } + rule(exprs: subtree(:nodes)) { Expressions.new(nodes) } + rule(selector: sequence(:steps)) { Selector.new(steps) } + rule(selector: simple(:step)) { Selector.new([step]) } + rule(variable: simple(:selector)) { Variable.new(selector) } + rule(each: simple(:values), body: simple(:body)) do + Each.new(values, body) + end + + rule(if: simple(:cond), true_body: simple(:true_body)) do + If.new(cond, true_body) + end + + rule( + if: simple(:cond), + true_body: simple(:true_body), + false_body: simple(:false_body) + ) do + If.new(cond, true_body, false_body) + end + end + end + end +end diff --git a/lib/gitlab/changelog/committer.rb b/lib/gitlab/changelog/committer.rb index 617017faa58..31661650eff 100644 --- a/lib/gitlab/changelog/committer.rb +++ b/lib/gitlab/changelog/committer.rb @@ -4,8 +4,6 @@ module Gitlab module Changelog # A class used for committing a release's changelog to a Git repository. class Committer - CommitError = Class.new(StandardError) - def initialize(project, user) @project = project @user = user @@ -25,7 +23,7 @@ module Gitlab # When retrying, we need to reprocess the existing changelog from # scratch, otherwise we may end up throwing away changes. As such, all # the logic is contained within the retry block. - Retriable.retriable(on: CommitError) do + Retriable.retriable(on: Error) do commit = Gitlab::Git::Commit.last_for_path( @project.repository, branch, @@ -57,7 +55,7 @@ module Gitlab result = service.execute - raise CommitError.new(result[:message]) if result[:status] != :success + raise Error.new(result[:message]) if result[:status] != :success end end diff --git a/lib/gitlab/changelog/config.rb b/lib/gitlab/changelog/config.rb index 3f06b612687..105050936ce 100644 --- a/lib/gitlab/changelog/config.rb +++ b/lib/gitlab/changelog/config.rb @@ -4,8 +4,6 @@ module Gitlab module Changelog # Configuration settings used when generating changelogs. class Config - ConfigError = Class.new(StandardError) - # When rendering changelog entries, authors are not included. AUTHORS_NONE = 'none' @@ -37,17 +35,14 @@ module Gitlab end if (template = hash['template']) - # We use the full namespace here (and further down) as otherwise Rails - # may use the wrong constant when autoloading is used. - config.template = - ::Gitlab::Changelog::Template::Compiler.new.compile(template) + config.template = Parser.new.parse_and_transform(template) end if (categories = hash['categories']) if categories.is_a?(Hash) config.categories = categories else - raise ConfigError, 'The "categories" configuration key must be a Hash' + raise Error, 'The "categories" configuration key must be a Hash' end end @@ -57,8 +52,7 @@ module Gitlab def initialize(project) @project = project @date_format = DEFAULT_DATE_FORMAT - @template = - ::Gitlab::Changelog::Template::Compiler.new.compile(DEFAULT_TEMPLATE) + @template = Parser.new.parse_and_transform(DEFAULT_TEMPLATE) @categories = {} end diff --git a/lib/gitlab/changelog/error.rb b/lib/gitlab/changelog/error.rb new file mode 100644 index 00000000000..0bd886fbdb7 --- /dev/null +++ b/lib/gitlab/changelog/error.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module Gitlab + module Changelog + # An error raised when a changelog couldn't be generated. + Error = Class.new(StandardError) + end +end diff --git a/lib/gitlab/changelog/eval_state.rb b/lib/gitlab/changelog/eval_state.rb new file mode 100644 index 00000000000..a0439df60cf --- /dev/null +++ b/lib/gitlab/changelog/eval_state.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Gitlab + module Changelog + # A class for tracking state when evaluating a template + class EvalState + MAX_LOOPS = 4 + + def initialize + @loops = 0 + end + + def enter_loop + if @loops == MAX_LOOPS + raise Error, "You can only nest up to #{MAX_LOOPS} loops" + end + + @loops += 1 + retval = yield + @loops -= 1 + + retval + end + end + end +end diff --git a/lib/gitlab/changelog/parser.rb b/lib/gitlab/changelog/parser.rb new file mode 100644 index 00000000000..a4c8da283cd --- /dev/null +++ b/lib/gitlab/changelog/parser.rb @@ -0,0 +1,176 @@ +# frozen_string_literal: true + +module Gitlab + module Changelog + # A parser for the template syntax used for generating changelogs. + # + # As a quick primer on the template syntax, a basic template looks like + # this: + # + # {% each users %} + # Name: {{name}} + # Age: {{age}} + # + # {% if birthday %} + # This user is celebrating their birthday today! Yay! + # {% end %} + # {% end %} + # + # For more information, refer to the Parslet documentation found at + # http://kschiess.github.io/parslet/. + class Parser < Parslet::Parser + root(:exprs) + + rule(:exprs) do + ( + variable | if_expr | each_expr | escaped | text | newline + ).repeat.as(:exprs) + end + + rule(:space) { match('[ \\t]') } + rule(:whitespace) { match('\s').repeat } + rule(:lf) { str("\n") } + rule(:newline) { lf.as(:text) } + + # Escaped newlines are ignored, allowing the user to control the + # whitespace in the output. All other escape sequences are treated as + # literal text. + # + # For example, this: + # + # foo \ + # bar + # + # Is parsed into this: + # + # foo bar + rule(:escaped) do + backslash = str('\\') + + (backslash >> lf).ignore | (backslash >> chars).as(:text) + end + + # A sequence of regular characters, with the exception of newlines and + # escaped newlines. + rule(:chars) do + char = match("[^{\\\\\n]") + + # The rules here are such that we do treat single curly braces or + # non-opening tags (e.g. `{foo}`) as text, but not opening tags + # themselves (e.g. `{{`). + ( + char.repeat(1) | curly_open >> (curly_open | percent).absent? + ).repeat(1) + end + + rule(:text) { chars.as(:text) } + + # An integer, limited to 10 digits (= a 32 bits integer). + # + # The size is limited to prevents users from creating integers that are + # too large, as this may result in runtime errors. + rule(:integer) { match('\d').repeat(1, 10).as(:int) } + + # An identifier to look up in a data structure. + # + # We only support simple ASCII identifiers as we simply don't have a need + # for more complex identifiers (e.g. those containing multibyte + # characters). + rule(:ident) { match('[a-zA-Z_]').repeat(1).as(:ident) } + + # A selector is used for reading a value, consisting of one or more + # "steps". + # + # Examples: + # + # name + # users.0.name + # 0 + # it + rule(:selector) do + step = ident | integer + + whitespace >> + (step >> (str('.') >> step).repeat).as(:selector) >> + whitespace + end + + rule(:curly_open) { str('{') } + rule(:curly_close) { str('}') } + rule(:percent) { str('%') } + + # A variable tag. + # + # Examples: + # + # {{name}} + # {{users.0.name}} + rule(:variable) do + curly_open.repeat(2) >> selector.as(:variable) >> curly_close.repeat(2) + end + + rule(:expr_open) { curly_open >> percent >> whitespace } + rule(:expr_close) do + # Since whitespace control is important (as Markdown is whitespace + # sensitive), we default to stripping a newline that follows a %} tag. + # This is less annoying compared to having to opt-in to this behaviour. + whitespace >> percent >> curly_close >> lf.maybe.ignore + end + + rule(:end_tag) { expr_open >> str('end') >> expr_close } + + # An `if` expression, with an optional `else` clause. + # + # Examples: + # + # {% if foo %} + # yes + # {% end %} + # + # {% if foo %} + # yes + # {% else %} + # no + # {% end %} + rule(:if_expr) do + else_tag = + expr_open >> str('else') >> expr_close >> exprs.as(:false_body) + + expr_open >> + str('if') >> + space.repeat(1) >> + selector.as(:if) >> + expr_close >> + exprs.as(:true_body) >> + else_tag.maybe >> + end_tag + end + + # An `each` expression, used for iterating over collections. + # + # Example: + # + # {% each users %} + # * {{name}} + # {% end %} + rule(:each_expr) do + expr_open >> + str('each') >> + space.repeat(1) >> + selector.as(:each) >> + expr_close >> + exprs.as(:body) >> + end_tag + end + + def parse_and_transform(input) + AST::Transformer.new.apply(parse(input)) + rescue Parslet::ParseFailed => ex + # We raise a custom error so it's easier to catch different changelog + # related errors. In addition, this ensures the caller of this method + # doesn't depend on a Parslet specific error class. + raise Error.new("Failed to parse the template: #{ex.message}") + end + end + end +end diff --git a/lib/gitlab/changelog/release.rb b/lib/gitlab/changelog/release.rb index 4c78eb5080c..f2a01c2b0dc 100644 --- a/lib/gitlab/changelog/release.rb +++ b/lib/gitlab/changelog/release.rb @@ -54,14 +54,16 @@ module Gitlab end def to_markdown + state = EvalState.new + data = { 'categories' => entries_for_template } + # While not critical, we would like release sections to be separated by # an empty line in the changelog; ensuring it's readable even in its # raw form. # - # Since it can be a bit tricky to get this right using Liquid, we + # Since it can be a bit tricky to get this right in a template, we # enforce an empty line separator ourselves. - markdown = - @config.template.render('categories' => entries_for_template).strip + markdown = @config.template.evaluate(state, data).strip # The release header can't be changed using the Liquid template, as we # need this to be in a known format. Without this restriction, we won't @@ -80,14 +82,20 @@ module Gitlab end def entries_for_template - @entries.map do |category, entries| - { + rows = [] + + @entries.each do |category, entries| + next if entries.empty? + + rows << { 'title' => category, 'count' => entries.length, 'single_change' => entries.length == 1, 'entries' => entries } end + + rows end end end diff --git a/lib/gitlab/changelog/template.tpl b/lib/gitlab/changelog/template.tpl index 838b7080f68..584939dff51 100644 --- a/lib/gitlab/changelog/template.tpl +++ b/lib/gitlab/changelog/template.tpl @@ -6,6 +6,7 @@ - [{{ title }}]({{ commit.reference }})\ {% if author.contributor %} by {{ author.reference }}{% end %}\ {% if merge_request %} ([merge request]({{ merge_request.reference }})){% end %} + {% end %} {% end %} diff --git a/lib/gitlab/changelog/template/compiler.rb b/lib/gitlab/changelog/template/compiler.rb deleted file mode 100644 index fa7724aa2da..00000000000 --- a/lib/gitlab/changelog/template/compiler.rb +++ /dev/null @@ -1,154 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Changelog - module Template - # Compiler is used for turning a minimal user templating language into an - # ERB template, without giving the user access to run arbitrary code. - # - # The template syntax is deliberately made as minimal as possible, and - # only supports the following: - # - # * Printing a value - # * Iterating over collections - # * if/else - # - # The syntax looks as follows: - # - # {% each users %} - # - # Name: {{user}} - # Likes cats: {% if likes_cats %}yes{% else %}no{% end %} - # - # {% end %} - # - # Newlines can be escaped by ending a line with a backslash. So this: - # - # foo \ - # bar - # - # Is the same as this: - # - # foo bar - # - # Templates are compiled into ERB templates, while taking care to make - # sure the user can't run arbitrary code. By using ERB we can let it do - # the heavy lifting of rendering data; all we need to provide is a - # translation layer. - # - # # Security - # - # The template syntax this compiler exposes is safe to be used by - # untrusted users. Not only are they unable to run arbitrary code, the - # compiler also enforces a limit on the integer sizes and the number of - # nested loops. ERB tags added by the user are also disabled. - class Compiler - # A pattern to match a single integer, with an upper size limit. - # - # We enforce a limit of 10 digits (= a 32 bits integer) so users can't - # trigger the allocation of infinitely large bignums, or trigger - # RangeError errors when using such integers to access an array value. - INTEGER = /^\d{1,10}$/.freeze - - # The name/path of a variable, such as `user.address.city`. - # - # It's important that this regular expression _doesn't_ allow for - # anything but letters, numbers, and underscores, otherwise a user may - # use those to "escape" our template and run arbirtary Ruby code. For - # example, take this variable: - # - # {{') ::Kernel.exit #'}} - # - # This would then be compiled into: - # - # <%= read(variables, '') ::Kernel.exit #'') %> - # - # Restricting the allowed characters makes this impossible. - VAR_NAME = /([\w\.]+)/.freeze - - # A variable tag, such as `{{username}}`. - VAR = /{{ \s* #{VAR_NAME} \s* }}/x.freeze - - # The opening tag for a statement. - STM_START = /{% \s*/x.freeze - - # The closing tag for a statement. - STM_END = /\s* %}/x.freeze - - # A regular `end` closing tag. - NORMAL_END = /#{STM_START} end #{STM_END}/x.freeze - - # An `end` closing tag on its own line, without any non-whitespace - # preceding or following it. - # - # These tags need some special care to make it easier to control - # whitespace. - LONELY_END = /^\s*#{NORMAL_END}\s$/x.freeze - - # An `else` tag. - ELSE = /#{STM_START} else #{STM_END}/x.freeze - - # The start of an `each` tag. - EACH = /#{STM_START} each \s+ #{VAR_NAME} #{STM_END}/x.freeze - - # The start of an `if` tag. - IF = /#{STM_START} if \s+ #{VAR_NAME} #{STM_END}/x.freeze - - # The pattern to use for escaping newlines. - ESCAPED_NEWLINE = /\\\n$/.freeze - - # The start tag for ERB tags. These tags will be escaped, preventing - # users from using ERB directly. - ERB_START_TAG = /<\\?\s*\\?\s*%/.freeze - - def compile(template) - transformed_lines = ['<% it = variables %>'] - - # ERB tags must be stripped here, otherwise a user may introduce ERB - # tags by making clever use of whitespace. See - # https://gitlab.com/gitlab-org/gitlab/-/issues/300224 for more - # information. - template = template.gsub(ERB_START_TAG, '<%%') - - template.each_line { |line| transformed_lines << transform(line) } - - # We use the full namespace here as otherwise Rails may use the wrong - # constant when autoloading is used. - ::Gitlab::Changelog::Template::Template.new(transformed_lines.join) - end - - def transform(line) - line.gsub!(ESCAPED_NEWLINE, '') - - # This replacement ensures that "end" blocks on their own lines - # don't add extra newlines. Using an ERB -%> tag sadly swallows too - # many newlines. - line.gsub!(LONELY_END, '<% end %>') - line.gsub!(NORMAL_END, '<% end %>') - line.gsub!(ELSE, '<% else -%>') - - line.gsub!(EACH) do - # No, `it; variables` isn't a syntax error. Using `;` marks - # `variables` as block-local, making it possible to re-assign it - # without affecting outer definitions of this variable. We use - # this to scope template variables to the right input Hash. - "<% each(#{read_path(Regexp.last_match(1))}) do |it; variables| -%><% variables = it -%>" - end - - line.gsub!(IF) { "<% if truthy?(#{read_path(Regexp.last_match(1))}) -%>" } - line.gsub!(VAR) { "<%= #{read_path(Regexp.last_match(1))} %>" } - line - end - - def read_path(path) - return path if path == 'it' - - args = path.split('.') - args.map! { |arg| arg.match?(INTEGER) ? "#{arg}" : "'#{arg}'" } - - "read(variables, #{args.join(', ')})" - end - end - end - end -end diff --git a/lib/gitlab/changelog/template/context.rb b/lib/gitlab/changelog/template/context.rb deleted file mode 100644 index 8a0796d767e..00000000000 --- a/lib/gitlab/changelog/template/context.rb +++ /dev/null @@ -1,70 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Changelog - module Template - # Context is used to provide a binding/context to ERB templates used for - # rendering changelogs. - # - # This class extends BasicObject so that we only expose the bare minimum - # needed to render the ERB template. - class Context < BasicObject - MAX_NESTED_LOOPS = 4 - - def initialize(variables) - @variables = variables - @loop_nesting = 0 - end - - def get_binding - ::Kernel.binding - end - - def each(value, &block) - max = MAX_NESTED_LOOPS - - if @loop_nesting == max - ::Kernel.raise( - ::Template::TemplateError.new("You can only nest up to #{max} loops") - ) - end - - @loop_nesting += 1 - result = value.each(&block) if value.respond_to?(:each) - @loop_nesting -= 1 - - result - end - - # rubocop: disable Style/TrivialAccessors - def variables - @variables - end - # rubocop: enable Style/TrivialAccessors - - def read(source, *steps) - current = source - - steps.each do |step| - case current - when ::Hash - current = current[step] - when ::Array - return '' unless step.is_a?(::Integer) - - current = current[step] - else - break - end - end - - current - end - - def truthy?(value) - value.respond_to?(:any?) ? value.any? : !!value - end - end - end - end -end diff --git a/lib/gitlab/changelog/template/template.rb b/lib/gitlab/changelog/template/template.rb deleted file mode 100644 index 0ff2852d6d4..00000000000 --- a/lib/gitlab/changelog/template/template.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Changelog - module Template - # A wrapper around an ERB template user for rendering changelogs. - class Template - TemplateError = Class.new(StandardError) - - def initialize(erb) - # Don't change the trim mode, as this may require changes to the - # regular expressions used to turn the template syntax into ERB - # tags. - @erb = ERB.new(erb, trim_mode: '-') - end - - def render(data) - context = Context.new(data).get_binding - - # ERB produces a SyntaxError when processing templates, as it - # internally uses eval() for this. - @erb.result(context) - rescue SyntaxError - raise TemplateError.new("The template's syntax is invalid") - end - end - end - end -end diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb index c51349b9113..02c80923973 100644 --- a/lib/gitlab/gitaly_client.rb +++ b/lib/gitlab/gitaly_client.rb @@ -226,6 +226,7 @@ module Gitlab metadata['username'] = context_data['meta.user'] if context_data&.fetch('meta.user', nil) metadata['remote_ip'] = context_data['meta.remote_ip'] if context_data&.fetch('meta.remote_ip', nil) metadata.merge!(Feature::Gitaly.server_feature_flags) + metadata.merge!(route_to_primary) deadline_info = request_deadline(timeout) metadata.merge!(deadline_info.slice(:deadline_type)) @@ -233,6 +234,24 @@ module Gitlab { metadata: metadata, deadline: deadline_info[:deadline] } end + # Gitlab::Git::HookEnv will set the :gitlab_git_env variable in case we're + # running in the context of a Gitaly hook call, which may make use of + # quarantined object directories. We thus need to pass along the path of + # the quarantined object directory to Gitaly, otherwise it won't be able to + # find these quarantined objects. Given that the quarantine directory is + # generated with a random name, they'll have different names when multiple + # Gitaly nodes take part in a single transaction. As a result, we are + # forced to route all requests to the primary node which has injected the + # quarantine object directory to us. + def self.route_to_primary + return {} unless Gitlab::SafeRequestStore.active? + + return {} unless Gitlab::SafeRequestStore[:gitlab_git_env] + + { 'gitaly-route-repository-accessor-policy' => 'primary-only' } + end + private_class_method :route_to_primary + def self.request_deadline(timeout) # timeout being 0 means the request is allowed to run indefinitely. # We can't allow that inside a request, but this won't count towards Gitaly diff --git a/lib/gitlab/template/base_template.rb b/lib/gitlab/template/base_template.rb index b659bff52ad..02b3da54685 100644 --- a/lib/gitlab/template/base_template.rb +++ b/lib/gitlab/template/base_template.rb @@ -95,19 +95,29 @@ module Gitlab File.join(base_dir, categories[category]) end - # If template is organized by category it returns { category_name: [{ name: template_name }, { name: template2_name }] } - # If no category is present returns [{ name: template_name }, { name: template2_name}] - def dropdown_names(project = nil) - return [] if project && !project.repository.exists? + # `repository_template_names` - reads through Gitaly the actual templates names within a + # given project's repository. This is only used by issue and merge request templates, + # that need to call this once and then cache the returned value. + # + # `template_names` - is an alias to `repository_template_names`. It would read through + # Gitaly the actual template names within a given project's repository for all file templates + # other than `issue` and `merge request` description templates, which would instead + # overwrite the `template_names` method to return a redis cached version, by reading cached values + # from `repository.issue_template_names_by_category` and `repository.merge_request_template_names_by_category` + # methods. + def repository_template_names(project) + template_names_by_category(self.all(project)) + end + alias_method :template_names, :repository_template_names - if categories.any? - categories.keys.map do |category| - files = self.by_category(category, project) - [category, files.map { |t| { name: t.name } }] - end.to_h - else - files = self.all(project) - files.map { |t| { name: t.name } } + def template_names_by_category(items) + grouped = items.group_by(&:category) + categories = grouped.keys + + categories.each_with_object({}) do |category, hash| + hash[category] = grouped[category].map do |item| + { name: item.name, id: item.key, key: item.key, project_id: item.try(:project_id) } + end end end diff --git a/lib/gitlab/template/finders/repo_template_finder.rb b/lib/gitlab/template/finders/repo_template_finder.rb index 8e234148a63..9f0ba97bcdf 100644 --- a/lib/gitlab/template/finders/repo_template_finder.rb +++ b/lib/gitlab/template/finders/repo_template_finder.rb @@ -11,8 +11,8 @@ module Gitlab def initialize(project, base_dir, extension, categories = {}) @categories = categories @extension = extension - @repository = project.repository - @commit = @repository.head_commit if @repository.exists? + @repository = project&.repository + @commit = @repository.head_commit if @repository&.exists? super(base_dir) end @@ -51,7 +51,7 @@ module Gitlab private def select_directory(file_name) - return [] unless @commit + return unless @commit # Insert root as directory directories = ["", *@categories.keys] diff --git a/lib/gitlab/template/issue_template.rb b/lib/gitlab/template/issue_template.rb index 01b191733d4..3049f43b322 100644 --- a/lib/gitlab/template/issue_template.rb +++ b/lib/gitlab/template/issue_template.rb @@ -15,6 +15,16 @@ module Gitlab def finder(project) Gitlab::Template::Finders::RepoTemplateFinder.new(project, self.base_dir, self.extension, self.categories) end + + def template_names(project) + return {} unless project&.repository&.exists? + + # here we rely on project.repository caching mechanism. Ideally we would want the template finder to have its + # own caching mechanism to avoid the back and forth call jumps between finder and model. + # + # follow-up issue: https://gitlab.com/gitlab-org/gitlab/-/issues/300279 + project.repository.issue_template_names_by_category + end end end end diff --git a/lib/gitlab/template/merge_request_template.rb b/lib/gitlab/template/merge_request_template.rb index 357b31cd82e..9442f3b13fb 100644 --- a/lib/gitlab/template/merge_request_template.rb +++ b/lib/gitlab/template/merge_request_template.rb @@ -15,6 +15,16 @@ module Gitlab def finder(project) Gitlab::Template::Finders::RepoTemplateFinder.new(project, self.base_dir, self.extension, self.categories) end + + def template_names(project) + return {} unless project&.repository&.exists? + + # here we rely on project.repository caching mechanism. Ideally we would want the template finder to have its + # own caching mechanism to avoid the back and forth call jumps between finder and model. + # + # follow-up issue: https://gitlab.com/gitlab-org/gitlab/-/issues/300279 + project.repository.merge_request_template_names_by_category + end end end end diff --git a/lib/gitlab/usage_data_counters/known_events/ecosystem.yml b/lib/gitlab/usage_data_counters/known_events/ecosystem.yml new file mode 100644 index 00000000000..3fd02164f74 --- /dev/null +++ b/lib/gitlab/usage_data_counters/known_events/ecosystem.yml @@ -0,0 +1,22 @@ +--- +# Ecosystem category +- name: i_ecosystem_jira_service_close_issue + category: ecosystem + redis_slot: ecosystem + aggregation: weekly + feature_flag: usage_data_track_ecosystem_jira_service +- name: i_ecosystem_jira_service_cross_reference + category: ecosystem + redis_slot: ecosystem + aggregation: weekly + feature_flag: usage_data_track_ecosystem_jira_service +- name: i_ecosystem_jira_service_list_issues + category: ecosystem + redis_slot: ecosystem + aggregation: weekly + feature_flag: usage_data_track_ecosystem_jira_service +- name: i_ecosystem_jira_service_create_issue + category: ecosystem + redis_slot: ecosystem + aggregation: weekly + feature_flag: usage_data_track_ecosystem_jira_service diff --git a/locale/gitlab.pot b/locale/gitlab.pot index a6662403fc7..5cbdd05f14c 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -19648,6 +19648,9 @@ msgstr "" msgid "New User" msgstr "" +msgid "New application" +msgstr "" + msgid "New branch" msgstr "" @@ -28293,6 +28296,12 @@ msgstr "" msgid "System Info" msgstr "" +msgid "System OAuth applications" +msgstr "" + +msgid "System OAuth applications don't belong to any user and can only be managed by admins" +msgstr "" + msgid "System default (%{default})" msgstr "" @@ -30877,6 +30886,9 @@ msgstr "" msgid "Troubleshoot and monitor your application with tracing" msgstr "" +msgid "Trusted" +msgstr "" + msgid "Try again" msgstr "" @@ -35309,6 +35321,12 @@ msgstr "" msgid "this document" msgstr "" +msgid "this issue cannot be assigned to a confidential epic since it is public" +msgstr "" + +msgid "this issue cannot be made public since it belongs to a confidential epic" +msgstr "" + msgid "time summary" msgstr "" diff --git a/spec/controllers/projects/templates_controller_spec.rb b/spec/controllers/projects/templates_controller_spec.rb index 01593f4133c..fe282baf769 100644 --- a/spec/controllers/projects/templates_controller_spec.rb +++ b/spec/controllers/projects/templates_controller_spec.rb @@ -165,7 +165,8 @@ RSpec.describe Projects::TemplatesController do expect(response).to have_gitlab_http_status(:ok) expect(json_response.size).to eq(2) - expect(json_response).to match(expected_template_names) + expect(json_response.size).to eq(2) + expect(json_response.map { |x| x.slice('name') }).to match(expected_template_names) end it 'fails for user with no access' do diff --git a/spec/finders/license_template_finder_spec.rb b/spec/finders/license_template_finder_spec.rb index 93f13632b6f..754b92faccc 100644 --- a/spec/finders/license_template_finder_spec.rb +++ b/spec/finders/license_template_finder_spec.rb @@ -3,12 +3,7 @@ require 'spec_helper' RSpec.describe LicenseTemplateFinder do - describe '#execute' do - subject(:result) { described_class.new(nil, params).execute } - - let(:categories) { categorised_licenses.keys } - let(:categorised_licenses) { result.group_by(&:category) } - + RSpec.shared_examples 'filters by popular category' do context 'popular: true' do let(:params) { { popular: true } } @@ -26,6 +21,15 @@ RSpec.describe LicenseTemplateFinder do expect(categorised_licenses[:Other]).to be_present end end + end + + describe '#execute' do + subject(:result) { described_class.new(nil, params).execute } + + let(:categories) { categorised_licenses.keys } + let(:categorised_licenses) { result.group_by(&:category) } + + it_behaves_like 'filters by popular category' context 'popular: nil' do let(:params) { { popular: nil } } @@ -48,4 +52,31 @@ RSpec.describe LicenseTemplateFinder do end end end + + describe '#template_names' do + let(:params) { {} } + + subject(:template_names) { described_class.new(nil, params).template_names } + + let(:categories) { categorised_licenses.keys } + let(:categorised_licenses) { template_names } + + it_behaves_like 'filters by popular category' + + context 'popular: nil' do + let(:params) { { popular: nil } } + + it 'returns all licenses known by the Licensee gem' do + from_licensee = Licensee::License.all.map { |l| l.key } + + expect(template_names.values.flatten.map { |x| x[:key] }).to match_array(from_licensee) + end + end + + context 'template names hash keys' do + it 'has all the expected keys' do + expect(template_names.values.flatten.first.keys).to match_array(%i(id key name project_id)) + end + end + end end diff --git a/spec/finders/template_finder_spec.rb b/spec/finders/template_finder_spec.rb index 2da864b9a46..164975fdfb6 100644 --- a/spec/finders/template_finder_spec.rb +++ b/spec/finders/template_finder_spec.rb @@ -5,6 +5,102 @@ require 'spec_helper' RSpec.describe TemplateFinder do using RSpec::Parameterized::TableSyntax + let_it_be(:template_files) do + { + "Dockerfile/project_dockerfiles_template.dockerfile" => "project_dockerfiles_template content", + "gitignore/project_gitignores_template.gitignore" => "project_gitignores_template content", + "gitlab-ci/project_gitlab_ci_ymls_template.yml" => "project_gitlab_ci_ymls_template content", + ".gitlab/issue_templates/project_issues_template.md" => "project_issues_template content", + ".gitlab/merge_request_templates/project_merge_requests_template.md" => "project_merge_requests_template content" + } + end + + RSpec.shared_examples 'fetches predefined vendor templates' do + where(:type, :vendored_name) do + :dockerfiles | 'Binary' + :gitignores | 'Actionscript' + :gitlab_ci_ymls | 'Android' + :metrics_dashboard_ymls | 'Default' + :gitlab_ci_syntax_ymls | 'Artifacts example' + end + + with_them do + it 'returns all vendored templates when no name is specified' do + expect(result).to include(have_attributes(name: vendored_name)) + end + + context 'with name param' do + let(:params) { { name: vendored_name } } + + it 'returns only the specified vendored template when a name is specified' do + expect(result).to have_attributes(name: vendored_name) + end + + context 'with mistaken name param' do + let(:params) { { name: 'unknown' } } + + it 'returns nil when an unknown name is specified' do + expect(result).to be_nil + end + end + end + end + end + + RSpec.shared_examples 'no issues and merge requests templates available' do + context 'with issue and merge request templates' do + where(:type, :vendored_name) do + :issues | nil + :merge_requests | nil + end + + with_them do + context 'when fetching all templates' do + it 'returns empty array' do + expect(result).to eq([]) + end + end + + context 'when looking for specific template by name' do + let(:params) { { name: 'anything' } } + + it 'raises an error' do + expect { result }.to raise_exception(Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError) + end + end + end + end + end + + RSpec.shared_examples 'fetches issues and merge requests templates' do + where(:type, :template_name) do + :issues | 'project_issues_template' + :merge_requests | 'project_merge_requests_template' + end + + with_them do + it 'returns all repository template files for issues and merge requests' do + expect(result).to include(have_attributes(name: template_name)) + end + + context 'with name param' do + let(:params) { { name: template_name } } + + it 'returns only the specified vendored template when a name is specified' do + expect(result).to have_attributes(name: template_name) + end + + context 'with mistaken name param' do + let(:params) { { name: 'unknown' } } + + it 'raises an error when an unknown name is specified' do + expect { result }.to raise_exception(Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError) + end + end + end + end + end + describe '#build' do let(:project) { build_stubbed(:project) } @@ -15,6 +111,8 @@ RSpec.describe TemplateFinder do :licenses | ::LicenseTemplateFinder :metrics_dashboard_ymls | described_class :gitlab_ci_syntax_ymls | described_class + :issues | described_class + :merge_requests | described_class end with_them do @@ -26,6 +124,37 @@ RSpec.describe TemplateFinder do end describe '#execute' do + let_it_be(:project) { nil } + let(:params) { {} } + + subject(:result) { described_class.new(type, project, params).execute } + + context 'when no project is passed in' do + it_behaves_like 'fetches predefined vendor templates' + it_behaves_like 'no issues and merge requests templates available' + end + + context 'when project has no repository' do + let_it_be(:project) { create(:project) } + + it_behaves_like 'fetches predefined vendor templates' + it_behaves_like 'no issues and merge requests templates available' + end + + context 'when project has a repository' do + let_it_be(:project) { create(:project, :custom_repo, files: template_files) } + + it_behaves_like 'fetches predefined vendor templates' + it_behaves_like 'fetches issues and merge requests templates' + end + end + + describe '#template_names' do + let_it_be(:project) { nil } + let(:params) { {} } + + subject(:result) { described_class.new(type, project, params).template_names.values.flatten.map { |el| OpenStruct.new(el) } } + where(:type, :vendored_name) do :dockerfiles | 'Binary' :gitignores | 'Actionscript' @@ -35,22 +164,67 @@ RSpec.describe TemplateFinder do end with_them do - it 'returns all vendored templates when no name is specified' do - result = described_class.new(type, nil).execute + context 'when no project is passed in' do + it 'returns all vendored templates when no name is specified' do + expect(result).to include(have_attributes(name: vendored_name)) + end + end - expect(result).to include(have_attributes(name: vendored_name)) + context 'when project has no repository' do + let_it_be(:project) { create(:project) } + + it 'returns all vendored templates when no name is specified' do + expect(result).to include(have_attributes(name: vendored_name)) + end end - it 'returns only the specified vendored template when a name is specified' do - result = described_class.new(type, nil, name: vendored_name).execute + context 'when project has a repository' do + let_it_be(:project) { create(:project, :custom_repo, files: template_files) } - expect(result).to have_attributes(name: vendored_name) + it 'returns all vendored templates when no name is specified' do + expect(result).to include(have_attributes(name: vendored_name)) + end end - it 'returns nil when an unknown name is specified' do - result = described_class.new(type, nil, name: 'unknown').execute + context 'template names hash keys' do + it 'has all the expected keys' do + expect(result.first.to_h.keys).to match_array(%i(id key name project_id)) + end + end + end + + where(:type, :template_name) do + :issues | 'project_issues_template' + :merge_requests | 'project_merge_requests_template' + end + + with_them do + context 'when no project is passed in' do + it 'returns all vendored templates when no name is specified' do + expect(result).to eq([]) + end + end + + context 'when project has no repository' do + let_it_be(:project) { create(:project) } + + it 'returns all vendored templates when no name is specified' do + expect(result).to eq([]) + end + end + + context 'when project has a repository' do + let_it_be(:project) { create(:project, :custom_repo, files: template_files) } + + it 'returns all vendored templates when no name is specified' do + expect(result).to include(have_attributes(name: template_name)) + end - expect(result).to be_nil + context 'template names hash keys' do + it 'has all the expected keys' do + expect(result.first.to_h.keys).to match_array(%i(id key name project_id)) + end + end end end end diff --git a/spec/frontend/jobs/components/sidebar_detail_row_spec.js b/spec/frontend/jobs/components/sidebar_detail_row_spec.js index 42d11266dad..4560a4f1043 100644 --- a/spec/frontend/jobs/components/sidebar_detail_row_spec.js +++ b/spec/frontend/jobs/components/sidebar_detail_row_spec.js @@ -1,61 +1,55 @@ -import Vue from 'vue'; -import sidebarDetailRow from '~/jobs/components/sidebar_detail_row.vue'; +import { shallowMount } from '@vue/test-utils'; +import { GlLink } from '@gitlab/ui'; +import SidebarDetailRow from '~/jobs/components/sidebar_detail_row.vue'; describe('Sidebar detail row', () => { - let SidebarDetailRow; - let vm; + let wrapper; - beforeEach(() => { - SidebarDetailRow = Vue.extend(sidebarDetailRow); - }); + const title = 'this is the title'; + const value = 'this is the value'; + const helpUrl = '/help/ci/runners/README.html'; - afterEach(() => { - vm.$destroy(); - }); + const findHelpLink = () => wrapper.findComponent(GlLink); - it('should render no title', () => { - vm = new SidebarDetailRow({ + const createComponent = (props) => { + wrapper = shallowMount(SidebarDetailRow, { propsData: { - value: 'this is the value', + ...props, }, - }).$mount(); + }); + }; - expect(vm.$el.textContent.replace(/\s+/g, ' ').trim()).toEqual('this is the value'); + afterEach(() => { + wrapper.destroy(); + wrapper = null; }); - beforeEach(() => { - vm = new SidebarDetailRow({ - propsData: { - title: 'this is the title', - value: 'this is the value', - }, - }).$mount(); - }); + describe('with title/value and without helpUrl', () => { + beforeEach(() => { + createComponent({ title, value }); + }); - it('should render provided title and value', () => { - expect(vm.$el.textContent.replace(/\s+/g, ' ').trim()).toEqual( - 'this is the title: this is the value', - ); - }); + it('should render the provided title and value', () => { + expect(wrapper.text()).toBe(`${title}: ${value}`); + }); - describe('when helpUrl not provided', () => { - it('should not render help', () => { - expect(vm.$el.querySelector('.help-button')).toBeNull(); + it('should not render the help link', () => { + expect(findHelpLink().exists()).toBe(false); }); }); describe('when helpUrl provided', () => { beforeEach(() => { - vm = new SidebarDetailRow({ - propsData: { - helpUrl: 'help url', - value: 'foo', - }, - }).$mount(); + createComponent({ + helpUrl, + title, + value, + }); }); - it('should render help', () => { - expect(vm.$el.querySelector('.help-button a').getAttribute('href')).toEqual('help url'); + it('should render the help link', () => { + expect(findHelpLink().exists()).toBe(true); + expect(findHelpLink().attributes('href')).toBe(helpUrl); }); }); }); diff --git a/spec/frontend/projects/pipelines/charts/components/app_spec.js b/spec/frontend/projects/pipelines/charts/components/app_spec.js index 6dcfacb46cb..6a7595fea06 100644 --- a/spec/frontend/projects/pipelines/charts/components/app_spec.js +++ b/spec/frontend/projects/pipelines/charts/components/app_spec.js @@ -87,6 +87,22 @@ describe('ProjectsPipelinesChartsApp', () => { expect(tabs.attributes('value')).toBe('1'); }); + + it('should not try to push history if the tab does not change', async () => { + setWindowLocation(`${TEST_HOST}/gitlab-org/gitlab-test/-/pipelines/charts`); + + mergeUrlParams.mockImplementation(({ chart }, path) => `${path}?chart=${chart}`); + + const tabs = findGlTabs(); + + expect(tabs.attributes('value')).toBe('0'); + + tabs.vm.$emit('input', 0); + + await wrapper.vm.$nextTick(); + + expect(updateHistory).not.toHaveBeenCalled(); + }); }); describe('when provided with a query param', () => { @@ -105,6 +121,38 @@ describe('ProjectsPipelinesChartsApp', () => { createComponent({ provide: { shouldRenderDeploymentFrequencyCharts: true } }); expect(findGlTabs().attributes('value')).toBe(tab); }); + + it('should set the tab when the back button is clicked', async () => { + let popstateHandler; + + window.addEventListener = jest.fn(); + + window.addEventListener.mockImplementation((event, handler) => { + if (event === 'popstate') { + popstateHandler = handler; + } + }); + + getParameterValues.mockImplementation((name) => { + expect(name).toBe('chart'); + return []; + }); + + createComponent({ provide: { shouldRenderDeploymentFrequencyCharts: true } }); + + expect(findGlTabs().attributes('value')).toBe('0'); + + getParameterValues.mockImplementationOnce((name) => { + expect(name).toBe('chart'); + return ['deployments']; + }); + + popstateHandler(); + + await wrapper.vm.$nextTick(); + + expect(findGlTabs().attributes('value')).toBe('1'); + }); }); describe('when shouldRenderDeploymentFrequencyCharts is false', () => { diff --git a/spec/graphql/resolvers/ci/config_resolver_spec.rb b/spec/graphql/resolvers/ci/config_resolver_spec.rb index ca7ae73fef8..73e9fab9f99 100644 --- a/spec/graphql/resolvers/ci/config_resolver_spec.rb +++ b/spec/graphql/resolvers/ci/config_resolver_spec.rb @@ -36,7 +36,8 @@ RSpec.describe Resolvers::Ci::ConfigResolver do File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci_includes.yml')) end - it 'lints the ci config file' do + it 'lints the ci config file and returns the merged yaml file' do + expect(response[:merged_yaml]).to eq(content) expect(response[:status]).to eq(:valid) expect(response[:errors]).to be_empty end diff --git a/spec/helpers/issuables_description_templates_helper_spec.rb b/spec/helpers/issuables_description_templates_helper_spec.rb new file mode 100644 index 00000000000..42643b755f8 --- /dev/null +++ b/spec/helpers/issuables_description_templates_helper_spec.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe IssuablesDescriptionTemplatesHelper, :clean_gitlab_redis_cache do + include_context 'project issuable templates context' + + describe '#issuable_templates' do + let_it_be(:inherited_from) { nil } + let_it_be(:user) { create(:user) } + let_it_be(:parent_group, reload: true) { create(:group) } + let_it_be(:project, reload: true) { create(:project, :custom_repo, files: issuable_template_files) } + let_it_be(:group_member) { create(:group_member, :developer, group: parent_group, user: user) } + let_it_be(:project_member) { create(:project_member, :developer, user: user, project: project) } + + it 'returns empty hash when template type does not exist' do + expect(helper.issuable_templates(build(:project), 'non-existent-template-type')).to eq([]) + end + + context 'with cached issuable templates' do + before do + allow(Gitlab::Template::IssueTemplate).to receive(:template_names).and_return({}) + allow(Gitlab::Template::MergeRequestTemplate).to receive(:template_names).and_return({}) + + helper.issuable_templates(project, 'issues') + helper.issuable_templates(project, 'merge_request') + end + + it 'does not call TemplateFinder' do + expect(Gitlab::Template::IssueTemplate).not_to receive(:template_names) + expect(Gitlab::Template::MergeRequestTemplate).not_to receive(:template_names) + helper.issuable_templates(project, 'issues') + helper.issuable_templates(project, 'merge_request') + end + end + + context 'when project has no parent group' do + it_behaves_like 'project issuable templates' + end + + context 'when project has parent group' do + before do + project.update!(group: parent_group) + end + + context 'when project parent group does not have a file template project' do + it_behaves_like 'project issuable templates' + end + + context 'when project parent group has a file template project' do + let_it_be(:file_template_project) { create(:project, :custom_repo, group: parent_group, files: issuable_template_files) } + let_it_be(:group, reload: true) { create(:group, parent: parent_group) } + let_it_be(:project, reload: true) { create(:project, :custom_repo, group: group, files: issuable_template_files) } + + before do + project.update!(group: group) + parent_group.update_columns(file_template_project_id: file_template_project.id) + end + + it_behaves_like 'project issuable templates' + end + end + end + + describe '#issuable_templates_names' do + let(:project) { double(Project, id: 21) } + + let(:templates) do + [ + { name: "another_issue_template", id: "another_issue_template", project_id: project.id }, + { name: "custom_issue_template", id: "custom_issue_template", project_id: project.id } + ] + end + + it 'returns project templates only' do + allow(helper).to receive(:ref_project).and_return(project) + allow(helper).to receive(:issuable_templates).and_return(templates) + + expect(helper.issuable_templates_names(Issue.new)).to eq(%w[another_issue_template custom_issue_template]) + end + + context 'when there are not templates in the project' do + let(:templates) { {} } + + it 'returns empty array' do + allow(helper).to receive(:ref_project).and_return(project) + allow(helper).to receive(:issuable_templates).and_return(templates) + + expect(helper.issuable_templates_names(Issue.new)).to eq([]) + end + end + end +end diff --git a/spec/lib/gitlab/changelog/ast_spec.rb b/spec/lib/gitlab/changelog/ast_spec.rb new file mode 100644 index 00000000000..fa15ac979fe --- /dev/null +++ b/spec/lib/gitlab/changelog/ast_spec.rb @@ -0,0 +1,246 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Changelog::AST::Identifier do + let(:state) { Gitlab::Changelog::EvalState.new } + + describe '#evaluate' do + it 'evaluates a selector' do + data = { 'number' => 10 } + + expect(described_class.new('number').evaluate(state, data)).to eq(10) + end + + it 'returns nil if the key is not set' do + expect(described_class.new('number').evaluate(state, {})).to be_nil + end + + it 'returns nil if the input is not a Hash' do + expect(described_class.new('number').evaluate(state, 45)).to be_nil + end + + it 'returns the current data when using the special identifier "it"' do + expect(described_class.new('it').evaluate(state, 45)).to eq(45) + end + end +end + +RSpec.describe Gitlab::Changelog::AST::Integer do + let(:state) { Gitlab::Changelog::EvalState.new } + + describe '#evaluate' do + it 'evaluates a selector' do + expect(described_class.new(0).evaluate(state, [10])).to eq(10) + end + + it 'returns nil if the index is not set' do + expect(described_class.new(1).evaluate(state, [10])).to be_nil + end + + it 'returns nil if the input is not an Array' do + expect(described_class.new(0).evaluate(state, {})).to be_nil + end + end +end + +RSpec.describe Gitlab::Changelog::AST::Selector do + let(:state) { Gitlab::Changelog::EvalState.new } + let(:data) { { 'numbers' => [10] } } + + describe '#evaluate' do + it 'evaluates a selector' do + ident = Gitlab::Changelog::AST::Identifier.new('numbers') + int = Gitlab::Changelog::AST::Integer.new(0) + + expect(described_class.new([ident, int]).evaluate(state, data)).to eq(10) + end + + it 'evaluates a selector that returns nil' do + int = Gitlab::Changelog::AST::Integer.new(0) + + expect(described_class.new([int]).evaluate(state, data)).to be_nil + end + end +end + +RSpec.describe Gitlab::Changelog::AST::Variable do + let(:state) { Gitlab::Changelog::EvalState.new } + let(:data) { { 'numbers' => [10] } } + + describe '#evaluate' do + it 'evaluates a variable' do + node = Gitlab::Changelog::Parser + .new + .parse_and_transform('{{numbers.0}}') + .nodes[0] + + expect(node.evaluate(state, data)).to eq('10') + end + + it 'evaluates an undefined variable' do + node = + Gitlab::Changelog::Parser.new.parse_and_transform('{{foobar}}').nodes[0] + + expect(node.evaluate(state, data)).to eq('') + end + + it 'evaluates the special variable "it"' do + node = + Gitlab::Changelog::Parser.new.parse_and_transform('{{it}}').nodes[0] + + expect(node.evaluate(state, data)).to eq(data.to_s) + end + end +end + +RSpec.describe Gitlab::Changelog::AST::Expressions do + let(:state) { Gitlab::Changelog::EvalState.new } + + describe '#evaluate' do + it 'evaluates all expressions' do + node = Gitlab::Changelog::Parser + .new + .parse_and_transform('{{number}}foo') + + expect(node.evaluate(state, { 'number' => 10 })).to eq('10foo') + end + end +end + +RSpec.describe Gitlab::Changelog::AST::Text do + let(:state) { Gitlab::Changelog::EvalState.new } + + describe '#evaluate' do + it 'returns the text' do + expect(described_class.new('foo').evaluate(state, {})).to eq('foo') + end + end +end + +RSpec.describe Gitlab::Changelog::AST::If do + let(:state) { Gitlab::Changelog::EvalState.new } + + describe '#evaluate' do + it 'evaluates a truthy if expression without an else clause' do + node = Gitlab::Changelog::Parser + .new + .parse_and_transform('{% if thing %}foo{% end %}') + .nodes[0] + + expect(node.evaluate(state, { 'thing' => true })).to eq('foo') + end + + it 'evaluates a falsy if expression without an else clause' do + node = Gitlab::Changelog::Parser + .new + .parse_and_transform('{% if thing %}foo{% end %}') + .nodes[0] + + expect(node.evaluate(state, { 'thing' => false })).to eq('') + end + + it 'evaluates a falsy if expression with an else clause' do + node = Gitlab::Changelog::Parser + .new + .parse_and_transform('{% if thing %}foo{% else %}bar{% end %}') + .nodes[0] + + expect(node.evaluate(state, { 'thing' => false })).to eq('bar') + end + end + + describe '#truthy?' do + it 'returns true for a non-empty String' do + expect(described_class.new.truthy?('foo')).to eq(true) + end + + it 'returns true for a non-empty Array' do + expect(described_class.new.truthy?([10])).to eq(true) + end + + it 'returns true for a Boolean true' do + expect(described_class.new.truthy?(true)).to eq(true) + end + + it 'returns false for an empty String' do + expect(described_class.new.truthy?('')).to eq(false) + end + + it 'returns true for an empty Array' do + expect(described_class.new.truthy?([])).to eq(false) + end + + it 'returns false for a Boolean false' do + expect(described_class.new.truthy?(false)).to eq(false) + end + end +end + +RSpec.describe Gitlab::Changelog::AST::Each do + let(:state) { Gitlab::Changelog::EvalState.new } + + describe '#evaluate' do + it 'evaluates the expression' do + data = { 'animals' => [{ 'name' => 'Cat' }, { 'name' => 'Dog' }] } + node = Gitlab::Changelog::Parser + .new + .parse_and_transform('{% each animals %}{{name}}{% end %}') + .nodes[0] + + expect(node.evaluate(state, data)).to eq('CatDog') + end + + it 'returns an empty string when the input is not a collection' do + data = { 'animals' => 10 } + node = Gitlab::Changelog::Parser + .new + .parse_and_transform('{% each animals %}{{name}}{% end %}') + .nodes[0] + + expect(node.evaluate(state, data)).to eq('') + end + + it 'disallows too many nested loops' do + data = { + 'foo' => [ + { + 'bar' => [ + { + 'baz' => [ + { + 'quix' => [ + { + 'foo' => [{ 'name' => 'Alice' }] + } + ] + } + ] + } + ] + } + ] + } + + template = <<~TPL + {% each foo %} + {% each bar %} + {% each baz %} + {% each quix %} + {% each foo %} + {{name}} + {% end %} + {% end %} + {% end %} + {% end %} + {% end %} + TPL + + node = + Gitlab::Changelog::Parser.new.parse_and_transform(template).nodes[0] + + expect { node.evaluate(state, data) } + .to raise_error(Gitlab::Changelog::Error) + end + end +end diff --git a/spec/lib/gitlab/changelog/committer_spec.rb b/spec/lib/gitlab/changelog/committer_spec.rb index f0d6bc2b6b5..1e04fe346cb 100644 --- a/spec/lib/gitlab/changelog/committer_spec.rb +++ b/spec/lib/gitlab/changelog/committer_spec.rb @@ -88,7 +88,7 @@ RSpec.describe Gitlab::Changelog::Committer do end context "when the changelog changes before saving the changes" do - it 'raises a CommitError' do + it 'raises a Error' do release1 = Gitlab::Changelog::Release .new(version: '1.0.0', date: Time.utc(2020, 1, 1), config: config) @@ -121,7 +121,7 @@ RSpec.describe Gitlab::Changelog::Committer do branch: 'master', message: 'Test commit' ) - end.to raise_error(described_class::CommitError) + end.to raise_error(Gitlab::Changelog::Error) end end end diff --git a/spec/lib/gitlab/changelog/config_spec.rb b/spec/lib/gitlab/changelog/config_spec.rb index adf82fa3ac2..51988acf3d1 100644 --- a/spec/lib/gitlab/changelog/config_spec.rb +++ b/spec/lib/gitlab/changelog/config_spec.rb @@ -41,13 +41,15 @@ RSpec.describe Gitlab::Changelog::Config do ) expect(config.date_format).to eq('foo') - expect(config.template).to be_instance_of(Gitlab::Changelog::Template::Template) + expect(config.template) + .to be_instance_of(Gitlab::Changelog::AST::Expressions) + expect(config.categories).to eq({ 'foo' => 'bar' }) end - it 'raises ConfigError when the categories are not a Hash' do + it 'raises Error when the categories are not a Hash' do expect { described_class.from_hash(project, 'categories' => 10) } - .to raise_error(described_class::ConfigError) + .to raise_error(Gitlab::Changelog::Error) end end diff --git a/spec/lib/gitlab/changelog/parser_spec.rb b/spec/lib/gitlab/changelog/parser_spec.rb new file mode 100644 index 00000000000..1d353f5eb35 --- /dev/null +++ b/spec/lib/gitlab/changelog/parser_spec.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Changelog::Parser do + let(:parser) { described_class.new } + + describe '#root' do + it 'parses an empty template' do + expect(parser.root).to parse('') + end + + it 'parses a variable with a single identifier step' do + expect(parser.root).to parse('{{foo}}') + end + + it 'parses a variable with a single integer step' do + expect(parser.root).to parse('{{0}}') + end + + it 'parses a variable with multiple selector steps' do + expect(parser.root).to parse('{{foo.bar}}') + end + + it 'parses a variable with an integer selector step' do + expect(parser.root).to parse('{{foo.bar.0}}') + end + + it 'parses the special "it" variable' do + expect(parser.root).to parse('{{it}}') + end + + it 'parses a text node' do + expect(parser.root).to parse('foo') + end + + it 'parses an if expression' do + expect(parser.root).to parse('{% if foo %}bar{% end %}') + end + + it 'parses an if-else expression' do + expect(parser.root).to parse('{% if foo %}bar{% else %}baz{% end %}') + end + + it 'parses an each expression' do + expect(parser.root).to parse('{% each foo %}foo{% end %}') + end + + it 'parses an escaped newline' do + expect(parser.root).to parse("foo\\\nbar") + end + + it 'parses a regular newline' do + expect(parser.root).to parse("foo\nbar") + end + + it 'parses the default changelog template' do + expect(parser.root).to parse(Gitlab::Changelog::Config::DEFAULT_TEMPLATE) + end + + it 'raises an error when parsing an integer selector that is too large' do + expect(parser.root).not_to parse('{{100000000000}}') + end + end + + describe '#parse_and_transform' do + it 'parses and transforms a template' do + node = parser.parse_and_transform('foo') + + expect(node).to be_instance_of(Gitlab::Changelog::AST::Expressions) + end + + it 'raises parsing errors using a custom error class' do + expect { parser.parse_and_transform('{% each') } + .to raise_error(Gitlab::Changelog::Error) + end + end +end diff --git a/spec/lib/gitlab/changelog/release_spec.rb b/spec/lib/gitlab/changelog/release_spec.rb index 50a23d23299..f95244d6750 100644 --- a/spec/lib/gitlab/changelog/release_spec.rb +++ b/spec/lib/gitlab/changelog/release_spec.rb @@ -93,6 +93,28 @@ RSpec.describe Gitlab::Changelog::Release do OUT end end + + context 'when a category has no entries' do + it "isn't included in the output" do + config.categories['kittens'] = 'Kittens' + config.categories['fixed'] = 'Bug fixes' + + release.add_entry( + title: 'Entry title', + commit: commit, + category: 'fixed' + ) + + expect(release.to_markdown).to eq(<<~OUT) + ## 1.0.0 (2021-01-05) + + ### Bug fixes (1 change) + + - [Entry title](#{commit.to_reference(full: true)}) + + OUT + end + end end describe '#header_start_position' do diff --git a/spec/lib/gitlab/changelog/template/compiler_spec.rb b/spec/lib/gitlab/changelog/template/compiler_spec.rb deleted file mode 100644 index 8b09bc90529..00000000000 --- a/spec/lib/gitlab/changelog/template/compiler_spec.rb +++ /dev/null @@ -1,136 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::Changelog::Template::Compiler do - def compile(template, data = {}) - Gitlab::Changelog::Template::Compiler.new.compile(template).render(data) - end - - describe '#compile' do - it 'compiles an empty template' do - expect(compile('')).to eq('') - end - - it 'compiles a template with an undefined variable' do - expect(compile('{{number}}')).to eq('') - end - - it 'compiles a template with a defined variable' do - expect(compile('{{number}}', 'number' => 42)).to eq('42') - end - - it 'compiles a template with the special "it" variable' do - expect(compile('{{it}}', 'values' => 10)).to eq({ 'values' => 10 }.to_s) - end - - it 'compiles a template containing an if statement' do - expect(compile('{% if foo %}yes{% end %}', 'foo' => true)).to eq('yes') - end - - it 'compiles a template containing an if/else statement' do - expect(compile('{% if foo %}yes{% else %}no{% end %}', 'foo' => false)) - .to eq('no') - end - - it 'compiles a template that iterates over an Array' do - expect(compile('{% each numbers %}{{it}}{% end %}', 'numbers' => [1, 2, 3])) - .to eq('123') - end - - it 'compiles a template that iterates over a Hash' do - output = compile( - '{% each pairs %}{{0}}={{1}}{% end %}', - 'pairs' => { 'key' => 'value' } - ) - - expect(output).to eq('key=value') - end - - it 'compiles a template that iterates over a Hash of Arrays' do - output = compile( - '{% each values %}{{key}}{% end %}', - 'values' => [{ 'key' => 'value' }] - ) - - expect(output).to eq('value') - end - - it 'compiles a template with a variable path' do - output = compile('{{foo.bar}}', 'foo' => { 'bar' => 10 }) - - expect(output).to eq('10') - end - - it 'compiles a template with a variable path that uses an Array index' do - output = compile('{{foo.values.1}}', 'foo' => { 'values' => [10, 20] }) - - expect(output).to eq('20') - end - - it 'compiles a template with a variable path that uses a Hash and a numeric index' do - output = compile('{{foo.1}}', 'foo' => { 'key' => 'value' }) - - expect(output).to eq('') - end - - it 'compiles a template with a variable path that uses an Array and a String based index' do - output = compile('{{foo.numbers.bla}}', 'foo' => { 'numbers' => [10, 20] }) - - expect(output).to eq('') - end - - it 'ignores ERB tags provided by the user' do - input = '<% exit %> <%= exit %> <%= foo -%>' - - expect(compile(input)).to eq(input) - end - - it 'removes newlines introduced by end statements on their own lines' do - output = compile(<<~TPL, 'foo' => true) - {% if foo %} - foo - {% end %} - TPL - - expect(output).to eq("foo\n") - end - - it 'supports escaping of trailing newlines' do - output = compile(<<~TPL) - foo \ - bar\ - baz - TPL - - expect(output).to eq("foo barbaz\n") - end - - # rubocop: disable Lint/InterpolationCheck - it 'ignores embedded Ruby expressions' do - input = '#{exit}' - - expect(compile(input)).to eq(input) - end - # rubocop: enable Lint/InterpolationCheck - - it 'ignores ERB tags inside variable tags' do - input = '{{<%= exit %>}}' - - expect(compile(input)).to eq(input) - end - - it 'ignores malicious code that tries to escape a variable' do - input = "{{') ::Kernel.exit # '}}" - - expect(compile(input)).to eq(input) - end - - it 'ignores malicious code that makes use of whitespace' do - input = "x<\\\n%::Kernel.system(\"id\")%>" - - expect(Kernel).not_to receive(:system).with('id') - expect(compile(input)).to eq('x<%::Kernel.system("id")%>') - end - end -end diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb index 7fcb11c4dfd..fd332c8b840 100644 --- a/spec/lib/gitlab/gitaly_client_spec.rb +++ b/spec/lib/gitlab/gitaly_client_spec.rb @@ -296,6 +296,32 @@ RSpec.describe Gitlab::GitalyClient do end end + context 'gitlab_git_env' do + let(:policy) { 'gitaly-route-repository-accessor-policy' } + + context 'when RequestStore is disabled' do + it 'does not force-route to primary' do + expect(described_class.request_kwargs('default', timeout: 1)[:metadata][policy]).to be_nil + end + end + + context 'when RequestStore is enabled without git_env', :request_store do + it 'does not force-orute to primary' do + expect(described_class.request_kwargs('default', timeout: 1)[:metadata][policy]).to be_nil + end + end + + context 'when RequestStore is enabled with git_env', :request_store do + before do + Gitlab::SafeRequestStore[:gitlab_git_env] = true + end + + it 'enables force-routing to primary' do + expect(described_class.request_kwargs('default', timeout: 1)[:metadata][policy]).to eq('primary-only') + end + end + end + context 'deadlines', :request_store do let(:request_deadline) { real_time + 10.0 } 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 f0b8ce6c2fb..579cc1e372a 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 @@ -27,6 +27,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s 'deploy_token_packages', 'user_packages', 'compliance', + 'ecosystem', 'analytics', 'ide_edit', 'search', diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index cd0873bddd2..48b487f90a6 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -6,6 +6,8 @@ RSpec.describe JiraService do include AssetsHelpers let_it_be(:project) { create(:project, :repository) } + + let(:current_user) { build_stubbed(:user) } let(:url) { 'http://jira.example.com' } let(:api_url) { 'http://api-jira.example.com' } let(:username) { 'jira-username' } @@ -498,25 +500,38 @@ RSpec.describe JiraService do WebMock.stub_request(:post, @remote_link_url).with(basic_auth: %w(gitlab_jira_username gitlab_jira_password)) end + let(:external_issue) { ExternalIssue.new('JIRA-123', project) } + + def close_issue + @jira_service.close_issue(resource, external_issue, current_user) + end + it 'calls Jira API' do - @jira_service.close_issue(resource, ExternalIssue.new('JIRA-123', project)) + close_issue expect(WebMock).to have_requested(:post, @comment_url).with( body: /Issue solved with/ ).once end + it 'tracks usage' do + expect(Gitlab::UsageDataCounters::HLLRedisCounter) + .to receive(:track_event) + .with('i_ecosystem_jira_service_close_issue', values: current_user.id) + + close_issue + end + it 'does not fail if remote_link.all on issue returns nil' do allow(JIRA::Resource::Remotelink).to receive(:all).and_return(nil) - expect { @jira_service.close_issue(resource, ExternalIssue.new('JIRA-123', project)) } - .not_to raise_error + expect { close_issue }.not_to raise_error end # Check https://developer.atlassian.com/jiradev/jira-platform/guides/other/guide-jira-remote-issue-links/fields-in-remote-issue-links # for more information it 'creates Remote Link reference in Jira for comment' do - @jira_service.close_issue(resource, ExternalIssue.new('JIRA-123', project)) + close_issue favicon_path = "http://localhost/assets/#{find_asset('favicon.png').digest_path}" @@ -540,7 +555,7 @@ RSpec.describe JiraService do context 'when "comment_on_event_enabled" is set to false' do it 'creates Remote Link reference but does not create comment' do allow(@jira_service).to receive_messages(comment_on_event_enabled: false) - @jira_service.close_issue(resource, ExternalIssue.new('JIRA-123', project)) + close_issue expect(WebMock).not_to have_requested(:post, @comment_url) expect(WebMock).to have_requested(:post, @remote_link_url) @@ -562,7 +577,7 @@ RSpec.describe JiraService do expect(remote_link).to receive(:save!) - @jira_service.close_issue(resource, ExternalIssue.new('JIRA-123', project)) + close_issue expect(WebMock).not_to have_requested(:post, @comment_url) end @@ -571,7 +586,7 @@ RSpec.describe JiraService do it 'does not send comment or remote links to issues already closed' do allow_any_instance_of(JIRA::Resource::Issue).to receive(:resolution).and_return(true) - @jira_service.close_issue(resource, ExternalIssue.new('JIRA-123', project)) + close_issue expect(WebMock).not_to have_requested(:post, @comment_url) expect(WebMock).not_to have_requested(:post, @remote_link_url) @@ -580,7 +595,7 @@ RSpec.describe JiraService do it 'does not send comment or remote links to issues with unknown resolution' do allow_any_instance_of(JIRA::Resource::Issue).to receive(:respond_to?).with(:resolution).and_return(false) - @jira_service.close_issue(resource, ExternalIssue.new('JIRA-123', project)) + close_issue expect(WebMock).not_to have_requested(:post, @comment_url) expect(WebMock).not_to have_requested(:post, @remote_link_url) @@ -589,7 +604,7 @@ RSpec.describe JiraService do it 'references the GitLab commit' do stub_config_setting(base_url: custom_base_url) - @jira_service.close_issue(resource, ExternalIssue.new('JIRA-123', project)) + close_issue expect(WebMock).to have_requested(:post, @comment_url).with( body: %r{#{custom_base_url}/#{project.full_path}/-/commit/#{commit_id}} @@ -604,7 +619,7 @@ RSpec.describe JiraService do { script_name: '/gitlab' } end - @jira_service.close_issue(resource, ExternalIssue.new('JIRA-123', project)) + close_issue expect(WebMock).to have_requested(:post, @comment_url).with( body: %r{#{Gitlab.config.gitlab.url}/#{project.full_path}/-/commit/#{commit_id}} @@ -615,7 +630,7 @@ RSpec.describe JiraService do allow(@jira_service).to receive(:log_error) WebMock.stub_request(:post, @transitions_url).with(basic_auth: %w(gitlab_jira_username gitlab_jira_password)).and_raise("Bad Request") - @jira_service.close_issue(resource, ExternalIssue.new('JIRA-123', project)) + close_issue expect(@jira_service).to have_received(:log_error).with( "Issue transition failed", @@ -628,7 +643,7 @@ RSpec.describe JiraService do end it 'calls the api with jira_issue_transition_id' do - @jira_service.close_issue(resource, ExternalIssue.new('JIRA-123', project)) + close_issue expect(WebMock).to have_requested(:post, @transitions_url).with( body: /999/ @@ -639,7 +654,7 @@ RSpec.describe JiraService do it 'calls the api with transition ids separated by comma' do allow(@jira_service).to receive_messages(jira_issue_transition_id: '1,2,3') - @jira_service.close_issue(resource, ExternalIssue.new('JIRA-123', project)) + close_issue 1.upto(3) do |transition_id| expect(WebMock).to have_requested(:post, @transitions_url).with( @@ -651,7 +666,7 @@ RSpec.describe JiraService do it 'calls the api with transition ids separated by semicolon' do allow(@jira_service).to receive_messages(jira_issue_transition_id: '1;2;3') - @jira_service.close_issue(resource, ExternalIssue.new('JIRA-123', project)) + close_issue 1.upto(3) do |transition_id| expect(WebMock).to have_requested(:post, @transitions_url).with( @@ -702,6 +717,14 @@ RSpec.describe JiraService do body: /mentioned this issue in/ ).once end + + it 'tracks usage' do + expect(Gitlab::UsageDataCounters::HLLRedisCounter) + .to receive(:track_event) + .with('i_ecosystem_jira_service_cross_reference', values: user.id) + + subject + end end context 'when resource is a commit' do diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 02ebf9447c0..30a3ded2272 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -4117,7 +4117,7 @@ RSpec.describe Project, factory_default: :keep do end end - describe '#remove_pages' do + describe '#legacy_remove_pages' do let(:project) { create(:project).tap { |project| project.mark_pages_as_deployed } } let(:pages_metadatum) { project.pages_metadatum } let(:namespace) { project.namespace } @@ -4136,34 +4136,22 @@ RSpec.describe Project, factory_default: :keep do expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project).and_return(true) expect(PagesWorker).to receive(:perform_in).with(5.minutes, :remove, namespace.full_path, anything) - expect { project.remove_pages }.to change { pages_metadatum.reload.deployed }.from(true).to(false) + expect { project.legacy_remove_pages }.to change { pages_metadatum.reload.deployed }.from(true).to(false) end - it 'is run when the project is destroyed' do - expect(project).to receive(:remove_pages).and_call_original - - expect { project.destroy }.not_to raise_error - end - - context 'when there is an old pages deployment' do - let!(:old_deployment_from_another_project) { create(:pages_deployment) } - let!(:old_deployment) { create(:pages_deployment, project: project) } + it 'does nothing if updates on legacy storage are disabled' do + stub_feature_flags(pages_update_legacy_storage: false) - it 'schedules a destruction of pages deployments' do - expect(DestroyPagesDeploymentsWorker).to( - receive(:perform_async).with(project.id) - ) + expect(Gitlab::PagesTransfer).not_to receive(:new) + expect(PagesWorker).not_to receive(:perform_in) - project.remove_pages - end + project.legacy_remove_pages + end - it 'removes pages deployments', :sidekiq_inline do - expect do - project.remove_pages - end.to change { PagesDeployment.count }.by(-1) + it 'is run when the project is destroyed' do + expect(project).to receive(:legacy_remove_pages).and_call_original - expect(PagesDeployment.find_by_id(old_deployment.id)).to be_nil - end + expect { project.destroy }.not_to raise_error end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 9e1729e8de4..3a4de7ba279 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -1949,8 +1949,8 @@ RSpec.describe Repository do :root_ref, :merged_branch_names, :has_visible_content?, - :issue_template_names, - :merge_request_template_names, + :issue_template_names_by_category, + :merge_request_template_names_by_category, :user_defined_metrics_dashboard_paths, :xcode_project?, :has_ambiguous_refs? diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index a05f1730708..e4945397fe3 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -672,7 +672,7 @@ RSpec.describe API::Repositories do allow(spy) .to receive(:execute) - .and_raise(Gitlab::Changelog::Committer::CommitError.new('oops')) + .and_raise(Gitlab::Changelog::Error.new('oops')) post( api("/projects/#{project.id}/repository/changelog", user), diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index dd37d87e3f5..a4c70ac0266 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -161,7 +161,7 @@ RSpec.describe MergeRequests::MergeService do commit = double('commit', safe_message: "Fixes #{jira_issue.to_reference}") allow(merge_request).to receive(:commits).and_return([commit]) - expect_any_instance_of(JiraService).to receive(:close_issue).with(merge_request, jira_issue).once + expect_any_instance_of(JiraService).to receive(:close_issue).with(merge_request, jira_issue, user).once service.execute(merge_request) end diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb index 20f06619e02..f59749f0b63 100644 --- a/spec/services/notes/create_service_spec.rb +++ b/spec/services/notes/create_service_spec.rb @@ -459,6 +459,26 @@ RSpec.describe Notes::CreateService do .and change { existing_note.updated_at } end + context 'failure in when_saved' do + let(:service) { described_class.new(project, user, reply_opts) } + + it 'converts existing note to DiscussionNote' do + expect do + existing_note + + allow(service).to receive(:when_saved).and_raise(ActiveRecord::StatementInvalid) + + travel_to(Time.current + 1.minute) do + service.execute + rescue ActiveRecord::StatementInvalid + end + + existing_note.reload + end.to change { existing_note.type }.from(nil).to('DiscussionNote') + .and change { existing_note.updated_at } + end + end + it 'returns a DiscussionNote with its parent discussion refreshed correctly' do discussion_notes = subject.discussion.notes diff --git a/spec/services/pages/delete_services_spec.rb b/spec/services/pages/delete_services_spec.rb index 440549020a2..f1edf93b0c1 100644 --- a/spec/services/pages/delete_services_spec.rb +++ b/spec/services/pages/delete_services_spec.rb @@ -3,35 +3,74 @@ require 'spec_helper' RSpec.describe Pages::DeleteService do - shared_examples 'remove pages' do - let_it_be(:project) { create(:project, path: "my.project")} - let_it_be(:admin) { create(:admin) } - let_it_be(:domain) { create(:pages_domain, project: project) } - let_it_be(:service) { described_class.new(project, admin)} + let_it_be(:admin) { create(:admin) } - it 'deletes published pages' do - expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project).and_return true - expect(PagesWorker).to receive(:perform_in).with(5.minutes, :remove, project.namespace.full_path, anything) + let(:project) { create(:project, path: "my.project")} + let!(:domain) { create(:pages_domain, project: project) } + let(:service) { described_class.new(project, admin)} - Sidekiq::Testing.inline! { service.execute } + before do + project.mark_pages_as_deployed + end + + it 'deletes published pages', :sidekiq_inline do + expect(project.pages_deployed?).to be(true) + + expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project).and_return true + expect(PagesWorker).to receive(:perform_in).with(5.minutes, :remove, project.namespace.full_path, anything) + + service.execute + + expect(project.pages_deployed?).to be(false) + end + + it "doesn't remove anything from the legacy storage if updates on it are disabled", :sidekiq_inline do + stub_feature_flags(pages_update_legacy_storage: false) + + expect(project.pages_deployed?).to be(true) + + expect(PagesWorker).not_to receive(:perform_in) + + service.execute - expect(project.reload.pages_metadatum.deployed?).to be(false) - end + expect(project.pages_deployed?).to be(false) + end + + it 'deletes all domains', :sidekiq_inline do + expect(project.pages_domains.count).to eq(1) + + service.execute + + expect(project.reload.pages_domains.count).to eq(0) + end - it 'deletes all domains' do - expect(project.pages_domains.count).to be 1 + it 'schedules a destruction of pages deployments' do + expect(DestroyPagesDeploymentsWorker).to( + receive(:perform_async).with(project.id) + ) - Sidekiq::Testing.inline! { service.execute } + service.execute + end + + it 'removes pages deployments', :sidekiq_inline do + create(:pages_deployment, project: project) - expect(project.reload.pages_domains.count).to be 0 - end + expect do + service.execute + end.to change { PagesDeployment.count }.by(-1) end - context 'with feature flag enabled' do - before do - expect(PagesRemoveWorker).to receive(:perform_async).and_call_original - end + it 'marks pages as not deployed, deletes domains and schedules worker to remove pages from disk' do + expect(project.pages_deployed?).to eq(true) + expect(project.pages_domains.count).to eq(1) + + service.execute + + expect(project.pages_deployed?).to eq(false) + expect(project.pages_domains.count).to eq(0) + + expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project).and_return true - it_behaves_like 'remove pages' + Sidekiq::Worker.drain_all end end diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb index 3439a18f3a5..6bf2876f640 100644 --- a/spec/services/projects/update_pages_service_spec.rb +++ b/spec/services/projects/update_pages_service_spec.rb @@ -16,7 +16,7 @@ RSpec.describe Projects::UpdatePagesService do subject { described_class.new(project, build) } before do - project.remove_pages + project.legacy_remove_pages end context '::TMP_EXTRACT_PATH' do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index cd3818b256d..64c1479a412 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -34,6 +34,7 @@ require 'rspec-parameterized' require 'shoulda/matchers' require 'test_prof/recipes/rspec/let_it_be' require 'test_prof/factory_default' +require 'parslet/rig/rspec' rspec_profiling_is_configured = ENV['RSPEC_PROFILING_POSTGRES_URL'].present? || diff --git a/spec/support/shared_examples/helpers/issuable_description_templates_shared_examples.rb b/spec/support/shared_examples/helpers/issuable_description_templates_shared_examples.rb new file mode 100644 index 00000000000..2dcb938d70b --- /dev/null +++ b/spec/support/shared_examples/helpers/issuable_description_templates_shared_examples.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +RSpec.shared_context 'project issuable templates context' do + let_it_be(:issuable_template_files) do + { + '.gitlab/issue_templates/issue-bar.md' => 'Issue Template Bar', + '.gitlab/issue_templates/issue-foo.md' => 'Issue Template Foo', + '.gitlab/issue_templates/issue-bad.txt' => 'Issue Template Bad', + '.gitlab/issue_templates/issue-baz.xyz' => 'Issue Template Baz', + + '.gitlab/merge_request_templates/merge_request-bar.md' => 'Merge Request Template Bar', + '.gitlab/merge_request_templates/merge_request-foo.md' => 'Merge Request Template Foo', + '.gitlab/merge_request_templates/merge_request-bad.txt' => 'Merge Request Template Bad', + '.gitlab/merge_request_templates/merge_request-baz.xyz' => 'Merge Request Template Baz' + } + end +end + +RSpec.shared_examples 'project issuable templates' do + context 'issuable templates' do + before do + allow(helper).to receive(:current_user).and_return(user) + end + + it 'returns only md files as issue templates' do + expect(helper.issuable_templates(project, 'issue')).to eq(templates('issue', nil)) + end + + it 'returns only md files as merge_request templates' do + expect(helper.issuable_templates(project, 'merge_request')).to eq(templates('merge_request', nil)) + end + end + + def expected_templates(issuable_type) + expectation = {} + + expectation["Project Templates"] = templates(issuable_type, project) + expectation["Group #{inherited_from.namespace.full_name}"] = templates(issuable_type, inherited_from) if inherited_from.present? + + expectation + end + + def templates(issuable_type, inherited_from) + [ + { id: "#{issuable_type}-bar", key: "#{issuable_type}-bar", name: "#{issuable_type}-bar", project_id: inherited_from&.id }, + { id: "#{issuable_type}-foo", key: "#{issuable_type}-foo", name: "#{issuable_type}-foo", project_id: inherited_from&.id } + ] + end +end diff --git a/spec/workers/namespaceless_project_destroy_worker_spec.rb b/spec/workers/namespaceless_project_destroy_worker_spec.rb index 618cd9cabe9..cd66af82364 100644 --- a/spec/workers/namespaceless_project_destroy_worker_spec.rb +++ b/spec/workers/namespaceless_project_destroy_worker_spec.rb @@ -49,7 +49,7 @@ RSpec.describe NamespacelessProjectDestroyWorker do subject.perform(project.id) end - it 'does not do anything in Project#remove_pages method' do + it 'does not do anything in Project#legacy_remove_pages method' do expect(Gitlab::PagesTransfer).not_to receive(:new) subject.perform(project.id) diff --git a/spec/workers/pages_remove_worker_spec.rb b/spec/workers/pages_remove_worker_spec.rb index 638e87043f2..864aa763fa9 100644 --- a/spec/workers/pages_remove_worker_spec.rb +++ b/spec/workers/pages_remove_worker_spec.rb @@ -3,24 +3,23 @@ require 'spec_helper' RSpec.describe PagesRemoveWorker do - let_it_be(:project) { create(:project, path: "my.project")} - let_it_be(:domain) { create(:pages_domain, project: project) } + let(:project) { create(:project, path: "my.project")} + let!(:domain) { create(:pages_domain, project: project) } + subject { described_class.new.perform(project.id) } + before do + project.mark_pages_as_deployed + end + it 'deletes published pages' do + expect(project.pages_deployed?).to be(true) + expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project).and_return true expect(PagesWorker).to receive(:perform_in).with(5.minutes, :remove, project.namespace.full_path, anything) subject - expect(project.reload.pages_metadatum.deployed?).to be(false) - end - - it 'deletes all domains' do - expect(project.pages_domains.count).to be 1 - - subject - - expect(project.reload.pages_domains.count).to be 0 + expect(project.reload.pages_deployed?).to be(false) end end |