Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
191020103b
commit
eef37e3313
|
@ -55,8 +55,14 @@ update-qa-cache:
|
|||
before_script:
|
||||
- source scripts/utils.sh
|
||||
- install_gitlab_gem
|
||||
- tooling/bin/find_change_diffs ${CHANGES_DIFFS_DIR}
|
||||
script:
|
||||
- ./scripts/trigger-build omnibus
|
||||
- |
|
||||
if tooling/bin/qa/check_if_only_quarantined_specs ${CHANGES_DIFFS_DIR}; then
|
||||
exit 0
|
||||
else
|
||||
./scripts/trigger-build omnibus
|
||||
fi
|
||||
# These jobs often time out, so temporarily use private runners and a long timeout: https://gitlab.com/gitlab-org/gitlab/-/issues/238563
|
||||
tags:
|
||||
- prm
|
||||
|
@ -66,14 +72,19 @@ update-qa-cache:
|
|||
artifacts: false
|
||||
- job: build-assets-image
|
||||
artifacts: false
|
||||
- detect-tests
|
||||
artifacts:
|
||||
expire_in: 7d
|
||||
paths:
|
||||
- ${CHANGES_FILE}
|
||||
- ${CHANGES_DIFFS_DIR}/*
|
||||
variables:
|
||||
CHANGES_FILE: tmp/changed_files.txt
|
||||
CHANGES_DIFFS_DIR: tmp/diffs
|
||||
|
||||
.package-and-qa-ff-base:
|
||||
needs:
|
||||
- detect-tests
|
||||
variables:
|
||||
CHANGED_FILES: tmp/changed_files.txt
|
||||
script:
|
||||
- export GITLAB_QA_OPTIONS="--set-feature-flags $(scripts/changed-feature-flags --files $(cat $CHANGED_FILES | tr ' ' ',') --state $QA_FF_STATE)"
|
||||
- export GITLAB_QA_OPTIONS="--set-feature-flags $(scripts/changed-feature-flags --files $(cat $CHANGES_FILE | tr ' ' ',') --state $QA_FF_STATE)"
|
||||
- echo $GITLAB_QA_OPTIONS
|
||||
- ./scripts/trigger-build omnibus
|
||||
|
||||
|
|
|
@ -6,11 +6,6 @@
|
|||
- .default-before_script
|
||||
- .rails-cache
|
||||
|
||||
.minimal-bundle-install:
|
||||
script:
|
||||
- export BUNDLE_WITHOUT="${BUNDLE_WITHOUT}:default:test:puma:kerberos:metrics:omnibus:ed25519"
|
||||
- bundle_install_script
|
||||
|
||||
.base-script:
|
||||
script:
|
||||
# Only install knapsack after bundle install! Otherwise oddly some native
|
||||
|
@ -241,11 +236,11 @@ update-gitaly-binaries-cache:
|
|||
.coverage-base:
|
||||
extends:
|
||||
- .default-retry
|
||||
- .default-before_script
|
||||
- .coverage-cache
|
||||
variables:
|
||||
SETUP_DB: "false"
|
||||
USE_BUNDLE_INSTALL: "false"
|
||||
before_script:
|
||||
- source scripts/utils.sh
|
||||
- export BUNDLE_WITHOUT="${BUNDLE_WITHOUT}:default:test:puma:kerberos:metrics:omnibus:ed25519"
|
||||
- bundle_install_script
|
||||
|
||||
rspec migration pg12:
|
||||
extends:
|
||||
|
@ -476,21 +471,38 @@ rspec:coverage:
|
|||
# so we use `dependencies` here.
|
||||
dependencies:
|
||||
- setup-test-env
|
||||
# FOSS/EE jobs
|
||||
- rspec migration pg12
|
||||
- rspec unit pg12
|
||||
- rspec integration pg12
|
||||
- rspec system pg12
|
||||
# FOSS/EE minimal jobs
|
||||
- rspec migration pg12 minimal
|
||||
- rspec unit pg12 minimal
|
||||
- rspec integration pg12 minimal
|
||||
- rspec system pg12 minimal
|
||||
# EE jobs
|
||||
- rspec-ee migration pg12
|
||||
- rspec-ee unit pg12
|
||||
- rspec-ee integration pg12
|
||||
- rspec-ee system pg12
|
||||
# EE minimal jobs
|
||||
- rspec-ee migration pg12 minimal
|
||||
- rspec-ee unit pg12 minimal
|
||||
- rspec-ee integration pg12 minimal
|
||||
- rspec-ee system pg12 minimal
|
||||
# Geo jobs
|
||||
- rspec-ee unit pg12 geo
|
||||
- rspec-ee integration pg12 geo
|
||||
- rspec-ee system pg12 geo
|
||||
# Geo minimal jobs
|
||||
- rspec-ee unit pg12 geo minimal
|
||||
- rspec-ee integration pg12 geo minimal
|
||||
- rspec-ee system pg12 geo minimal
|
||||
# Memory jobs
|
||||
- memory-static
|
||||
- memory-on-boot
|
||||
script:
|
||||
- !reference [.minimal-bundle-install, script]
|
||||
- run_timed_command "bundle exec scripts/merge-simplecov"
|
||||
- run_timed_command "bundle exec scripts/gather-test-memory-data"
|
||||
coverage: '/LOC \((\d+\.\d+%)\) covered.$/'
|
||||
|
@ -515,7 +527,6 @@ rspec:feature-flags:
|
|||
- job: "haml-lint ee"
|
||||
optional: true
|
||||
script:
|
||||
- !reference [.minimal-bundle-install, script]
|
||||
- if [ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]; then
|
||||
run_timed_command "bundle exec scripts/used-feature-flags" || (scripts/slack master-broken "☠️ \`${CI_JOB_NAME}\` failed! ☠️ See ${CI_JOB_URL}" ci_failing "GitLab Bot" && exit 1);
|
||||
else
|
||||
|
|
|
@ -1258,7 +1258,7 @@
|
|||
|
||||
.rails:rules:detect-tests:
|
||||
rules:
|
||||
- changes: *code-backstage-patterns
|
||||
- changes: *code-backstage-qa-patterns
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
|
||||
.rails:rules:detect-previous-failed-tests:
|
||||
|
|
|
@ -111,115 +111,9 @@ Geo secondary sites have a [Geo tracking database](https://gitlab.com/gitlab-org
|
|||
|
||||
- [ ] Be sure to commit the relevant changes in `ee/db/geo/structure.sql`
|
||||
|
||||
### Add verification state fields on the Geo primary site
|
||||
### Add verification state to the Model
|
||||
|
||||
The Geo primary site needs to checksum every replicable in order for secondaries to verify their own checksums. To do this, Geo requires fields on the Model. There are two ways to add the necessary verification state fields. If the table is large and wide, then it may be a good idea to add verification state fields to a separate table (Option 2). Consult a database expert if needed.
|
||||
|
||||
#### Add verification state fields to the model table (Option 1)
|
||||
|
||||
- [ ] Create the migration file in `db/migrate`:
|
||||
|
||||
```shell
|
||||
bin/rails generate migration AddVerificationStateToCoolWidgets
|
||||
```
|
||||
|
||||
- [ ] Replace the contents of the migration file with:
|
||||
|
||||
```ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddVerificationStateToCoolWidgets < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
change_table(:cool_widgets) do |t|
|
||||
t.integer :verification_state, default: 0, limit: 2, null: false
|
||||
t.column :verification_started_at, :datetime_with_timezone
|
||||
t.integer :verification_retry_count, limit: 2, null: false
|
||||
t.column :verification_retry_at, :datetime_with_timezone
|
||||
t.column :verified_at, :datetime_with_timezone
|
||||
t.binary :verification_checksum, using: 'verification_checksum::bytea'
|
||||
|
||||
t.text :verification_failure # rubocop:disable Migration/AddLimitToTextColumns
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
- [ ] If deviating from the above example, then be sure to order columns according to [our guidelines](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/ordering_table_columns.md).
|
||||
- [ ] If `cool_widgets` is a high-traffic table, follow [the database documentation to use `with_lock_retries`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/migration_style_guide.md#when-to-use-the-helper-method)
|
||||
- [ ] Adding a `text` column also [requires](../database/strings_and_the_text_data_type.md#add-a-text-column-to-an-existing-table) setting a limit. Create the migration file in `db/migrate`:
|
||||
|
||||
```shell
|
||||
bin/rails generate migration AddVerificationFailureLimitToCoolWidgets
|
||||
```
|
||||
|
||||
- [ ] Replace the contents of the migration file with:
|
||||
|
||||
```ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddVerificationFailureLimitToCoolWidgets < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
CONSTRAINT_NAME = 'cool_widget_verification_failure_text_limit'
|
||||
|
||||
def up
|
||||
add_text_limit :cool_widget, :verification_failure, 255, constraint_name: CONSTRAINT_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_check_constraint(:cool_widget, CONSTRAINT_NAME)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
- [ ] Add indexes on verification fields to ensure verification can be performed efficiently. Some or all of these indexes can be omitted if the table is guaranteed to be small. Ask a database expert if you are considering omitting indexes. Create the migration file in `db/migrate`:
|
||||
|
||||
```shell
|
||||
bin/rails generate migration AddVerificationIndexesToCoolWidgets
|
||||
```
|
||||
|
||||
- [ ] Replace the contents of the migration file with:
|
||||
|
||||
```ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddVerificationIndexesToCoolWidgets < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
VERIFICATION_STATE_INDEX_NAME = "index_cool_widgets_on_verification_state"
|
||||
PENDING_VERIFICATION_INDEX_NAME = "index_cool_widgets_pending_verification"
|
||||
FAILED_VERIFICATION_INDEX_NAME = "index_cool_widgets_failed_verification"
|
||||
NEEDS_VERIFICATION_INDEX_NAME = "index_cool_widgets_needs_verification"
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_concurrent_index :cool_widgets, :verification_state, name: VERIFICATION_STATE_INDEX_NAME
|
||||
add_concurrent_index :cool_widgets, :verified_at, where: "(verification_state = 0)", order: { verified_at: 'ASC NULLS FIRST' }, name: PENDING_VERIFICATION_INDEX_NAME
|
||||
add_concurrent_index :cool_widgets, :verification_retry_at, where: "(verification_state = 3)", order: { verification_retry_at: 'ASC NULLS FIRST' }, name: FAILED_VERIFICATION_INDEX_NAME
|
||||
add_concurrent_index :cool_widgets, :verification_state, where: "(verification_state = 0 OR verification_state = 3)", name: NEEDS_VERIFICATION_INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index_by_name :cool_widgets, VERIFICATION_STATE_INDEX_NAME
|
||||
remove_concurrent_index_by_name :cool_widgets, PENDING_VERIFICATION_INDEX_NAME
|
||||
remove_concurrent_index_by_name :cool_widgets, FAILED_VERIFICATION_INDEX_NAME
|
||||
remove_concurrent_index_by_name :cool_widgets, NEEDS_VERIFICATION_INDEX_NAME
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
- [ ] Run database migrations:
|
||||
|
||||
```shell
|
||||
bin/rake db:migrate
|
||||
```
|
||||
|
||||
- [ ] Be sure to commit the relevant changes in `db/structure.sql`
|
||||
|
||||
#### Add verification state fields to a separate table (Option 2)
|
||||
The Geo primary site needs to checksum every replicable so secondaries can verify their own checksums. To do this, Geo requires the Model to have an associated table to track verification state.
|
||||
|
||||
- [ ] Create the migration file in `db/migrate`:
|
||||
|
||||
|
@ -273,6 +167,7 @@ The Geo primary site needs to checksum every replicable in order for secondaries
|
|||
```
|
||||
|
||||
- [ ] If deviating from the above example, then be sure to order columns according to [our guidelines](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/ordering_table_columns.md).
|
||||
|
||||
- [ ] Run database migrations:
|
||||
|
||||
```shell
|
||||
|
@ -287,7 +182,14 @@ That's all of the required database changes.
|
|||
|
||||
#### Step 1. Implement replication and verification
|
||||
|
||||
- [ ] Include `Gitlab::Geo::ReplicableModel` in the `CoolWidget` class, and specify the Replicator class `with_replicator Geo::CoolWidgetReplicator`.
|
||||
- [ ] Add the following lines to the `cool_widget` model to accomplish some important tasks:
|
||||
- Include `Gitlab::Geo::ReplicableModel` in the `CoolWidget` class, and specify the Replicator class `with_replicator Geo::CoolWidgetReplicator`.
|
||||
- Include the `::Gitlab::Geo::VerificationState` concern.
|
||||
- Delegate verification related methods to the `cool_widget_state` model.
|
||||
- For verification, override some scopes to use the `cool_widget_states` table instead of the model table.
|
||||
- Implement the `verification_state_object` method to return the object that holds
|
||||
the verification details
|
||||
- Override some methods to use the `cool_widget_states` table in verification-related queries.
|
||||
|
||||
Pay some attention to method `pool_repository`. Not every repository type uses repository pooling. As Geo prefers to use repository snapshotting, it can lead to data loss. Make sure to overwrite `pool_repository` so it returns nil for repositories that do not have pools.
|
||||
|
||||
|
@ -297,6 +199,7 @@ That's all of the required database changes.
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CoolWidget < ApplicationRecord
|
||||
...
|
||||
include ::Gitlab::Geo::ReplicableModel
|
||||
include ::Gitlab::Geo::VerificationState
|
||||
|
||||
|
@ -304,31 +207,62 @@ That's all of the required database changes.
|
|||
|
||||
mount_uploader :file, CoolWidgetUploader
|
||||
|
||||
has_one :cool_widget_state, autosave: false, inverse_of: :cool_widget, class_name: 'Geo::CoolWidgetState'
|
||||
|
||||
delegate :verification_retry_at, :verification_retry_at=,
|
||||
:verified_at, :verified_at=,
|
||||
:verification_checksum, :verification_checksum=,
|
||||
:verification_failure, :verification_failure=,
|
||||
:verification_retry_count, :verification_retry_count=,
|
||||
:verification_state=, :verification_state,
|
||||
:verification_started_at=, :verification_started_at,
|
||||
to: :cool_widget_state
|
||||
...
|
||||
|
||||
scope :with_verification_state, ->(state) { joins(:cool_widget_state).where(cool_widget_states: { verification_state: verification_state_value(state) }) }
|
||||
scope :checksummed, -> { joins(:cool_widget_state).where.not(cool_widget_states: { verification_checksum: nil } ) }
|
||||
scope :not_checksummed, -> { joins(:cool_widget_state).where(cool_widget_states: { verification_checksum: nil } ) }
|
||||
|
||||
# Override the `all` default if not all records can be replicated. For an
|
||||
# example of an existing Model that needs to do this, see
|
||||
# `EE::MergeRequestDiff`.
|
||||
# scope :available_replicables, -> { all }
|
||||
|
||||
# @param primary_key_in [Range, CoolWidget] arg to pass to primary_key_in scope
|
||||
# @return [ActiveRecord::Relation<CoolWidget>] everything that should be synced to this node, restricted by primary key
|
||||
def self.replicables_for_current_secondary(primary_key_in)
|
||||
# This issue template does not help you write this method.
|
||||
#
|
||||
# This method is called only on Geo secondary sites. It is called when
|
||||
# we want to know which records to replicate. This is not easy to automate
|
||||
# because for example:
|
||||
#
|
||||
# * The "selective sync" feature allows admins to choose which namespaces # to replicate, per secondary site. Most Models are scoped to a
|
||||
# namespace, but the nature of the relationship to a namespace varies
|
||||
# between Models.
|
||||
# * The "selective sync" feature allows admins to choose which shards to
|
||||
# replicate, per secondary site. Repositories are associated with
|
||||
# shards. Most blob types are not, but Project Uploads are.
|
||||
# * Remote stored replicables are not replicated, by default. But the
|
||||
# setting `sync_object_storage` enables replication of remote stored
|
||||
# replicables.
|
||||
#
|
||||
# Search the codebase for examples, and consult a Geo expert if needed.
|
||||
def verification_state_object
|
||||
cool_widget_state
|
||||
end
|
||||
...
|
||||
|
||||
class_methods do
|
||||
extend ::Gitlab::Utils::Override
|
||||
...
|
||||
|
||||
# @param primary_key_in [Range, CoolWidget] arg to pass to primary_key_in scope
|
||||
# @return [ActiveRecord::Relation<CoolWidget>] everything that should be synced to this node, restricted by primary key
|
||||
def replicables_for_current_secondary(primary_key_in)
|
||||
# This issue template does not help you write this method.
|
||||
#
|
||||
# This method is called only on Geo secondary sites. It is called when
|
||||
# we want to know which records to replicate. This is not easy to automate
|
||||
# because for example:
|
||||
#
|
||||
# * The "selective sync" feature allows admins to choose which namespaces # to replicate, per secondary site. Most Models are scoped to a
|
||||
# namespace, but the nature of the relationship to a namespace varies
|
||||
# between Models.
|
||||
# * The "selective sync" feature allows admins to choose which shards to
|
||||
# replicate, per secondary site. Repositories are associated with
|
||||
# shards. Most blob types are not, but Project Uploads are.
|
||||
# * Remote stored replicables are not replicated, by default. But the
|
||||
# setting `sync_object_storage` enables replication of remote stored
|
||||
# replicables.
|
||||
#
|
||||
# Search the codebase for examples, and consult a Geo expert if needed.
|
||||
end
|
||||
|
||||
override :verification_state_table_class
|
||||
def verification_state_table_class
|
||||
CoolWidgetState
|
||||
end
|
||||
end
|
||||
|
||||
# Geo checks this method in FrameworkRepositorySyncService to avoid
|
||||
|
@ -336,6 +270,11 @@ That's all of the required database changes.
|
|||
def pool_repository
|
||||
nil
|
||||
end
|
||||
...
|
||||
|
||||
def cool_widget_state
|
||||
super || build_cool_widget_state
|
||||
end
|
||||
|
||||
...
|
||||
end
|
||||
|
@ -343,6 +282,15 @@ That's all of the required database changes.
|
|||
|
||||
- [ ] Implement `CoolWidget.replicables_for_current_secondary` above.
|
||||
- [ ] Ensure `CoolWidget.replicables_for_current_secondary` is well-tested. Search the codebase for `replicables_for_current_secondary` to find examples of parameterized table specs. You may need to add more `FactoryBot` traits.
|
||||
- [ ] Add the following shared examples to `ee/spec/models/ee/cool_widget_spec.rb`:
|
||||
|
||||
```ruby
|
||||
include_examples 'a replicable model with a separate table for verification state' do
|
||||
let(:verifiable_model_record) { build(:cool_widget) } # add extra params if needed to make sure the record is included in `available_verifiables`
|
||||
let(:unverifiable_model_record) { build(:cool_widget) } # add extra params if needed to make sure the record is NOT included in `available_verifiables`
|
||||
end
|
||||
```
|
||||
|
||||
- [ ] Create `ee/app/replicators/geo/cool_widget_replicator.rb`. Implement the `#repository` method which should return a `<Repository>` instance, and implement the class method `.model` to return the `CoolWidget` class:
|
||||
|
||||
```ruby
|
||||
|
@ -529,13 +477,7 @@ That's all of the required database changes.
|
|||
|
||||
- [ ] Make sure the factory also allows setting a `project` attribute. If the model does not have a direct relation to a project, you can use a `transient` attribute. Check out `spec/factories/merge_request_diffs.rb` for an example.
|
||||
|
||||
##### If you added verification state fields to a separate table (option 2 above), then you need to make additional model and factory changes
|
||||
|
||||
If you did not add verification state fields to a separate table, `cool_widget_states`, then skip to [Step 2. Implement metrics gathering](#step-2-implement-metrics-gathering).
|
||||
|
||||
Otherwise, you can follow [the example of Merge Request Diffs](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/63309).
|
||||
|
||||
- [ ] Add a `Geo::CoolWidgetState` model in `ee/app/models/geo/cool_widget_state.rb`:
|
||||
- [ ] Following [the example of Merge Request Diffs](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/63309) add a `Geo::CoolWidgetState` model in `ee/app/models/ee/geo/cool_widget_state.rb`:
|
||||
|
||||
``` ruby
|
||||
# frozen_string_literal: true
|
||||
|
@ -569,63 +511,6 @@ Otherwise, you can follow [the example of Merge Request Diffs](https://gitlab.co
|
|||
end
|
||||
```
|
||||
|
||||
- [ ] Add the following lines to the `cool_widget` model to accomplish some important tasks:
|
||||
- Include the `::Gitlab::Geo::VerificationState` concern.
|
||||
- Delegate verification related methods to the `cool_widget_state` model.
|
||||
- Override some scopes to use the `cool_widget_states` table instead of the model table, for verification.
|
||||
- Override some methods to use the `cool_widget_states` table in verification related queries.
|
||||
|
||||
```ruby
|
||||
class CoolWidget < ApplicationRecord
|
||||
...
|
||||
include ::Gitlab::Geo::VerificationState
|
||||
|
||||
has_one :cool_widget_state, autosave: true, inverse_of: :cool_widget, class_name: 'Geo::CoolWidgetState'
|
||||
|
||||
delegate :verification_retry_at, :verification_retry_at=,
|
||||
:verified_at, :verified_at=,
|
||||
:verification_checksum, :verification_checksum=,
|
||||
:verification_failure, :verification_failure=,
|
||||
:verification_retry_count, :verification_retry_count=,
|
||||
:verification_state=, :verification_state,
|
||||
:verification_started_at=, :verification_started_at,
|
||||
to: :cool_widget_state
|
||||
...
|
||||
|
||||
scope :with_verification_state, ->(state) { joins(:cool_widget_state).where(cool_widget_states: { verification_state: verification_state_value(state) }) }
|
||||
scope :checksummed, -> { joins(:cool_widget_state).where.not(cool_widget_states: { verification_checksum: nil } ) }
|
||||
scope :not_checksummed, -> { joins(:cool_widget_state).where(cool_widget_states: { verification_checksum: nil } ) }
|
||||
|
||||
...
|
||||
|
||||
class_methods do
|
||||
extend ::Gitlab::Utils::Override
|
||||
...
|
||||
override :verification_state_table_name
|
||||
def verification_state_table_name
|
||||
'cool_widget_states'
|
||||
end
|
||||
|
||||
override :verification_state_model_key
|
||||
def verification_state_model_key
|
||||
'cool_widget_id'
|
||||
end
|
||||
|
||||
override :verification_arel_table
|
||||
def verification_arel_table
|
||||
CoolWidgetState.arel_table
|
||||
end
|
||||
end
|
||||
...
|
||||
|
||||
def cool_widget_state
|
||||
super || build_cool_widget_state
|
||||
end
|
||||
|
||||
...
|
||||
end
|
||||
```
|
||||
|
||||
#### Step 2. Implement metrics gathering
|
||||
|
||||
Metrics are gathered by `Geo::MetricsUpdateWorker`, persisted in `GeoNodeStatus` for display in the UI, and sent to Prometheus:
|
||||
|
|
|
@ -37,6 +37,7 @@ It is also a good idea to first open a proof-of-concept merge request. It can be
|
|||
You can look into the following examples of MRs for implementing replication/verification for a new blob type:
|
||||
- [Add db changes](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60935) and [add verification for MR diffs using SSF](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/63309)
|
||||
- [Verify Terraform state versions](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/58800)
|
||||
- [Verify LFS objects](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/63981)
|
||||
|
||||
### Modify database schemas to prepare to add Geo support for Cool Widgets
|
||||
|
||||
|
@ -114,113 +115,9 @@ Geo secondary sites have a [Geo tracking database](https://gitlab.com/gitlab-org
|
|||
|
||||
### Add verification state fields on the Geo primary site
|
||||
|
||||
The Geo primary site needs to checksum every replicable in order for secondaries to verify their own checksums. To do this, Geo requires fields on the Model. There are two ways to add the necessary verification state fields. If the table is large and wide, then it may be a good idea to add verification state fields to a separate table (Option 2). Consult a database expert if needed.
|
||||
The Geo primary site needs to checksum every replicable so secondaries can verify their own checksums. To do this, Geo requires fields on the Model. Add verification state fields to a separate table. Consult a database expert if needed.
|
||||
|
||||
#### Add verification state fields to the model table (Option 1)
|
||||
|
||||
- [ ] Create the migration file in `db/migrate`:
|
||||
|
||||
```shell
|
||||
bin/rails generate migration AddVerificationStateToCoolWidgets
|
||||
```
|
||||
|
||||
- [ ] Replace the contents of the migration file with:
|
||||
|
||||
```ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddVerificationStateToCoolWidgets < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
change_table(:cool_widgets) do |t|
|
||||
t.integer :verification_state, default: 0, limit: 2, null: false
|
||||
t.column :verification_started_at, :datetime_with_timezone
|
||||
t.integer :verification_retry_count, limit: 2, null: false
|
||||
t.column :verification_retry_at, :datetime_with_timezone
|
||||
t.column :verified_at, :datetime_with_timezone
|
||||
t.binary :verification_checksum, using: 'verification_checksum::bytea'
|
||||
|
||||
t.text :verification_failure # rubocop:disable Migration/AddLimitToTextColumns
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
- [ ] If deviating from the above example, then be sure to order columns according to [our guidelines](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/ordering_table_columns.md).
|
||||
- [ ] If `cool_widgets` is a high-traffic table, follow [the database documentation to use `with_lock_retries`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/migration_style_guide.md#when-to-use-the-helper-method)
|
||||
- [ ] Adding a `text` column also [requires](../database/strings_and_the_text_data_type.md#add-a-text-column-to-an-existing-table) setting a limit. Create the migration file in `db/migrate`:
|
||||
|
||||
```shell
|
||||
bin/rails generate migration AddVerificationFailureLimitToCoolWidgets
|
||||
```
|
||||
|
||||
- [ ] Replace the contents of the migration file with:
|
||||
|
||||
```ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddVerificationFailureLimitToCoolWidgets < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
CONSTRAINT_NAME = 'cool_widget_verification_failure_text_limit'
|
||||
|
||||
def up
|
||||
add_text_limit :cool_widget, :verification_failure, 255, constraint_name: CONSTRAINT_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_check_constraint(:cool_widget, CONSTRAINT_NAME)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
- [ ] Add indexes on verification fields to ensure verification can be performed efficiently. Some or all of these indexes can be omitted if the table is guaranteed to be small. Ask a database expert if you are considering omitting indexes. Create the migration file in `db/migrate`:
|
||||
|
||||
```shell
|
||||
bin/rails generate migration AddVerificationIndexesToCoolWidgets
|
||||
```
|
||||
|
||||
- [ ] Replace the contents of the migration file with:
|
||||
|
||||
```ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddVerificationIndexesToCoolWidgets < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
VERIFICATION_STATE_INDEX_NAME = "index_cool_widgets_on_verification_state"
|
||||
PENDING_VERIFICATION_INDEX_NAME = "index_cool_widgets_pending_verification"
|
||||
FAILED_VERIFICATION_INDEX_NAME = "index_cool_widgets_failed_verification"
|
||||
NEEDS_VERIFICATION_INDEX_NAME = "index_cool_widgets_needs_verification"
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_concurrent_index :cool_widgets, :verification_state, name: VERIFICATION_STATE_INDEX_NAME
|
||||
add_concurrent_index :cool_widgets, :verified_at, where: "(verification_state = 0)", order: { verified_at: 'ASC NULLS FIRST' }, name: PENDING_VERIFICATION_INDEX_NAME
|
||||
add_concurrent_index :cool_widgets, :verification_retry_at, where: "(verification_state = 3)", order: { verification_retry_at: 'ASC NULLS FIRST' }, name: FAILED_VERIFICATION_INDEX_NAME
|
||||
add_concurrent_index :cool_widgets, :verification_state, where: "(verification_state = 0 OR verification_state = 3)", name: NEEDS_VERIFICATION_INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index_by_name :cool_widgets, VERIFICATION_STATE_INDEX_NAME
|
||||
remove_concurrent_index_by_name :cool_widgets, PENDING_VERIFICATION_INDEX_NAME
|
||||
remove_concurrent_index_by_name :cool_widgets, FAILED_VERIFICATION_INDEX_NAME
|
||||
remove_concurrent_index_by_name :cool_widgets, NEEDS_VERIFICATION_INDEX_NAME
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
- [ ] Run database migrations:
|
||||
|
||||
```shell
|
||||
bin/rake db:migrate
|
||||
```
|
||||
|
||||
- [ ] Be sure to commit the relevant changes in `db/structure.sql`
|
||||
|
||||
#### Add verification state fields to a separate table (Option 2)
|
||||
#### Add verification state fields to a new table
|
||||
|
||||
- [ ] Create the migration file in `db/migrate`:
|
||||
|
||||
|
@ -274,12 +171,15 @@ The Geo primary site needs to checksum every replicable in order for secondaries
|
|||
```
|
||||
|
||||
- [ ] If deviating from the above example, then be sure to order columns according to [our guidelines](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/ordering_table_columns.md).
|
||||
|
||||
- [ ] Run database migrations:
|
||||
|
||||
```shell
|
||||
bin/rake db:migrate
|
||||
```
|
||||
|
||||
- [ ] If `cool_widgets` is a high-traffic table, follow [the database documentation to use `with_lock_retries`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/migration_style_guide.md#when-to-use-the-helper-method)
|
||||
|
||||
- [ ] Be sure to commit the relevant changes in `db/structure.sql`
|
||||
|
||||
That's all of the required database changes.
|
||||
|
@ -288,14 +188,22 @@ That's all of the required database changes.
|
|||
|
||||
#### Step 1. Implement replication and verification
|
||||
|
||||
- [ ] Include `Gitlab::Geo::ReplicableModel` in the `CoolWidget` class, and specify the Replicator class `with_replicator Geo::CoolWidgetReplicator`.
|
||||
- [ ] Add the following lines to the `cool_widget` model to accomplish some important tasks:
|
||||
- Include `Gitlab::Geo::ReplicableModel` in the `CoolWidget` class, and specify the Replicator class `with_replicator Geo::CoolWidgetReplicator`.
|
||||
- Include the `::Gitlab::Geo::VerificationState` concern.
|
||||
- Delegate verification related methods to the `cool_widget_state` model.
|
||||
- For verification, override some scopes to use the `cool_widget_states` table instead of the model table.
|
||||
- Implement the `verification_state_object` method to return the object that holds
|
||||
the verification details
|
||||
- Override some methods to use the `cool_widget_states` table in verification-related queries.
|
||||
|
||||
At this point the `CoolWidget` class should look like this:
|
||||
|
||||
```ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
|
||||
class CoolWidget < ApplicationRecord
|
||||
...
|
||||
include ::Gitlab::Geo::ReplicableModel
|
||||
include ::Gitlab::Geo::VerificationState
|
||||
|
||||
|
@ -303,38 +211,84 @@ That's all of the required database changes.
|
|||
|
||||
mount_uploader :file, CoolWidgetUploader
|
||||
|
||||
has_one :cool_widget_state, autosave: false, inverse_of: :cool_widget, class_name: 'Geo::CoolWidgetState'
|
||||
|
||||
delegate :verification_retry_at, :verification_retry_at=,
|
||||
:verified_at, :verified_at=,
|
||||
:verification_checksum, :verification_checksum=,
|
||||
:verification_failure, :verification_failure=,
|
||||
:verification_retry_count, :verification_retry_count=,
|
||||
:verification_state=, :verification_state,
|
||||
:verification_started_at=, :verification_started_at,
|
||||
to: :cool_widget_state
|
||||
...
|
||||
|
||||
scope :with_verification_state, ->(state) { joins(:cool_widget_state).where(cool_widget_states: { verification_state: verification_state_value(state) }) }
|
||||
scope :checksummed, -> { joins(:cool_widget_state).where.not(cool_widget_states: { verification_checksum: nil } ) }
|
||||
scope :not_checksummed, -> { joins(:cool_widget_state).where(cool_widget_states: { verification_checksum: nil } ) }
|
||||
|
||||
# Override the `all` default if not all records can be replicated. For an
|
||||
# example of an existing Model that needs to do this, see
|
||||
# `EE::MergeRequestDiff`.
|
||||
# scope :available_replicables, -> { all }
|
||||
|
||||
# @param primary_key_in [Range, CoolWidget] arg to pass to primary_key_in scope
|
||||
# @return [ActiveRecord::Relation<CoolWidget>] everything that should be synced to this node, restricted by primary key
|
||||
def self.replicables_for_current_secondary(primary_key_in)
|
||||
# This issue template does not help you write this method.
|
||||
#
|
||||
# This method is called only on Geo secondary sites. It is called when
|
||||
# we want to know which records to replicate. This is not easy to automate
|
||||
# because for example:
|
||||
#
|
||||
# * The "selective sync" feature allows admins to choose which namespaces # to replicate, per secondary site. Most Models are scoped to a
|
||||
# namespace, but the nature of the relationship to a namespace varies
|
||||
# between Models.
|
||||
# * The "selective sync" feature allows admins to choose which shards to
|
||||
# replicate, per secondary site. Repositories are associated with
|
||||
# shards. Most blob types are not, but Project Uploads are.
|
||||
# * Remote stored replicables are not replicated, by default. But the
|
||||
# setting `sync_object_storage` enables replication of remote stored
|
||||
# replicables.
|
||||
#
|
||||
# Search the codebase for examples, and consult a Geo expert if needed.
|
||||
def verification_state_object
|
||||
cool_widget_state
|
||||
end
|
||||
...
|
||||
|
||||
class_methods do
|
||||
extend ::Gitlab::Utils::Override
|
||||
...
|
||||
|
||||
# @param primary_key_in [Range, CoolWidget] arg to pass to primary_key_in scope
|
||||
# @return [ActiveRecord::Relation<CoolWidget>] everything that should be synced to this node, restricted by primary key
|
||||
def self.replicables_for_current_secondary(primary_key_in)
|
||||
# This issue template does not help you write this method.
|
||||
#
|
||||
# This method is called only on Geo secondary sites. It is called when
|
||||
# we want to know which records to replicate. This is not easy to automate
|
||||
# because for example:
|
||||
#
|
||||
# * The "selective sync" feature allows admins to choose which namespaces # to replicate, per secondary site. Most Models are scoped to a
|
||||
# namespace, but the nature of the relationship to a namespace varies
|
||||
# between Models.
|
||||
# * The "selective sync" feature allows admins to choose which shards to
|
||||
# replicate, per secondary site. Repositories are associated with
|
||||
# shards. Most blob types are not, but Project Uploads are.
|
||||
# * Remote stored replicables are not replicated, by default. But the
|
||||
# setting `sync_object_storage` enables replication of remote stored
|
||||
# replicables.
|
||||
#
|
||||
# Search the codebase for examples, and consult a Geo expert if needed.
|
||||
end
|
||||
|
||||
override :verification_state_table_class
|
||||
def verification_state_table_class
|
||||
CoolWidgetState
|
||||
end
|
||||
end
|
||||
...
|
||||
|
||||
def cool_widget_state
|
||||
super || build_cool_widget_state
|
||||
end
|
||||
|
||||
...
|
||||
end
|
||||
```
|
||||
|
||||
- [ ] Implement `CoolWidget.replicables_for_current_secondary` above.
|
||||
- [ ] Ensure `CoolWidget.replicables_for_current_secondary` is well-tested. Search the codebase for `replicables_for_current_secondary` to find examples of parameterized table specs. You may need to add more `FactoryBot` traits.
|
||||
- [ ] Add the following shared examples to `ee/spec/models/ee/cool_widget_spec.rb`:
|
||||
|
||||
```ruby
|
||||
include_examples 'a replicable model with a separate table for verification state' do
|
||||
let(:verifiable_model_record) { build(:cool_widget) } # add extra params if needed to make sure the record is included in `available_verifiables`
|
||||
let(:unverifiable_model_record) { build(:cool_widget) } # add extra params if needed to make sure the record is NOT included in `available_verifiables`
|
||||
end
|
||||
```
|
||||
|
||||
- [ ] Create `ee/app/replicators/geo/cool_widget_replicator.rb`. Implement the `#carrierwave_uploader` method which should return a `CarrierWave::Uploader`, and implement the class method `.model` to return the `CoolWidget` class:
|
||||
|
||||
```ruby
|
||||
|
@ -498,13 +452,7 @@ That's all of the required database changes.
|
|||
|
||||
- [ ] Make sure the factory also allows setting a `project` attribute. If the model does not have a direct relation to a project, you can use a `transient` attribute. Check out `spec/factories/merge_request_diffs.rb` for an example.
|
||||
|
||||
##### If you added verification state fields to a separate table (option 2 above), then you need to make additional model and factory changes
|
||||
|
||||
If you did not add verification state fields to a separate table, `cool_widget_states`, then skip to [Step 2. Implement metrics gathering](#step-2-implement-metrics-gathering).
|
||||
|
||||
Otherwise, you can follow [the example of Merge Request Diffs](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/63309).
|
||||
|
||||
- [ ] Add a `Geo::CoolWidgetState` model in `ee/app/models/ee/geo/cool_widget_state.rb`:
|
||||
- [ ] Following [the example of Merge Request Diffs](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/63309) add a `Geo::CoolWidgetState` model in `ee/app/models/ee/geo/cool_widget_state.rb`:
|
||||
|
||||
``` ruby
|
||||
module Geo
|
||||
|
@ -536,63 +484,6 @@ Otherwise, you can follow [the example of Merge Request Diffs](https://gitlab.co
|
|||
end
|
||||
```
|
||||
|
||||
- [ ] Add the following lines to the `cool_widget` model to accomplish some important tasks:
|
||||
- Include the `::Gitlab::Geo::VerificationState` concern.
|
||||
- Delegate verification related methods to the `cool_widget_state` model.
|
||||
- Override some scopes to use the `cool_widget_states` table instead of the model table, for verification.
|
||||
- Override some methods to use the `cool_widget_states` table in verification related queries.
|
||||
|
||||
```ruby
|
||||
class CoolWidget < ApplicationRecord
|
||||
...
|
||||
include ::Gitlab::Geo::VerificationState
|
||||
|
||||
has_one :cool_widget_state, autosave: true, inverse_of: :cool_widget, class_name: 'Geo::CoolWidgetState'
|
||||
|
||||
delegate :verification_retry_at, :verification_retry_at=,
|
||||
:verified_at, :verified_at=,
|
||||
:verification_checksum, :verification_checksum=,
|
||||
:verification_failure, :verification_failure=,
|
||||
:verification_retry_count, :verification_retry_count=,
|
||||
:verification_state=, :verification_state,
|
||||
:verification_started_at=, :verification_started_at,
|
||||
to: :cool_widget_state
|
||||
...
|
||||
|
||||
scope :with_verification_state, ->(state) { joins(:cool_widget_state).where(cool_widget_states: { verification_state: verification_state_value(state) }) }
|
||||
scope :checksummed, -> { joins(:cool_widget_state).where.not(cool_widget_states: { verification_checksum: nil } ) }
|
||||
scope :not_checksummed, -> { joins(:cool_widget_state).where(cool_widget_states: { verification_checksum: nil } ) }
|
||||
|
||||
...
|
||||
|
||||
class_methods do
|
||||
extend ::Gitlab::Utils::Override
|
||||
...
|
||||
override :verification_state_table_name
|
||||
def verification_state_table_name
|
||||
'cool_widget_states'
|
||||
end
|
||||
|
||||
override :verification_state_model_key
|
||||
def verification_state_model_key
|
||||
'cool_widget_id'
|
||||
end
|
||||
|
||||
override :verification_arel_table
|
||||
def verification_arel_table
|
||||
CoolWidgetState.arel_table
|
||||
end
|
||||
end
|
||||
...
|
||||
|
||||
def cool_widget_state
|
||||
super || build_cool_widget_state
|
||||
end
|
||||
|
||||
...
|
||||
end
|
||||
```
|
||||
|
||||
#### Step 2. Implement metrics gathering
|
||||
|
||||
Metrics are gathered by `Geo::MetricsUpdateWorker`, persisted in `GeoNodeStatus` for display in the UI, and sent to Prometheus:
|
||||
|
|
|
@ -123,7 +123,7 @@ Naming/FileName:
|
|||
- 'ee/lib/generators/**/*'
|
||||
- 'scripts/**/*'
|
||||
- 'spec/**/*'
|
||||
- 'tooling/bin/*'
|
||||
- 'tooling/bin/**/*'
|
||||
- 'ee/spec/**/*'
|
||||
- 'jh/spec/**/*'
|
||||
- 'qa/bin/*'
|
||||
|
@ -708,10 +708,6 @@ Cop/UserAdmin:
|
|||
- 'spec/**/*.rb'
|
||||
- 'ee/spec/**/*.rb'
|
||||
|
||||
Performance/OpenStruct:
|
||||
Exclude:
|
||||
- 'ee/spec/**/*.rb'
|
||||
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/327495
|
||||
Style/RegexpLiteral:
|
||||
Enabled: false
|
||||
|
|
|
@ -2172,20 +2172,6 @@ Cop/UserAdmin:
|
|||
- 'lib/gitlab/visibility_level.rb'
|
||||
- 'qa/qa/runtime/api/client.rb'
|
||||
|
||||
# WIP https://gitlab.com/gitlab-org/gitlab/-/issues/325744
|
||||
Performance/OpenStruct:
|
||||
Exclude:
|
||||
- 'Guardfile'
|
||||
- 'app/finders/snippets_finder.rb'
|
||||
- 'app/helpers/application_settings_helper.rb'
|
||||
- 'ee/lib/gitlab/graphql/aggregations/epics/epic_node.rb'
|
||||
- 'ee/lib/gitlab/graphql/aggregations/epics/epic_node.rb'
|
||||
- 'lib/api/wikis.rb'
|
||||
- 'lib/gitlab/ci/ansi2html.rb'
|
||||
- 'lib/gitlab/git/diff_collection.rb'
|
||||
- 'lib/gitlab/import_export/after_export_strategies/base_after_export_strategy.rb'
|
||||
- 'lib/mattermost/session.rb'
|
||||
|
||||
# WIP: https://gitlab.com/gitlab-org/gitlab/-/issues/324629
|
||||
Gitlab/DelegatePredicateMethods:
|
||||
Exclude:
|
||||
|
|
|
@ -1 +1 @@
|
|||
d03d5db1ce9e3cdb66d63936cf7875b03bb49212
|
||||
59ec40d9ed6d719bcccc77193e031ffee3098afc
|
||||
|
|
|
@ -15,6 +15,8 @@ import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
|
|||
import { n__, s__, __ } from '~/locale';
|
||||
import sidebarEventHub from '~/sidebar/event_hub';
|
||||
import Tracking from '~/tracking';
|
||||
import { formatDate } from '~/lib/utils/datetime_utility';
|
||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import AccessorUtilities from '../../lib/utils/accessor';
|
||||
import { inactiveId, LIST, ListType, toggleFormEventPrefix } from '../constants';
|
||||
import eventHub from '../eventhub';
|
||||
|
@ -40,7 +42,7 @@ export default {
|
|||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
mixins: [Tracking.mixin()],
|
||||
mixins: [Tracking.mixin(), glFeatureFlagMixin()],
|
||||
inject: {
|
||||
boardId: {
|
||||
default: '',
|
||||
|
@ -86,6 +88,13 @@ export default {
|
|||
listTitle() {
|
||||
return this.list?.label?.description || this.list?.assignee?.name || this.list.title || '';
|
||||
},
|
||||
listIterationPeriod() {
|
||||
const iteration = this.list?.iteration;
|
||||
return iteration ? this.getIterationPeriod(iteration) : '';
|
||||
},
|
||||
isIterationList() {
|
||||
return this.listType === ListType.iteration;
|
||||
},
|
||||
showListHeaderButton() {
|
||||
return !this.disabled && this.listType !== ListType.closed;
|
||||
},
|
||||
|
@ -96,7 +105,10 @@ export default {
|
|||
return this.listType === ListType.assignee && this.showListDetails;
|
||||
},
|
||||
showIterationListDetails() {
|
||||
return this.listType === ListType.iteration && this.showListDetails;
|
||||
return this.isIterationList && this.showListDetails;
|
||||
},
|
||||
iterationCadencesAvailable() {
|
||||
return this.isIterationList && this.glFeatures.iterationCadences;
|
||||
},
|
||||
showListDetails() {
|
||||
return !this.list.collapsed || !this.isSwimlanesHeader;
|
||||
|
@ -208,6 +220,16 @@ export default {
|
|||
updateListFunction() {
|
||||
this.updateList({ listId: this.list.id, collapsed: this.list.collapsed });
|
||||
},
|
||||
/**
|
||||
* TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/344619
|
||||
* This method also exists as a utility function in ee/../iterations/utils.js
|
||||
* Remove the duplication when the EE code is separated from this compoment.
|
||||
*/
|
||||
getIterationPeriod({ startDate, dueDate }) {
|
||||
const start = formatDate(startDate, 'mmm d, yyyy', true);
|
||||
const due = formatDate(dueDate, 'mmm d, yyyy', true);
|
||||
return `${start} - ${due}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -307,6 +329,13 @@ export default {
|
|||
class="board-title-main-text gl-text-truncate"
|
||||
>
|
||||
{{ listTitle }}
|
||||
<div
|
||||
v-if="iterationCadencesAvailable"
|
||||
class="gl-display-inline-block"
|
||||
data-testid="board-list-iteration-period"
|
||||
>
|
||||
<time class="gl-text-gray-400">{{ listIterationPeriod }}</time>
|
||||
</div>
|
||||
</span>
|
||||
<span
|
||||
v-if="listType === 'assignee'"
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
<script>
|
||||
import { GlSprintf } from '@gitlab/ui';
|
||||
import { escape } from 'lodash';
|
||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import { n__, s__ } from '~/locale';
|
||||
|
||||
const mergeCommitCount = s__('mrWidgetCommitsAdded|1 merge commit');
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlSprintf,
|
||||
},
|
||||
mixins: [glFeatureFlagMixin()],
|
||||
props: {
|
||||
isSquashEnabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
isFastForwardEnabled: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
commitsCount: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0,
|
||||
},
|
||||
targetBranch: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
targetBranchEscaped() {
|
||||
return escape(this.targetBranch);
|
||||
},
|
||||
commitsCountMessage() {
|
||||
return n__('%d commit', '%d commits', this.isSquashEnabled ? 1 : this.commitsCount);
|
||||
},
|
||||
message() {
|
||||
return this.isFastForwardEnabled
|
||||
? s__('mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}.')
|
||||
: s__(
|
||||
'mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}%{squashedCommits}.',
|
||||
);
|
||||
},
|
||||
textDecorativeComponent() {
|
||||
return this.glFeatures.restructuredMrWidget ? 'span' : 'strong';
|
||||
},
|
||||
},
|
||||
mergeCommitCount,
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span>
|
||||
<gl-sprintf :message="message">
|
||||
<template #commitCount>
|
||||
<component :is="textDecorativeComponent" class="commits-count-message">{{
|
||||
commitsCountMessage
|
||||
}}</component>
|
||||
</template>
|
||||
<template #mergeCommitCount>
|
||||
<component :is="textDecorativeComponent">{{ $options.mergeCommitCount }}</component>
|
||||
</template>
|
||||
<template #targetBranch>
|
||||
<span class="label-branch">{{ targetBranchEscaped }}</span>
|
||||
</template>
|
||||
<template v-if="glFeatures.restructuredMrWidget" #squashedCommits>
|
||||
<template v-if="isSquashEnabled">
|
||||
{{ __('(commits will be squashed)') }}</template
|
||||
></template
|
||||
>
|
||||
</gl-sprintf>
|
||||
</span>
|
||||
</template>
|
|
@ -1,8 +1,10 @@
|
|||
<script>
|
||||
import { s__, n__ } from '~/locale';
|
||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
|
||||
export default {
|
||||
name: 'MRWidgetRelatedLinks',
|
||||
mixins: [glFeatureFlagMixin()],
|
||||
props: {
|
||||
relatedLinks: {
|
||||
type: Object,
|
||||
|
@ -14,6 +16,11 @@ export default {
|
|||
required: false,
|
||||
default: '',
|
||||
},
|
||||
showAssignToMe: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
closesText() {
|
||||
|
@ -30,16 +37,25 @@ export default {
|
|||
};
|
||||
</script>
|
||||
<template>
|
||||
<section class="mr-info-list gl-ml-7 gl-pb-5">
|
||||
<p v-if="relatedLinks.closing">
|
||||
<section>
|
||||
<p
|
||||
v-if="relatedLinks.closing"
|
||||
:class="{ 'gl-display-line gl-m-0': glFeatures.restructuredMrWidget }"
|
||||
>
|
||||
{{ closesText }}
|
||||
<span v-html="relatedLinks.closing /* eslint-disable-line vue/no-v-html */"></span>
|
||||
</p>
|
||||
<p v-if="relatedLinks.mentioned">
|
||||
<p
|
||||
v-if="relatedLinks.mentioned"
|
||||
:class="{ 'gl-display-line gl-m-0': glFeatures.restructuredMrWidget }"
|
||||
>
|
||||
{{ n__('mrWidget|Mentions issue', 'mrWidget|Mentions issues', relatedLinks.mentionedCount) }}
|
||||
<span v-html="relatedLinks.mentioned /* eslint-disable-line vue/no-v-html */"></span>
|
||||
</p>
|
||||
<p v-if="relatedLinks.assignToMe">
|
||||
<p
|
||||
v-if="relatedLinks.assignToMe && showAssignToMe"
|
||||
:class="{ 'gl-display-line gl-m-0': glFeatures.restructuredMrWidget }"
|
||||
>
|
||||
<span v-html="relatedLinks.assignToMe /* eslint-disable-line vue/no-v-html */"></span>
|
||||
</p>
|
||||
</section>
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
<script>
|
||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
|
||||
export default {
|
||||
mixins: [glFeatureFlagMixin()],
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
|
@ -20,7 +23,10 @@ export default {
|
|||
<template>
|
||||
<li>
|
||||
<div class="commit-message-editor">
|
||||
<div class="d-flex flex-wrap align-items-center justify-content-between">
|
||||
<div
|
||||
:class="{ 'gl-mb-3': glFeatures.restructuredMrWidget }"
|
||||
class="d-flex flex-wrap align-items-center justify-content-between"
|
||||
>
|
||||
<label class="col-form-label" :for="inputId">
|
||||
<strong>{{ label }}</strong>
|
||||
</label>
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
<script>
|
||||
import { GlButton, GlSprintf } from '@gitlab/ui';
|
||||
import { escape } from 'lodash';
|
||||
import { __, n__, s__ } from '~/locale';
|
||||
|
||||
const mergeCommitCount = s__('mrWidgetCommitsAdded|1 merge commit');
|
||||
import { GlButton } from '@gitlab/ui';
|
||||
import { __ } from '~/locale';
|
||||
import AddedCommitMessage from '../added_commit_message.vue';
|
||||
|
||||
export default {
|
||||
mergeCommitCount,
|
||||
components: {
|
||||
GlButton,
|
||||
GlSprintf,
|
||||
AddedCommitMessage,
|
||||
},
|
||||
props: {
|
||||
isSquashEnabled: {
|
||||
|
@ -39,9 +36,6 @@ export default {
|
|||
collapseIcon() {
|
||||
return this.expanded ? 'chevron-down' : 'chevron-right';
|
||||
},
|
||||
commitsCountMessage() {
|
||||
return n__('%d commit', '%d commits', this.isSquashEnabled ? 1 : this.commitsCount);
|
||||
},
|
||||
modifyLinkMessage() {
|
||||
if (this.isFastForwardEnabled) return __('Modify commit message');
|
||||
else if (this.isSquashEnabled) return __('Modify commit messages');
|
||||
|
@ -50,16 +44,6 @@ export default {
|
|||
ariaLabel() {
|
||||
return this.expanded ? __('Collapse') : __('Expand');
|
||||
},
|
||||
targetBranchEscaped() {
|
||||
return escape(this.targetBranch);
|
||||
},
|
||||
message() {
|
||||
return this.isFastForwardEnabled
|
||||
? s__('mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}.')
|
||||
: s__(
|
||||
'mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}.',
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggle() {
|
||||
|
@ -86,17 +70,12 @@ export default {
|
|||
<span v-if="expanded">{{ __('Collapse') }}</span>
|
||||
<span v-else>
|
||||
<span class="vertical-align-middle">
|
||||
<gl-sprintf :message="message">
|
||||
<template #commitCount>
|
||||
<strong class="commits-count-message">{{ commitsCountMessage }}</strong>
|
||||
</template>
|
||||
<template #mergeCommitCount>
|
||||
<strong>{{ $options.mergeCommitCount }}</strong>
|
||||
</template>
|
||||
<template #targetBranch>
|
||||
<span class="label-branch">{{ targetBranchEscaped }}</span>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
<added-commit-message
|
||||
:is-squash-enabled="isSquashEnabled"
|
||||
:is-fast-forward-enabled="isFastForwardEnabled"
|
||||
:commits-count="commitsCount"
|
||||
:target-branch="targetBranch"
|
||||
/>
|
||||
</span>
|
||||
<gl-button variant="link" class="modify-message-button">
|
||||
{{ modifyLinkMessage }}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script>
|
||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import statusIcon from '../mr_widget_status_icon.vue';
|
||||
|
||||
export default {
|
||||
|
@ -6,11 +7,12 @@ export default {
|
|||
components: {
|
||||
statusIcon,
|
||||
},
|
||||
mixins: [glFeatureFlagMixin()],
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="mr-widget-body media">
|
||||
<status-icon :show-disabled-button="true" status="loading" />
|
||||
<status-icon :show-disabled-button="!glFeatures.restructuredMrWidget" status="loading" />
|
||||
<div class="media-body space-children">
|
||||
<span class="bold"> {{ s__('mrWidget|Checking if merge request can be merged…') }} </span>
|
||||
</div>
|
||||
|
|
|
@ -37,7 +37,7 @@ export default {
|
|||
<template>
|
||||
<div class="mr-widget-body media">
|
||||
<status-icon status="success" />
|
||||
<p class="media-body gl-m-0! gl-font-weight-bold">
|
||||
<p class="media-body gl-m-0! gl-font-weight-bold gl-text-gray-900!">
|
||||
<template v-if="canMerge">
|
||||
{{ __('Ready to merge!') }}
|
||||
</template>
|
||||
|
|
|
@ -35,6 +35,8 @@ import eventHub from '../../event_hub';
|
|||
import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables';
|
||||
import MergeRequestStore from '../../stores/mr_widget_store';
|
||||
import statusIcon from '../mr_widget_status_icon.vue';
|
||||
import AddedCommitMessage from '../added_commit_message.vue';
|
||||
import RelatedLinks from '../mr_widget_related_links.vue';
|
||||
import CommitEdit from './commit_edit.vue';
|
||||
import CommitMessageDropdown from './commit_message_dropdown.vue';
|
||||
import CommitsHeader from './commits_header.vue';
|
||||
|
@ -113,6 +115,8 @@ export default {
|
|||
import(
|
||||
'ee_component/vue_merge_request_widget/components/merge_train_failed_pipeline_confirmation_dialog.vue'
|
||||
),
|
||||
AddedCommitMessage,
|
||||
RelatedLinks,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
|
@ -134,6 +138,7 @@ export default {
|
|||
isSquashReadOnly: this.mr.squashIsReadonly,
|
||||
squashCommitMessage: this.mr.squashCommitMessage,
|
||||
isPipelineFailedModalVisible: false,
|
||||
editCommitMessage: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -162,7 +167,7 @@ export default {
|
|||
},
|
||||
isMergeAllowed() {
|
||||
if (this.glFeatures.mergeRequestWidgetGraphql) {
|
||||
return this.state.mergeable || false;
|
||||
return this.state.mergeable;
|
||||
}
|
||||
|
||||
return this.mr.isMergeAllowed;
|
||||
|
@ -279,6 +284,10 @@ export default {
|
|||
return enableSquashBeforeMerge && this.commitsCount > 1;
|
||||
},
|
||||
shouldShowMergeControls() {
|
||||
if (this.glFeatures.restructuredMrWidget) {
|
||||
return this.restructuredWidgetShowMergeButtons;
|
||||
}
|
||||
|
||||
return this.isMergeAllowed || this.isAutoMergeAvailable;
|
||||
},
|
||||
shouldShowSquashEdit() {
|
||||
|
@ -297,15 +306,26 @@ export default {
|
|||
showDangerMessageForMergeTrain() {
|
||||
return this.preferredAutoMergeStrategy === MT_MERGE_STRATEGY && this.isPipelineFailed;
|
||||
},
|
||||
restructuredWidgetShowMergeButtons() {
|
||||
if (this.glFeatures.restructuredMrWidget) {
|
||||
return this.isMergeAllowed && this.state.userPermissions.canMerge;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.glFeatures.mergeRequestWidgetGraphql) {
|
||||
eventHub.$on('ApprovalUpdated', this.updateGraphqlState);
|
||||
eventHub.$on('MRWidgetUpdateRequested', this.updateGraphqlState);
|
||||
eventHub.$on('mr.discussion.updated', this.updateGraphqlState);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.glFeatures.mergeRequestWidgetGraphql) {
|
||||
eventHub.$off('ApprovalUpdated', this.updateGraphqlState);
|
||||
eventHub.$off('MRWidgetUpdateRequested', this.updateGraphqlState);
|
||||
eventHub.$off('mr.discussion.updated', this.updateGraphqlState);
|
||||
}
|
||||
|
||||
if (this.pollingInterval) {
|
||||
|
@ -492,7 +512,12 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
:class="{
|
||||
'gl-border-t-1 gl-border-t-solid gl-border-gray-100 gl-bg-gray-10 gl-pl-7':
|
||||
glFeatures.restructuredMrWidget,
|
||||
}"
|
||||
>
|
||||
<div v-if="loading" class="mr-widget-body">
|
||||
<div class="gl-w-full mr-ready-to-merge-loader">
|
||||
<gl-skeleton-loader :width="418" :height="30">
|
||||
|
@ -504,11 +529,16 @@ export default {
|
|||
</div>
|
||||
</div>
|
||||
<template v-else>
|
||||
<div class="mr-widget-body media">
|
||||
<status-icon :status="iconClass" />
|
||||
<div
|
||||
class="mr-widget-body media"
|
||||
:class="{
|
||||
'mr-widget-body-line-height-1': glFeatures.restructuredMrWidget,
|
||||
}"
|
||||
>
|
||||
<status-icon v-if="!glFeatures.restructuredMrWidget" :status="iconClass" />
|
||||
<div class="media-body">
|
||||
<div class="mr-widget-body-controls gl-display-flex gl-align-items-center">
|
||||
<gl-button-group class="gl-align-self-start">
|
||||
<div class="mr-widget-body-controls gl-display-flex gl-align-items-center gl-flex-wrap">
|
||||
<gl-button-group v-if="restructuredWidgetShowMergeButtons" class="gl-align-self-start">
|
||||
<gl-button
|
||||
size="medium"
|
||||
category="primary"
|
||||
|
@ -555,6 +585,7 @@ export default {
|
|||
</gl-button-group>
|
||||
<div
|
||||
v-if="shouldShowMergeControls"
|
||||
:class="{ 'gl-w-full gl-order-n1 gl-mb-5': glFeatures.restructuredMrWidget }"
|
||||
class="gl-display-flex gl-align-items-center gl-flex-wrap"
|
||||
>
|
||||
<merge-train-helper-icon
|
||||
|
@ -570,7 +601,11 @@ export default {
|
|||
id="remove-source-branch-input"
|
||||
v-model="removeSourceBranch"
|
||||
:disabled="isRemoveSourceBranchButtonDisabled"
|
||||
class="js-remove-source-branch-checkbox gl-mx-3 gl-display-flex gl-align-items-center"
|
||||
:class="{
|
||||
'gl-mx-3': !glFeatures.restructuredMrWidget,
|
||||
'gl-mr-5': glFeatures.restructuredMrWidget,
|
||||
}"
|
||||
class="js-remove-source-branch-checkbox gl-display-flex gl-align-items-center"
|
||||
>
|
||||
{{ __('Delete source branch') }}
|
||||
</gl-form-checkbox>
|
||||
|
@ -581,31 +616,141 @@ export default {
|
|||
v-model="squashBeforeMerge"
|
||||
:help-path="mr.squashBeforeMergeHelpPath"
|
||||
:is-disabled="isSquashReadOnly"
|
||||
class="gl-mx-3"
|
||||
:class="{
|
||||
'gl-mx-3': !glFeatures.restructuredMrWidget,
|
||||
'gl-mr-5': glFeatures.restructuredMrWidget,
|
||||
}"
|
||||
/>
|
||||
|
||||
<gl-form-checkbox
|
||||
v-if="
|
||||
glFeatures.restructuredMrWidget && (shouldShowSquashEdit || shouldShowMergeEdit)
|
||||
"
|
||||
v-model="editCommitMessage"
|
||||
class="gl-display-flex gl-align-items-center"
|
||||
>
|
||||
{{ __('Edit commit message') }}
|
||||
</gl-form-checkbox>
|
||||
</div>
|
||||
<template v-else>
|
||||
<div class="bold js-resolve-mr-widget-items-message gl-ml-3">
|
||||
<div
|
||||
v-if="hasPipelineMustSucceedConflict"
|
||||
class="gl-display-flex gl-align-items-center"
|
||||
data-testid="pipeline-succeed-conflict"
|
||||
<div
|
||||
v-else-if="!glFeatures.restructuredMrWidget"
|
||||
class="bold js-resolve-mr-widget-items-message gl-ml-3"
|
||||
>
|
||||
<div
|
||||
v-if="hasPipelineMustSucceedConflict"
|
||||
class="gl-display-flex gl-align-items-center"
|
||||
data-testid="pipeline-succeed-conflict"
|
||||
>
|
||||
<gl-sprintf :message="pipelineMustSucceedConflictText" />
|
||||
<gl-link
|
||||
:href="mr.pipelineMustSucceedDocsPath"
|
||||
target="_blank"
|
||||
class="gl-display-flex gl-ml-2"
|
||||
>
|
||||
<gl-sprintf :message="pipelineMustSucceedConflictText" />
|
||||
<gl-link
|
||||
:href="mr.pipelineMustSucceedDocsPath"
|
||||
target="_blank"
|
||||
class="gl-display-flex gl-ml-2"
|
||||
<gl-icon name="question" />
|
||||
</gl-link>
|
||||
</div>
|
||||
<gl-sprintf v-else :message="mergeDisabledText" />
|
||||
</div>
|
||||
<template v-if="glFeatures.restructuredMrWidget">
|
||||
<div v-show="editCommitMessage" class="gl-w-full gl-order-n1">
|
||||
<ul
|
||||
:class="{
|
||||
'content-list': !glFeatures.restructuredMrWidget,
|
||||
'gl-list-style-none gl-p-0 gl-pt-4': glFeatures.restructuredMrWidget,
|
||||
}"
|
||||
class="border-top commits-list flex-list"
|
||||
>
|
||||
<commit-edit
|
||||
v-if="shouldShowSquashEdit"
|
||||
v-model="squashCommitMessage"
|
||||
:label="__('Squash commit message')"
|
||||
input-id="squash-message-edit"
|
||||
class="gl-m-0! gl-p-0!"
|
||||
>
|
||||
<gl-icon name="question" />
|
||||
</gl-link>
|
||||
</div>
|
||||
<gl-sprintf v-else :message="mergeDisabledText" />
|
||||
<template #header>
|
||||
<commit-message-dropdown v-model="squashCommitMessage" :commits="commits" />
|
||||
</template>
|
||||
</commit-edit>
|
||||
<commit-edit
|
||||
v-if="shouldShowMergeEdit"
|
||||
v-model="commitMessage"
|
||||
:label="__('Merge commit message')"
|
||||
input-id="merge-message-edit"
|
||||
class="gl-m-0! gl-p-0!"
|
||||
>
|
||||
<template #checkbox>
|
||||
<label>
|
||||
<input
|
||||
id="include-description"
|
||||
type="checkbox"
|
||||
@change="updateMergeCommitMessage($event.target.checked)"
|
||||
/>
|
||||
{{ __('Include merge request description') }}
|
||||
</label>
|
||||
</template>
|
||||
</commit-edit>
|
||||
</ul>
|
||||
</div>
|
||||
<div
|
||||
v-if="!restructuredWidgetShowMergeButtons"
|
||||
class="gl-w-full gl-order-n1 gl-text-gray-500"
|
||||
>
|
||||
<strong>
|
||||
{{ __('Merge details') }}
|
||||
</strong>
|
||||
<ul class="gl-pl-4 gl-m-0">
|
||||
<li class="gl-line-height-normal">
|
||||
<added-commit-message
|
||||
:is-squash-enabled="squashBeforeMerge"
|
||||
:is-fast-forward-enabled="!shouldShowMergeEdit"
|
||||
:commits-count="commitsCount"
|
||||
:target-branch="stateData.targetBranch"
|
||||
/>
|
||||
</li>
|
||||
<li class="gl-line-height-normal">
|
||||
<template v-if="removeSourceBranch">
|
||||
{{ __('Source branch will be deleted.') }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ __('Source branch will not be deleted.') }}
|
||||
</template>
|
||||
</li>
|
||||
<li v-if="mr.relatedLinks" class="gl-line-height-normal">
|
||||
<related-links
|
||||
:state="mr.state"
|
||||
:related-links="mr.relatedLinks"
|
||||
:show-assign-to-me="false"
|
||||
class="mr-ready-merge-related-links gl-display-inline"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
:class="{ 'gl-mb-5': restructuredWidgetShowMergeButtons }"
|
||||
class="gl-w-full gl-order-n1 gl-text-gray-500"
|
||||
>
|
||||
<added-commit-message
|
||||
:is-squash-enabled="squashBeforeMerge"
|
||||
:is-fast-forward-enabled="!shouldShowMergeEdit"
|
||||
:commits-count="commitsCount"
|
||||
:target-branch="stateData.targetBranch"
|
||||
/>
|
||||
<template v-if="mr.relatedLinks">
|
||||
·
|
||||
<related-links
|
||||
:state="mr.state"
|
||||
:related-links="mr.relatedLinks"
|
||||
:show-assign-to-me="false"
|
||||
class="mr-ready-merge-related-links gl-display-inline"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div
|
||||
v-if="showDangerMessageForMergeTrain"
|
||||
v-if="showDangerMessageForMergeTrain && !glFeatures.restructuredMrWidget"
|
||||
class="gl-mt-5 gl-text-gray-500"
|
||||
data-testid="failed-pipeline-merge-train-text"
|
||||
>
|
||||
|
@ -613,7 +758,7 @@ export default {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="shouldShowMergeControls">
|
||||
<template v-if="shouldShowMergeControls && !glFeatures.restructuredMrWidget">
|
||||
<div
|
||||
v-if="!shouldShowMergeEdit"
|
||||
class="mr-fast-forward-message"
|
||||
|
@ -622,7 +767,7 @@ export default {
|
|||
{{ __('Fast-forward merge without a merge commit') }}
|
||||
</div>
|
||||
<commits-header
|
||||
v-if="shouldShowSquashEdit || shouldShowMergeEdit"
|
||||
v-if="!glFeatures.restructuredMrWidget && (shouldShowSquashEdit || shouldShowMergeEdit)"
|
||||
:is-squash-enabled="squashBeforeMerge"
|
||||
:commits-count="commitsCount"
|
||||
:target-branch="stateData.targetBranch"
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
<script>
|
||||
import { GlIcon, GlTooltipDirective, GlFormCheckbox } from '@gitlab/ui';
|
||||
import { GlIcon, GlTooltipDirective, GlFormCheckbox, GlLink } from '@gitlab/ui';
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import { SQUASH_BEFORE_MERGE } from '../../i18n';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlIcon,
|
||||
GlFormCheckbox,
|
||||
GlLink,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
mixins: [glFeatureFlagsMixin()],
|
||||
i18n: {
|
||||
...SQUASH_BEFORE_MERGE,
|
||||
},
|
||||
|
@ -33,6 +36,9 @@ export default {
|
|||
tooltipTitle() {
|
||||
return this.isDisabled ? this.$options.i18n.tooltipTitle : null;
|
||||
},
|
||||
helpIconName() {
|
||||
return this.glFeatures.restructuredMrWidget ? 'question-o' : 'question';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -51,18 +57,18 @@ export default {
|
|||
>
|
||||
{{ $options.i18n.checkboxLabel }}
|
||||
</gl-form-checkbox>
|
||||
<a
|
||||
<gl-link
|
||||
v-if="helpPath"
|
||||
v-gl-tooltip
|
||||
:href="helpPath"
|
||||
:title="$options.i18n.helpLabel"
|
||||
:class="{ 'gl-text-blue-600': glFeatures.restructuredMrWidget }"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
<gl-icon name="question" />
|
||||
<gl-icon :name="helpIconName" />
|
||||
<span class="sr-only">
|
||||
{{ $options.i18n.helpLabel }}
|
||||
</span>
|
||||
</a>
|
||||
</gl-link>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -91,6 +91,7 @@ export default {
|
|||
MrWidgetApprovals,
|
||||
SecurityReportsApp: () => import('~/vue_shared/security_reports/security_reports_app.vue'),
|
||||
MergeChecksFailed: () => import('./components/states/merge_checks_failed.vue'),
|
||||
ReadyToMerge: ReadyToMergeState,
|
||||
},
|
||||
apollo: {
|
||||
state: {
|
||||
|
@ -213,6 +214,9 @@ export default {
|
|||
window.gon?.features?.refactorMrWidgetsExtensionsUser
|
||||
);
|
||||
},
|
||||
isRestructuredMrWidgetEnabled() {
|
||||
return window.gon?.features?.restructuredMrWidget;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'mr.machineValue': {
|
||||
|
@ -547,12 +551,17 @@ export default {
|
|||
|
||||
<div class="mr-widget-section">
|
||||
<component :is="componentName" :mr="mr" :service="service" />
|
||||
|
||||
<div class="mr-widget-info">
|
||||
<ready-to-merge
|
||||
v-if="isRestructuredMrWidgetEnabled && mr.commitsCount"
|
||||
:mr="mr"
|
||||
:service="service"
|
||||
/>
|
||||
<div v-else class="mr-widget-info">
|
||||
<mr-widget-related-links
|
||||
v-if="shouldRenderRelatedLinks"
|
||||
:state="mr.state"
|
||||
:related-links="mr.relatedLinks"
|
||||
class="mr-info-list gl-ml-7 gl-pb-5"
|
||||
/>
|
||||
|
||||
<source-branch-removal-status v-if="shouldRenderSourceBranchRemovalStatus" />
|
||||
|
|
|
@ -18,7 +18,9 @@ fragment ReadyToMerge on Project {
|
|||
commitCount
|
||||
diffHeadSha
|
||||
userPermissions {
|
||||
canMerge
|
||||
removeSourceBranch
|
||||
updateMergeRequest
|
||||
}
|
||||
targetBranch
|
||||
mergeError
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { stateKey } from './state_maps';
|
||||
|
||||
export default function deviseState() {
|
||||
if (this.hasMergeChecksFailed) {
|
||||
if (!this.commitsCount) {
|
||||
return stateKey.nothingToMerge;
|
||||
} else if (this.hasMergeChecksFailed && !this.autoMergeEnabled) {
|
||||
return stateKey.mergeChecksFailed;
|
||||
} else if (this.projectArchived) {
|
||||
return stateKey.archived;
|
||||
} else if (this.branchMissing) {
|
||||
return stateKey.missingBranch;
|
||||
} else if (!this.commitsCount) {
|
||||
return stateKey.nothingToMerge;
|
||||
} else if (this.mergeStatus === 'unchecked' || this.mergeStatus === 'checking') {
|
||||
return stateKey.checking;
|
||||
} else if (this.hasConflicts) {
|
||||
|
|
|
@ -269,7 +269,9 @@ $tabs-holder-z-index: 250;
|
|||
}
|
||||
|
||||
.mr-widget-body {
|
||||
line-height: 28px;
|
||||
&:not(.mr-widget-body-line-height-1) {
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
@include clearfix;
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
description: Control the job concurrency in GitLab CI/CD
|
||||
---
|
||||
|
||||
# Resource Group **(FREE)**
|
||||
# Resource group **(FREE)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/15536) in GitLab 12.7.
|
||||
|
||||
|
@ -13,7 +13,7 @@ By default, pipelines in GitLab CI/CD run in parallel. The parallelization is an
|
|||
the feedback loop in merge requests, however, there are some situations that
|
||||
you may want to limit the concurrency on deployment
|
||||
jobs to run them one by one.
|
||||
Resource Group allows you to strategically control
|
||||
Use resource groups to strategically control
|
||||
the concurrency of the jobs for optimizing your continuous deployments workflow with safety.
|
||||
|
||||
## Add a resource group
|
||||
|
@ -139,9 +139,59 @@ Depending on the process mode of the resource group:
|
|||
- `deploy-1`, `deploy-2`, and `deploy-3` do not run in parallel.
|
||||
- `deploy-3` runs first, `deploy-2` runs second and `deploy-1` runs last.
|
||||
|
||||
## Pipeline-level concurrency control with Cross-Project/Parent-Child pipelines
|
||||
## Pipeline-level concurrency control with cross-project/parent-child pipelines
|
||||
|
||||
See the how to [control the pipeline concurrency in cross-project pipelines](../yaml/index.md#pipeline-level-concurrency-control-with-cross-projectparent-child-pipelines).
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/39057) in GitLab 13.9.
|
||||
|
||||
You can define `resource_group` for downstream pipelines that are sensitive to concurrent
|
||||
executions. The [`trigger` keyword](../yaml/index.md#trigger) can trigger downstream pipelines and the
|
||||
[`resource_group` keyword](../yaml/index.md#resource_group) can co-exist with it. `resource_group` is useful to control the
|
||||
concurrency of deployment pipelines, while other jobs can continue to run concurrently.
|
||||
|
||||
The following example has two pipeline configurations in a project. When a pipeline starts running,
|
||||
non-sensitive jobs are executed first and aren't affected by concurrent executions in other
|
||||
pipelines. However, GitLab ensures that there are no other deployment pipelines running before
|
||||
triggering a deployment (child) pipeline. If other deployment pipelines are running, GitLab waits
|
||||
until those pipelines finish before running another one.
|
||||
|
||||
```yaml
|
||||
# .gitlab-ci.yml (parent pipeline)
|
||||
|
||||
build:
|
||||
stage: build
|
||||
script: echo "Building..."
|
||||
|
||||
test:
|
||||
stage: test
|
||||
script: echo "Testing..."
|
||||
|
||||
deploy:
|
||||
stage: deploy
|
||||
trigger:
|
||||
include: deploy.gitlab-ci.yml
|
||||
strategy: depend
|
||||
resource_group: AWS-production
|
||||
```
|
||||
|
||||
```yaml
|
||||
# deploy.gitlab-ci.yml (child pipeline)
|
||||
|
||||
stages:
|
||||
- provision
|
||||
- deploy
|
||||
|
||||
provision:
|
||||
stage: provision
|
||||
script: echo "Provisioning..."
|
||||
|
||||
deployment:
|
||||
stage: deploy
|
||||
script: echo "Deploying..."
|
||||
```
|
||||
|
||||
You must define [`strategy: depend`](../yaml/index.md#triggerstrategy)
|
||||
with the `trigger` keyword. This ensures that the lock isn't released until the downstream pipeline
|
||||
finishes.
|
||||
|
||||
## API
|
||||
|
||||
|
|
|
@ -3941,20 +3941,24 @@ In this example, a new pipeline causes a running pipeline to be:
|
|||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/15536) in GitLab 12.7.
|
||||
|
||||
Sometimes running multiple jobs at the same time in an environment
|
||||
can lead to errors during the deployment.
|
||||
Use `resource_group` to create a [resource group](../resource_groups/index.md) that
|
||||
ensures a job is mutually exclusive across different pipelines for the same project.
|
||||
|
||||
To avoid these errors, use the `resource_group` attribute to make sure that
|
||||
the runner doesn't run certain jobs concurrently. Resource groups behave similar
|
||||
to semaphores in other programming languages.
|
||||
For example, if multiple jobs that belong to the same resource group are queued simultaneously,
|
||||
only one of the jobs starts. The other jobs wait until the `resource_group` is free.
|
||||
|
||||
When the `resource_group` keyword is defined for a job in the `.gitlab-ci.yml` file,
|
||||
job executions are mutually exclusive across different pipelines for the same project.
|
||||
If multiple jobs belonging to the same resource group are enqueued simultaneously,
|
||||
only one of the jobs is picked by the runner. The other jobs wait until the
|
||||
`resource_group` is free.
|
||||
Resource groups behave similar to semaphores in other programming languages.
|
||||
|
||||
For example:
|
||||
You can define multiple resource groups per environment. For example,
|
||||
when deploying to physical devices, you might have multiple physical devices. Each device
|
||||
can be deployed to, but only one deployment can occur per device at any given time.
|
||||
|
||||
**Keyword type**: Job keyword. You can use it only as part of a job.
|
||||
|
||||
**Possible inputs**: Only letters, digits, `-`, `_`, `/`, `$`, `{`, `}`, `.`, and spaces.
|
||||
It can't start or end with `/`.
|
||||
|
||||
**Example of `resource_group`**:
|
||||
|
||||
```yaml
|
||||
deploy-to-production:
|
||||
|
@ -3962,71 +3966,12 @@ deploy-to-production:
|
|||
resource_group: production
|
||||
```
|
||||
|
||||
In this case, two `deploy-to-production` jobs in two separate pipelines can never run at the same time. As a result,
|
||||
In this example, two `deploy-to-production` jobs in two separate pipelines can never run at the same time. As a result,
|
||||
you can ensure that concurrent deployments never happen to the production environment.
|
||||
|
||||
You can define multiple resource groups per environment. For example,
|
||||
when deploying to physical devices, you may have multiple physical devices. Each device
|
||||
can be deployed to, but there can be only one deployment per device at any given time.
|
||||
**Related topics**:
|
||||
|
||||
The `resource_group` value can only contain letters, digits, `-`, `_`, `/`, `$`, `{`, `}`, `.`, and spaces.
|
||||
It can't start or end with `/`.
|
||||
|
||||
For more information, see [Resource Group documentation](../resource_groups/index.md).
|
||||
|
||||
#### Pipeline-level concurrency control with Cross-Project/Parent-Child pipelines
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/39057) in GitLab 13.9.
|
||||
|
||||
You can define `resource_group` for downstream pipelines that are sensitive to concurrent
|
||||
executions. The [`trigger` keyword](#trigger) can trigger downstream pipelines. The
|
||||
[`resource_group` keyword](#resource_group) can co-exist with it. This is useful to control the
|
||||
concurrency for deployment pipelines, while running non-sensitive jobs concurrently.
|
||||
|
||||
The following example has two pipeline configurations in a project. When a pipeline starts running,
|
||||
non-sensitive jobs are executed first and aren't affected by concurrent executions in other
|
||||
pipelines. However, GitLab ensures that there are no other deployment pipelines running before
|
||||
triggering a deployment (child) pipeline. If other deployment pipelines are running, GitLab waits
|
||||
until those pipelines finish before running another one.
|
||||
|
||||
```yaml
|
||||
# .gitlab-ci.yml (parent pipeline)
|
||||
|
||||
build:
|
||||
stage: build
|
||||
script: echo "Building..."
|
||||
|
||||
test:
|
||||
stage: test
|
||||
script: echo "Testing..."
|
||||
|
||||
deploy:
|
||||
stage: deploy
|
||||
trigger:
|
||||
include: deploy.gitlab-ci.yml
|
||||
strategy: depend
|
||||
resource_group: AWS-production
|
||||
```
|
||||
|
||||
```yaml
|
||||
# deploy.gitlab-ci.yml (child pipeline)
|
||||
|
||||
stages:
|
||||
- provision
|
||||
- deploy
|
||||
|
||||
provision:
|
||||
stage: provision
|
||||
script: echo "Provisioning..."
|
||||
|
||||
deployment:
|
||||
stage: deploy
|
||||
script: echo "Deploying..."
|
||||
```
|
||||
|
||||
You must define [`strategy: depend`](#triggerstrategy)
|
||||
with the `trigger` keyword. This ensures that the lock isn't released until the downstream pipeline
|
||||
finishes.
|
||||
- [Pipeline-level concurrency control with cross-project/parent-child pipelines](../resource_groups/index.md#pipeline-level-concurrency-control-with-cross-projectparent-child-pipelines).
|
||||
|
||||
### `release`
|
||||
|
||||
|
|
|
@ -145,7 +145,7 @@ Legacy classes inherited from `BaseService` for historical reasons.
|
|||
|
||||
In Service classes the use of `execute` and `#execute` is preferred over `call` and `#call`.
|
||||
|
||||
Classes that are not service objects should be [created elsewhere](directory_structure.md#use-namespaces-to-define-bounded-contexts, such as in `lib`.
|
||||
Classes that are not service objects should be [created elsewhere](directory_structure.md#use-namespaces-to-define-bounded-contexts), such as in `lib`.
|
||||
|
||||
#### ServiceResponse
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ You might receive notifications for one of the following reasons:
|
|||
or edit, or someone mentions you.
|
||||
- You've [enabled notifications in an issue, merge request, or epic](#notifications-on-issues-merge-requests-and-epics).
|
||||
- You've configured notifications for the [project](#change-level-of-project-notifications) or [group](#group-notifications).
|
||||
- You're subscribed to group or project pipeline notifications via the pipeline emails [integration](../project/integrations/overview.md).
|
||||
|
||||
NOTE:
|
||||
Administrators can block notifications, preventing them from being sent.
|
||||
|
|
|
@ -1110,6 +1110,9 @@ msgstr ""
|
|||
msgid "(check progress)"
|
||||
msgstr ""
|
||||
|
||||
msgid "(commits will be squashed)"
|
||||
msgstr ""
|
||||
|
||||
msgid "(deleted)"
|
||||
msgstr ""
|
||||
|
||||
|
@ -12424,6 +12427,9 @@ msgstr ""
|
|||
msgid "Edit comment"
|
||||
msgstr ""
|
||||
|
||||
msgid "Edit commit message"
|
||||
msgstr ""
|
||||
|
||||
msgid "Edit deploy freeze"
|
||||
msgstr ""
|
||||
|
||||
|
@ -21588,6 +21594,9 @@ msgstr ""
|
|||
msgid "Merge commit message"
|
||||
msgstr ""
|
||||
|
||||
msgid "Merge details"
|
||||
msgstr ""
|
||||
|
||||
msgid "Merge events"
|
||||
msgstr ""
|
||||
|
||||
|
@ -32449,6 +32458,12 @@ msgstr ""
|
|||
msgid "Source branch"
|
||||
msgstr ""
|
||||
|
||||
msgid "Source branch will be deleted."
|
||||
msgstr ""
|
||||
|
||||
msgid "Source branch will not be deleted."
|
||||
msgstr ""
|
||||
|
||||
msgid "Source branch: %{source_branch_open}%{source_branch}%{source_branch_close}"
|
||||
msgstr ""
|
||||
|
||||
|
@ -40956,7 +40971,7 @@ msgstr ""
|
|||
msgid "most recent deployment"
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
|
||||
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}%{squashedCommits}."
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
|
||||
|
|
|
@ -9,7 +9,7 @@ exports[`New ready to merge state component renders permission text if canMerge
|
|||
/>
|
||||
|
||||
<p
|
||||
class="media-body gl-m-0! gl-font-weight-bold"
|
||||
class="media-body gl-m-0! gl-font-weight-bold gl-text-gray-900!"
|
||||
>
|
||||
|
||||
Ready to merge by members who can write to the target branch.
|
||||
|
@ -27,7 +27,7 @@ exports[`New ready to merge state component renders permission text if canMerge
|
|||
/>
|
||||
|
||||
<p
|
||||
class="media-body gl-m-0! gl-font-weight-bold"
|
||||
class="media-body gl-m-0! gl-font-weight-bold gl-text-gray-900!"
|
||||
>
|
||||
|
||||
Ready to merge!
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { GlSprintf } from '@gitlab/ui';
|
||||
import CommitsHeader from '~/vue_merge_request_widget/components/states/commits_header.vue';
|
||||
|
||||
|
@ -6,7 +6,7 @@ describe('Commits header component', () => {
|
|||
let wrapper;
|
||||
|
||||
const createComponent = (props) => {
|
||||
wrapper = shallowMount(CommitsHeader, {
|
||||
wrapper = mount(CommitsHeader, {
|
||||
stubs: {
|
||||
GlSprintf,
|
||||
},
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { GlFormCheckbox } from '@gitlab/ui';
|
||||
import { GlFormCheckbox, GlLink } from '@gitlab/ui';
|
||||
import { createLocalVue, shallowMount } from '@vue/test-utils';
|
||||
import SquashBeforeMerge from '~/vue_merge_request_widget/components/states/squash_before_merge.vue';
|
||||
import { SQUASH_BEFORE_MERGE } from '~/vue_merge_request_widget/i18n';
|
||||
|
@ -77,7 +77,7 @@ describe('Squash before merge component', () => {
|
|||
value: false,
|
||||
});
|
||||
|
||||
const aboutLink = wrapper.find('a');
|
||||
const aboutLink = wrapper.findComponent(GlLink);
|
||||
|
||||
expect(aboutLink.exists()).toBe(false);
|
||||
});
|
||||
|
@ -88,7 +88,7 @@ describe('Squash before merge component', () => {
|
|||
helpPath: 'test-path',
|
||||
});
|
||||
|
||||
const aboutLink = wrapper.find('a');
|
||||
const aboutLink = wrapper.findComponent(GlLink);
|
||||
|
||||
expect(aboutLink.exists()).toBe(true);
|
||||
});
|
||||
|
@ -99,7 +99,7 @@ describe('Squash before merge component', () => {
|
|||
helpPath: 'test-path',
|
||||
});
|
||||
|
||||
const aboutLink = wrapper.find('a');
|
||||
const aboutLink = wrapper.findComponent(GlLink);
|
||||
|
||||
expect(aboutLink.attributes('href')).toEqual('test-path');
|
||||
});
|
||||
|
|
|
@ -74,6 +74,7 @@ describe('getStateKey', () => {
|
|||
|
||||
expect(bound()).toEqual('nothingToMerge');
|
||||
|
||||
context.commitsCount = 1;
|
||||
context.branchMissing = true;
|
||||
|
||||
expect(bound()).toEqual('missingBranch');
|
||||
|
|
|
@ -6,7 +6,7 @@ shared_examples 'deployment metrics examples' do
|
|||
environment = project.environments.production.first || create(:environment, :production, project: project)
|
||||
create(:deployment, :success, args.merge(environment: environment))
|
||||
|
||||
# this is needed for the dora_deployment_frequency_in_vsa feature flag so we have aggregated data
|
||||
# this is needed for the DORA API so we have aggregated data
|
||||
::Dora::DailyMetrics::RefreshWorker.new.perform(environment.id, Time.current.to_date.to_s) if Gitlab.ee?
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'gitlab'
|
||||
require 'pathname'
|
||||
|
||||
# This script saves the diffs of changes in an MR to the directory specified as the first argument
|
||||
|
||||
gitlab_token = ENV.fetch('PROJECT_TOKEN_FOR_CI_SCRIPTS_API_USAGE')
|
||||
gitlab_endpoint = ENV.fetch('CI_API_V4_URL')
|
||||
mr_project_path = ENV.fetch('CI_MERGE_REQUEST_PROJECT_PATH')
|
||||
mr_iid = ENV.fetch('CI_MERGE_REQUEST_IID')
|
||||
|
||||
abort("ERROR: Please specify a directory to write MR diffs into.") if ARGV.empty?
|
||||
output_diffs_dir = Pathname.new(ARGV.shift).expand_path
|
||||
|
||||
Gitlab.configure do |config|
|
||||
config.endpoint = gitlab_endpoint
|
||||
config.private_token = gitlab_token
|
||||
end
|
||||
|
||||
Gitlab.merge_request_changes(mr_project_path, mr_iid).changes.each do |change|
|
||||
next if change['diff'].empty?
|
||||
|
||||
output_diffs_dir.join(File.dirname(change['new_path'])).mkpath
|
||||
output_diffs_dir.join("#{change['new_path']}.diff").write(change['diff'])
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'pathname'
|
||||
|
||||
# This script assumes the first argument is a directory of files containing diffs of changes from an MR. It exits with a
|
||||
# success code if all diffs add a line that quarantines a test. If any diffs are not specs, or they are specs that don't
|
||||
# quarantine a test, it exits with code 1 to indicate failure (i.e., there was _not_ only quarantined specs).
|
||||
|
||||
abort("ERROR: Please specify the directory containing MR diffs.") if ARGV.empty?
|
||||
diffs_dir = Pathname.new(ARGV.shift).expand_path
|
||||
|
||||
diffs_dir.glob('**/*').each do |path|
|
||||
next if path.directory?
|
||||
|
||||
exit 1 unless path.to_s.end_with?('_spec.rb.diff')
|
||||
exit 1 unless path.read.match?(/^\+.*, quarantine:/)
|
||||
end
|
Loading…
Reference in New Issue