Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
		
							parent
							
								
									ba557e8fea
								
							
						
					
					
						commit
						7503415f61
					
				|  | @ -3160,7 +3160,6 @@ Layout/LineLength: | ||||||
|     - 'lib/gitlab/database/reindexing/coordinator.rb' |     - 'lib/gitlab/database/reindexing/coordinator.rb' | ||||||
|     - 'lib/gitlab/database/reindexing/grafana_notifier.rb' |     - 'lib/gitlab/database/reindexing/grafana_notifier.rb' | ||||||
|     - 'lib/gitlab/database/reindexing/reindex_concurrently.rb' |     - 'lib/gitlab/database/reindexing/reindex_concurrently.rb' | ||||||
|     - 'lib/gitlab/database/schema_cleaner.rb' |  | ||||||
|     - 'lib/gitlab/database/schema_migrations/context.rb' |     - 'lib/gitlab/database/schema_migrations/context.rb' | ||||||
|     - 'lib/gitlab/database/similarity_score.rb' |     - 'lib/gitlab/database/similarity_score.rb' | ||||||
|     - 'lib/gitlab/database/with_lock_retries.rb' |     - 'lib/gitlab/database/with_lock_retries.rb' | ||||||
|  |  | ||||||
|  | @ -5,9 +5,9 @@ import { getCookie, setCookie } from '~/lib/utils/common_utils'; | ||||||
| import ValueStreamMetrics from '~/analytics/shared/components/value_stream_metrics.vue'; | import ValueStreamMetrics from '~/analytics/shared/components/value_stream_metrics.vue'; | ||||||
| import { VSA_METRICS_GROUPS } from '~/analytics/shared/constants'; | import { VSA_METRICS_GROUPS } from '~/analytics/shared/constants'; | ||||||
| import { toYmd } from '~/analytics/shared/utils'; | import { toYmd } from '~/analytics/shared/utils'; | ||||||
| import PathNavigation from '~/cycle_analytics/components/path_navigation.vue'; | import PathNavigation from '~/analytics/cycle_analytics/components/path_navigation.vue'; | ||||||
| import StageTable from '~/cycle_analytics/components/stage_table.vue'; | import StageTable from '~/analytics/cycle_analytics/components/stage_table.vue'; | ||||||
| import ValueStreamFilters from '~/cycle_analytics/components/value_stream_filters.vue'; | import ValueStreamFilters from '~/analytics/cycle_analytics/components/value_stream_filters.vue'; | ||||||
| import UrlSync from '~/vue_shared/components/url_sync.vue'; | import UrlSync from '~/vue_shared/components/url_sync.vue'; | ||||||
| import { __ } from '~/locale'; | import { __ } from '~/locale'; | ||||||
| import { SUMMARY_METRICS_REQUEST, METRICS_REQUESTS } from '../constants'; | import { SUMMARY_METRICS_REQUEST, METRICS_REQUESTS } from '../constants'; | ||||||
|  | @ -8,7 +8,7 @@ import { | ||||||
|   GlTable, |   GlTable, | ||||||
|   GlBadge, |   GlBadge, | ||||||
| } from '@gitlab/ui'; | } from '@gitlab/ui'; | ||||||
| import FormattedStageCount from '~/cycle_analytics/components/formatted_stage_count.vue'; | import FormattedStageCount from '~/analytics/cycle_analytics/components/formatted_stage_count.vue'; | ||||||
| import { __ } from '~/locale'; | import { __ } from '~/locale'; | ||||||
| import Tracking from '~/tracking'; | import Tracking from '~/tracking'; | ||||||
| import { | import { | ||||||
|  | @ -3,7 +3,7 @@ import { | ||||||
|   extractFilterQueryParameters, |   extractFilterQueryParameters, | ||||||
|   extractPaginationQueryParameters, |   extractPaginationQueryParameters, | ||||||
| } from '~/analytics/shared/utils'; | } from '~/analytics/shared/utils'; | ||||||
| import Translate from '../vue_shared/translate'; | import Translate from '~/vue_shared/translate'; | ||||||
| import CycleAnalytics from './components/base.vue'; | import CycleAnalytics from './components/base.vue'; | ||||||
| import createStore from './store'; | import createStore from './store'; | ||||||
| import { buildCycleAnalyticsInitialData } from './utils'; | import { buildCycleAnalyticsInitialData } from './utils'; | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import { | import { | ||||||
|   PAGINATION_SORT_FIELD_END_EVENT, |   PAGINATION_SORT_FIELD_END_EVENT, | ||||||
|   PAGINATION_SORT_DIRECTION_DESC, |   PAGINATION_SORT_DIRECTION_DESC, | ||||||
| } from '~/cycle_analytics/constants'; | } from '~/analytics/cycle_analytics/constants'; | ||||||
| 
 | 
 | ||||||
| export default () => ({ | export default () => ({ | ||||||
|   id: null, |   id: null, | ||||||
|  | @ -1,3 +1,3 @@ | ||||||
| import initCycleAnalytics from '~/cycle_analytics'; | import initCycleAnalytics from '~/analytics/cycle_analytics'; | ||||||
| 
 | 
 | ||||||
| initCycleAnalytics(); | initCycleAnalytics(); | ||||||
|  |  | ||||||
|  | @ -2334,9 +2334,7 @@ class User < ApplicationRecord | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def check_password_weakness |   def check_password_weakness | ||||||
|     if Feature.enabled?(:block_weak_passwords) && |     if password.present? && Security::WeakPasswords.weak_for_user?(password, self) | ||||||
|         password.present? && |  | ||||||
|         Security::WeakPasswords.weak_for_user?(password, self) |  | ||||||
|       errors.add(:password, _('must not contain commonly used combinations of words and letters')) |       errors.add(:password, _('must not contain commonly used combinations of words and letters')) | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  | @ -1,8 +0,0 @@ | ||||||
| --- |  | ||||||
| name: block_weak_passwords |  | ||||||
| introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/86310 |  | ||||||
| rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/363445 |  | ||||||
| milestone: '15.4' |  | ||||||
| type: development |  | ||||||
| group: group::authentication and authorization |  | ||||||
| default_enabled: false |  | ||||||
|  | @ -1,8 +0,0 @@ | ||||||
| --- |  | ||||||
| name: dast_api_scanner |  | ||||||
| introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73564 |  | ||||||
| rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/345837 |  | ||||||
| milestone: '14.7' |  | ||||||
| type: development |  | ||||||
| group: group::dynamic analysis |  | ||||||
| default_enabled: true |  | ||||||
|  | @ -0,0 +1,8 @@ | ||||||
|  | --- | ||||||
|  | name: automatic_lock_writes_on_table | ||||||
|  | introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/99287 | ||||||
|  | rollout_issue_url: | ||||||
|  | milestone: '15.7' | ||||||
|  | type: ops | ||||||
|  | group: group::pods | ||||||
|  | default_enabled: false | ||||||
|  | @ -14,7 +14,7 @@ A short downtime is expected for all methods. | ||||||
| 
 | 
 | ||||||
| ## Omnibus installations | ## Omnibus installations | ||||||
| 
 | 
 | ||||||
| If you have used the [Omnibus packages](https://about.gitlab.com/install/) to install GitLab, then | If you have used the [Omnibus packages](https://about.gitlab.com/install/) to install GitLab, | ||||||
| you should already have `gitlab-ctl` in your `PATH`. | you should already have `gitlab-ctl` in your `PATH`. | ||||||
| 
 | 
 | ||||||
| `gitlab-ctl` interacts with the Omnibus packages and can be used to restart the | `gitlab-ctl` interacts with the Omnibus packages and can be used to restart the | ||||||
|  | @ -88,16 +88,14 @@ sudo gitlab-ctl reconfigure | ||||||
| Reconfiguring GitLab should occur in the event that something in its | Reconfiguring GitLab should occur in the event that something in its | ||||||
| configuration (`/etc/gitlab/gitlab.rb`) has changed. | configuration (`/etc/gitlab/gitlab.rb`) has changed. | ||||||
| 
 | 
 | ||||||
| When you run this command, [Chef](https://www.chef.io/products/chef-infra), the underlying configuration management | When you run `gitlab-ctl reconfigure`, [Chef](https://www.chef.io/products/chef-infra), | ||||||
| application that powers Omnibus GitLab, makes sure that all things like directories, | the underlying configuration management application that powers Omnibus GitLab, runs some checks. | ||||||
| permissions, and services are in place and in the same shape that they were | Chef ensures directories, permissions, and services are in place and working. | ||||||
| initially shipped. |  | ||||||
| 
 | 
 | ||||||
| It also [restarts GitLab components](#how-to-restart-gitlab) | Chef also [restarts GitLab components](#how-to-restart-gitlab) if any of their configuration files have changed. | ||||||
| where needed, if any of their configuration files have changed. |  | ||||||
| 
 | 
 | ||||||
| If you manually edit any files in `/var/opt/gitlab` that are managed by Chef, | If you manually edit any files in `/var/opt/gitlab` that are managed by Chef, | ||||||
| running reconfigure reverts the changes and restarts the services that | running `reconfigure` reverts the changes and restarts the services that | ||||||
| depend on those files. | depend on those files. | ||||||
| 
 | 
 | ||||||
| ## Installations from source | ## Installations from source | ||||||
|  | @ -118,7 +116,7 @@ This should restart Puma, Sidekiq, GitLab Workhorse, and [Mailroom](reply_by_ema | ||||||
| 
 | 
 | ||||||
| ## Helm chart installations | ## Helm chart installations | ||||||
| 
 | 
 | ||||||
| There is no single command to restart the entire GitLab application installed via | There is no single command to restart the entire GitLab application installed through | ||||||
| the [cloud-native Helm chart](https://docs.gitlab.com/charts/). Usually, it should be | the [cloud-native Helm chart](https://docs.gitlab.com/charts/). Usually, it should be | ||||||
| enough to restart a specific component separately (for example, `gitaly`, `puma`, | enough to restart a specific component separately (for example, `gitaly`, `puma`, | ||||||
| `workhorse`, or `gitlab-shell`) by deleting all the pods related to it: | `workhorse`, or `gitlab-shell`) by deleting all the pods related to it: | ||||||
|  |  | ||||||
|  | @ -6,7 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w | ||||||
| 
 | 
 | ||||||
| # Uploads administration **(FREE SELF)** | # Uploads administration **(FREE SELF)** | ||||||
| 
 | 
 | ||||||
| Uploads represent all user data that may be sent to GitLab as a single file. As an example, avatars and notes' attachments are uploads. Uploads are integral to GitLab functionality, and therefore cannot be disabled. | Uploads represent all user data that may be sent to GitLab as a single file. For example, avatars and note attachments are uploads. Uploads are integral to GitLab functionality and therefore cannot be disabled. | ||||||
| 
 | 
 | ||||||
| ## Using local storage | ## Using local storage | ||||||
| 
 | 
 | ||||||
|  | @ -14,15 +14,15 @@ This is the default configuration. To change the location where the uploads are | ||||||
| stored locally, use the steps in this section based on your installation method: | stored locally, use the steps in this section based on your installation method: | ||||||
| 
 | 
 | ||||||
| NOTE: | NOTE: | ||||||
| For historical reasons, instance level uploads (for example the [favicon](../user/admin_area/appearance.md#favicon)) are stored into a base directory, | For historical reasons, uploads for the whole instance (for example the [favicon](../user/admin_area/appearance.md#favicon)) are stored in a base directory, | ||||||
| which by default is `uploads/-/system`. It is strongly discouraged to change the base | which by default is `uploads/-/system`. Changing the base | ||||||
| directory on an existing GitLab installation. | directory on an existing GitLab installation is strongly discouraged. | ||||||
| 
 | 
 | ||||||
| **In Omnibus GitLab installations:** | **In Omnibus GitLab installations:** | ||||||
| 
 | 
 | ||||||
| _The uploads are stored by default in `/var/opt/gitlab/gitlab-rails/uploads`._ | _The uploads are stored by default in `/var/opt/gitlab/gitlab-rails/uploads`._ | ||||||
| 
 | 
 | ||||||
| 1. To change the storage path for example to `/mnt/storage/uploads`, edit | 1. To change the storage path, for example to `/mnt/storage/uploads`, edit | ||||||
|    `/etc/gitlab/gitlab.rb` and add the following line: |    `/etc/gitlab/gitlab.rb` and add the following line: | ||||||
| 
 | 
 | ||||||
|    ```ruby |    ```ruby | ||||||
|  | @ -38,7 +38,7 @@ _The uploads are stored by default in `/var/opt/gitlab/gitlab-rails/uploads`._ | ||||||
| _The uploads are stored by default in | _The uploads are stored by default in | ||||||
| `/home/git/gitlab/public/uploads`._ | `/home/git/gitlab/public/uploads`._ | ||||||
| 
 | 
 | ||||||
| 1. To change the storage path for example to `/mnt/storage/uploads`, edit | 1. To change the storage path, for example to `/mnt/storage/uploads`, edit | ||||||
|    `/home/git/gitlab/config/gitlab.yml` and add or amend the following lines: |    `/home/git/gitlab/config/gitlab.yml` and add or amend the following lines: | ||||||
| 
 | 
 | ||||||
|    ```yaml |    ```yaml | ||||||
|  | @ -57,7 +57,7 @@ This configuration relies on valid AWS credentials to be configured already. | ||||||
| 
 | 
 | ||||||
| [Read more about using object storage with GitLab](object_storage.md). | [Read more about using object storage with GitLab](object_storage.md). | ||||||
| 
 | 
 | ||||||
| We recommend using the [consolidated object storage settings](object_storage.md#consolidated-object-storage-configuration). The following instructions apply to the original configuration format. | You should use the [consolidated object storage settings](object_storage.md#consolidated-object-storage-configuration). The following instructions apply to the original configuration format. | ||||||
| 
 | 
 | ||||||
| ### Object Storage Settings | ### Object Storage Settings | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2098,8 +2098,8 @@ Input type: `DastSiteProfileCreateInput` | ||||||
| | <a id="mutationdastsiteprofilecreatefullpath"></a>`fullPath` | [`ID!`](#id) | Project the site profile belongs to. | | | <a id="mutationdastsiteprofilecreatefullpath"></a>`fullPath` | [`ID!`](#id) | Project the site profile belongs to. | | ||||||
| | <a id="mutationdastsiteprofilecreateprofilename"></a>`profileName` | [`String!`](#string) | Name of the site profile. | | | <a id="mutationdastsiteprofilecreateprofilename"></a>`profileName` | [`String!`](#string) | Name of the site profile. | | ||||||
| | <a id="mutationdastsiteprofilecreaterequestheaders"></a>`requestHeaders` | [`String`](#string) | Comma-separated list of request header names and values to be added to every request made by DAST. | | | <a id="mutationdastsiteprofilecreaterequestheaders"></a>`requestHeaders` | [`String`](#string) | Comma-separated list of request header names and values to be added to every request made by DAST. | | ||||||
| | <a id="mutationdastsiteprofilecreatescanfilepath"></a>`scanFilePath` | [`String`](#string) | File Path or URL used as input for the scan method. Will not be saved or updated if `dast_api_scanner` feature flag is disabled. | | | <a id="mutationdastsiteprofilecreatescanfilepath"></a>`scanFilePath` | [`String`](#string) | File Path or URL used as input for the scan method. | | ||||||
| | <a id="mutationdastsiteprofilecreatescanmethod"></a>`scanMethod` | [`DastScanMethodType`](#dastscanmethodtype) | Scan method by the scanner. Is not saved or updated if `dast_api_scanner` feature flag is disabled. | | | <a id="mutationdastsiteprofilecreatescanmethod"></a>`scanMethod` | [`DastScanMethodType`](#dastscanmethodtype) | Scan method by the scanner. | | ||||||
| | <a id="mutationdastsiteprofilecreatetargettype"></a>`targetType` | [`DastTargetTypeEnum`](#dasttargettypeenum) | Type of target to be scanned. | | | <a id="mutationdastsiteprofilecreatetargettype"></a>`targetType` | [`DastTargetTypeEnum`](#dasttargettypeenum) | Type of target to be scanned. | | ||||||
| | <a id="mutationdastsiteprofilecreatetargeturl"></a>`targetUrl` | [`String`](#string) | URL of the target to be scanned. | | | <a id="mutationdastsiteprofilecreatetargeturl"></a>`targetUrl` | [`String`](#string) | URL of the target to be scanned. | | ||||||
| 
 | 
 | ||||||
|  | @ -2146,8 +2146,8 @@ Input type: `DastSiteProfileUpdateInput` | ||||||
| | <a id="mutationdastsiteprofileupdateid"></a>`id` | [`DastSiteProfileID!`](#dastsiteprofileid) | ID of the site profile to be updated. | | | <a id="mutationdastsiteprofileupdateid"></a>`id` | [`DastSiteProfileID!`](#dastsiteprofileid) | ID of the site profile to be updated. | | ||||||
| | <a id="mutationdastsiteprofileupdateprofilename"></a>`profileName` | [`String!`](#string) | Name of the site profile. | | | <a id="mutationdastsiteprofileupdateprofilename"></a>`profileName` | [`String!`](#string) | Name of the site profile. | | ||||||
| | <a id="mutationdastsiteprofileupdaterequestheaders"></a>`requestHeaders` | [`String`](#string) | Comma-separated list of request header names and values to be added to every request made by DAST. | | | <a id="mutationdastsiteprofileupdaterequestheaders"></a>`requestHeaders` | [`String`](#string) | Comma-separated list of request header names and values to be added to every request made by DAST. | | ||||||
| | <a id="mutationdastsiteprofileupdatescanfilepath"></a>`scanFilePath` | [`String`](#string) | File Path or URL used as input for the scan method. Will not be saved or updated if `dast_api_scanner` feature flag is disabled. | | | <a id="mutationdastsiteprofileupdatescanfilepath"></a>`scanFilePath` | [`String`](#string) | File Path or URL used as input for the scan method. | | ||||||
| | <a id="mutationdastsiteprofileupdatescanmethod"></a>`scanMethod` | [`DastScanMethodType`](#dastscanmethodtype) | Scan method by the scanner. Is not saved or updated if `dast_api_scanner` feature flag is disabled. | | | <a id="mutationdastsiteprofileupdatescanmethod"></a>`scanMethod` | [`DastScanMethodType`](#dastscanmethodtype) | Scan method by the scanner. | | ||||||
| | <a id="mutationdastsiteprofileupdatetargettype"></a>`targetType` | [`DastTargetTypeEnum`](#dasttargettypeenum) | Type of target to be scanned. | | | <a id="mutationdastsiteprofileupdatetargettype"></a>`targetType` | [`DastTargetTypeEnum`](#dasttargettypeenum) | Type of target to be scanned. | | ||||||
| | <a id="mutationdastsiteprofileupdatetargeturl"></a>`targetUrl` | [`String`](#string) | URL of the target to be scanned. | | | <a id="mutationdastsiteprofileupdatetargeturl"></a>`targetUrl` | [`String`](#string) | URL of the target to be scanned. | | ||||||
| 
 | 
 | ||||||
|  | @ -11708,8 +11708,8 @@ Represents a DAST Site Profile. | ||||||
| | <a id="dastsiteprofileprofilename"></a>`profileName` | [`String`](#string) | Name of the site profile. | | | <a id="dastsiteprofileprofilename"></a>`profileName` | [`String`](#string) | Name of the site profile. | | ||||||
| | <a id="dastsiteprofilereferencedinsecuritypolicies"></a>`referencedInSecurityPolicies` | [`[String!]`](#string) | List of security policy names that are referencing given project. | | | <a id="dastsiteprofilereferencedinsecuritypolicies"></a>`referencedInSecurityPolicies` | [`[String!]`](#string) | List of security policy names that are referencing given project. | | ||||||
| | <a id="dastsiteprofilerequestheaders"></a>`requestHeaders` | [`String`](#string) | Comma-separated list of request header names and values to be added to every request made by DAST. | | | <a id="dastsiteprofilerequestheaders"></a>`requestHeaders` | [`String`](#string) | Comma-separated list of request header names and values to be added to every request made by DAST. | | ||||||
| | <a id="dastsiteprofilescanfilepath"></a>`scanFilePath` | [`String`](#string) | Scan File Path used as input for the scanner. Will always return `null` if `dast_api_scanner` feature flag is disabled. | | | <a id="dastsiteprofilescanfilepath"></a>`scanFilePath` | [`String`](#string) | Scan File Path used as input for the scanner. | | ||||||
| | <a id="dastsiteprofilescanmethod"></a>`scanMethod` | [`DastScanMethodType`](#dastscanmethodtype) | Scan method used by the scanner. Always returns `null` if `dast_api_scanner` feature flag is disabled. | | | <a id="dastsiteprofilescanmethod"></a>`scanMethod` | [`DastScanMethodType`](#dastscanmethodtype) | Scan method used by the scanner. | | ||||||
| | <a id="dastsiteprofiletargettype"></a>`targetType` | [`DastTargetTypeEnum`](#dasttargettypeenum) | Type of target to be scanned. | | | <a id="dastsiteprofiletargettype"></a>`targetType` | [`DastTargetTypeEnum`](#dasttargettypeenum) | Type of target to be scanned. | | ||||||
| | <a id="dastsiteprofiletargeturl"></a>`targetUrl` | [`String`](#string) | URL of the target to be scanned. | | | <a id="dastsiteprofiletargeturl"></a>`targetUrl` | [`String`](#string) | URL of the target to be scanned. | | ||||||
| | <a id="dastsiteprofileuserpermissions"></a>`userPermissions` | [`DastSiteProfilePermissions!`](#dastsiteprofilepermissions) | Permissions for the current user on the resource. | | | <a id="dastsiteprofileuserpermissions"></a>`userPermissions` | [`DastSiteProfilePermissions!`](#dastsiteprofilepermissions) | Permissions for the current user on the resource. | | ||||||
|  |  | ||||||
|  | @ -61,12 +61,8 @@ Self-managed installations can configure the following additional password requi | ||||||
| ## Block weak passwords | ## Block weak passwords | ||||||
| 
 | 
 | ||||||
| > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/23610) in GitLab 15.4 [with a flag](../../administration/feature_flags.md) named `block_weak_passwords`, weak passwords aren't accepted. Disabled by default on self-managed. | > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/23610) in GitLab 15.4 [with a flag](../../administration/feature_flags.md) named `block_weak_passwords`, weak passwords aren't accepted. Disabled by default on self-managed. | ||||||
| > - [Enabled](https://gitlab.com/gitlab-org/gitlab/-/issues/363445) on GitLab.com. | > - [Enabled](https://gitlab.com/gitlab-org/gitlab/-/issues/363445) on GitLab.com in GitLab 15.6. | ||||||
| 
 | > - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/363445) and enabled on self-managed in GitLab 15.7. Feature flag `block_weak_passwords` removed. | ||||||
| FLAG: |  | ||||||
| On self-managed GitLab, by default blocking weak passwords is not available. To make it available, ask an administrator |  | ||||||
| to [enable the feature flag](../../administration/feature_flags.md) named `block_weak_passwords`. On GitLab.com, this |  | ||||||
| feature is available but can be configured by GitLab.com administrators only. |  | ||||||
| 
 | 
 | ||||||
| GitLab disallows weak passwords. Your password is considered weak when it: | GitLab disallows weak passwords. Your password is considered weak when it: | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -6,7 +6,9 @@ | ||||||
| # Each table / view needs to have assigned gitlab_schema. Names supported today: | # Each table / view needs to have assigned gitlab_schema. Names supported today: | ||||||
| # | # | ||||||
| # - gitlab_shared - defines a set of tables that are found on all databases (data accessed is dependent on connection) | # - gitlab_shared - defines a set of tables that are found on all databases (data accessed is dependent on connection) | ||||||
| # - gitlab_main / gitlab_ci - defines a set of tables that can only exist on a given database | # - gitlab_main / gitlab_ci - defines a set of tables that can only exist on a given application database | ||||||
|  | # - gitlab_geo - defines a set of tables that can only exist on the geo database | ||||||
|  | # - gitlab_internal - defines all internal tables of Rails and PostgreSQL | ||||||
| # | # | ||||||
| # Tables for the purpose of tests should be prefixed with `_test_my_table_name` | # Tables for the purpose of tests should be prefixed with `_test_my_table_name` | ||||||
| 
 | 
 | ||||||
|  | @ -55,7 +57,7 @@ module Gitlab | ||||||
|         tables.map { |table| table_schema(table) }.to_set |         tables.map { |table| table_schema(table) }.to_set | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       def self.table_schema(name) |       def self.table_schema(name, undefined: true) | ||||||
|         schema_name, table_name = name.split('.', 2) # Strip schema name like: `public.` |         schema_name, table_name = name.split('.', 2) # Strip schema name like: `public.` | ||||||
| 
 | 
 | ||||||
|         # Most of names do not have schemas, ensure that this is table |         # Most of names do not have schemas, ensure that this is table | ||||||
|  | @ -84,6 +86,8 @@ module Gitlab | ||||||
| 
 | 
 | ||||||
|         return :gitlab_ci if table_name.start_with?('_test_gitlab_ci_') |         return :gitlab_ci if table_name.start_with?('_test_gitlab_ci_') | ||||||
| 
 | 
 | ||||||
|  |         return :gitlab_geo if table_name.start_with?('_test_gitlab_geo_') | ||||||
|  | 
 | ||||||
|         # All tables that start with `_test_` without a following schema are shared and ignored |         # All tables that start with `_test_` without a following schema are shared and ignored | ||||||
|         return :gitlab_shared if table_name.start_with?('_test_') |         return :gitlab_shared if table_name.start_with?('_test_') | ||||||
| 
 | 
 | ||||||
|  | @ -91,7 +95,7 @@ module Gitlab | ||||||
|         return :gitlab_internal if table_name.start_with?('pg_') |         return :gitlab_internal if table_name.start_with?('pg_') | ||||||
| 
 | 
 | ||||||
|         # When undefined it's best to return a unique name so that we don't incorrectly assume that 2 undefined schemas belong on the same database |         # When undefined it's best to return a unique name so that we don't incorrectly assume that 2 undefined schemas belong on the same database | ||||||
|         :"undefined_#{table_name}" |         undefined ? :"undefined_#{table_name}" : nil | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       def self.tables_to_schema |       def self.tables_to_schema | ||||||
|  |  | ||||||
|  | @ -51,6 +51,10 @@ module Gitlab | ||||||
|         include Gitlab::Database::MigrationHelpers::RestrictGitlabSchema |         include Gitlab::Database::MigrationHelpers::RestrictGitlabSchema | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|  |       class V2_1 < V2_0 # rubocop:disable Naming/ClassAndModuleCamelCase | ||||||
|  |         include Gitlab::Database::MigrationHelpers::AutomaticLockWritesOnTables | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|       def self.[](version) |       def self.[](version) | ||||||
|         version = version.to_s |         version = version.to_s | ||||||
|         name = "V#{version.tr('.', '_')}" |         name = "V#{version.tr('.', '_')}" | ||||||
|  | @ -61,7 +65,7 @@ module Gitlab | ||||||
| 
 | 
 | ||||||
|       # The current version to be used in new migrations |       # The current version to be used in new migrations | ||||||
|       def self.current_version |       def self.current_version | ||||||
|         2.0 |         2.1 | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  | @ -0,0 +1,74 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | module Gitlab | ||||||
|  |   module Database | ||||||
|  |     module MigrationHelpers | ||||||
|  |       module AutomaticLockWritesOnTables | ||||||
|  |         extend ActiveSupport::Concern | ||||||
|  | 
 | ||||||
|  |         included do | ||||||
|  |           class_attribute :skip_automatic_lock_on_writes | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         def exec_migration(connection, direction) | ||||||
|  |           return super if %w[main ci].exclude?(Gitlab::Database.db_config_name(connection)) | ||||||
|  |           return super if automatic_lock_on_writes_disabled? | ||||||
|  | 
 | ||||||
|  |           # This compares the tables only on the `public` schema. Partitions are not affected | ||||||
|  |           tables = connection.tables | ||||||
|  |           super | ||||||
|  |           new_tables = connection.tables - tables | ||||||
|  | 
 | ||||||
|  |           new_tables.each do |table_name| | ||||||
|  |             lock_writes_on_table(connection, table_name) if should_lock_writes_on_table?(table_name) | ||||||
|  |           end | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         private | ||||||
|  | 
 | ||||||
|  |         def automatic_lock_on_writes_disabled? | ||||||
|  |           # Feature flags are set on the main database, see tables features/feature_gates. | ||||||
|  |           # That is why we switch the ActiveRecord::Base.connection temporarily here back to the 'main' database | ||||||
|  |           # for the cases when the migration is targeting another database, like the 'ci' database. | ||||||
|  |           with_restored_connection_stack do |_| | ||||||
|  |             Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas.with_suppressed do | ||||||
|  |               skip_automatic_lock_on_writes || | ||||||
|  |                 Gitlab::Utils.to_boolean(ENV['SKIP_AUTOMATIC_LOCK_ON_WRITES']) || | ||||||
|  |                 Feature.disabled?(:automatic_lock_writes_on_table, type: :ops) | ||||||
|  |             end | ||||||
|  |           end | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         def should_lock_writes_on_table?(table_name) | ||||||
|  |           # currently gitlab_schema represents only present existing tables, this is workaround for deleted tables | ||||||
|  |           # that should be skipped as they will be removed in a future migration. | ||||||
|  |           return false if Gitlab::Database::GitlabSchema::DELETED_TABLES[table_name] | ||||||
|  | 
 | ||||||
|  |           table_schema = Gitlab::Database::GitlabSchema.table_schema(table_name.to_s, undefined: false) | ||||||
|  | 
 | ||||||
|  |           if table_schema.nil? | ||||||
|  |             error_message = <<~ERROR | ||||||
|  |               No gitlab_schema is defined for the table #{table_name}. Please consider | ||||||
|  |               adding it to the file config 'lib/gitlab/database/gitlab_schemas.yml' | ||||||
|  |             ERROR | ||||||
|  |             raise error_message | ||||||
|  |           end | ||||||
|  | 
 | ||||||
|  |           return false unless %i[gitlab_main gitlab_ci].include?(table_schema) | ||||||
|  | 
 | ||||||
|  |           Gitlab::Database.gitlab_schemas_for_connection(connection).exclude?(table_schema) | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         def lock_writes_on_table(connection, table_name) | ||||||
|  |           database_name = Gitlab::Database.db_config_name(connection) | ||||||
|  |           LockWritesManager.new( | ||||||
|  |             table_name: table_name, | ||||||
|  |             connection: connection, | ||||||
|  |             database_name: database_name, | ||||||
|  |             logger: Logger.new($stdout) | ||||||
|  |           ).lock_writes | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -25,7 +25,23 @@ module Gitlab | ||||||
|         # The intention here is to not introduce an assumption about the standard schema, |         # The intention here is to not introduce an assumption about the standard schema, | ||||||
|         # unless we have a good reason to do so. |         # unless we have a good reason to do so. | ||||||
|         structure.gsub!(/public\.(\w+)/, '\1') |         structure.gsub!(/public\.(\w+)/, '\1') | ||||||
|         structure.gsub!(/CREATE EXTENSION IF NOT EXISTS (\w+) WITH SCHEMA public;/, 'CREATE EXTENSION IF NOT EXISTS \1;') |         structure.gsub!( | ||||||
|  |           /CREATE EXTENSION IF NOT EXISTS (\w+) WITH SCHEMA public;/, | ||||||
|  |           'CREATE EXTENSION IF NOT EXISTS \1;' | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         # Table lock-writes triggers should not be added to the schema | ||||||
|  |         # These triggers are added by the rake task gitlab:db:lock_writes for a decomposed database. | ||||||
|  |         structure.gsub!( | ||||||
|  |           %r{ | ||||||
|  |             ^CREATE.TRIGGER.gitlab_schema_write_trigger_\w+ | ||||||
|  |             \s | ||||||
|  |             BEFORE.INSERT.OR.DELETE.OR.UPDATE.OR.TRUNCATE.ON.\w+ | ||||||
|  |             \s | ||||||
|  |             FOR.EACH.STATEMENT.EXECUTE.FUNCTION.gitlab_schema_prevent_write\(\);$ | ||||||
|  |           }x, | ||||||
|  |           '' | ||||||
|  |         ) | ||||||
| 
 | 
 | ||||||
|         structure.gsub!(/\n{3,}/, "\n\n") |         structure.gsub!(/\n{3,}/, "\n\n") | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -55,7 +55,7 @@ | ||||||
|     "@gitlab/at.js": "1.5.7", |     "@gitlab/at.js": "1.5.7", | ||||||
|     "@gitlab/favicon-overlay": "2.0.0", |     "@gitlab/favicon-overlay": "2.0.0", | ||||||
|     "@gitlab/svgs": "3.11.0", |     "@gitlab/svgs": "3.11.0", | ||||||
|     "@gitlab/ui": "49.11.2", |     "@gitlab/ui": "50.1.2", | ||||||
|     "@gitlab/visual-review-tools": "1.7.3", |     "@gitlab/visual-review-tools": "1.7.3", | ||||||
|     "@gitlab/web-ide": "0.0.1-dev-20221114183058", |     "@gitlab/web-ide": "0.0.1-dev-20221114183058", | ||||||
|     "@rails/actioncable": "6.1.4-7", |     "@rails/actioncable": "6.1.4-7", | ||||||
|  |  | ||||||
|  | @ -9,9 +9,10 @@ module RuboCop | ||||||
|         include MigrationHelpers |         include MigrationHelpers | ||||||
| 
 | 
 | ||||||
|         ENFORCED_SINCE = 2021_09_02_00_00_00 |         ENFORCED_SINCE = 2021_09_02_00_00_00 | ||||||
|  |         CURRENT_DATABASE_MIGRATION_CLASS = 'Gitlab::Database::Migration[2.1]' | ||||||
| 
 | 
 | ||||||
|         MSG_INHERIT = 'Don\'t inherit from ActiveRecord::Migration but use Gitlab::Database::Migration[1.0] instead. See https://docs.gitlab.com/ee/development/migration_style_guide.html#migration-helpers-and-versioning.' |         MSG_INHERIT = 'Don\'t inherit from ActiveRecord::Migration but use Gitlab::Database::Migration[2.1] instead. See https://docs.gitlab.com/ee/development/migration_style_guide.html#migration-helpers-and-versioning.' | ||||||
|         MSG_INCLUDE = 'Don\'t include migration helper modules directly. Inherit from Gitlab::Database::Migration[1.0] instead. See https://docs.gitlab.com/ee/development/migration_style_guide.html#migration-helpers-and-versioning.' |         MSG_INCLUDE = 'Don\'t include migration helper modules directly. Inherit from Gitlab::Database::Migration[2.1] instead. See https://docs.gitlab.com/ee/development/migration_style_guide.html#migration-helpers-and-versioning.' | ||||||
| 
 | 
 | ||||||
|         ACTIVERECORD_MIGRATION_CLASS = 'ActiveRecord::Migration' |         ACTIVERECORD_MIGRATION_CLASS = 'ActiveRecord::Migration' | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -486,34 +486,22 @@ RSpec.describe RegistrationsController do | ||||||
| 
 | 
 | ||||||
|       subject { post(:create, params: new_user_params) } |       subject { post(:create, params: new_user_params) } | ||||||
| 
 | 
 | ||||||
|       context 'when block_weak_passwords is enabled (default)' do |       it 'renders the form with errors' do | ||||||
|         it 'renders the form with errors' do |         expect { subject }.not_to change(User, :count) | ||||||
|           expect { subject }.not_to change(User, :count) |  | ||||||
| 
 | 
 | ||||||
|           expect(controller.current_user).to be_nil |         expect(controller.current_user).to be_nil | ||||||
|           expect(response).to render_template(:new) |         expect(response).to render_template(:new) | ||||||
|           expect(response.body).to include(_('Password must not contain commonly used combinations of words and letters')) |         expect(response.body).to include(_('Password must not contain commonly used combinations of words and letters')) | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         it 'tracks the error' do |  | ||||||
|           subject |  | ||||||
|           expect_snowplow_event( |  | ||||||
|             category: 'Gitlab::Tracking::Helpers::WeakPasswordErrorEvent', |  | ||||||
|             action: 'track_weak_password_error', |  | ||||||
|             controller: 'RegistrationsController', |  | ||||||
|             method: 'create' |  | ||||||
|           ) |  | ||||||
|         end |  | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       context 'when block_weak_passwords is disabled' do |       it 'tracks the error' do | ||||||
|         before do |         subject | ||||||
|           stub_feature_flags(block_weak_passwords: false) |         expect_snowplow_event( | ||||||
|         end |           category: 'Gitlab::Tracking::Helpers::WeakPasswordErrorEvent', | ||||||
| 
 |           action: 'track_weak_password_error', | ||||||
|         it 'permits weak passwords' do |           controller: 'RegistrationsController', | ||||||
|           expect { subject }.to change(User, :count).by(1) |           method: 'create' | ||||||
|         end |         ) | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -77,3 +77,17 @@ ALTER TABLE ONLY public.abuse_reports | ||||||
| 
 | 
 | ||||||
| CREATE INDEX index_abuse_reports_on_user_id ON public.abuse_reports USING btree (user_id); | CREATE INDEX index_abuse_reports_on_user_id ON public.abuse_reports USING btree (user_id); | ||||||
| 
 | 
 | ||||||
|  | CREATE TRIGGER gitlab_schema_write_trigger_for_users BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON users FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); | ||||||
|  | 
 | ||||||
|  | CREATE FUNCTION gitlab_schema_prevent_write() RETURNS trigger | ||||||
|  |     LANGUAGE plpgsql | ||||||
|  |     AS $$ | ||||||
|  | BEGIN | ||||||
|  |     IF COALESCE(NULLIF(current_setting(CONCAT('lock_writes.', TG_TABLE_NAME), true), ''), 'true') THEN | ||||||
|  |       RAISE EXCEPTION 'Table: "%" is write protected within this Gitlab database.', TG_TABLE_NAME | ||||||
|  |         USING ERRCODE = 'modifying_sql_data_not_permitted', | ||||||
|  |         HINT = 'Make sure you are using the right database connection'; | ||||||
|  | END IF; | ||||||
|  | RETURN NEW; | ||||||
|  | END | ||||||
|  | $$; | ||||||
|  |  | ||||||
|  | @ -24,3 +24,16 @@ ALTER TABLE ONLY abuse_reports | ||||||
|     ADD CONSTRAINT abuse_reports_pkey PRIMARY KEY (id); |     ADD CONSTRAINT abuse_reports_pkey PRIMARY KEY (id); | ||||||
| 
 | 
 | ||||||
| CREATE INDEX index_abuse_reports_on_user_id ON abuse_reports USING btree (user_id); | CREATE INDEX index_abuse_reports_on_user_id ON abuse_reports USING btree (user_id); | ||||||
|  | 
 | ||||||
|  | CREATE FUNCTION gitlab_schema_prevent_write() RETURNS trigger | ||||||
|  |     LANGUAGE plpgsql | ||||||
|  |     AS $$ | ||||||
|  | BEGIN | ||||||
|  |     IF COALESCE(NULLIF(current_setting(CONCAT('lock_writes.', TG_TABLE_NAME), true), ''), 'true') THEN | ||||||
|  |       RAISE EXCEPTION 'Table: "%" is write protected within this Gitlab database.', TG_TABLE_NAME | ||||||
|  |         USING ERRCODE = 'modifying_sql_data_not_permitted', | ||||||
|  |         HINT = 'Make sure you are using the right database connection'; | ||||||
|  | END IF; | ||||||
|  | RETURN NEW; | ||||||
|  | END | ||||||
|  | $$; | ||||||
|  |  | ||||||
|  | @ -4,12 +4,12 @@ import Vue from 'vue'; | ||||||
| import Vuex from 'vuex'; | import Vuex from 'vuex'; | ||||||
| import { extendedWrapper } from 'helpers/vue_test_utils_helper'; | import { extendedWrapper } from 'helpers/vue_test_utils_helper'; | ||||||
| import ValueStreamMetrics from '~/analytics/shared/components/value_stream_metrics.vue'; | import ValueStreamMetrics from '~/analytics/shared/components/value_stream_metrics.vue'; | ||||||
| import BaseComponent from '~/cycle_analytics/components/base.vue'; | import BaseComponent from '~/analytics/cycle_analytics/components/base.vue'; | ||||||
| import PathNavigation from '~/cycle_analytics/components/path_navigation.vue'; | import PathNavigation from '~/analytics/cycle_analytics/components/path_navigation.vue'; | ||||||
| import StageTable from '~/cycle_analytics/components/stage_table.vue'; | import StageTable from '~/analytics/cycle_analytics/components/stage_table.vue'; | ||||||
| import ValueStreamFilters from '~/cycle_analytics/components/value_stream_filters.vue'; | import ValueStreamFilters from '~/analytics/cycle_analytics/components/value_stream_filters.vue'; | ||||||
| import { NOT_ENOUGH_DATA_ERROR } from '~/cycle_analytics/constants'; | import { NOT_ENOUGH_DATA_ERROR } from '~/analytics/cycle_analytics/constants'; | ||||||
| import initState from '~/cycle_analytics/store/state'; | import initState from '~/analytics/cycle_analytics/store/state'; | ||||||
| import { | import { | ||||||
|   transformedProjectStagePathData, |   transformedProjectStagePathData, | ||||||
|   selectedStage, |   selectedStage, | ||||||
|  | @ -7,8 +7,8 @@ import { | ||||||
|   filterMilestones, |   filterMilestones, | ||||||
|   filterLabels, |   filterLabels, | ||||||
| } from 'jest/vue_shared/components/filtered_search_bar/store/modules/filters/mock_data'; | } from 'jest/vue_shared/components/filtered_search_bar/store/modules/filters/mock_data'; | ||||||
| import FilterBar from '~/cycle_analytics/components/filter_bar.vue'; | import FilterBar from '~/analytics/cycle_analytics/components/filter_bar.vue'; | ||||||
| import storeConfig from '~/cycle_analytics/store'; | import storeConfig from '~/analytics/cycle_analytics/store'; | ||||||
| import * as commonUtils from '~/lib/utils/common_utils'; | import * as commonUtils from '~/lib/utils/common_utils'; | ||||||
| import * as urlUtils from '~/lib/utils/url_utility'; | import * as urlUtils from '~/lib/utils/url_utility'; | ||||||
| import { | import { | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import { shallowMount } from '@vue/test-utils'; | import { shallowMount } from '@vue/test-utils'; | ||||||
| import Component from '~/cycle_analytics/components/formatted_stage_count.vue'; | import Component from '~/analytics/cycle_analytics/components/formatted_stage_count.vue'; | ||||||
| 
 | 
 | ||||||
| describe('Formatted Stage Count', () => { | describe('Formatted Stage Count', () => { | ||||||
|   let wrapper = null; |   let wrapper = null; | ||||||
|  | @ -12,7 +12,7 @@ import { | ||||||
|   PAGINATION_TYPE, |   PAGINATION_TYPE, | ||||||
|   PAGINATION_SORT_DIRECTION_DESC, |   PAGINATION_SORT_DIRECTION_DESC, | ||||||
|   PAGINATION_SORT_FIELD_END_EVENT, |   PAGINATION_SORT_FIELD_END_EVENT, | ||||||
| } from '~/cycle_analytics/constants'; | } from '~/analytics/cycle_analytics/constants'; | ||||||
| import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; | import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; | ||||||
| import { getDateInPast } from '~/lib/utils/datetime_utility'; | import { getDateInPast } from '~/lib/utils/datetime_utility'; | ||||||
| 
 | 
 | ||||||
|  | @ -2,7 +2,7 @@ import { GlPath, GlSkeletonLoader } from '@gitlab/ui'; | ||||||
| import { mount } from '@vue/test-utils'; | import { mount } from '@vue/test-utils'; | ||||||
| import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; | import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; | ||||||
| import { extendedWrapper } from 'helpers/vue_test_utils_helper'; | import { extendedWrapper } from 'helpers/vue_test_utils_helper'; | ||||||
| import Component from '~/cycle_analytics/components/path_navigation.vue'; | import Component from '~/analytics/cycle_analytics/components/path_navigation.vue'; | ||||||
| import { transformedProjectStagePathData, selectedStage } from './mock_data'; | import { transformedProjectStagePathData, selectedStage } from './mock_data'; | ||||||
| 
 | 
 | ||||||
| describe('Project PathNavigation', () => { | describe('Project PathNavigation', () => { | ||||||
|  | @ -3,8 +3,8 @@ import { shallowMount, mount } from '@vue/test-utils'; | ||||||
| import { nextTick } from 'vue'; | import { nextTick } from 'vue'; | ||||||
| import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; | import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; | ||||||
| import { extendedWrapper } from 'helpers/vue_test_utils_helper'; | import { extendedWrapper } from 'helpers/vue_test_utils_helper'; | ||||||
| import StageTable from '~/cycle_analytics/components/stage_table.vue'; | import StageTable from '~/analytics/cycle_analytics/components/stage_table.vue'; | ||||||
| import { PAGINATION_SORT_FIELD_DURATION } from '~/cycle_analytics/constants'; | import { PAGINATION_SORT_FIELD_DURATION } from '~/analytics/cycle_analytics/constants'; | ||||||
| import { issueEvents, issueStage, reviewStage, reviewEvents } from './mock_data'; | import { issueEvents, issueStage, reviewStage, reviewEvents } from './mock_data'; | ||||||
| 
 | 
 | ||||||
| let wrapper = null; | let wrapper = null; | ||||||
|  | @ -1,8 +1,8 @@ | ||||||
| import axios from 'axios'; | import axios from 'axios'; | ||||||
| import MockAdapter from 'axios-mock-adapter'; | import MockAdapter from 'axios-mock-adapter'; | ||||||
| import testAction from 'helpers/vuex_action_helper'; | import testAction from 'helpers/vuex_action_helper'; | ||||||
| import * as actions from '~/cycle_analytics/store/actions'; | import * as actions from '~/analytics/cycle_analytics/store/actions'; | ||||||
| import * as getters from '~/cycle_analytics/store/getters'; | import * as getters from '~/analytics/cycle_analytics/store/getters'; | ||||||
| import httpStatusCodes from '~/lib/utils/http_status'; | import httpStatusCodes from '~/lib/utils/http_status'; | ||||||
| import { | import { | ||||||
|   allowedStages, |   allowedStages, | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import * as getters from '~/cycle_analytics/store/getters'; | import * as getters from '~/analytics/cycle_analytics/store/getters'; | ||||||
| 
 | 
 | ||||||
| import { | import { | ||||||
|   allowedStages, |   allowedStages, | ||||||
|  | @ -1,10 +1,10 @@ | ||||||
| import { useFakeDate } from 'helpers/fake_date'; | import { useFakeDate } from 'helpers/fake_date'; | ||||||
| import * as types from '~/cycle_analytics/store/mutation_types'; | import * as types from '~/analytics/cycle_analytics/store/mutation_types'; | ||||||
| import mutations from '~/cycle_analytics/store/mutations'; | import mutations from '~/analytics/cycle_analytics/store/mutations'; | ||||||
| import { | import { | ||||||
|   PAGINATION_SORT_FIELD_END_EVENT, |   PAGINATION_SORT_FIELD_END_EVENT, | ||||||
|   PAGINATION_SORT_DIRECTION_DESC, |   PAGINATION_SORT_DIRECTION_DESC, | ||||||
| } from '~/cycle_analytics/constants'; | } from '~/analytics/cycle_analytics/constants'; | ||||||
| import { | import { | ||||||
|   selectedStage, |   selectedStage, | ||||||
|   rawIssueEvents, |   rawIssueEvents, | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import { mount } from '@vue/test-utils'; | import { mount } from '@vue/test-utils'; | ||||||
| import TotalTime from '~/cycle_analytics/components/total_time.vue'; | import TotalTime from '~/analytics/cycle_analytics/components/total_time.vue'; | ||||||
| 
 | 
 | ||||||
| describe('TotalTime', () => { | describe('TotalTime', () => { | ||||||
|   let wrapper = null; |   let wrapper = null; | ||||||
|  | @ -4,7 +4,7 @@ import { | ||||||
|   formatMedianValues, |   formatMedianValues, | ||||||
|   filterStagesByHiddenStatus, |   filterStagesByHiddenStatus, | ||||||
|   buildCycleAnalyticsInitialData, |   buildCycleAnalyticsInitialData, | ||||||
| } from '~/cycle_analytics/utils'; | } from '~/analytics/cycle_analytics/utils'; | ||||||
| import { | import { | ||||||
|   selectedStage, |   selectedStage, | ||||||
|   allowedStages, |   allowedStages, | ||||||
|  | @ -1,8 +1,8 @@ | ||||||
| import { shallowMount } from '@vue/test-utils'; | import { shallowMount } from '@vue/test-utils'; | ||||||
| import Daterange from '~/analytics/shared/components/daterange.vue'; | import Daterange from '~/analytics/shared/components/daterange.vue'; | ||||||
| import ProjectsDropdownFilter from '~/analytics/shared/components/projects_dropdown_filter.vue'; | import ProjectsDropdownFilter from '~/analytics/shared/components/projects_dropdown_filter.vue'; | ||||||
| import FilterBar from '~/cycle_analytics/components/filter_bar.vue'; | import FilterBar from '~/analytics/cycle_analytics/components/filter_bar.vue'; | ||||||
| import ValueStreamFilters from '~/cycle_analytics/components/value_stream_filters.vue'; | import ValueStreamFilters from '~/analytics/cycle_analytics/components/value_stream_filters.vue'; | ||||||
| import { | import { | ||||||
|   createdAfter as startDate, |   createdAfter as startDate, | ||||||
|   createdBefore as endDate, |   createdBefore as endDate, | ||||||
|  | @ -3,7 +3,7 @@ | ||||||
| # See https://docs.gitlab.com/ee/development/migration_style_guide.html | # See https://docs.gitlab.com/ee/development/migration_style_guide.html | ||||||
| # for more information on how to write migrations for GitLab. | # for more information on how to write migrations for GitLab. | ||||||
| 
 | 
 | ||||||
| class CreateModelGeneratorTestFoos < Gitlab::Database::Migration[2.0] | class CreateModelGeneratorTestFoos < Gitlab::Database::Migration[2.1] | ||||||
|   # When using the methods "add_concurrent_index" or "remove_concurrent_index" |   # When using the methods "add_concurrent_index" or "remove_concurrent_index" | ||||||
|   # you must disable the use of transactions |   # you must disable the use of transactions | ||||||
|   # as these methods can not run in an existing transaction. |   # as these methods can not run in an existing transaction. | ||||||
|  |  | ||||||
|  | @ -0,0 +1,333 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | require 'spec_helper' | ||||||
|  | 
 | ||||||
|  | RSpec.describe Gitlab::Database::MigrationHelpers::AutomaticLockWritesOnTables, | ||||||
|  |   :reestablished_active_record_base, query_analyzers: false do | ||||||
|  |   using RSpec::Parameterized::TableSyntax | ||||||
|  | 
 | ||||||
|  |   let(:schema_class) { Class.new(Gitlab::Database::Migration[2.1]) } | ||||||
|  |   let(:gitlab_main_table_name) { :_test_gitlab_main_table } | ||||||
|  |   let(:gitlab_ci_table_name) { :_test_gitlab_ci_table } | ||||||
|  |   let(:gitlab_geo_table_name) { :_test_gitlab_geo_table } | ||||||
|  |   let(:gitlab_shared_table_name) { :_test_table } | ||||||
|  | 
 | ||||||
|  |   before do | ||||||
|  |     stub_feature_flags(automatic_lock_writes_on_table: true) | ||||||
|  |     reconfigure_db_connection(model: ActiveRecord::Base, config_model: config_model) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   shared_examples 'does not lock writes on table' do |config_model| | ||||||
|  |     let(:config_model) { config_model } | ||||||
|  | 
 | ||||||
|  |     it 'allows deleting records from the table' do | ||||||
|  |       allow_next_instance_of(Gitlab::Database::LockWritesManager) do |instance| | ||||||
|  |         expect(instance).not_to receive(:lock_writes) | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       run_migration | ||||||
|  | 
 | ||||||
|  |       expect do | ||||||
|  |         migration_class.connection.execute("DELETE FROM #{table_name}") | ||||||
|  |       end.not_to raise_error | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   shared_examples 'locks writes on table' do |config_model| | ||||||
|  |     let(:config_model) { config_model } | ||||||
|  | 
 | ||||||
|  |     it 'errors on deleting' do | ||||||
|  |       allow_next_instance_of(Gitlab::Database::LockWritesManager) do |instance| | ||||||
|  |         expect(instance).to receive(:lock_writes).and_call_original | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       run_migration | ||||||
|  | 
 | ||||||
|  |       expect do | ||||||
|  |         migration_class.connection.execute("DELETE FROM #{table_name}") | ||||||
|  |       end.to raise_error(ActiveRecord::StatementInvalid, /is write protected/) | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   context 'when executing create_table migrations' do | ||||||
|  |     let(:create_gitlab_main_table_migration_class) { create_table_migration(gitlab_main_table_name) } | ||||||
|  |     let(:create_gitlab_ci_table_migration_class) { create_table_migration(gitlab_ci_table_name) } | ||||||
|  |     let(:create_gitlab_shared_table_migration_class) { create_table_migration(gitlab_shared_table_name) } | ||||||
|  | 
 | ||||||
|  |     context 'when single database' do | ||||||
|  |       let(:config_model) { Gitlab::Database.database_base_models[:main] } | ||||||
|  | 
 | ||||||
|  |       before do | ||||||
|  |         skip_if_multiple_databases_are_setup | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       it 'does not lock any newly created tables' do | ||||||
|  |         allow_next_instance_of(Gitlab::Database::LockWritesManager) do |instance| | ||||||
|  |           expect(instance).not_to receive(:lock_writes) | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         create_gitlab_main_table_migration_class.migrate(:up) | ||||||
|  |         create_gitlab_ci_table_migration_class.migrate(:up) | ||||||
|  |         create_gitlab_shared_table_migration_class.migrate(:up) | ||||||
|  | 
 | ||||||
|  |         expect do | ||||||
|  |           create_gitlab_main_table_migration_class.connection.execute("DELETE FROM #{gitlab_main_table_name}") | ||||||
|  |           create_gitlab_ci_table_migration_class.connection.execute("DELETE FROM #{gitlab_ci_table_name}") | ||||||
|  |           create_gitlab_shared_table_migration_class.connection.execute("DELETE FROM #{gitlab_shared_table_name}") | ||||||
|  |         end.not_to raise_error | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     context 'when multiple databases' do | ||||||
|  |       before do | ||||||
|  |         skip_if_multiple_databases_not_setup | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       let(:skip_automatic_lock_on_writes) { false } | ||||||
|  |       let(:migration_class) { create_table_migration(table_name, skip_automatic_lock_on_writes) } | ||||||
|  |       let(:run_migration) { migration_class.migrate(:up) } | ||||||
|  | 
 | ||||||
|  |       context 'for creating a gitlab_main table' do | ||||||
|  |         let(:table_name) { gitlab_main_table_name } | ||||||
|  | 
 | ||||||
|  |         it_behaves_like 'does not lock writes on table', Gitlab::Database.database_base_models[:main] | ||||||
|  |         it_behaves_like 'locks writes on table', Gitlab::Database.database_base_models[:ci] | ||||||
|  | 
 | ||||||
|  |         context 'when table listed as a deleted table' do | ||||||
|  |           before do | ||||||
|  |             stub_const("Gitlab::Database::GitlabSchema::DELETED_TABLES", { table_name.to_s => :gitlab_main }) | ||||||
|  |           end | ||||||
|  | 
 | ||||||
|  |           it_behaves_like 'does not lock writes on table', Gitlab::Database.database_base_models[:ci] | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         context 'when the migration skips automatic locking of tables' do | ||||||
|  |           let(:skip_automatic_lock_on_writes) { true } | ||||||
|  | 
 | ||||||
|  |           it_behaves_like 'does not lock writes on table', Gitlab::Database.database_base_models[:ci] | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         context 'when the SKIP_AUTOMATIC_LOCK_ON_WRITES feature flag is set' do | ||||||
|  |           before do | ||||||
|  |             stub_env('SKIP_AUTOMATIC_LOCK_ON_WRITES' => 'true') | ||||||
|  |           end | ||||||
|  | 
 | ||||||
|  |           it_behaves_like 'does not lock writes on table', Gitlab::Database.database_base_models[:ci] | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         context 'when the automatic_lock_writes_on_table feature flag is disabled' do | ||||||
|  |           before do | ||||||
|  |             stub_feature_flags(automatic_lock_writes_on_table: false) | ||||||
|  |           end | ||||||
|  | 
 | ||||||
|  |           it_behaves_like 'does not lock writes on table', Gitlab::Database.database_base_models[:ci] | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       context 'for creating a gitlab_ci table' do | ||||||
|  |         let(:table_name) { gitlab_ci_table_name } | ||||||
|  | 
 | ||||||
|  |         it_behaves_like 'does not lock writes on table', Gitlab::Database.database_base_models[:ci] | ||||||
|  |         it_behaves_like 'locks writes on table', Gitlab::Database.database_base_models[:main] | ||||||
|  | 
 | ||||||
|  |         context 'when table listed as a deleted table' do | ||||||
|  |           before do | ||||||
|  |             stub_const("Gitlab::Database::GitlabSchema::DELETED_TABLES", { table_name.to_s => :gitlab_ci }) | ||||||
|  |           end | ||||||
|  | 
 | ||||||
|  |           it_behaves_like 'does not lock writes on table', Gitlab::Database.database_base_models[:main] | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         context 'when the migration skips automatic locking of tables' do | ||||||
|  |           let(:skip_automatic_lock_on_writes) { true } | ||||||
|  | 
 | ||||||
|  |           it_behaves_like 'does not lock writes on table', Gitlab::Database.database_base_models[:main] | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         context 'when the SKIP_AUTOMATIC_LOCK_ON_WRITES feature flag is set' do | ||||||
|  |           before do | ||||||
|  |             stub_env('SKIP_AUTOMATIC_LOCK_ON_WRITES' => 'true') | ||||||
|  |           end | ||||||
|  | 
 | ||||||
|  |           it_behaves_like 'does not lock writes on table', Gitlab::Database.database_base_models[:main] | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         context 'when the automatic_lock_writes_on_table feature flag is disabled' do | ||||||
|  |           before do | ||||||
|  |             stub_feature_flags(automatic_lock_writes_on_table: false) | ||||||
|  |           end | ||||||
|  | 
 | ||||||
|  |           it_behaves_like 'does not lock writes on table', Gitlab::Database.database_base_models[:main] | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       context 'for creating gitlab_shared table' do | ||||||
|  |         let(:table_name) { gitlab_shared_table_name } | ||||||
|  | 
 | ||||||
|  |         it_behaves_like 'does not lock writes on table', Gitlab::Database.database_base_models[:main] | ||||||
|  |         it_behaves_like 'does not lock writes on table', Gitlab::Database.database_base_models[:ci] | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       context 'for creating a gitlab_geo table' do | ||||||
|  |         before do | ||||||
|  |           skip unless geo_configured? | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         let(:table_name) { gitlab_geo_table_name } | ||||||
|  | 
 | ||||||
|  |         it_behaves_like 'does not lock writes on table', Gitlab::Database.database_base_models[:geo] | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       context 'for creating an unknown gitlab_schema table' do | ||||||
|  |         let(:table_name) { :foobar } # no gitlab_schema defined | ||||||
|  |         let(:config_model) { Gitlab::Database.database_base_models[:main] } | ||||||
|  | 
 | ||||||
|  |         it "raises an error about undefined gitlab_schema" do | ||||||
|  |           expected_error_message = <<~ERROR | ||||||
|  |               No gitlab_schema is defined for the table #{table_name}. Please consider | ||||||
|  |               adding it to the file config 'lib/gitlab/database/gitlab_schemas.yml' | ||||||
|  |           ERROR | ||||||
|  | 
 | ||||||
|  |           expect { run_migration }.to raise_error(expected_error_message) | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   context 'when renaming a table' do | ||||||
|  |     before do | ||||||
|  |       skip_if_multiple_databases_not_setup | ||||||
|  |       create_table_migration(old_table_name).migrate(:up) # create the table first before renaming it | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     let(:migration_class) { rename_table_migration(old_table_name, table_name) } | ||||||
|  |     let(:run_migration) { migration_class.migrate(:up) } | ||||||
|  | 
 | ||||||
|  |     context 'when a gitlab_main table' do | ||||||
|  |       let(:old_table_name) { gitlab_main_table_name } | ||||||
|  |       let(:table_name) { :_test_gitlab_main_new_table } | ||||||
|  |       let(:database_base_model) { Gitlab::Database.database_base_models[:main] } | ||||||
|  | 
 | ||||||
|  |       it_behaves_like 'does not lock writes on table', Gitlab::Database.database_base_models[:main] | ||||||
|  |       it_behaves_like 'locks writes on table', Gitlab::Database.database_base_models[:ci] | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     context 'when a gitlab_ci table' do | ||||||
|  |       let(:old_table_name) { gitlab_ci_table_name } | ||||||
|  |       let(:table_name) { :_test_gitlab_ci_new_table } | ||||||
|  |       let(:database_base_model) { Gitlab::Database.database_base_models[:ci] } | ||||||
|  | 
 | ||||||
|  |       it_behaves_like 'does not lock writes on table', Gitlab::Database.database_base_models[:ci] | ||||||
|  |       it_behaves_like 'locks writes on table', Gitlab::Database.database_base_models[:main] | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   context 'when reversing drop_table migrations' do | ||||||
|  |     let(:drop_gitlab_main_table_migration_class) { drop_table_migration(gitlab_main_table_name) } | ||||||
|  |     let(:drop_gitlab_ci_table_migration_class) { drop_table_migration(gitlab_ci_table_name) } | ||||||
|  |     let(:drop_gitlab_shared_table_migration_class) { drop_table_migration(gitlab_shared_table_name) } | ||||||
|  | 
 | ||||||
|  |     context 'when single database' do | ||||||
|  |       let(:config_model) { Gitlab::Database.database_base_models[:main] } | ||||||
|  | 
 | ||||||
|  |       before do | ||||||
|  |         skip_if_multiple_databases_are_setup | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       it 'does not lock any newly created tables' do | ||||||
|  |         allow_next_instance_of(Gitlab::Database::LockWritesManager) do |instance| | ||||||
|  |           expect(instance).not_to receive(:lock_writes) | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         drop_gitlab_main_table_migration_class.connection.execute("CREATE TABLE #{gitlab_main_table_name}()") | ||||||
|  |         drop_gitlab_ci_table_migration_class.connection.execute("CREATE TABLE #{gitlab_ci_table_name}()") | ||||||
|  |         drop_gitlab_shared_table_migration_class.connection.execute("CREATE TABLE #{gitlab_shared_table_name}()") | ||||||
|  | 
 | ||||||
|  |         drop_gitlab_main_table_migration_class.migrate(:up) | ||||||
|  |         drop_gitlab_ci_table_migration_class.migrate(:up) | ||||||
|  |         drop_gitlab_shared_table_migration_class.migrate(:up) | ||||||
|  | 
 | ||||||
|  |         drop_gitlab_main_table_migration_class.migrate(:down) | ||||||
|  |         drop_gitlab_ci_table_migration_class.migrate(:down) | ||||||
|  |         drop_gitlab_shared_table_migration_class.migrate(:down) | ||||||
|  | 
 | ||||||
|  |         expect do | ||||||
|  |           drop_gitlab_main_table_migration_class.connection.execute("DELETE FROM #{gitlab_main_table_name}") | ||||||
|  |           drop_gitlab_ci_table_migration_class.connection.execute("DELETE FROM #{gitlab_ci_table_name}") | ||||||
|  |           drop_gitlab_shared_table_migration_class.connection.execute("DELETE FROM #{gitlab_shared_table_name}") | ||||||
|  |         end.not_to raise_error | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     context 'when multiple databases' do | ||||||
|  |       before do | ||||||
|  |         skip_if_multiple_databases_not_setup | ||||||
|  |         migration_class.connection.execute("CREATE TABLE #{table_name}()") | ||||||
|  |         migration_class.migrate(:up) | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       let(:migration_class) { drop_table_migration(table_name) } | ||||||
|  |       let(:run_migration) { migration_class.migrate(:down) } | ||||||
|  | 
 | ||||||
|  |       context 'for re-creating a gitlab_main table' do | ||||||
|  |         let(:table_name) { gitlab_main_table_name } | ||||||
|  | 
 | ||||||
|  |         it_behaves_like 'does not lock writes on table', Gitlab::Database.database_base_models[:main] | ||||||
|  |         it_behaves_like 'locks writes on table', Gitlab::Database.database_base_models[:ci] | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       context 'for re-creating a gitlab_ci table' do | ||||||
|  |         let(:table_name) { gitlab_ci_table_name } | ||||||
|  | 
 | ||||||
|  |         it_behaves_like 'does not lock writes on table', Gitlab::Database.database_base_models[:ci] | ||||||
|  |         it_behaves_like 'locks writes on table', Gitlab::Database.database_base_models[:main] | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       context 'for re-creating a gitlab_shared table' do | ||||||
|  |         let(:table_name) { gitlab_shared_table_name } | ||||||
|  | 
 | ||||||
|  |         it_behaves_like 'does not lock writes on table', Gitlab::Database.database_base_models[:main] | ||||||
|  |         it_behaves_like 'does not lock writes on table', Gitlab::Database.database_base_models[:ci] | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def create_table_migration(table_name, skip_lock_on_writes = false) | ||||||
|  |     migration_class = Class.new(schema_class) do | ||||||
|  |       class << self; attr_accessor :table_name; end | ||||||
|  |       def change | ||||||
|  |         create_table self.class.table_name | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |     migration_class.skip_automatic_lock_on_writes = skip_lock_on_writes | ||||||
|  |     migration_class.tap { |klass| klass.table_name = table_name } | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def rename_table_migration(old_table_name, new_table_name) | ||||||
|  |     migration_class = Class.new(schema_class) do | ||||||
|  |       class << self; attr_accessor :old_table_name, :new_table_name; end | ||||||
|  |       def change | ||||||
|  |         rename_table self.class.old_table_name, self.class.new_table_name | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     migration_class.tap do |klass| | ||||||
|  |       klass.old_table_name = old_table_name | ||||||
|  |       klass.new_table_name = new_table_name | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def drop_table_migration(table_name) | ||||||
|  |     migration_class = Class.new(schema_class) do | ||||||
|  |       class << self; attr_accessor :table_name; end | ||||||
|  |       def change | ||||||
|  |         drop_table(self.class.table_name) {} | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |     migration_class.tap { |klass| klass.table_name = table_name } | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def geo_configured? | ||||||
|  |     !!ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: 'geo') | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -14,7 +14,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers::RestrictGitlabSchema, query_a | ||||||
| 
 | 
 | ||||||
|   describe '#restrict_gitlab_migration' do |   describe '#restrict_gitlab_migration' do | ||||||
|     it 'invalid schema raises exception' do |     it 'invalid schema raises exception' do | ||||||
|       expect { schema_class.restrict_gitlab_migration gitlab_schema: :gitlab_non_exisiting } |       expect { schema_class.restrict_gitlab_migration gitlab_schema: :gitlab_non_existing } | ||||||
|         .to raise_error /Unknown 'gitlab_schema:/ |         .to raise_error /Unknown 'gitlab_schema:/ | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|  | @ -102,7 +102,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers::RestrictGitlabSchema, query_a | ||||||
|         "does add index to projects in gitlab_main and gitlab_ci" => { |         "does add index to projects in gitlab_main and gitlab_ci" => { | ||||||
|           migration: ->(klass) do |           migration: ->(klass) do | ||||||
|             def change |             def change | ||||||
|               # Due to running in transactin we cannot use `add_concurrent_index` |               # Due to running in transaction we cannot use `add_concurrent_index` | ||||||
|               add_index :projects, :hidden |               add_index :projects, :hidden | ||||||
|             end |             end | ||||||
|           end, |           end, | ||||||
|  |  | ||||||
|  | @ -19,6 +19,15 @@ RSpec.describe Gitlab::Database::SchemaCleaner do | ||||||
|     expect(subject).not_to match(/public\.\w+/) |     expect(subject).not_to match(/public\.\w+/) | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  |   it 'cleans up all the gitlab_schema_prevent_write table triggers' do | ||||||
|  |     expect(subject).not_to match(/CREATE TRIGGER gitlab_schema_write_trigger_for_\w+/) | ||||||
|  |     expect(subject).not_to match(/FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write/) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   it 'keeps the lock_writes trigger functions' do | ||||||
|  |     expect(subject).to match(/CREATE FUNCTION gitlab_schema_prevent_write/) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|   it 'cleans up the full schema as expected (blackbox test with example)' do |   it 'cleans up the full schema as expected (blackbox test with example)' do | ||||||
|     expected_schema = fixture_file(File.join('gitlab', 'database', 'structure_example_cleaned.sql')) |     expected_schema = fixture_file(File.join('gitlab', 'database', 'structure_example_cleaned.sql')) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -345,52 +345,33 @@ RSpec.describe User do | ||||||
|       context 'check_password_weakness' do |       context 'check_password_weakness' do | ||||||
|         let(:weak_password) { "qwertyuiop" } |         let(:weak_password) { "qwertyuiop" } | ||||||
| 
 | 
 | ||||||
|         context 'when feature flag is disabled' do |         it 'checks for password weakness when password changes' do | ||||||
|           before do |           expect(Security::WeakPasswords).to receive(:weak_for_user?) | ||||||
|             stub_feature_flags(block_weak_passwords: false) |             .with(weak_password, user).and_call_original | ||||||
|           end |           user.password = weak_password | ||||||
| 
 |           expect(user).not_to be_valid | ||||||
|           it 'does not add an error when password is weak' do |  | ||||||
|             expect(Security::WeakPasswords).not_to receive(:weak_for_user?) |  | ||||||
| 
 |  | ||||||
|             user.password = weak_password |  | ||||||
|             expect(user).to be_valid |  | ||||||
|           end |  | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         context 'when feature flag is enabled' do |         it 'adds an error when password is weak' do | ||||||
|           before do |           user.password = weak_password | ||||||
|             stub_feature_flags(block_weak_passwords: true) |           expect(user).not_to be_valid | ||||||
|           end |           expect(user.errors).to be_of_kind(:password, 'must not contain commonly used combinations of words and letters') | ||||||
|  |         end | ||||||
| 
 | 
 | ||||||
|           it 'checks for password weakness when password changes' do |         it 'is valid when password is not weak' do | ||||||
|             expect(Security::WeakPasswords).to receive(:weak_for_user?) |           user.password = ::User.random_password | ||||||
|               .with(weak_password, user).and_call_original |           expect(user).to be_valid | ||||||
|             user.password = weak_password |         end | ||||||
|             expect(user).not_to be_valid |  | ||||||
|           end |  | ||||||
| 
 | 
 | ||||||
|           it 'adds an error when password is weak' do |         it 'is valid when weak password was already set' do | ||||||
|             user.password = weak_password |           user = build(:user, password: weak_password) | ||||||
|             expect(user).not_to be_valid |           user.save!(validate: false) | ||||||
|             expect(user.errors).to be_of_kind(:password, 'must not contain commonly used combinations of words and letters') |  | ||||||
|           end |  | ||||||
| 
 | 
 | ||||||
|           it 'is valid when password is not weak' do |           expect(Security::WeakPasswords).not_to receive(:weak_for_user?) | ||||||
|             user.password = ::User.random_password |  | ||||||
|             expect(user).to be_valid |  | ||||||
|           end |  | ||||||
| 
 | 
 | ||||||
|           it 'is valid when weak password was already set' do |           # Change an unrelated value | ||||||
|             user = build(:user, password: weak_password) |           user.name = "Example McExampleFace" | ||||||
|             user.save!(validate: false) |           expect(user).to be_valid | ||||||
| 
 |  | ||||||
|             expect(Security::WeakPasswords).not_to receive(:weak_for_user?) |  | ||||||
| 
 |  | ||||||
|             # Change an unrelated value |  | ||||||
|             user.name = "Example McExampleFace" |  | ||||||
|             expect(user).to be_valid |  | ||||||
|           end |  | ||||||
|         end |         end | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
|  |  | ||||||
|  | @ -6,7 +6,7 @@ require_relative '../../../../rubocop/cop/migration/versioned_migration_class' | ||||||
| RSpec.describe RuboCop::Cop::Migration::VersionedMigrationClass do | RSpec.describe RuboCop::Cop::Migration::VersionedMigrationClass do | ||||||
|   let(:migration) do |   let(:migration) do | ||||||
|     <<~SOURCE |     <<~SOURCE | ||||||
|       class TestMigration < Gitlab::Database::Migration[1.0] |       class TestMigration < Gitlab::Database::Migration[2.1] | ||||||
|         def up |         def up | ||||||
|           execute 'select 1' |           execute 'select 1' | ||||||
|         end |         end | ||||||
|  | @ -49,23 +49,23 @@ RSpec.describe RuboCop::Cop::Migration::VersionedMigrationClass do | ||||||
|       it 'adds an offence if inheriting from ActiveRecord::Migration' do |       it 'adds an offence if inheriting from ActiveRecord::Migration' do | ||||||
|         expect_offense(<<~RUBY) |         expect_offense(<<~RUBY) | ||||||
|           class MyMigration < ActiveRecord::Migration[6.1] |           class MyMigration < ActiveRecord::Migration[6.1] | ||||||
|           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Don't inherit from ActiveRecord::Migration but use Gitlab::Database::Migration[1.0] instead. See https://docs.gitlab.com/ee/development/migration_style_guide.html#migration-helpers-and-versioning. |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Don't inherit from ActiveRecord::Migration but use Gitlab::Database::Migration[2.1] instead. See https://docs.gitlab.com/ee/development/migration_style_guide.html#migration-helpers-and-versioning. | ||||||
|           end |           end | ||||||
|         RUBY |         RUBY | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       it 'adds an offence if including Gitlab::Database::MigrationHelpers directly' do |       it 'adds an offence if including Gitlab::Database::MigrationHelpers directly' do | ||||||
|         expect_offense(<<~RUBY) |         expect_offense(<<~RUBY) | ||||||
|           class MyMigration < Gitlab::Database::Migration[1.0] |           class MyMigration < Gitlab::Database::Migration[2.1] | ||||||
|             include Gitlab::Database::MigrationHelpers |             include Gitlab::Database::MigrationHelpers | ||||||
|             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Don't include migration helper modules directly. Inherit from Gitlab::Database::Migration[1.0] instead. See https://docs.gitlab.com/ee/development/migration_style_guide.html#migration-helpers-and-versioning. |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Don't include migration helper modules directly. Inherit from Gitlab::Database::Migration[2.1] instead. See https://docs.gitlab.com/ee/development/migration_style_guide.html#migration-helpers-and-versioning. | ||||||
|           end |           end | ||||||
|         RUBY |         RUBY | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       it 'excludes ActiveRecord classes defined inside the migration' do |       it 'excludes ActiveRecord classes defined inside the migration' do | ||||||
|         expect_no_offenses(<<~RUBY) |         expect_no_offenses(<<~RUBY) | ||||||
|           class TestMigration < Gitlab::Database::Migration[1.0] |           class TestMigration < Gitlab::Database::Migration[2.1] | ||||||
|             class TestModel < ApplicationRecord |             class TestModel < ApplicationRecord | ||||||
|             end |             end | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1125,10 +1125,10 @@ | ||||||
|   resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.11.0.tgz#91e8e25583cddef48c0c79175203e5b0a4eaa519" |   resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.11.0.tgz#91e8e25583cddef48c0c79175203e5b0a4eaa519" | ||||||
|   integrity sha512-1cJu1WXPoOHfGgv5fT3nmA9cgAQ3U1Fm/oMSVYUgBxU35R0I8W704GMLsIZwBuQ/S/Ow7WLwIkoOhLb/spNKPg== |   integrity sha512-1cJu1WXPoOHfGgv5fT3nmA9cgAQ3U1Fm/oMSVYUgBxU35R0I8W704GMLsIZwBuQ/S/Ow7WLwIkoOhLb/spNKPg== | ||||||
| 
 | 
 | ||||||
| "@gitlab/ui@49.11.2": | "@gitlab/ui@50.1.2": | ||||||
|   version "49.11.2" |   version "50.1.2" | ||||||
|   resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-49.11.2.tgz#290bba7a3d4682365ad81747cf54a2f9927526c1" |   resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-50.1.2.tgz#d24100b6d6fc77c7708d9139f09e2142b1292d31" | ||||||
|   integrity sha512-qu5qcl+4niYBCPIZS9ZU0i1h/IGL4ZOp4hDsEAIUFGJg9Sp0TBmwdjwKJQbvnexDS3xs1eSBzi+kQ57H+c9wQQ== |   integrity sha512-bRzF9SkDGY2WrmIuFriFLyRMym2ydAeJB71FCXfHvhei3EWAeaiZv5oEB/a4oMN8nmt0rt4GrPQ5PpGiQKoKfQ== | ||||||
|   dependencies: |   dependencies: | ||||||
|     "@popperjs/core" "^2.11.2" |     "@popperjs/core" "^2.11.2" | ||||||
|     bootstrap-vue "2.20.1" |     bootstrap-vue "2.20.1" | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue