Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
		
							parent
							
								
									ff2a881e20
								
							
						
					
					
						commit
						9968d39440
					
				|  | @ -302,7 +302,7 @@ export default { | |||
| 
 | ||||
|     <div :class="newSubgroup && 'row gl-mb-3'"> | ||||
|       <gl-form-group v-if="newSubgroup" class="col-sm-6 gl-pr-0" :label="inputLabels.subgroupPath"> | ||||
|         <div class="input-group gl-flex-nowrap"> | ||||
|         <div class="input-group gl-flex-wrap-nowrap"> | ||||
|           <gl-button-group class="gl-w-full"> | ||||
|             <gl-button class="js-group-namespace-button gl-text-truncate gl-flex-grow-0!" label> | ||||
|               {{ basePath }} | ||||
|  |  | |||
|  | @ -20,7 +20,7 @@ export default { | |||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <div class="d-flex-center gl-flex-nowrap text-nowrap js-ide-status-mr"> | ||||
|   <div class="d-flex-center gl-flex-wrap-nowrap text-nowrap js-ide-status-mr"> | ||||
|     <gl-icon name="merge-request" /> | ||||
|     <span class="ml-1 d-none d-sm-block">{{ s__('WebIDE|Merge request') }}</span> | ||||
|     <gl-link class="ml-1" :href="url">{{ text }}</gl-link> | ||||
|  |  | |||
|  | @ -31,7 +31,7 @@ export default { | |||
| 
 | ||||
| <template> | ||||
|   <dropdown-button class="gl-w-full!"> | ||||
|     <span class="row gl-flex-nowrap"> | ||||
|     <span class="row gl-flex-wrap-nowrap"> | ||||
|       <span class="col-auto flex-fill text-truncate"> | ||||
|         <gl-icon :size="16" :aria-label="__('Current Branch')" name="branch" /> {{ branchLabel }} | ||||
|       </span> | ||||
|  |  | |||
|  | @ -130,7 +130,7 @@ export default { | |||
|         </div> | ||||
|         <div class="gl-w-full gl-display-flex"> | ||||
|           <ul | ||||
|             class="merge-request-tabs nav-tabs nav nav-links gl-display-flex gl-flex-nowrap gl-m-0 gl-p-0 gl-border-b-0" | ||||
|             class="merge-request-tabs nav-tabs nav nav-links gl-display-flex gl-flex-wrap-nowrap gl-m-0 gl-p-0 gl-border-b-0" | ||||
|           > | ||||
|             <li | ||||
|               v-for="(tab, index) in tabs" | ||||
|  |  | |||
|  | @ -82,7 +82,7 @@ export default { | |||
|         <status-icon :icon-name="data.icon.name" :size="12" class="gl-m-auto" /> | ||||
|       </div> | ||||
|       <div class="gl-w-full"> | ||||
|         <div class="gl-display-flex gl-flex-nowrap"> | ||||
|         <div class="gl-display-flex gl-flex-wrap-nowrap"> | ||||
|           <div class="gl-flex-wrap gl-display-flex gl-w-full"> | ||||
|             <div class="gl-display-flex gl-align-items-center"> | ||||
|               <p v-safe-html="generateText(data.text)" class="gl-m-0"></p> | ||||
|  |  | |||
|  | @ -1,56 +0,0 @@ | |||
| <script> | ||||
| import * as Sentry from '@sentry/browser'; | ||||
| import Vue from 'vue'; | ||||
| import Vuex from 'vuex'; | ||||
| 
 | ||||
| Vue.use(Vuex); | ||||
| 
 | ||||
| export default { | ||||
|   props: { | ||||
|     dashboardUrl: { | ||||
|       type: String, | ||||
|       required: false, | ||||
|       default: '', | ||||
|     }, | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       metricEmbedComponent: null, | ||||
|       namespace: 'alertMetrics', | ||||
|     }; | ||||
|   }, | ||||
|   mounted() { | ||||
|     if (this.dashboardUrl) { | ||||
|       Promise.all([ | ||||
|         import('~/monitoring/components/embeds/metric_embed.vue'), | ||||
|         import('~/monitoring/stores'), | ||||
|       ]) | ||||
|         .then(([{ default: MetricEmbed }, { monitoringDashboard }]) => { | ||||
|           this.$store = new Vuex.Store({ | ||||
|             modules: { | ||||
|               [this.namespace]: monitoringDashboard, | ||||
|             }, | ||||
|           }); | ||||
|           this.metricEmbedComponent = MetricEmbed; | ||||
|         }) | ||||
|         .catch((e) => Sentry.captureException(e)); | ||||
|     } | ||||
|   }, | ||||
| }; | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <div class="gl-py-3"> | ||||
|     <div v-if="dashboardUrl" ref="metricsChart"> | ||||
|       <component | ||||
|         :is="metricEmbedComponent" | ||||
|         v-if="metricEmbedComponent" | ||||
|         :dashboard-url="dashboardUrl" | ||||
|         :namespace="namespace" | ||||
|       /> | ||||
|     </div> | ||||
|     <div v-else ref="emptyState"> | ||||
|       {{ s__("AlertManagement|Metrics weren't available in the alerts payload.") }} | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | @ -295,7 +295,7 @@ export default { | |||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <div class="form-row gl-mb-5 work-item-assignees gl-relative gl-flex-nowrap"> | ||||
|   <div class="form-row gl-mb-5 work-item-assignees gl-relative gl-flex-wrap-nowrap"> | ||||
|     <span | ||||
|       :id="assigneesTitleId" | ||||
|       class="gl-font-weight-bold gl-mt-2 col-lg-2 col-3 gl-pt-2 min-w-fit-content gl-overflow-wrap-break" | ||||
|  |  | |||
|  | @ -272,7 +272,7 @@ export default { | |||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <div class="form-row gl-mb-5 work-item-labels gl-relative gl-flex-nowrap"> | ||||
|   <div class="form-row gl-mb-5 work-item-labels gl-relative gl-flex-wrap-nowrap"> | ||||
|     <span | ||||
|       :id="labelsTitleId" | ||||
|       class="gl-font-weight-bold gl-mt-2 col-lg-2 col-3 gl-pt-2 min-w-fit-content gl-overflow-wrap-break" | ||||
|  |  | |||
|  | @ -217,7 +217,7 @@ export default { | |||
| 
 | ||||
| <template> | ||||
|   <gl-form-group | ||||
|     class="work-item-dropdown gl-flex-nowrap" | ||||
|     class="work-item-dropdown gl-flex-wrap-nowrap" | ||||
|     :label="$options.i18n.MILESTONE" | ||||
|     label-for="milestone-value" | ||||
|     label-class="gl-pb-0! gl-mt-3 gl-overflow-wrap-break" | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ | |||
|     = text_field_tag :name, @name, placeholder: "My awesome project", class: "js-project-name form-control gl-form-input input-lg", autofocus: true, required: true, aria: { required: true }, data: { qa_selector: 'project_name_field' } | ||||
|   .form-group.col-12.col-sm-6.gl-pr-0 | ||||
|     = label_tag :namespace_id, _('Project URL'), class: 'label-bold' | ||||
|     .input-group.gl-flex-nowrap | ||||
|     .input-group.gl-flex-wrap-nowrap | ||||
|       - if current_user.can_select_namespace? | ||||
|         - namespace_id = namespace_id_from(params) | ||||
|         .js-vue-new-project-url-select{ data: { namespace_full_path: GroupFinder.new(current_user).execute(id: namespace_id)&.full_path || current_user.namespace.full_path, | ||||
|  |  | |||
|  | @ -16,7 +16,7 @@ | |||
|   .form-group.project-path.col-sm-6.gl-pr-0 | ||||
|     = f.label :namespace_id, class: 'label-bold' do | ||||
|       %span= _('Project URL') | ||||
|     .input-group.gl-flex-nowrap | ||||
|     .input-group.gl-flex-wrap-nowrap | ||||
|       - if current_user.can_select_namespace? | ||||
|         - namespace_id = namespace_id_from(params) | ||||
|         .js-vue-new-project-url-select{ data: { namespace_full_path: GroupFinder.new(current_user).execute(id: namespace_id)&.full_path || @current_user_group&.full_path, | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| - f ||= local_assigns[:f] | ||||
| 
 | ||||
| .project-templates-buttons | ||||
|   = gl_tabs_nav({ class: 'nav-links scrolling-tabs gl-display-flex gl-flex-grow-1 gl-flex-nowrap gl-border-0' }) do | ||||
|   = gl_tabs_nav({ class: 'nav-links scrolling-tabs gl-display-flex gl-flex-grow-1 gl-flex-wrap-nowrap gl-border-0' }) do | ||||
|     = gl_tab_link_to '#built-in', tab_class: 'built-in-tab', class: 'active', data: { toggle: 'tab' } do | ||||
|       = _('Built-in') | ||||
|       = gl_tab_counter_badge Gitlab::ProjectTemplate.all.count + Gitlab::SampleDataTemplate.all.count | ||||
|  |  | |||
|  | @ -27,7 +27,7 @@ | |||
|     = render "projects/merge_requests/mr_box" | ||||
|     .merge-request-tabs-holder{ class: "#{'js-tabs-affix' unless ENV['RAILS_ENV'] == 'test'} #{'gl-static' if moved_mr_sidebar_enabled?}" } | ||||
|       .merge-request-tabs-container.gl-display-flex.gl-justify-content-space-between{ class: "#{'is-merge-request' if Feature.enabled?(:moved_mr_sidebar, @project) && !fluid_layout}" } | ||||
|         %ul.merge-request-tabs.nav-tabs.nav.nav-links.gl-display-flex.gl-flex-nowrap.gl-m-0.gl-p-0{ class: "#{'gl-w-full gl-lg-w-auto!' if Feature.enabled?(:moved_mr_sidebar, @project)}" } | ||||
|         %ul.merge-request-tabs.nav-tabs.nav.nav-links.gl-display-flex.gl-flex-wrap-nowrap.gl-m-0.gl-p-0{ class: "#{'gl-w-full gl-lg-w-auto!' if Feature.enabled?(:moved_mr_sidebar, @project)}" } | ||||
|           = render "projects/merge_requests/tabs/tab", class: "notes-tab", qa_selector: "notes_tab" do | ||||
|             = tab_link_for @merge_request, :show, force_link: @commit.present? do | ||||
|               = _("Overview") | ||||
|  |  | |||
|  | @ -22,7 +22,7 @@ | |||
|         .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller | ||||
|           .fade-left= sprite_icon('chevron-lg-left', size: 12) | ||||
|           .fade-right= sprite_icon('chevron-lg-right', size: 12) | ||||
|           %ul.merge-request-tabs.nav.nav-tabs.nav-links.no-top.no-bottom.gl-display-flex.gl-flex-nowrap.gl-m-0.gl-p-0.js-tabs-affix | ||||
|           %ul.merge-request-tabs.nav.nav-tabs.nav-links.no-top.no-bottom.gl-display-flex.gl-flex-wrap-nowrap.gl-m-0.gl-p-0.js-tabs-affix | ||||
|             %li.commits-tab.new-tab | ||||
|               = link_to url_for(safe_params), data: {target: 'div#commits', action: 'new', toggle: 'tabvue'} do | ||||
|                 = _("Commits") | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ | |||
| - include_private = local_assigns.fetch(:include_private, false) | ||||
| - params[:scope] ||= [] | ||||
| 
 | ||||
| = gl_tabs_nav({ class: 'js-snippets-nav-tabs gl-border-b-0 gl-overflow-x-auto gl-flex-grow-1 gl-flex-nowrap' }) do | ||||
| = gl_tabs_nav({ class: 'js-snippets-nav-tabs gl-border-b-0 gl-overflow-x-auto gl-flex-grow-1 gl-flex-wrap-nowrap' }) do | ||||
|   = gl_tab_link_to subject_snippets_path(subject), { item_active: params[:scope].empty? } do | ||||
|     = _('All') | ||||
|     = gl_tab_counter_badge(include_private ? counts[:total] : counts[:are_public_or_internal]) | ||||
|  |  | |||
|  | @ -8,11 +8,9 @@ class AddIndexToVulnerabilityFindingsOnUuid < Gitlab::Database::Migration[2.1] | |||
|   # TODO: Index to be created synchronously in https://gitlab.com/gitlab-org/gitlab/-/issues/397740 | ||||
| 
 | ||||
|   def up | ||||
|     index_sql = <<-SQL | ||||
|     prepare_async_index_from_sql <<-SQL | ||||
|       CREATE UNIQUE INDEX CONCURRENTLY #{INDEX_NAME} ON vulnerability_occurrences (uuid) include (vulnerability_id); | ||||
|     SQL | ||||
| 
 | ||||
|     prepare_async_index_from_sql(:vulnerability_occurrences, INDEX_NAME, index_sql) | ||||
|   end | ||||
| 
 | ||||
|   def down | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| --- | ||||
| status: proposed | ||||
| status: ongoing | ||||
| creation-date: "2022-11-25" | ||||
| authors: [ "@theoretick" ] | ||||
| coach: "@DylanGriffith" | ||||
|  | @ -123,6 +123,23 @@ Given our existing familiarity with the tool and its extensibility, it should | |||
| remain our engine of choice. Changes to the detection engine are out of scope | ||||
| unless benchmarking unveils performance concerns. | ||||
| 
 | ||||
| Notable alternatives include high-performance regex engines such as [hyperscan](https://github.com/intel/hyperscan) or it's portable fork [vectorscan](https://github.com/VectorCamp/vectorscan). | ||||
| 
 | ||||
| ### High-level architecture | ||||
| 
 | ||||
| The implementation of the secret scanning service is highly dependent on the outcomes of our benchmarking | ||||
| and capacity planning against both GitLab.com and our | ||||
| [Reference Architectures](../../../administration/reference_architectures/index.md). | ||||
| As the scanning capability must be an on-by-default component of both our SaaS and self-managed | ||||
| instances [the PoC](#iterations), the deployment characteristics must be considered to determine whether | ||||
| this is a standalone component or executed as a subprocess of the existing Sidekiq worker fleet | ||||
| (similar to the implementation of our Elasticsearch indexing service). | ||||
| 
 | ||||
| Similarly, the scan target volume will require a robust and scalable enqueueing system to limit resource consumption. | ||||
| 
 | ||||
| See [this thread](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/105142#note_1194863310) | ||||
| for past discussion around scaling approaches. | ||||
| 
 | ||||
| ### Push event detection flow | ||||
| 
 | ||||
| ```mermaid | ||||
|  | @ -151,17 +168,20 @@ sequenceDiagram | |||
| 
 | ||||
| ## Iterations | ||||
| 
 | ||||
| 1. Requirements definition for detection coverage and actions | ||||
| 1. PoC of secret scanning service | ||||
|     1. gRPC commit retrieval from Gitaly | ||||
|     1. blob scanning | ||||
|     1. benchmarking of issuables, comments, job logs and blobs to gain confidence that the total costs will be viable | ||||
| 1. Implementation of secret scanning service MVC (targeting individual commits) | ||||
| 1. Security and readiness review | ||||
| 1. Deployment and monitoring | ||||
| 1. Implementation of secret scanning service MVC (targeting arbitrary text blobs) | ||||
| 1. Deployment and monitoring | ||||
| 1. High priority domain object rollout (priority `TBD`) | ||||
|     1. Issuable comments | ||||
|     1. Issuable bodies | ||||
|     1. Job logs | ||||
| - [x] Define [requirements for detection coverage and actions](https://gitlab.com/gitlab-org/gitlab/-/issues/376716) | ||||
| - [x] Implement [Clientside detection of GitLab tokens within comments/issues](https://gitlab.com/gitlab-org/gitlab/-/issues/368434) | ||||
| - [ ] PoC of secret scanning service | ||||
|       - [ ] Benchmarking of issuables, comments, job logs and blobs to gain confidence that the total costs will be viable | ||||
|       - [ ] Capacity planning for addition of service component to Reference Architectures headroom | ||||
|       - [ ] Service capabilities | ||||
|           - [ ] gRPC commit retrieval from Gitaly | ||||
|           - [ ] blob scanning | ||||
| - [ ] Implementation of secret scanning service MVC (targeting individual commits) | ||||
| - [ ] Security and readiness review | ||||
| - [ ] Deployment and monitoring | ||||
| - [ ] Implementation of secret scanning service MVC (targeting arbitrary text blobs) | ||||
| - [ ] Deployment and monitoring | ||||
| - [ ] High priority domain object rollout (priority `TBD`) | ||||
|       - [ ] Issuable comments | ||||
|       - [ ] Issuable bodies | ||||
|       - [ ] Job logs | ||||
|  |  | |||
|  | @ -76,7 +76,7 @@ tab of the pipeline's details page. | |||
| 
 | ||||
| ### Project quality view | ||||
| 
 | ||||
| The project quality view displays an overview of the code quality findings. | ||||
| The project quality view displays an overview of the code quality findings. The view can be found under **Analytics > CI/CD**, and requires [`project_quality_summary_page`](../../user/feature_flags.md) feature flag to be enabled for this particular project. | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
|  |  | |||
|  | @ -15,6 +15,9 @@ We recommend that you use fuzz testing in addition to the other security scanner | |||
| and your own test processes. If you're using [GitLab CI/CD](../../../ci/index.md), | ||||
| you can run your coverage-guided fuzz testing as part your CI/CD workflow. | ||||
| 
 | ||||
| <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> | ||||
| For an overview, see [Coverage Fuzzing](https://www.youtube.com/watch?v=bbIenVVcjW0). | ||||
| 
 | ||||
| ## Coverage-guided fuzz testing process | ||||
| 
 | ||||
| The fuzz testing process: | ||||
|  |  | |||
|  | @ -15,6 +15,9 @@ visible from the source code. | |||
| Dynamic Application Security Testing (DAST) examines applications for | ||||
| vulnerabilities like these in deployed environments. | ||||
| 
 | ||||
| <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> | ||||
| For an overview, see [Dynamic Application Security Testing (DAST)](https://www.youtube.com/watch?v=nbeDUoLZJTo). | ||||
| 
 | ||||
| NOTE: | ||||
| To learn how four of the top six attacks were application-based and how | ||||
| to protect your organization, download our | ||||
|  |  | |||
|  | @ -13,6 +13,9 @@ info: To determine the technical writer assigned to the Stage/Group associated w | |||
| Use the dependency list to review your project's dependencies and key | ||||
| details about those dependencies, including their known vulnerabilities. It is a collection of dependencies in your project, including existing and new findings. | ||||
| 
 | ||||
| <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> | ||||
| For an overview, see [Project Dependency](https://www.youtube.com/watch?v=ckqkn9Tnbw4). | ||||
| 
 | ||||
| To see the dependency list, go to your project and select **Security and Compliance > Dependency list**. | ||||
| 
 | ||||
| This information is sometimes referred to as a Software Bill of Materials, SBOM, or BOM. | ||||
|  |  | |||
|  | @ -350,5 +350,17 @@ a minimum of the Guest role in the specified Dependency Proxy group: | |||
| ```plaintext | ||||
| ERROR: gitlab.example.com:443/group1/dependency_proxy/containers/alpine:latest: not found | ||||
| 
 | ||||
| failed to solve with frontend dockerfile.v0: failed to create LLB definition: gitlab.example.com:443/group1/dependency_proxy/containers/alpine:latest: not found | ||||
| failed to solve with frontend dockerfile.v0: failed to create LLB definition:  | ||||
| gitlab.example.com:443/group1/dependency_proxy/containers/alpine:latest: not found | ||||
| ``` | ||||
| 
 | ||||
| ```plaintext | ||||
| ERROR: Job failed: failed to pull image | ||||
| "gitlab.example.com:443/group1/dependency_proxy/containers/alpine:latest" with specified policies [always]: | ||||
| Error response from daemon: error parsing HTTP 404 response body: | ||||
| unexpected end of JSON input: "" (manager.go:237:1s) | ||||
| ``` | ||||
| 
 | ||||
| NOTE: | ||||
| The work to correctly display `Access denied` errors in such cases is being tracked in the following issue: | ||||
| [Dependency proxy error reporting confusing - "404/not found" should be "access denied" when not group member](https://gitlab.com/gitlab-org/gitlab/-/issues/354826). | ||||
|  |  | |||
|  | @ -3,14 +3,17 @@ stage: Manage | |||
| group: Integrations | ||||
| info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments | ||||
| --- | ||||
| <!--- start_remove The following content will be removed on remove_date: '2024-05-22' --> | ||||
| 
 | ||||
| # Slack notifications **(FREE)** | ||||
| # Slack notifications (deprecated) **(FREE)** | ||||
| 
 | ||||
| WARNING: | ||||
| This feature was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/372411) on GitLab.com | ||||
| in GitLab 15.9 and is [planned for removal](https://gitlab.com/groups/gitlab-org/-/epics/8673). | ||||
| For GitLab.com, use the [GitLab for Slack app](gitlab_slack_application.md) instead. | ||||
| For self-managed GitLab instances, you can continue to use this feature. | ||||
| This feature was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/372411) in GitLab 15.9 | ||||
| and is planned for removal in 17.0. Use the [GitLab for Slack app](gitlab_slack_application.md) instead. | ||||
| This change is a breaking change. | ||||
| 
 | ||||
| For the planned support of the GitLab for Slack app for self-managed instances, | ||||
| see [epic 1211](https://gitlab.com/groups/gitlab-org/-/epics/1211). | ||||
| 
 | ||||
| The Slack notifications integration enables your GitLab project to send events | ||||
| (such as issue creation) to your existing Slack team as notifications. Setting up | ||||
|  | @ -150,3 +153,5 @@ p.each do |project| | |||
|   project.slack_integration.update!(:active, false) | ||||
| end | ||||
| ``` | ||||
| 
 | ||||
| <!--- end_remove --> | ||||
|  |  | |||
|  | @ -77,11 +77,13 @@ module Gitlab | |||
|           async_index | ||||
|         end | ||||
| 
 | ||||
|         def prepare_async_index_from_sql(table_name, index_name, definition) | ||||
|         def prepare_async_index_from_sql(definition) | ||||
|           Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas.require_ddl_mode! | ||||
| 
 | ||||
|           return unless async_index_creation_available? | ||||
| 
 | ||||
|           table_name, index_name = extract_table_and_index_names_from_concurrent_index!(definition) | ||||
| 
 | ||||
|           if index_name_exists?(table_name, index_name) | ||||
|             Gitlab::AppLogger.warn( | ||||
|               message: 'Index not prepared because it already exists', | ||||
|  | @ -137,7 +139,30 @@ module Gitlab | |||
|         end | ||||
| 
 | ||||
|         def async_index_creation_available? | ||||
|           connection.table_exists?(:postgres_async_indexes) | ||||
|           table_exists?(:postgres_async_indexes) | ||||
|         end | ||||
| 
 | ||||
|         private | ||||
| 
 | ||||
|         delegate :table_exists?, to: :connection, private: true | ||||
| 
 | ||||
|         def extract_table_and_index_names_from_concurrent_index!(definition) | ||||
|           statement = index_statement_from!(definition) | ||||
| 
 | ||||
|           raise 'Index statement not found!' unless statement | ||||
|           raise 'Index must be created concurrently!' unless statement.concurrent | ||||
|           raise 'Table does not exist!' unless table_exists?(statement.relation.relname) | ||||
| 
 | ||||
|           [statement.relation.relname, statement.idxname] | ||||
|         end | ||||
| 
 | ||||
|         # This raises `PgQuery::ParseError` if the given statement | ||||
|         # is syntactically incorrect, therefore, validates that the | ||||
|         # index definition is correct. | ||||
|         def index_statement_from!(definition) | ||||
|           parsed_query = PgQuery.parse(definition) | ||||
| 
 | ||||
|           parsed_query.tree.stmts[0].stmt.index_stmt | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|  |  | |||
|  | @ -3822,9 +3822,6 @@ msgstr "" | |||
| msgid "AlertManagement|Metrics" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "AlertManagement|Metrics weren't available in the alerts payload." | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "AlertManagement|More information" | ||||
| msgstr "" | ||||
| 
 | ||||
|  | @ -25468,6 +25465,9 @@ msgstr "" | |||
| msgid "Learn more." | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Learn more: %{url}" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "LearnGitLab|%{percentage}%{percentSymbol} completed" | ||||
| msgstr "" | ||||
| 
 | ||||
|  | @ -28369,19 +28369,10 @@ msgstr "" | |||
| msgid "NamespaceStorageSize|push to your repository, create pipelines, create issues or add comments. To reduce storage capacity, delete unused repositories, artifacts, wikis, issues, and pipelines." | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "NamespaceStorage|%{name_with_link} namespace has approximately %{percent} (%{size}) namespace storage space remaining." | ||||
| msgid "NamespaceStorage|%{name_with_link} is now read-only. Projects under this namespace are locked and actions are restricted." | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "NamespaceStorage|%{name_with_link} namespace has exceeded its namespace storage limit." | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "NamespaceStorage|%{name}(%{url}) namespace has approximately %{percent} (%{size}) namespace storage space remaining." | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "NamespaceStorage|%{name}(%{url}) namespace has exceeded its namespace storage limit." | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "NamespaceStorage|Action required: Approximately %{percentage_of_available_storage}%% of namespace storage remains for %{namespace_name}" | ||||
| msgid "NamespaceStorage|%{name} is now read-only. Projects under this namespace are locked and actions are restricted." | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "NamespaceStorage|Action required: Storage has been exceeded for %{namespace_name}" | ||||
|  | @ -28390,10 +28381,37 @@ msgstr "" | |||
| msgid "NamespaceStorage|Buy more storage" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "NamespaceStorage|We recommend that you buy additional storage to ensure your service is not interrupted." | ||||
| msgid "NamespaceStorage|If %{name_with_link} exceeds the storage quota, all projects in the namespace will be locked and actions will be restricted." | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "NamespaceStorage|We recommend that you buy additional storage to resume normal service." | ||||
| msgid "NamespaceStorage|If %{name} exceeds the storage quota, all projects in the namespace will be locked and actions will be restricted." | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "NamespaceStorage|Learn about which actions are restricted: %{url}" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "NamespaceStorage|Learn about which actions become restricted: %{url}" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "NamespaceStorage|Manage your storage usage or purchase additional storage." | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "NamespaceStorage|See storage usage statistics: %{url}" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "NamespaceStorage|Which actions are restricted?" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "NamespaceStorage|Which actions become restricted?" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "NamespaceStorage|You have used %{used_storage_percentage} of the storage quota for %{name_with_link} (%{current_size} of %{limit})." | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "NamespaceStorage|You have used %{used_storage_percentage} of the storage quota for %{name} (%{current_size} of %{limit})." | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "NamespaceStorage|You have used %{used_storage_percentage}%% of the storage quota for %{namespace_name}" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "NamespaceUserCap|Pending users must be reviewed and approved by a group owner. Learn more about %{user_caps_link_start}user caps%{link_end} and %{users_pending_approval_link_start}users pending approval%{link_end}." | ||||
|  |  | |||
|  | @ -348,7 +348,8 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_ | |||
|           expect(page).to have_content('Invalid two-factor code') | ||||
|         end | ||||
| 
 | ||||
|         it 'allows login with invalid code, then valid code' do | ||||
|         it 'allows login with invalid code, then valid code', | ||||
|           quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/402322' do | ||||
|           expect(authentication_metrics) | ||||
|             .to increment(:user_authenticated_counter) | ||||
|             .and increment(:user_two_factor_authenticated_counter) | ||||
|  | @ -362,7 +363,8 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_ | |||
|           expect(page).to have_current_path root_path, ignore_query: true | ||||
|         end | ||||
| 
 | ||||
|         it 'triggers ActiveSession.cleanup for the user', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/402322' do | ||||
|         it 'triggers ActiveSession.cleanup for the user', | ||||
|           quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/402322' do | ||||
|           expect(authentication_metrics) | ||||
|             .to increment(:user_authenticated_counter) | ||||
|             .and increment(:user_two_factor_authenticated_counter) | ||||
|  |  | |||
|  | @ -1,63 +0,0 @@ | |||
| import { shallowMount } from '@vue/test-utils'; | ||||
| import axios from 'axios'; | ||||
| import MockAdapter from 'axios-mock-adapter'; | ||||
| import { nextTick } from 'vue'; | ||||
| import waitForPromises from 'helpers/wait_for_promises'; | ||||
| import MetricEmbed from '~/monitoring/components/embeds/metric_embed.vue'; | ||||
| import AlertMetrics from '~/vue_shared/alert_details/components/alert_metrics.vue'; | ||||
| 
 | ||||
| jest.mock('~/monitoring/stores', () => ({ | ||||
|   monitoringDashboard: {}, | ||||
| })); | ||||
| 
 | ||||
| jest.mock('~/monitoring/components/embeds/metric_embed.vue', () => ({ | ||||
|   render(h) { | ||||
|     return h('div'); | ||||
|   }, | ||||
| })); | ||||
| 
 | ||||
| describe('Alert Metrics', () => { | ||||
|   let wrapper; | ||||
|   const mock = new MockAdapter(axios); | ||||
| 
 | ||||
|   function mountComponent({ props } = {}) { | ||||
|     wrapper = shallowMount(AlertMetrics, { | ||||
|       propsData: { | ||||
|         ...props, | ||||
|       }, | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   const findChart = () => wrapper.findComponent(MetricEmbed); | ||||
|   const findEmptyState = () => wrapper.findComponent({ ref: 'emptyState' }); | ||||
| 
 | ||||
|   afterEach(() => { | ||||
|     if (wrapper) { | ||||
|       wrapper.destroy(); | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
|   afterAll(() => { | ||||
|     mock.restore(); | ||||
|   }); | ||||
| 
 | ||||
|   describe('Empty state', () => { | ||||
|     it('should display a message when metrics dashboard url is not provided', () => { | ||||
|       mountComponent(); | ||||
|       expect(findChart().exists()).toBe(false); | ||||
|       expect(findEmptyState().text()).toBe("Metrics weren't available in the alerts payload."); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   describe('Chart', () => { | ||||
|     it('should be rendered when dashboard url is provided', async () => { | ||||
|       mountComponent({ props: { dashboardUrl: 'metrics.url' } }); | ||||
| 
 | ||||
|       await waitForPromises(); | ||||
|       await nextTick(); | ||||
| 
 | ||||
|       expect(findEmptyState().exists()).toBe(false); | ||||
|       expect(findChart().exists()).toBe(true); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
|  | @ -144,10 +144,10 @@ RSpec.describe Gitlab::Database::AsyncIndexes::MigrationHelpers, feature_categor | |||
|   end | ||||
| 
 | ||||
|   describe '#prepare_async_index_from_sql' do | ||||
|     let(:index_definition) { "CREATE INDEX #{index_name} ON #{table_name} USING btree(id)" } | ||||
|     let(:index_definition) { "CREATE INDEX CONCURRENTLY #{index_name} ON #{table_name} USING btree(id)" } | ||||
| 
 | ||||
|     subject(:prepare_async_index_from_sql) do | ||||
|       migration.prepare_async_index_from_sql(table_name, index_name, index_definition) | ||||
|       migration.prepare_async_index_from_sql(index_definition) | ||||
|     end | ||||
| 
 | ||||
|     before do | ||||
|  | @ -162,38 +162,68 @@ RSpec.describe Gitlab::Database::AsyncIndexes::MigrationHelpers, feature_categor | |||
|       expect(Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas).to have_received(:require_ddl_mode!) | ||||
|     end | ||||
| 
 | ||||
|     context 'when the async index creation is not available' do | ||||
|       before do | ||||
|         connection.drop_table(:postgres_async_indexes) | ||||
|       end | ||||
|     context 'when the given index is invalid' do | ||||
|       let(:index_definition) { "SELECT FROM users" } | ||||
| 
 | ||||
|       it 'does not raise an error' do | ||||
|         expect { prepare_async_index_from_sql }.not_to raise_error | ||||
|       it 'raises a RuntimeError' do | ||||
|         expect { prepare_async_index_from_sql }.to raise_error(RuntimeError, 'Index statement not found!') | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     context 'when the async index creation is available' do | ||||
|       context 'when there is already an index with the given name' do | ||||
|         before do | ||||
|           connection.add_index(table_name, 'id', name: index_name) | ||||
|         end | ||||
|     context 'when the given index is valid' do | ||||
|       context 'when the index algorithm is not concurrent' do | ||||
|         let(:index_definition) { "CREATE INDEX #{index_name} ON #{table_name} USING btree(id)" } | ||||
| 
 | ||||
|         it 'does not create the async index record' do | ||||
|           expect { prepare_async_index_from_sql }.not_to change { index_model.where(name: index_name).count } | ||||
|         it 'raises a RuntimeError' do | ||||
|           expect { prepare_async_index_from_sql }.to raise_error(RuntimeError, 'Index must be created concurrently!') | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context 'when there is no index with the given name' do | ||||
|         let(:async_index) { index_model.find_by(name: index_name) } | ||||
|       context 'when the index algorithm is concurrent' do | ||||
|         context 'when the statement tries to create an index for non-existing table' do | ||||
|           let(:index_definition) { "CREATE INDEX CONCURRENTLY #{index_name} ON foo_table USING btree(id)" } | ||||
| 
 | ||||
|         it 'creates the async index record' do | ||||
|           expect { prepare_async_index_from_sql }.to change { index_model.where(name: index_name).count }.by(1) | ||||
|           it 'raises a RuntimeError' do | ||||
|             expect { prepare_async_index_from_sql }.to raise_error(RuntimeError, 'Table does not exist!') | ||||
|           end | ||||
|         end | ||||
| 
 | ||||
|         it 'sets the async index attributes correctly' do | ||||
|           prepare_async_index_from_sql | ||||
|         context 'when the statement tries to create an index for an existing table' do | ||||
|           context 'when the async index creation is not available' do | ||||
|             before do | ||||
|               connection.drop_table(:postgres_async_indexes) | ||||
|             end | ||||
| 
 | ||||
|           expect(async_index).to have_attributes(table_name: table_name, definition: index_definition) | ||||
|             it 'does not raise an error' do | ||||
|               expect { prepare_async_index_from_sql }.not_to raise_error | ||||
|             end | ||||
|           end | ||||
| 
 | ||||
|           context 'when the async index creation is available' do | ||||
|             context 'when there is already an index with the given name' do | ||||
|               before do | ||||
|                 connection.add_index(table_name, 'id', name: index_name) | ||||
|               end | ||||
| 
 | ||||
|               it 'does not create the async index record' do | ||||
|                 expect { prepare_async_index_from_sql }.not_to change { index_model.where(name: index_name).count } | ||||
|               end | ||||
|             end | ||||
| 
 | ||||
|             context 'when there is no index with the given name' do | ||||
|               let(:async_index) { index_model.find_by(name: index_name) } | ||||
| 
 | ||||
|               it 'creates the async index record' do | ||||
|                 expect { prepare_async_index_from_sql }.to change { index_model.where(name: index_name).count }.by(1) | ||||
|               end | ||||
| 
 | ||||
|               it 'sets the async index attributes correctly' do | ||||
|                 prepare_async_index_from_sql | ||||
| 
 | ||||
|                 expect(async_index).to have_attributes(table_name: table_name, definition: index_definition) | ||||
|               end | ||||
|             end | ||||
|           end | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|  |  | |||
|  | @ -151,9 +151,27 @@ RSpec.describe Notify do | |||
|         it 'has the correct subject and body' do | ||||
|           aggregate_failures do | ||||
|             is_expected.to have_referable_subject(issue, reply: true) | ||||
|             is_expected.to have_body_text(previous_assignee.name) | ||||
|             is_expected.to have_body_text(assignee.name) | ||||
|             is_expected.to have_body_text(project_issue_path(project, issue)) | ||||
|             is_expected.to have_body_text("Assignee changed from <strong>#{previous_assignee.name}</strong> to <strong>#{assignee.name}</strong>") | ||||
|             is_expected.to have_body_text(%(<a href="#{project_issue_url(project, issue)}">view it on GitLab</a>)) | ||||
|             is_expected.to have_body_text("You're receiving this email because of your account") | ||||
|           end | ||||
|         end | ||||
| 
 | ||||
|         context 'without new assignee' do | ||||
|           before do | ||||
|             issue.update!(assignees: []) | ||||
|           end | ||||
| 
 | ||||
|           it 'uses "Unassigned" placeholder' do | ||||
|             is_expected.to have_body_text("Assignee changed from <strong>#{previous_assignee.name}</strong> to <strong>Unassigned</strong>") | ||||
|           end | ||||
|         end | ||||
| 
 | ||||
|         context 'without previous assignees' do | ||||
|           subject { described_class.reassigned_issue_email(recipient.id, issue.id, [], current_user.id) } | ||||
| 
 | ||||
|           it 'uses short text' do | ||||
|             is_expected.to have_body_text("Assignee changed to <strong>#{assignee.name}</strong>") | ||||
|           end | ||||
|         end | ||||
| 
 | ||||
|  |  | |||
|  | @ -102,7 +102,7 @@ RSpec.describe CreateTestFailureIssues, feature_category: :tooling do | |||
| 
 | ||||
|           ### Test file path | ||||
| 
 | ||||
|           [`#{failed_test['file']}`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/#{failed_test['file']}) | ||||
|           [`#{failed_test['file']}`](https://gitlab.com/example/gitlab/-/blob/master/#{failed_test['file']}) | ||||
| 
 | ||||
|           <!-- Don't add anything after the report list since it's updated automatically --> | ||||
|           ### Reports (1) | ||||
|  | @ -131,8 +131,11 @@ RSpec.describe CreateTestFailureIssues, feature_category: :tooling do | |||
|         end | ||||
| 
 | ||||
|         it 'calls CreateIssue#execute(payload)' do | ||||
|           stub_const("#{described_class}::FILE_BASE_URL", 'https://gitlab.com/example/gitlab/-/blob/master/') | ||||
| 
 | ||||
|           expect(CreateIssue).to receive(:new).with(project: project, api_token: api_token) | ||||
|             .and_return(create_issue_stub) | ||||
| 
 | ||||
|           expect(create_issue_stub).to receive(:execute).with(expected_create_payload).and_return(issue_stub) | ||||
| 
 | ||||
|           creator.upsert(failed_test) | ||||
|  | @ -152,7 +155,7 @@ RSpec.describe CreateTestFailureIssues, feature_category: :tooling do | |||
| 
 | ||||
|           ### Test file path | ||||
| 
 | ||||
|           [`#{failed_test['file']}`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/#{failed_test['file']}) | ||||
|           [`#{failed_test['file']}`](https://gitlab.com/example/gitlab/-/blob/master/#{failed_test['file']}) | ||||
| 
 | ||||
|           <!-- Don't add anything after the report list since it's updated automatically --> | ||||
|           ### Reports (1) | ||||
|  | @ -165,7 +168,9 @@ RSpec.describe CreateTestFailureIssues, feature_category: :tooling do | |||
|           double('Issue', iid: 42, title: issue_title, description: issue_description, web_url: 'issue_web_url') | ||||
|         end | ||||
| 
 | ||||
|         let(:update_issue_stub) { double('UpdateIssue') } | ||||
|         let(:update_issue_stub) do | ||||
|           double('UpdateIssue', description: latest_format_issue_description) | ||||
|         end | ||||
| 
 | ||||
|         let(:expected_update_payload) do | ||||
|           { | ||||
|  |  | |||
|  | @ -1107,19 +1107,37 @@ RSpec.describe Issues::UpdateService, :mailer, feature_category: :team_planning | |||
|     end | ||||
| 
 | ||||
|     context 'updating asssignee_id' do | ||||
|       it 'changes assignee' do | ||||
|         expect_next_instance_of(NotificationService::Async) do |service| | ||||
|           expect(service).to receive(:reassigned_issue).with(issue, user, [user3]) | ||||
|         end | ||||
| 
 | ||||
|         update_issue(assignee_ids: [user2.id]) | ||||
| 
 | ||||
|         expect(issue.reload.assignees).to eq([user2]) | ||||
|       end | ||||
| 
 | ||||
|       it 'does not update assignee when assignee_id is invalid' do | ||||
|         expect(NotificationService).not_to receive(:new) | ||||
| 
 | ||||
|         update_issue(assignee_ids: [-1]) | ||||
| 
 | ||||
|         expect(issue.reload.assignees).to eq([user3]) | ||||
|       end | ||||
| 
 | ||||
|       it 'unassigns assignee when user id is 0' do | ||||
|         expect_next_instance_of(NotificationService::Async) do |service| | ||||
|           expect(service).to receive(:reassigned_issue).with(issue, user, [user3]) | ||||
|         end | ||||
| 
 | ||||
|         update_issue(assignee_ids: [0]) | ||||
| 
 | ||||
|         expect(issue.reload.assignees).to be_empty | ||||
|       end | ||||
| 
 | ||||
|       it 'does not update assignee_id when user cannot read issue' do | ||||
|         expect(NotificationService).not_to receive(:new) | ||||
| 
 | ||||
|         update_issue(assignee_ids: [create(:user).id]) | ||||
| 
 | ||||
|         expect(issue.reload.assignees).to eq([user3]) | ||||
|  | @ -1130,6 +1148,8 @@ RSpec.describe Issues::UpdateService, :mailer, feature_category: :team_planning | |||
| 
 | ||||
|         levels.each do |level| | ||||
|           it "does not update with unauthorized assignee when project is #{Gitlab::VisibilityLevel.level_name(level)}" do | ||||
|             expect(NotificationService).not_to receive(:new) | ||||
| 
 | ||||
|             assignee = create(:user) | ||||
|             project.update!(visibility_level: level) | ||||
|             feature_visibility_attr = :"#{issue.model_name.plural}_access_level" | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue