Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/groups/components/invite_members_banner.vue1
-rw-r--r--app/assets/javascripts/invite_members/components/invite_members_trigger.vue2
-rw-r--r--app/assets/javascripts/jobs/components/table/cells/duration_cell.vue1
-rw-r--r--app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab.vue2
-rw-r--r--app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue6
-rw-r--r--app/assets/javascripts/pipeline_wizard/components/widgets/text.vue126
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/pipeline_failed.vue2
-rw-r--r--app/models/member.rb5
-rw-r--r--app/views/groups/settings/_transfer.html.haml2
-rw-r--r--app/views/projects/branches/index.html.haml2
-rw-r--r--db/migrate/20220119170426_remove_temporary_vulnerability_occurrences_deduplication_index.rb20
-rw-r--r--db/schema_migrations/202201191704261
-rw-r--r--db/structure.sql2
-rw-r--r--doc/administration/packages/dependency_proxy.md4
-rw-r--r--doc/ci/jobs/index.md3
-rw-r--r--doc/ci/troubleshooting.md2
-rw-r--r--doc/development/internal_api/index.md23
-rw-r--r--lib/gitlab/background_migration/copy_column_using_background_migration_job.rb6
-rw-r--r--lib/gitlab/ci/config/entry/jobs.rb2
-rw-r--r--lib/gitlab/ci/templates/Kaniko.gitlab-ci.yml29
-rw-r--r--lib/gitlab/database/background_migration/batched_migration_wrapper.rb16
-rw-r--r--locale/gitlab.pot14
-rw-r--r--spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb4
-rw-r--r--spec/frontend/groups/components/invite_members_banner_spec.js1
-rw-r--r--spec/frontend/invite_members/components/invite_members_modal_spec.js2
-rw-r--r--spec/frontend/invite_members/components/invite_members_trigger_spec.js1
-rw-r--r--spec/frontend/mocks_spec.js11
-rw-r--r--spec/frontend/notebook/cells/code_spec.js12
-rw-r--r--spec/frontend/notebook/cells/output/index_spec.js36
-rw-r--r--spec/frontend/notebook/cells/prompt_spec.js14
-rw-r--r--spec/frontend/notebook/index_spec.js20
-rw-r--r--spec/frontend/notes/deprecated_notes_spec.js81
-rw-r--r--spec/frontend/pager_spec.js57
-rw-r--r--spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js6
-rw-r--r--spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_spec.js1
-rw-r--r--spec/frontend/pipeline_wizard/components/widgets/text_spec.js152
-rw-r--r--spec/frontend/vue_mr_widget/components/states/__snapshots__/mr_widget_pipeline_failed_spec.js.snap2
-rw-r--r--spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb9
-rw-r--r--spec/lib/gitlab/ci/config/entry/jobs_spec.rb8
-rw-r--r--spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb40
-rw-r--r--spec/models/member_spec.rb19
-rw-r--r--yarn.lock21
42 files changed, 558 insertions, 210 deletions
diff --git a/app/assets/javascripts/groups/components/invite_members_banner.vue b/app/assets/javascripts/groups/components/invite_members_banner.vue
index dfc1549fb4a..7afea815197 100644
--- a/app/assets/javascripts/groups/components/invite_members_banner.vue
+++ b/app/assets/javascripts/groups/components/invite_members_banner.vue
@@ -46,7 +46,6 @@ export default {
},
openModal() {
eventHub.$emit('openModal', {
- inviteeType: 'members',
source: this.$options.openModalSource,
});
this.track(this.$options.buttonClickEvent);
diff --git a/app/assets/javascripts/invite_members/components/invite_members_trigger.vue b/app/assets/javascripts/invite_members/components/invite_members_trigger.vue
index 7dd74f8803a..79b192e2495 100644
--- a/app/assets/javascripts/invite_members/components/invite_members_trigger.vue
+++ b/app/assets/javascripts/invite_members/components/invite_members_trigger.vue
@@ -71,7 +71,7 @@ export default {
return this.triggerElement === targetTriggerElement;
},
openModal() {
- eventHub.$emit('openModal', { inviteeType: 'members', source: this.triggerSource });
+ eventHub.$emit('openModal', { source: this.triggerSource });
},
},
TRIGGER_ELEMENT_BUTTON,
diff --git a/app/assets/javascripts/jobs/components/table/cells/duration_cell.vue b/app/assets/javascripts/jobs/components/table/cells/duration_cell.vue
index ba5732d3d43..19594c4955d 100644
--- a/app/assets/javascripts/jobs/components/table/cells/duration_cell.vue
+++ b/app/assets/javascripts/jobs/components/table/cells/duration_cell.vue
@@ -39,6 +39,7 @@ export default {
<time
v-gl-tooltip
:title="tooltipTitle(finishedTime)"
+ :datetime="finishedTime"
data-placement="top"
data-container="body"
>
diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab.vue b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab.vue
index d50945e0be2..adae97c6b6f 100644
--- a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab.vue
+++ b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab.vue
@@ -72,7 +72,7 @@ export default {
return value;
},
openInviteMembersModal(mode) {
- eventHub.$emit('openModal', { mode, inviteeType: 'members', source: 'learn-gitlab' });
+ eventHub.$emit('openModal', { mode, source: 'learn-gitlab' });
},
handleShowSuccessfulInvitationsAlert() {
this.showSuccessfulInvitationsAlert = true;
diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue
index 34a0190a503..d0ec02bbd0c 100644
--- a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue
+++ b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue
@@ -37,11 +37,7 @@ export default {
},
methods: {
openModal() {
- eventHub.$emit('openModal', {
- inviteeType: 'members',
- source: 'learn_gitlab',
- tasksToBeDoneEnabled: true,
- });
+ eventHub.$emit('openModal', { source: 'learn_gitlab' });
},
},
};
diff --git a/app/assets/javascripts/pipeline_wizard/components/widgets/text.vue b/app/assets/javascripts/pipeline_wizard/components/widgets/text.vue
new file mode 100644
index 00000000000..26235b20ce9
--- /dev/null
+++ b/app/assets/javascripts/pipeline_wizard/components/widgets/text.vue
@@ -0,0 +1,126 @@
+<script>
+import { GlFormGroup, GlFormInput } from '@gitlab/ui';
+import { uniqueId } from 'lodash';
+import { s__ } from '~/locale';
+
+const VALIDATION_STATE = {
+ NO_VALIDATION: null,
+ INVALID: false,
+ VALID: true,
+};
+
+export default {
+ name: 'TextWidget',
+ components: {
+ GlFormGroup,
+ GlFormInput,
+ },
+ props: {
+ label: {
+ type: String,
+ required: true,
+ },
+ description: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ placeholder: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ invalidFeedback: {
+ type: String,
+ required: false,
+ default: s__('PipelineWizardInputValidation|This value is not valid'),
+ },
+ id: {
+ type: String,
+ required: false,
+ default: () => uniqueId('textWidget-'),
+ },
+ pattern: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ validate: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ required: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ default: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ },
+ data() {
+ return {
+ touched: false,
+ value: this.default,
+ };
+ },
+ computed: {
+ validationState() {
+ if (!this.showValidationState) return VALIDATION_STATE.NO_VALIDATION;
+ if (this.isRequiredButEmpty) return VALIDATION_STATE.INVALID;
+ return this.needsValidationAndPasses ? VALIDATION_STATE.VALID : VALIDATION_STATE.INVALID;
+ },
+ showValidationState() {
+ return this.touched || this.validate;
+ },
+ isRequiredButEmpty() {
+ return this.required && !this.value;
+ },
+ needsValidationAndPasses() {
+ return !this.pattern || new RegExp(this.pattern).test(this.value);
+ },
+ invalidFeedbackMessage() {
+ return this.isRequiredButEmpty
+ ? s__('PipelineWizardInputValidation|This field is required')
+ : this.invalidFeedback;
+ },
+ },
+ watch: {
+ validationState(v) {
+ this.$emit('update:valid', v);
+ },
+ value(v) {
+ this.$emit('input', v.trim());
+ },
+ },
+ created() {
+ if (this.default) {
+ this.$emit('input', this.value);
+ }
+ },
+};
+</script>
+
+<template>
+ <div data-testid="text-widget">
+ <gl-form-group
+ :description="description"
+ :invalid-feedback="invalidFeedbackMessage"
+ :label="label"
+ :label-for="id"
+ :state="validationState"
+ >
+ <gl-form-input
+ :id="id"
+ v-model="value"
+ :placeholder="placeholder"
+ :state="validationState"
+ type="text"
+ @blur="touched = true"
+ />
+ </gl-form-group>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/pipeline_failed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/pipeline_failed.vue
index d88dad2e086..d204befef58 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/pipeline_failed.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/pipeline_failed.vue
@@ -20,7 +20,7 @@ export default {
},
i18n: {
failedMessage: s__(
- `mrWidget|The pipeline for this merge request did not complete. Push a new commit to fix the failure, or check the %{linkStart}troubleshooting documentation%{linkEnd} to see other possible actions.`,
+ `mrWidget|Merge blocked: pipeline must succeed. Push a commit that fixes the failure, or %{linkStart}learn about other solutions.%{linkEnd}`,
),
},
};
diff --git a/app/models/member.rb b/app/models/member.rb
index b46a497dd69..f01f34ad5b2 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -108,6 +108,8 @@ class Member < ApplicationRecord
.reorder(nil)
end
+ scope :active_state, -> { where(state: STATE_ACTIVE) }
+
scope :connected_to_user, -> { where.not(user_id: nil) }
# This scope is exclusively used to get the members
@@ -128,7 +130,8 @@ class Member < ApplicationRecord
end
scope :without_invites_and_requests, -> do
- non_request
+ active_state
+ .non_request
.non_invite
.non_minimal_access
end
diff --git a/app/views/groups/settings/_transfer.html.haml b/app/views/groups/settings/_transfer.html.haml
index 214752889d4..d52d9d59ab3 100644
--- a/app/views/groups/settings/_transfer.html.haml
+++ b/app/views/groups/settings/_transfer.html.haml
@@ -11,7 +11,7 @@
%li= warning_text.html_safe
%li= s_('GroupSettings|You can only transfer the group to a group you manage.')
%li= s_('GroupSettings|You will need to update your local repositories to point to the new location.')
- %li= s_("GroupSettings|If the parent group's visibility is lower than the group current visibility, visibility levels for subgroups and projects will be changed to match the new parent group's visibility.")
+ %li= s_("GroupSettings|If the parent group's visibility is lower than the group's current visibility, visibility levels for subgroups and projects will be changed to match the new parent group's visibility.")
- if group.paid?
.gl-alert.gl-alert-info.gl-mb-5
= sprite_icon('information-o', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index 2121d15643c..96acd863a4c 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -16,7 +16,9 @@
class: 'gl-button btn btn-danger btn-danger-secondary has-tooltip qa-delete-merged-branches',
title: s_("Branches|Delete all branches that are merged into '%{default_branch}'") % { default_branch: @project.repository.root_ref },
method: :delete,
+ aria: { label: s_('Branches|Delete merged branches') },
data: { confirm: s_('Branches|Deleting the merged branches cannot be undone. Are you sure?'),
+ confirm_btn_variant: 'danger',
container: 'body' } do
= s_('Branches|Delete merged branches')
= link_to new_project_branch_path(@project), class: 'gl-button btn btn-confirm' do
diff --git a/db/migrate/20220119170426_remove_temporary_vulnerability_occurrences_deduplication_index.rb b/db/migrate/20220119170426_remove_temporary_vulnerability_occurrences_deduplication_index.rb
new file mode 100644
index 00000000000..26859beb671
--- /dev/null
+++ b/db/migrate/20220119170426_remove_temporary_vulnerability_occurrences_deduplication_index.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class RemoveTemporaryVulnerabilityOccurrencesDeduplicationIndex < Gitlab::Database::Migration[1.0]
+ MIGRATION = 'RecalculateVulnerabilitiesOccurrencesUuid'
+ INDEX_NAME = 'tmp_idx_deduplicate_vulnerability_occurrences'
+
+ disable_ddl_transaction!
+
+ def up
+ finalize_background_migration(MIGRATION)
+
+ remove_concurrent_index_by_name(:vulnerability_occurrences, INDEX_NAME)
+ end
+
+ def down
+ add_concurrent_index :vulnerability_occurrences,
+ %i[project_id report_type location_fingerprint primary_identifier_id id],
+ name: INDEX_NAME
+ end
+end
diff --git a/db/schema_migrations/20220119170426 b/db/schema_migrations/20220119170426
new file mode 100644
index 00000000000..51274ac5979
--- /dev/null
+++ b/db/schema_migrations/20220119170426
@@ -0,0 +1 @@
+a488fecd8e6e99b8d32ac27f72c6a3575b0ed29baaf242133e5d1abbd5b64314 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 6bdf5ccb6fc..47697c716da 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -28352,8 +28352,6 @@ CREATE UNIQUE INDEX term_agreements_unique_index ON term_agreements USING btree
CREATE INDEX tmp_gitlab_subscriptions_max_seats_used_migration ON gitlab_subscriptions USING btree (id) WHERE ((start_date >= '2021-08-02'::date) AND (start_date <= '2021-11-20'::date) AND (max_seats_used <> 0) AND (max_seats_used > seats_in_use) AND (max_seats_used > seats));
-CREATE INDEX tmp_idx_deduplicate_vulnerability_occurrences ON vulnerability_occurrences USING btree (project_id, report_type, location_fingerprint, primary_identifier_id, id);
-
CREATE INDEX tmp_idx_vulnerability_occurrences_on_id_where_report_type_7_99 ON vulnerability_occurrences USING btree (id) WHERE (report_type = ANY (ARRAY[7, 99]));
CREATE INDEX tmp_index_container_repositories_on_id_migration_state ON container_repositories USING btree (id, migration_state);
diff --git a/doc/administration/packages/dependency_proxy.md b/doc/administration/packages/dependency_proxy.md
index 8fd9059e5af..93c6585230f 100644
--- a/doc/administration/packages/dependency_proxy.md
+++ b/doc/administration/packages/dependency_proxy.md
@@ -201,6 +201,8 @@ This section describes the earlier configuration format.
#### Migrate local Dependency Proxy blobs and manifests to object storage
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79663) in GitLab 14.8.
+
After [configuring object storage](#using-object-storage),
use the following task to migrate existing Dependency Proxy blobs and manifests from local storage
to remote storage. The processing is done in a background worker and requires no downtime.
@@ -240,7 +242,7 @@ total | filesystem | objectstg
10 | 0 | 10
```
-Verify that there are no files on disk in the `packages` folder:
+Verify that there are no files on disk in the `dependency_proxy` folder:
```shell
sudo find /var/opt/gitlab/gitlab-rails/shared/dependency_proxy -type f | grep -v tmp | wc -l
diff --git a/doc/ci/jobs/index.md b/doc/ci/jobs/index.md
index 104badb782c..39e14d0d20a 100644
--- a/doc/ci/jobs/index.md
+++ b/doc/ci/jobs/index.md
@@ -95,6 +95,9 @@ You can't use these keywords as job names:
- `variables`
- `cache`
- `include`
+- `true`
+- `false`
+- `nil`
Job names must be 255 characters or less. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/342800)
in GitLab 14.5, [with a feature flag](../../administration/feature_flags.md) named `ci_validate_job_length`.
diff --git a/doc/ci/troubleshooting.md b/doc/ci/troubleshooting.md
index 853d2f953fb..81cb924532c 100644
--- a/doc/ci/troubleshooting.md
+++ b/doc/ci/troubleshooting.md
@@ -241,7 +241,7 @@ This also applies if the pipeline has not been created yet, or if you are waitin
for an external CI service. If you don't use pipelines for your project, then you
should disable **Pipelines must succeed** so you can accept merge requests.
-### "The pipeline for this merge request did not complete. Push a new commit to fix the failure or check the troubleshooting documentation to see other possible actions." message
+#### "Merge blocked: pipeline must succeed. Push a new commit that fixes the failure" message
This message is shown if the [merge request pipeline](pipelines/merge_request_pipelines.md),
[merged results pipeline](pipelines/merged_results_pipelines.md),
diff --git a/doc/development/internal_api/index.md b/doc/development/internal_api/index.md
index 983953d2e09..db978253747 100644
--- a/doc/development/internal_api/index.md
+++ b/doc/development/internal_api/index.md
@@ -561,6 +561,29 @@ Example response:
}
```
+### Resolve Starboard vulnerabilities
+
+Called from the GitLab Agent Server (`kas`) to resolve Starboard security vulnerabilities.
+Accepts a list of finding UUIDs and marks all Starboard vulnerabilities not identified by
+the list as resolved.
+
+| Attribute | Type | Required | Description |
+|:----------|:-------------|:---------|:----------------------------------------------------------------------------------------------------------------------------------|
+| `uuids` | string array | yes | UUIDs of detected vulnerabilities, as collected from [Create Starboard vulnerability](#create-starboard-vulnerability) responses. |
+
+```plaintext
+POST internal/kubernetes/modules/starboard_vulnerability/scan_result
+```
+
+Example Request:
+
+```shell
+curl --request POST --header "Gitlab-Kas-Api-Request: <JWT token>" \
+ --header "Authorization: Bearer <agent token>" --header "Content-Type: application/json" \
+ --url "http://localhost:3000/api/v4/internal/kubernetes/modules/starboard_vulnerability/scan_result" \
+ --data '{ "uuids": ["102e8a0a-fe29-59bd-b46c-57c3e9bc6411", "5eb12985-0ed5-51f4-b545-fd8871dc2870"] }'
+```
+
## Subscriptions
The subscriptions endpoint is used by [CustomersDot](https://gitlab.com/gitlab-org/customers-gitlab-com) (`customers.gitlab.com`)
diff --git a/lib/gitlab/background_migration/copy_column_using_background_migration_job.rb b/lib/gitlab/background_migration/copy_column_using_background_migration_job.rb
index 415e3d869b3..137b4d4bc4e 100644
--- a/lib/gitlab/background_migration/copy_column_using_background_migration_job.rb
+++ b/lib/gitlab/background_migration/copy_column_using_background_migration_job.rb
@@ -13,7 +13,7 @@ module Gitlab
# - We skip the NULL checks as they may result in not using an index scan
# - The table that is migrated does _not_ need `id` as the primary key
# We use the provided primary_key column to perform the update.
- class CopyColumnUsingBackgroundMigrationJob
+ class CopyColumnUsingBackgroundMigrationJob < BaseJob
include Gitlab::Database::DynamicModelHelpers
# start_id - The start ID of the range of rows to update.
@@ -52,10 +52,6 @@ module Gitlab
private
- def connection
- ActiveRecord::Base.connection
- end
-
def relation_scoped_to_range(source_table, source_key_column, start_id, stop_id)
define_batchable_model(source_table, connection: connection).where(source_key_column => start_id..stop_id)
end
diff --git a/lib/gitlab/ci/config/entry/jobs.rb b/lib/gitlab/ci/config/entry/jobs.rb
index b0fd9cef10b..d2a25d59669 100644
--- a/lib/gitlab/ci/config/entry/jobs.rb
+++ b/lib/gitlab/ci/config/entry/jobs.rb
@@ -15,7 +15,7 @@ module Gitlab
validate do
each_unmatched_job do |name|
- errors.add(name, 'config should implement a script: or a trigger: keyword')
+ errors.add(name.to_s, 'config should implement a script: or a trigger: keyword')
end
unless has_visible_job?
diff --git a/lib/gitlab/ci/templates/Kaniko.gitlab-ci.yml b/lib/gitlab/ci/templates/Kaniko.gitlab-ci.yml
index 61ed48836ea..d46ac97ad1b 100644
--- a/lib/gitlab/ci/templates/Kaniko.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Kaniko.gitlab-ci.yml
@@ -19,30 +19,35 @@ kaniko-build:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [""]
script:
+ # if the user provide IMAGE_TAG then use it, else build the image tag using the default logic.
+ # Default logic
# Compose docker tag name
# Git Branch/Tag to Docker Image Tag Mapping
# * Default Branch: main -> latest
# * Branch: feature/my-feature -> branch-feature-my-feature
# * Tag: v1.0.0/beta2 -> v1.0.0-beta2
- |
- if [ "$CI_COMMIT_REF_NAME" = $CI_DEFAULT_BRANCH ]; then
- VERSION="latest"
- elif [ -n "$CI_COMMIT_TAG" ];then
- NOSLASH=$(echo "$CI_COMMIT_TAG" | tr -s / - )
- SANITIZED="${NOSLASH//[^a-zA-Z0-9\-\.]/}"
- VERSION="$SANITIZED"
- else \
- NOSLASH=$(echo "$CI_COMMIT_REF_NAME" | tr -s / - )
- SANITIZED="${NOSLASH//[^a-zA-Z0-9\-]/}"
- VERSION="branch-$SANITIZED"
+ if [ -z ${IMAGE_TAG+x} ]; then
+ if [ "$CI_COMMIT_REF_NAME" = $CI_DEFAULT_BRANCH ]; then
+ VERSION="latest"
+ elif [ -n "$CI_COMMIT_TAG" ];then
+ NOSLASH=$(echo "$CI_COMMIT_TAG" | tr -s / - )
+ SANITIZED="${NOSLASH//[^a-zA-Z0-9\-\.]/}"
+ VERSION="$SANITIZED"
+ else \
+ NOSLASH=$(echo "$CI_COMMIT_REF_NAME" | tr -s / - )
+ SANITIZED="${NOSLASH//[^a-zA-Z0-9\-]/}"
+ VERSION="branch-$SANITIZED"
+ fi
+ export IMAGE_TAG=$CI_REGISTRY_IMAGE:$VERSION
fi
- - echo $VERSION
+ - echo $IMAGE_TAG
- mkdir -p /kaniko/.docker
# Write credentials to access Gitlab Container Registry within the runner/ci
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"auth\":\"$(echo -n ${CI_REGISTRY_USER}:${CI_REGISTRY_PASSWORD} | base64 | tr -d '\n')\"}}}" > /kaniko/.docker/config.json
# Build and push the container. To disable push add --no-push
- DOCKERFILE_PATH=${DOCKERFILE_PATH:-"$KANIKO_BUILD_CONTEXT/Dockerfile"}
- - /kaniko/executor --context $KANIKO_BUILD_CONTEXT --dockerfile $DOCKERFILE_PATH --destination $CI_REGISTRY_IMAGE:$VERSION $KANIKO_ARGS
+ - /kaniko/executor --context $KANIKO_BUILD_CONTEXT --dockerfile $DOCKERFILE_PATH --destination $IMAGE_TAG $KANIKO_ARGS
# Run this job in a branch/tag where a Dockerfile exists
rules:
- exists:
diff --git a/lib/gitlab/database/background_migration/batched_migration_wrapper.rb b/lib/gitlab/database/background_migration/batched_migration_wrapper.rb
index 159d618451c..057f856d859 100644
--- a/lib/gitlab/database/background_migration/batched_migration_wrapper.rb
+++ b/lib/gitlab/database/background_migration/batched_migration_wrapper.rb
@@ -6,6 +6,10 @@ module Gitlab
class BatchedMigrationWrapper
extend Gitlab::Utils::StrongMemoize
+ def initialize(connection: ApplicationRecord.connection)
+ @connection = connection
+ end
+
# Wraps the execution of a batched_background_migration.
#
# Updates the job's tracking records with the status of the migration
@@ -29,12 +33,14 @@ module Gitlab
private
+ attr_reader :connection
+
def start_tracking_execution(tracking_record)
tracking_record.run!
end
def execute_batch(tracking_record)
- job_instance = tracking_record.migration_job_class.new
+ job_instance = migration_instance_for(tracking_record.migration_job_class)
job_instance.perform(
tracking_record.min_value,
@@ -50,6 +56,14 @@ module Gitlab
end
end
+ def migration_instance_for(job_class)
+ if job_class < Gitlab::BackgroundMigration::BaseJob
+ job_class.new(connection: connection)
+ else
+ job_class.new
+ end
+ end
+
def track_prometheus_metrics(tracking_record)
migration = tracking_record.batched_migration
base_labels = migration.prometheus_labels
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 371e0649c5d..d2812fc83a7 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -17479,7 +17479,7 @@ msgstr ""
msgid "GroupSettings|If not specified at the group or instance level, the default is %{default_initial_branch_name}. Does not affect existing repositories."
msgstr ""
-msgid "GroupSettings|If the parent group's visibility is lower than the group current visibility, visibility levels for subgroups and projects will be changed to match the new parent group's visibility."
+msgid "GroupSettings|If the parent group's visibility is lower than the group's current visibility, visibility levels for subgroups and projects will be changed to match the new parent group's visibility."
msgstr ""
msgid "GroupSettings|New runners registration token has been generated!"
@@ -26577,6 +26577,12 @@ msgstr ""
msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
msgstr ""
+msgid "PipelineWizardInputValidation|This field is required"
+msgstr ""
+
+msgid "PipelineWizardInputValidation|This value is not valid"
+msgstr ""
+
msgid "Pipelines"
msgstr ""
@@ -43483,6 +43489,9 @@ msgstr ""
msgid "mrWidget|Merge blocked: pipeline must succeed. It's waiting for a manual action to continue."
msgstr ""
+msgid "mrWidget|Merge blocked: pipeline must succeed. Push a commit that fixes the failure, or %{linkStart}learn about other solutions.%{linkEnd}"
+msgstr ""
+
msgid "mrWidget|Merge blocked: this merge request must be approved."
msgstr ""
@@ -43600,9 +43609,6 @@ msgstr ""
msgid "mrWidget|The changes were not merged into"
msgstr ""
-msgid "mrWidget|The pipeline for this merge request did not complete. Push a new commit to fix the failure, or check the %{linkStart}troubleshooting documentation%{linkEnd} to see other possible actions."
-msgstr ""
-
msgid "mrWidget|The source branch has been deleted"
msgstr ""
diff --git a/spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb b/spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb
index 8438c0af553..4d7ee11e366 100644
--- a/spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb
+++ b/spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb
@@ -57,7 +57,7 @@ RSpec.describe 'Merge request > User merges only if pipeline succeeds', :js do
wait_for_requests
expect(page).to have_css('button[disabled="disabled"]', text: 'Merge')
- expect(page).to have_content('The pipeline for this merge request did not complete. Push a new commit to fix the failure, or check the troubleshooting documentation to see other possible actions.')
+ expect(page).to have_content('Merge blocked: pipeline must succeed. Push a commit that fixes the failure, or learn about other solutions.')
end
end
@@ -70,7 +70,7 @@ RSpec.describe 'Merge request > User merges only if pipeline succeeds', :js do
wait_for_requests
expect(page).not_to have_button 'Merge'
- expect(page).to have_content('The pipeline for this merge request did not complete. Push a new commit to fix the failure, or check the troubleshooting documentation to see other possible actions.')
+ expect(page).to have_content('Merge blocked: pipeline must succeed. Push a commit that fixes the failure, or learn about other solutions.')
end
end
diff --git a/spec/frontend/groups/components/invite_members_banner_spec.js b/spec/frontend/groups/components/invite_members_banner_spec.js
index ef784018205..1924f400861 100644
--- a/spec/frontend/groups/components/invite_members_banner_spec.js
+++ b/spec/frontend/groups/components/invite_members_banner_spec.js
@@ -76,7 +76,6 @@ describe('InviteMembersBanner', () => {
it('calls openModal through the eventHub', () => {
expect(eventHub.$emit).toHaveBeenCalledWith('openModal', {
- inviteeType: 'members',
source: 'invite_members_banner',
});
});
diff --git a/spec/frontend/invite_members/components/invite_members_modal_spec.js b/spec/frontend/invite_members/components/invite_members_modal_spec.js
index 090efc4d4c3..15a366474e4 100644
--- a/spec/frontend/invite_members/components/invite_members_modal_spec.js
+++ b/spec/frontend/invite_members/components/invite_members_modal_spec.js
@@ -694,7 +694,7 @@ describe('InviteMembersModal', () => {
});
it('tracks the view for learn_gitlab source', () => {
- eventHub.$emit('openModal', { inviteeType: 'members', source: LEARN_GITLAB });
+ eventHub.$emit('openModal', { source: LEARN_GITLAB });
expect(ExperimentTracking).toHaveBeenCalledWith(INVITE_MEMBERS_FOR_TASK.name);
expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith(LEARN_GITLAB);
diff --git a/spec/frontend/invite_members/components/invite_members_trigger_spec.js b/spec/frontend/invite_members/components/invite_members_trigger_spec.js
index 429b6fad24a..28402c8331c 100644
--- a/spec/frontend/invite_members/components/invite_members_trigger_spec.js
+++ b/spec/frontend/invite_members/components/invite_members_trigger_spec.js
@@ -71,7 +71,6 @@ describe.each(triggerItems)('with triggerElement as %s', (triggerItem) => {
findButton().vm.$emit('click');
expect(spy).toHaveBeenCalledWith('openModal', {
- inviteeType: 'members',
source: triggerSource,
});
});
diff --git a/spec/frontend/mocks_spec.js b/spec/frontend/mocks_spec.js
index 110c418e579..0813d2073b9 100644
--- a/spec/frontend/mocks_spec.js
+++ b/spec/frontend/mocks_spec.js
@@ -8,13 +8,10 @@ describe('Mock auto-injection', () => {
failMock = jest.spyOn(global, 'fail').mockImplementation();
});
- it('~/lib/utils/axios_utils', () => {
- return Promise.all([
- expect(axios.get('http://gitlab.com')).rejects.toThrow('Unexpected unmocked request'),
- setImmediate(() => {
- expect(failMock).toHaveBeenCalledTimes(1);
- }),
- ]);
+ it('~/lib/utils/axios_utils', async () => {
+ await expect(axios.get('http://gitlab.com')).rejects.toThrow('Unexpected unmocked request');
+
+ expect(failMock).toHaveBeenCalledTimes(1);
});
it('jQuery.ajax()', () => {
diff --git a/spec/frontend/notebook/cells/code_spec.js b/spec/frontend/notebook/cells/code_spec.js
index 6a51731c909..9a2db061278 100644
--- a/spec/frontend/notebook/cells/code_spec.js
+++ b/spec/frontend/notebook/cells/code_spec.js
@@ -25,12 +25,10 @@ describe('Code component', () => {
};
describe('without output', () => {
- beforeEach((done) => {
+ beforeEach(() => {
vm = setupComponent(json.cells[0]);
- setImmediate(() => {
- done();
- });
+ return nextTick();
});
it('does not render output prompt', () => {
@@ -39,12 +37,10 @@ describe('Code component', () => {
});
describe('with output', () => {
- beforeEach((done) => {
+ beforeEach(() => {
vm = setupComponent(json.cells[2]);
- setImmediate(() => {
- done();
- });
+ return nextTick();
});
it('does not render output prompt', () => {
diff --git a/spec/frontend/notebook/cells/output/index_spec.js b/spec/frontend/notebook/cells/output/index_spec.js
index 7ece73d375c..8e04e4c146c 100644
--- a/spec/frontend/notebook/cells/output/index_spec.js
+++ b/spec/frontend/notebook/cells/output/index_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import json from 'test_fixtures/blob/notebook/basic.json';
import CodeComponent from '~/notebook/cells/output/index.vue';
@@ -18,13 +18,11 @@ describe('Output component', () => {
};
describe('text output', () => {
- beforeEach((done) => {
+ beforeEach(() => {
const textType = json.cells[2];
createComponent(textType.outputs[0]);
- setImmediate(() => {
- done();
- });
+ return nextTick();
});
it('renders as plain text', () => {
@@ -37,13 +35,11 @@ describe('Output component', () => {
});
describe('image output', () => {
- beforeEach((done) => {
+ beforeEach(() => {
const imageType = json.cells[3];
createComponent(imageType.outputs[0]);
- setImmediate(() => {
- done();
- });
+ return nextTick();
});
it('renders as an image', () => {
@@ -86,13 +82,11 @@ describe('Output component', () => {
});
describe('svg output', () => {
- beforeEach((done) => {
+ beforeEach(() => {
const svgType = json.cells[5];
createComponent(svgType.outputs[0]);
- setImmediate(() => {
- done();
- });
+ return nextTick();
});
it('renders as an svg', () => {
@@ -101,13 +95,11 @@ describe('Output component', () => {
});
describe('default to plain text', () => {
- beforeEach((done) => {
+ beforeEach(() => {
const unknownType = json.cells[6];
createComponent(unknownType.outputs[0]);
- setImmediate(() => {
- done();
- });
+ return nextTick();
});
it('renders as plain text', () => {
@@ -119,16 +111,14 @@ describe('Output component', () => {
expect(vm.$el.querySelector('.prompt span')).not.toBeNull();
});
- it("renders as plain text when doesn't recognise other types", (done) => {
+ it("renders as plain text when doesn't recognise other types", async () => {
const unknownType = json.cells[7];
createComponent(unknownType.outputs[0]);
- setImmediate(() => {
- expect(vm.$el.querySelector('pre')).not.toBeNull();
- expect(vm.$el.textContent.trim()).toContain('testing');
+ await nextTick();
- done();
- });
+ expect(vm.$el.querySelector('pre')).not.toBeNull();
+ expect(vm.$el.textContent.trim()).toContain('testing');
});
});
});
diff --git a/spec/frontend/notebook/cells/prompt_spec.js b/spec/frontend/notebook/cells/prompt_spec.js
index 8cdcd1f84de..89b2d7b2b90 100644
--- a/spec/frontend/notebook/cells/prompt_spec.js
+++ b/spec/frontend/notebook/cells/prompt_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import PromptComponent from '~/notebook/cells/prompt.vue';
const Component = Vue.extend(PromptComponent);
@@ -7,7 +7,7 @@ describe('Prompt component', () => {
let vm;
describe('input', () => {
- beforeEach((done) => {
+ beforeEach(() => {
vm = new Component({
propsData: {
type: 'In',
@@ -16,9 +16,7 @@ describe('Prompt component', () => {
});
vm.$mount();
- setImmediate(() => {
- done();
- });
+ return nextTick();
});
it('renders in label', () => {
@@ -31,7 +29,7 @@ describe('Prompt component', () => {
});
describe('output', () => {
- beforeEach((done) => {
+ beforeEach(() => {
vm = new Component({
propsData: {
type: 'Out',
@@ -40,9 +38,7 @@ describe('Prompt component', () => {
});
vm.$mount();
- setImmediate(() => {
- done();
- });
+ return nextTick();
});
it('renders in label', () => {
diff --git a/spec/frontend/notebook/index_spec.js b/spec/frontend/notebook/index_spec.js
index cd531d628b3..475c41a72f6 100644
--- a/spec/frontend/notebook/index_spec.js
+++ b/spec/frontend/notebook/index_spec.js
@@ -1,5 +1,5 @@
import { mount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import json from 'test_fixtures/blob/notebook/basic.json';
import jsonWithWorksheet from 'test_fixtures/blob/notebook/worksheets.json';
import Notebook from '~/notebook/index.vue';
@@ -17,12 +17,10 @@ describe('Notebook component', () => {
}
describe('without JSON', () => {
- beforeEach((done) => {
+ beforeEach(() => {
vm = buildComponent({});
- setImmediate(() => {
- done();
- });
+ return nextTick();
});
it('does not render', () => {
@@ -31,12 +29,10 @@ describe('Notebook component', () => {
});
describe('with JSON', () => {
- beforeEach((done) => {
+ beforeEach(() => {
vm = buildComponent(json);
- setImmediate(() => {
- done();
- });
+ return nextTick();
});
it('renders cells', () => {
@@ -57,12 +53,10 @@ describe('Notebook component', () => {
});
describe('with worksheets', () => {
- beforeEach((done) => {
+ beforeEach(() => {
vm = buildComponent(jsonWithWorksheet);
- setImmediate(() => {
- done();
- });
+ return nextTick();
});
it('renders cells', () => {
diff --git a/spec/frontend/notes/deprecated_notes_spec.js b/spec/frontend/notes/deprecated_notes_spec.js
index 34623f8aa13..7c52920da90 100644
--- a/spec/frontend/notes/deprecated_notes_spec.js
+++ b/spec/frontend/notes/deprecated_notes_spec.js
@@ -5,6 +5,7 @@ import $ from 'jquery';
import '~/behaviors/markdown/render_gfm';
import { createSpyObj } from 'helpers/jest_helpers';
import { TEST_HOST } from 'helpers/test_constants';
+import waitForPromises from 'helpers/wait_for_promises';
import { setTestTimeoutOnce } from 'helpers/timeout';
import axios from '~/lib/utils/axios_utils';
import * as urlUtility from '~/lib/utils/url_utility';
@@ -549,15 +550,14 @@ describe.skip('Old Notes (~/deprecated_notes.js)', () => {
expect($notesContainer.find('.note.being-posted').length).toBeGreaterThan(0);
});
- it('should remove placeholder note when new comment is done posting', (done) => {
+ it('should remove placeholder note when new comment is done posting', async () => {
mockNotesPost();
$('.js-comment-button').click();
- setImmediate(() => {
- expect($notesContainer.find('.note.being-posted').length).toEqual(0);
- done();
- });
+ await waitForPromises();
+
+ expect($notesContainer.find('.note.being-posted').length).toEqual(0);
});
describe('postComment', () => {
@@ -584,40 +584,37 @@ describe.skip('Old Notes (~/deprecated_notes.js)', () => {
});
});
- it('should show actual note element when new comment is done posting', (done) => {
+ it('should show actual note element when new comment is done posting', async () => {
mockNotesPost();
$('.js-comment-button').click();
- setImmediate(() => {
- expect($notesContainer.find(`#note_${note.id}`).length).toBeGreaterThan(0);
- done();
- });
+ await waitForPromises();
+
+ expect($notesContainer.find(`#note_${note.id}`).length).toBeGreaterThan(0);
});
- it('should reset Form when new comment is done posting', (done) => {
+ it('should reset Form when new comment is done posting', async () => {
mockNotesPost();
$('.js-comment-button').click();
- setImmediate(() => {
- expect($form.find('textarea.js-note-text').val()).toEqual('');
- done();
- });
+ await waitForPromises();
+
+ expect($form.find('textarea.js-note-text').val()).toEqual('');
});
- it('should show flash error message when new comment failed to be posted', (done) => {
+ it('should show flash error message when new comment failed to be posted', async () => {
mockNotesPostError();
jest.spyOn(notes, 'addFlash');
$('.js-comment-button').click();
- setImmediate(() => {
- expect(notes.addFlash).toHaveBeenCalled();
- // JSDom doesn't support the :visible selector yet
- expect(notes.flashContainer.style.display).not.toBe('none');
- done();
- });
+ await waitForPromises();
+
+ expect(notes.addFlash).toHaveBeenCalled();
+ // JSDom doesn't support the :visible selector yet
+ expect(notes.flashContainer.style.display).not.toBe('none');
});
});
@@ -657,16 +654,15 @@ describe.skip('Old Notes (~/deprecated_notes.js)', () => {
$form.find('textarea.js-note-text').val(sampleComment);
});
- it('should remove quick action placeholder when comment with quick actions is done posting', (done) => {
+ it('should remove quick action placeholder when comment with quick actions is done posting', async () => {
jest.spyOn(gl.awardsHandler, 'addAwardToEmojiBar');
$('.js-comment-button').click();
expect($notesContainer.find('.note.being-posted').length).toEqual(1); // Placeholder shown
- setImmediate(() => {
- expect($notesContainer.find('.note.being-posted').length).toEqual(0); // Placeholder removed
- done();
- });
+ await waitForPromises();
+
+ expect($notesContainer.find('.note.being-posted').length).toEqual(0); // Placeholder removed
});
});
@@ -692,16 +688,15 @@ describe.skip('Old Notes (~/deprecated_notes.js)', () => {
$form.find('textarea.js-note-text').val(sampleComment);
});
- it('should show message placeholder including lines starting with slash', (done) => {
+ it('should show message placeholder including lines starting with slash', async () => {
$('.js-comment-button').click();
expect($notesContainer.find('.note.being-posted').length).toEqual(1); // Placeholder shown
expect($notesContainer.find('.note-body p').text()).toEqual(sampleComment); // No quick action processing
- setImmediate(() => {
- expect($notesContainer.find('.note.being-posted').length).toEqual(0); // Placeholder removed
- done();
- });
+ await waitForPromises();
+
+ expect($notesContainer.find('.note.being-posted').length).toEqual(0); // Placeholder removed
});
});
@@ -730,23 +725,21 @@ describe.skip('Old Notes (~/deprecated_notes.js)', () => {
$form.find('textarea.js-note-text').html(sampleComment);
});
- it('should not render a script tag', (done) => {
+ it('should not render a script tag', async () => {
$('.js-comment-button').click();
- setImmediate(() => {
- const $noteEl = $notesContainer.find(`#note_${note.id}`);
- $noteEl.find('.js-note-edit').click();
- $noteEl.find('textarea.js-note-text').html(updatedComment);
- $noteEl.find('.js-comment-save-button').click();
+ await waitForPromises();
- const $updatedNoteEl = $notesContainer
- .find(`#note_${note.id}`)
- .find('.js-task-list-container');
+ const $noteEl = $notesContainer.find(`#note_${note.id}`);
+ $noteEl.find('.js-note-edit').click();
+ $noteEl.find('textarea.js-note-text').html(updatedComment);
+ $noteEl.find('.js-comment-save-button').click();
- expect($updatedNoteEl.find('.note-text').text().trim()).toEqual('');
+ const $updatedNoteEl = $notesContainer
+ .find(`#note_${note.id}`)
+ .find('.js-task-list-container');
- done();
- });
+ expect($updatedNoteEl.find('.note-text').text().trim()).toEqual('');
});
});
diff --git a/spec/frontend/pager_spec.js b/spec/frontend/pager_spec.js
index ff352303143..043ea470436 100644
--- a/spec/frontend/pager_spec.js
+++ b/spec/frontend/pager_spec.js
@@ -1,6 +1,7 @@
import MockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
import { TEST_HOST } from 'helpers/test_constants';
+import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
import { removeParams } from '~/lib/utils/url_utility';
import Pager from '~/pager';
@@ -64,67 +65,59 @@ describe('pager', () => {
Pager.init();
});
- it('shows loader while loading next page', (done) => {
+ it('shows loader while loading next page', async () => {
mockSuccess();
jest.spyOn(Pager.loading, 'show').mockImplementation(() => {});
Pager.getOld();
- setImmediate(() => {
- expect(Pager.loading.show).toHaveBeenCalled();
+ await waitForPromises();
- done();
- });
+ expect(Pager.loading.show).toHaveBeenCalled();
});
- it('hides loader on success', (done) => {
+ it('hides loader on success', async () => {
mockSuccess();
jest.spyOn(Pager.loading, 'hide').mockImplementation(() => {});
Pager.getOld();
- setImmediate(() => {
- expect(Pager.loading.hide).toHaveBeenCalled();
+ await waitForPromises();
- done();
- });
+ expect(Pager.loading.hide).toHaveBeenCalled();
});
- it('hides loader on error', (done) => {
+ it('hides loader on error', async () => {
mockError();
jest.spyOn(Pager.loading, 'hide').mockImplementation(() => {});
Pager.getOld();
- setImmediate(() => {
- expect(Pager.loading.hide).toHaveBeenCalled();
+ await waitForPromises();
- done();
- });
+ expect(Pager.loading.hide).toHaveBeenCalled();
});
- it('sends request to url with offset and limit params', (done) => {
+ it('sends request to url with offset and limit params', async () => {
Pager.offset = 100;
Pager.limit = 20;
Pager.getOld();
- setImmediate(() => {
- const [url, params] = axios.get.mock.calls[0];
+ await waitForPromises();
- expect(params).toEqual({
- params: {
- limit: 20,
- offset: 100,
- },
- });
+ const [url, params] = axios.get.mock.calls[0];
- expect(url).toBe('/some_list');
-
- done();
+ expect(params).toEqual({
+ params: {
+ limit: 20,
+ offset: 100,
+ },
});
+
+ expect(url).toBe('/some_list');
});
- it('disables if return count is less than limit', (done) => {
+ it('disables if return count is less than limit', async () => {
Pager.offset = 0;
Pager.limit = 20;
@@ -132,12 +125,10 @@ describe('pager', () => {
jest.spyOn(Pager.loading, 'hide').mockImplementation(() => {});
Pager.getOld();
- setImmediate(() => {
- expect(Pager.loading.hide).toHaveBeenCalled();
- expect(Pager.disable).toBe(true);
+ await waitForPromises();
- done();
- });
+ expect(Pager.loading.hide).toHaveBeenCalled();
+ expect(Pager.disable).toBe(true);
});
describe('has data-href attribute from list element', () => {
diff --git a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js
index 395e5d87ac1..3b113f4dcd7 100644
--- a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js
+++ b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js
@@ -114,11 +114,7 @@ describe('Learn GitLab Section Link', () => {
it('calls the eventHub', () => {
openInviteMembesrModalLink().vm.$emit('click');
- expect(eventHub.$emit).toHaveBeenCalledWith('openModal', {
- inviteeType: 'members',
- source: 'learn_gitlab',
- tasksToBeDoneEnabled: true,
- });
+ expect(eventHub.$emit).toHaveBeenCalledWith('openModal', { source: 'learn_gitlab' });
});
it('tracks the click', async () => {
diff --git a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_spec.js b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_spec.js
index f6e3a72b5e0..ee682b18af3 100644
--- a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_spec.js
+++ b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_spec.js
@@ -80,7 +80,6 @@ describe('Learn GitLab', () => {
expect(spy).toHaveBeenCalledWith('openModal', {
mode: 'celebrate',
- inviteeType: 'members',
source: 'learn-gitlab',
});
expect(cookieSpy).toHaveBeenCalledWith(INVITE_MODAL_OPEN_COOKIE);
diff --git a/spec/frontend/pipeline_wizard/components/widgets/text_spec.js b/spec/frontend/pipeline_wizard/components/widgets/text_spec.js
new file mode 100644
index 00000000000..a11c0214d15
--- /dev/null
+++ b/spec/frontend/pipeline_wizard/components/widgets/text_spec.js
@@ -0,0 +1,152 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlFormGroup, GlFormInput } from '@gitlab/ui';
+import TextWidget from '~/pipeline_wizard/components/widgets/text.vue';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+
+describe('Pipeline Wizard - Text Widget', () => {
+ const defaultProps = {
+ label: 'This label',
+ description: 'some description',
+ placeholder: 'some placeholder',
+ pattern: '^[a-z]+$',
+ invalidFeedback: 'some feedback',
+ };
+
+ let wrapper;
+
+ const findGlFormGroup = () => wrapper.findComponent(GlFormGroup);
+ const findGlFormGroupInvalidFeedback = () => findGlFormGroup().find('.invalid-feedback');
+ const findGlFormInput = () => wrapper.findComponent(GlFormInput);
+
+ const createComponent = (props = {}, mountFn = mountExtended) => {
+ wrapper = mountFn(TextWidget, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ });
+ };
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ });
+
+ it('creates an input element with the correct label', () => {
+ createComponent();
+
+ expect(wrapper.findByLabelText(defaultProps.label).exists()).toBe(true);
+ });
+
+ it('passes the description', () => {
+ createComponent({}, shallowMount);
+
+ expect(findGlFormGroup().attributes('description')).toBe(defaultProps.description);
+ });
+
+ it('sets the "text" type on the input component', () => {
+ createComponent();
+
+ expect(findGlFormInput().attributes('type')).toBe('text');
+ });
+
+ it('passes the placeholder', () => {
+ createComponent();
+
+ expect(findGlFormInput().attributes('placeholder')).toBe(defaultProps.placeholder);
+ });
+
+ it('emits an update event on input', async () => {
+ createComponent();
+
+ const localValue = 'somevalue';
+ await findGlFormInput().setValue(localValue);
+
+ expect(wrapper.emitted('input')).toEqual([[localValue]]);
+ });
+
+ it('passes invalid feedback message', () => {
+ createComponent();
+
+ expect(findGlFormGroupInvalidFeedback().text()).toBe(defaultProps.invalidFeedback);
+ });
+
+ it('provides invalid feedback', async () => {
+ createComponent({ validate: true });
+
+ await findGlFormInput().setValue('invalid%99');
+
+ expect(findGlFormGroup().classes()).toContain('is-invalid');
+ expect(findGlFormInput().classes()).toContain('is-invalid');
+ });
+
+ it('provides valid feedback', async () => {
+ createComponent({ validate: true });
+
+ await findGlFormInput().setValue('valid');
+
+ expect(findGlFormGroup().classes()).toContain('is-valid');
+ expect(findGlFormInput().classes()).toContain('is-valid');
+ });
+
+ it('does not show validation state when untouched', () => {
+ createComponent({ value: 'invalid99' });
+
+ expect(findGlFormGroup().classes()).not.toContain('is-valid');
+ expect(findGlFormGroup().classes()).not.toContain('is-invalid');
+ });
+
+ it('shows invalid state on blur', async () => {
+ createComponent();
+
+ await findGlFormInput().setValue('invalid%99');
+
+ expect(findGlFormGroup().classes()).not.toContain('is-invalid');
+
+ await findGlFormInput().trigger('blur');
+
+ expect(findGlFormInput().classes()).toContain('is-invalid');
+ expect(findGlFormGroup().classes()).toContain('is-invalid');
+ });
+
+ it('shows invalid state when toggling `validate` prop', async () => {
+ createComponent({
+ required: true,
+ validate: false,
+ });
+
+ expect(findGlFormGroup().classes()).not.toContain('is-invalid');
+
+ await wrapper.setProps({ validate: true });
+
+ expect(findGlFormGroup().classes()).toContain('is-invalid');
+ });
+
+ it('does not update validation if not required', async () => {
+ createComponent({
+ pattern: null,
+ validate: true,
+ });
+
+ expect(findGlFormGroup().classes()).not.toContain('is-invalid');
+ });
+
+ it('sets default value', () => {
+ const defaultValue = 'foo';
+ createComponent({
+ default: defaultValue,
+ });
+
+ expect(wrapper.findByLabelText(defaultProps.label).element.value).toBe(defaultValue);
+ });
+
+ it('emits default value on setup', () => {
+ const defaultValue = 'foo';
+ createComponent({
+ default: defaultValue,
+ });
+
+ expect(wrapper.emitted('input')).toEqual([[defaultValue]]);
+ });
+});
diff --git a/spec/frontend/vue_mr_widget/components/states/__snapshots__/mr_widget_pipeline_failed_spec.js.snap b/spec/frontend/vue_mr_widget/components/states/__snapshots__/mr_widget_pipeline_failed_spec.js.snap
index a124008b36a..98297630792 100644
--- a/spec/frontend/vue_mr_widget/components/states/__snapshots__/mr_widget_pipeline_failed_spec.js.snap
+++ b/spec/frontend/vue_mr_widget/components/states/__snapshots__/mr_widget_pipeline_failed_spec.js.snap
@@ -16,7 +16,7 @@ exports[`PipelineFailed should render error message with a disabled merge button
class="bold"
>
<gl-sprintf-stub
- message="The pipeline for this merge request did not complete. Push a new commit to fix the failure, or check the %{linkStart}troubleshooting documentation%{linkEnd} to see other possible actions."
+ message="Merge blocked: pipeline must succeed. Push a commit that fixes the failure, or %{linkStart}learn about other solutions.%{linkEnd}"
/>
</span>
</div>
diff --git a/spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb b/spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb
index d4fc24d0559..90d9bbb42c3 100644
--- a/spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb
+++ b/spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb
@@ -7,13 +7,14 @@ RSpec.describe Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJo
let(:test_table) { table(table_name) }
let(:sub_batch_size) { 1000 }
let(:pause_ms) { 0 }
+ let(:connection) { ApplicationRecord.connection }
let(:helpers) do
ActiveRecord::Migration.new.extend(Gitlab::Database::MigrationHelpers)
end
before do
- ActiveRecord::Base.connection.execute(<<~SQL)
+ connection.execute(<<~SQL)
CREATE TABLE #{table_name}
(
id integer NOT NULL,
@@ -34,12 +35,14 @@ RSpec.describe Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJo
after do
# Make sure that the temp table we created is dropped (it is not removed by the database_cleaner)
- ActiveRecord::Base.connection.execute(<<~SQL)
+ connection.execute(<<~SQL)
DROP TABLE IF EXISTS #{table_name};
SQL
end
- subject(:copy_columns) { described_class.new }
+ subject(:copy_columns) { described_class.new(connection: connection) }
+
+ it { expect(described_class).to be < Gitlab::BackgroundMigration::BaseJob }
describe '#perform' do
let(:migration_class) { described_class.name }
diff --git a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
index 9a2a67389fc..b03175cd80f 100644
--- a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
@@ -70,6 +70,14 @@ RSpec.describe Gitlab::Ci::Config::Entry::Jobs do
it 'reports error' do
expect(entry.errors).to include 'jobs rspec config should implement a script: or a trigger: keyword'
end
+
+ context 'when the job name cannot be cast directly to a symbol' do
+ let(:config) { { true => nil } }
+
+ it 'properly parses the job name without raising a NoMethodError' do
+ expect(entry.errors).to include 'jobs true config should implement a script: or a trigger: keyword'
+ end
+ end
end
context 'when no visible jobs present' do
diff --git a/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb b/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb
index b3c4522a2a1..4f5536d8771 100644
--- a/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb
+++ b/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb
@@ -194,4 +194,44 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper, '
it_behaves_like 'an error is raised', RuntimeError.new('Something broke!')
it_behaves_like 'an error is raised', SignalException.new('SIGTERM')
end
+
+ context 'when the batched background migration does not inherit from BaseJob' do
+ let(:migration_class) { Class.new }
+
+ before do
+ stub_const('Gitlab::BackgroundMigration::Foo', migration_class)
+ end
+
+ let(:connection) { double(:connection) }
+ let(:active_migration) { create(:batched_background_migration, :active, job_class_name: 'Foo') }
+ let!(:job_record) { create(:batched_background_migration_job, batched_migration: active_migration) }
+
+ it 'does not pass any argument' do
+ expect(Gitlab::BackgroundMigration::Foo).to receive(:new).with(no_args).and_return(job_instance)
+
+ expect(job_instance).to receive(:perform)
+
+ described_class.new(connection: connection).perform(job_record)
+ end
+ end
+
+ context 'when the batched background migration inherits from BaseJob' do
+ let(:connection) { double(:connection) }
+ let(:active_migration) { create(:batched_background_migration, :active, job_class_name: 'Foo') }
+ let!(:job_record) { create(:batched_background_migration_job, batched_migration: active_migration) }
+
+ let(:migration_class) { Class.new(::Gitlab::BackgroundMigration::BaseJob) }
+
+ before do
+ stub_const('Gitlab::BackgroundMigration::Foo', migration_class)
+ end
+
+ it 'passes the correct connection' do
+ expect(Gitlab::BackgroundMigration::Foo).to receive(:new).with(connection: connection).and_return(job_instance)
+
+ expect(job_instance).to receive(:perform)
+
+ described_class.new(connection: connection).perform(job_record)
+ end
+ end
end
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index 0f67531382d..6f271fea2c4 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -173,6 +173,8 @@ RSpec.describe Member do
let_it_be(:group) { create(:group) }
let_it_be(:blocked_pending_approval_user) { create(:user, :blocked_pending_approval ) }
let_it_be(:blocked_pending_approval_project_member) { create(:project_member, :invited, :developer, project: project, invite_email: blocked_pending_approval_user.email) }
+ let_it_be(:awaiting_group_member) { create(:group_member, :awaiting, group: group) }
+ let_it_be(:awaiting_project_member) { create(:project_member, :awaiting, project: project) }
before_all do
@owner_user = create(:user).tap { |u| group.add_owner(u) }
@@ -471,6 +473,8 @@ RSpec.describe Member do
it { is_expected.to include @blocked_maintainer }
it { is_expected.to include @blocked_developer }
it { is_expected.not_to include @member_with_minimal_access }
+ it { is_expected.not_to include awaiting_group_member }
+ it { is_expected.not_to include awaiting_project_member }
end
describe '.connected_to_user' do
@@ -561,6 +565,21 @@ RSpec.describe Member do
end
end
end
+
+ describe '.active_state' do
+ let_it_be(:active_group_member) { create(:group_member, group: group) }
+ let_it_be(:active_project_member) { create(:project_member, project: project) }
+
+ it 'includes members with an active state' do
+ expect(group.members.active_state).to include active_group_member
+ expect(project.members.active_state).to include active_project_member
+ end
+
+ it 'does not include members with an awaiting state' do
+ expect(group.members.active_state).not_to include awaiting_group_member
+ expect(project.members.active_state).not_to include awaiting_project_member
+ end
+ end
end
describe 'Delegate methods' do
diff --git a/yarn.lock b/yarn.lock
index 48a86fd48dd..32ef28d5737 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6423,13 +6423,6 @@ hoist-non-react-statics@^3.3.2:
dependencies:
react-is "^16.7.0"
-homedir-polyfill@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc"
- integrity sha1-TCu8inWJmP7r9e1oWA921GdotLw=
- dependencies:
- parse-passwd "^1.0.0"
-
hosted-git-info@^2.1.4:
version "2.8.8"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
@@ -8098,11 +8091,6 @@ lodash@^4.17.10, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
-loglevel@^1.6.8:
- version "1.7.1"
- resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197"
- integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==
-
loose-envify@^1.0.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
@@ -8896,7 +8884,7 @@ oauth-sign@~0.9.0:
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
-object-assign@^4.0.1, object-assign@^4.1.1:
+object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
@@ -9009,13 +8997,6 @@ opener@^1.5.2:
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598"
integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==
-opn@^5.5.0:
- version "5.5.0"
- resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc"
- integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==
- dependencies:
- is-wsl "^1.1.0"
-
optimism@^0.14.0:
version "0.14.0"
resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.14.0.tgz#256fb079a3428585b40a3a8462f907e0abd2fc49"