diff options
69 files changed, 839 insertions, 538 deletions
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index ac4763103b2..d928c645a6b 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -298,6 +298,7 @@ rules: - <<: *if-not-canonical-namespace when: never + - <<: *if-master-refs - changes: *ci-build-images-patterns - changes: *code-qa-patterns diff --git a/.rubocop_manual_todo.yml b/.rubocop_manual_todo.yml index 02962c0f798..06cf8b95db3 100644 --- a/.rubocop_manual_todo.yml +++ b/.rubocop_manual_todo.yml @@ -781,7 +781,7 @@ RSpec/AnyInstanceOf: - 'ee/spec/services/slash_commands/global_slack_handler_spec.rb' - 'ee/spec/support/helpers/ee/stub_configuration.rb' - 'ee/spec/support/shared_examples/controllers/analytics/cycle_analytics/shared_stage_shared_examples.rb' - - 'ee/spec/support/shared_examples/features/gold_trial_callout_shared_examples.rb' + - 'ee/spec/support/shared_examples/features/ultimate_trial_callout_shared_examples.rb' - 'ee/spec/support/shared_examples/lib/gitlab/geo/geo_logs_event_source_info_shared_examples.rb' - 'ee/spec/support/shared_examples/models/member_shared_examples.rb' - 'ee/spec/support/shared_examples/services/base_sync_service_shared_examples.rb' diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 02d22d5c70e..43cb55c6d01 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -ed7f8b993280a89b23d10eaaa286a0b4f3f604de +b7d1a76c7837d4df1896d52b8d10097216750ac7 diff --git a/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue b/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue index f56dc05a5d2..bdf52da209e 100644 --- a/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue +++ b/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue @@ -1,16 +1,16 @@ <script> +import createHttpIntegrationMutation from 'ee_else_ce/alerts_settings/graphql/mutations/create_http_integration.mutation.graphql'; +import updateHttpIntegrationMutation from 'ee_else_ce/alerts_settings/graphql/mutations/update_http_integration.mutation.graphql'; import createFlash, { FLASH_TYPES } from '~/flash'; import { fetchPolicies } from '~/lib/graphql'; import { s__ } from '~/locale'; import { typeSet } from '../constants'; -import createHttpIntegrationMutation from '../graphql/mutations/create_http_integration.mutation.graphql'; import createPrometheusIntegrationMutation from '../graphql/mutations/create_prometheus_integration.mutation.graphql'; import destroyHttpIntegrationMutation from '../graphql/mutations/destroy_http_integration.mutation.graphql'; import resetHttpTokenMutation from '../graphql/mutations/reset_http_token.mutation.graphql'; import resetPrometheusTokenMutation from '../graphql/mutations/reset_prometheus_token.mutation.graphql'; import updateCurrentHttpIntegrationMutation from '../graphql/mutations/update_current_http_integration.mutation.graphql'; import updateCurrentPrometheusIntegrationMutation from '../graphql/mutations/update_current_prometheus_integration.mutation.graphql'; -import updateHttpIntegrationMutation from '../graphql/mutations/update_http_integration.mutation.graphql'; import updatePrometheusIntegrationMutation from '../graphql/mutations/update_prometheus_integration.mutation.graphql'; import getCurrentIntegrationQuery from '../graphql/queries/get_current_integration.query.graphql'; import getHttpIntegrationsQuery from '../graphql/queries/get_http_integrations.query.graphql'; diff --git a/app/assets/javascripts/alerts_settings/graphql/fragments/http_integration_item.fragment.graphql b/app/assets/javascripts/alerts_settings/graphql/fragments/http_integration_item.fragment.graphql index 36446bbfe47..742228e2928 100644 --- a/app/assets/javascripts/alerts_settings/graphql/fragments/http_integration_item.fragment.graphql +++ b/app/assets/javascripts/alerts_settings/graphql/fragments/http_integration_item.fragment.graphql @@ -1,5 +1,5 @@ #import "./integration_item.fragment.graphql" -#import "./http_integration_payload_data.fragment.graphql" +#import "ee_else_ce/alerts_settings/graphql/fragments/http_integration_payload_data.fragment.graphql" fragment HttpIntegrationItem on AlertManagementHttpIntegration { ...IntegrationItem diff --git a/app/assets/javascripts/alerts_settings/graphql/fragments/http_integration_payload_data.fragment.graphql b/app/assets/javascripts/alerts_settings/graphql/fragments/http_integration_payload_data.fragment.graphql index b7ea50ebc44..df6ad0b712d 100644 --- a/app/assets/javascripts/alerts_settings/graphql/fragments/http_integration_payload_data.fragment.graphql +++ b/app/assets/javascripts/alerts_settings/graphql/fragments/http_integration_payload_data.fragment.graphql @@ -1,14 +1,3 @@ fragment HttpIntegrationPayloadData on AlertManagementHttpIntegration { - payloadExample - payloadAttributeMappings { - fieldName - path - type - label - } - payloadAlertFields { - path - type - label - } + id } diff --git a/app/assets/javascripts/alerts_settings/graphql/mutations/create_http_integration.mutation.graphql b/app/assets/javascripts/alerts_settings/graphql/mutations/create_http_integration.mutation.graphql index 0c7d7627b6a..babcdea935d 100644 --- a/app/assets/javascripts/alerts_settings/graphql/mutations/create_http_integration.mutation.graphql +++ b/app/assets/javascripts/alerts_settings/graphql/mutations/create_http_integration.mutation.graphql @@ -1,21 +1,7 @@ #import "../fragments/http_integration_item.fragment.graphql" -mutation createHttpIntegration( - $projectPath: ID! - $name: String! - $active: Boolean! - $payloadExample: JsonString - $payloadAttributeMappings: [AlertManagementPayloadAlertFieldInput!] -) { - httpIntegrationCreate( - input: { - projectPath: $projectPath - name: $name - active: $active - payloadExample: $payloadExample - payloadAttributeMappings: $payloadAttributeMappings - } - ) { +mutation createHttpIntegration($projectPath: ID!, $name: String!, $active: Boolean!) { + httpIntegrationCreate(input: { projectPath: $projectPath, name: $name, active: $active }) { errors integration { ...HttpIntegrationItem diff --git a/app/assets/javascripts/alerts_settings/graphql/mutations/update_http_integration.mutation.graphql b/app/assets/javascripts/alerts_settings/graphql/mutations/update_http_integration.mutation.graphql index 631937048c2..37df9ec25eb 100644 --- a/app/assets/javascripts/alerts_settings/graphql/mutations/update_http_integration.mutation.graphql +++ b/app/assets/javascripts/alerts_settings/graphql/mutations/update_http_integration.mutation.graphql @@ -1,21 +1,7 @@ #import "../fragments/http_integration_item.fragment.graphql" -mutation updateHttpIntegration( - $id: ID! - $name: String! - $active: Boolean! - $payloadExample: JsonString - $payloadAttributeMappings: [AlertManagementPayloadAlertFieldInput!] -) { - httpIntegrationUpdate( - input: { - id: $id - name: $name - active: $active - payloadExample: $payloadExample - payloadAttributeMappings: $payloadAttributeMappings - } - ) { +mutation updateHttpIntegration($id: ID!, $name: String!, $active: Boolean!) { + httpIntegrationUpdate(input: { id: $id, name: $name, active: $active }) { errors integration { ...HttpIntegrationItem diff --git a/app/assets/javascripts/alerts_settings/graphql/queries/get_http_integrations.query.graphql b/app/assets/javascripts/alerts_settings/graphql/queries/get_http_integrations.query.graphql index e50dd89347f..833a2d6c12f 100644 --- a/app/assets/javascripts/alerts_settings/graphql/queries/get_http_integrations.query.graphql +++ b/app/assets/javascripts/alerts_settings/graphql/queries/get_http_integrations.query.graphql @@ -1,11 +1,10 @@ -#import "../fragments/http_integration_payload_data.fragment.graphql" +#import "ee_else_ce/alerts_settings/graphql/fragments/http_integration_payload_data.fragment.graphql" # TODO: this query need to accept http integration id to request a sepcific integration query getHttpIntegrations($projectPath: ID!) { project(fullPath: $projectPath) { alertManagementHttpIntegrations { nodes { - id ...HttpIntegrationPayloadData } } diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js index 4b63143c4ba..30424fee46a 100644 --- a/app/assets/javascripts/behaviors/toggler_behavior.js +++ b/app/assets/javascripts/behaviors/toggler_behavior.js @@ -30,7 +30,7 @@ $(() => { } $('body').on('click', '.js-toggle-button', function toggleButton(e) { - e.currentTarget.classList.toggle(e.currentTarget.dataset.toggleOpenClass || 'open'); + e.currentTarget.classList.toggle(e.currentTarget.dataset.toggleOpenClass || 'selected'); toggleContainer($(this).closest('.js-toggle-container')); const targetTag = e.currentTarget.tagName.toLowerCase(); diff --git a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js index b31a926dbe9..0c3fdcf3e75 100644 --- a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js +++ b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js @@ -7,21 +7,19 @@ import { initInstallRunner } from '~/pages/shared/mount_runner_instructions'; import initSearchSettings from '~/search_settings'; import initSettingsPanels from '~/settings_panels'; -document.addEventListener('DOMContentLoaded', () => { - // Initialize expandable settings panels - initSettingsPanels(); +// Initialize expandable settings panels +initSettingsPanels(); - initFilteredSearch({ - page: FILTERED_SEARCH.ADMIN_RUNNERS, - filteredSearchTokenKeys: GroupRunnersFilteredSearchTokenKeys, - anchor: FILTERED_SEARCH.GROUP_RUNNERS_ANCHOR, - useDefaultState: false, - }); +initFilteredSearch({ + page: FILTERED_SEARCH.ADMIN_RUNNERS, + filteredSearchTokenKeys: GroupRunnersFilteredSearchTokenKeys, + anchor: FILTERED_SEARCH.GROUP_RUNNERS_ANCHOR, + useDefaultState: false, +}); - initSharedRunnersForm(); - initVariableList(); +initSharedRunnersForm(); +initVariableList(); - initInstallRunner(); +initInstallRunner(); - initSearchSettings(); -}); +initSearchSettings(); diff --git a/app/assets/javascripts/pages/projects/imports/show/index.js b/app/assets/javascripts/pages/projects/imports/show/index.js index d5f92baf054..8397826f8eb 100644 --- a/app/assets/javascripts/pages/projects/imports/show/index.js +++ b/app/assets/javascripts/pages/projects/imports/show/index.js @@ -1,5 +1,3 @@ import ProjectImport from '~/project_import'; -document.addEventListener('DOMContentLoaded', () => { - new ProjectImport(); // eslint-disable-line no-new -}); +new ProjectImport(); // eslint-disable-line no-new diff --git a/app/assets/javascripts/tracking.js b/app/assets/javascripts/tracking.js index 7b5562d4d2d..008eda5e505 100644 --- a/app/assets/javascripts/tracking.js +++ b/app/assets/javascripts/tracking.js @@ -1,4 +1,4 @@ -import { omitBy, isUndefined } from 'lodash'; +import { omitBy, isUndefined, get } from 'lodash'; const standardContext = { ...window.gl?.snowplowStandardContext }; @@ -30,11 +30,17 @@ const createEventPayload = (el, { suffix = '' } = {}) => { let value = el.dataset.trackValue || el.value || undefined; if (el.type === 'checkbox' && !el.checked) value = false; + let context = el.dataset.trackContext; + if (el.dataset.trackExperiment) { + const data = get(window, ['gon', 'global', 'experiment', el.dataset.trackExperiment]); + if (data) context = { schema: 'iglu:com.gitlab/gitlab_experiment/jsonschema/1-0-0', data }; + } + const data = { label: el.dataset.trackLabel, property: el.dataset.trackProperty, value, - context: el.dataset.trackContext, + context, }; return { diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js index a0f14f558d2..9e0fc10f5d3 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js @@ -177,7 +177,7 @@ export default class MergeRequestStore { this.ciStatus = `${this.ciStatus}-with-warnings`; } - this.commitsCount = mergeRequest.commitCount || 10; + this.commitsCount = mergeRequest.commitCount; this.branchMissing = !mergeRequest.sourceBranchExists || !mergeRequest.targetBranchExists; this.hasConflicts = mergeRequest.conflicts; this.hasMergeableDiscussionsState = mergeRequest.mergeableDiscussionsState === false; diff --git a/app/assets/javascripts/vue_shared/components/clipboard_button.vue b/app/assets/javascripts/vue_shared/components/clipboard_button.vue index bf1361f1a6a..a7699d19872 100644 --- a/app/assets/javascripts/vue_shared/components/clipboard_button.vue +++ b/app/assets/javascripts/vue_shared/components/clipboard_button.vue @@ -46,6 +46,11 @@ export default { required: false, default: false, }, + tooltipBoundary: { + type: String, + required: false, + default: null, + }, cssClass: { type: String, required: false, @@ -75,8 +80,11 @@ export default { <template> <gl-button - v-gl-tooltip="{ placement: tooltipPlacement, container: tooltipContainer }" - v-gl-tooltip.hover.blur + v-gl-tooltip.hover.blur="{ + placement: tooltipPlacement, + container: tooltipContainer, + boundary: tooltipBoundary, + }" :class="cssClass" :title="title" :data-clipboard-text="clipboardText" diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 820b00a902e..2e8cfb0d432 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -125,7 +125,7 @@ class SearchController < ApplicationController payload[:metadata] ||= {} payload[:metadata]['meta.search.group_id'] = params[:group_id] payload[:metadata]['meta.search.project_id'] = params[:project_id] - payload[:metadata]['meta.search.scope'] = params[:scope] + payload[:metadata]['meta.search.scope'] = params[:scope] || @scope payload[:metadata]['meta.search.filters.confidential'] = params[:confidential] payload[:metadata]['meta.search.filters.state'] = params[:state] payload[:metadata]['meta.search.force_search_results'] = params[:force_search_results] diff --git a/app/experiments/application_experiment.rb b/app/experiments/application_experiment.rb index 63d64745a96..ec73382ed3b 100644 --- a/app/experiments/application_experiment.rb +++ b/app/experiments/application_experiment.rb @@ -25,6 +25,10 @@ class ApplicationExperiment < Gitlab::Experiment # rubocop:disable Gitlab/Namesp )) end + def exclude! + @excluded = true + end + def rollout_strategy # no-op override in inherited class as desired end diff --git a/app/helpers/in_product_marketing_helper.rb b/app/helpers/in_product_marketing_helper.rb index a0e533d3fb8..ccf3fe51870 100644 --- a/app/helpers/in_product_marketing_helper.rb +++ b/app/helpers/in_product_marketing_helper.rb @@ -47,7 +47,7 @@ module InProductMarketingHelper s_('InProductMarketing|Are your runners ready?') ], trial: [ - s_('InProductMarketing|Start a free trial of GitLab Gold – no CC required'), + s_('InProductMarketing|Start a free trial of GitLab Ultimate – no CC required'), s_('InProductMarketing|Improve app security with a 30-day trial'), s_('InProductMarketing|Start with a GitLab Gold free trial') ], diff --git a/app/helpers/user_callouts_helper.rb b/app/helpers/user_callouts_helper.rb index f55a6c3c9e5..6a242d000ae 100644 --- a/app/helpers/user_callouts_helper.rb +++ b/app/helpers/user_callouts_helper.rb @@ -31,7 +31,7 @@ module UserCalloutsHelper render 'shared/flash_user_callout', flash_type: flash_type, message: message, feature_name: feature_name end - def render_dashboard_gold_trial(user) + def render_dashboard_ultimate_trial(user) end def render_account_recovery_regular_check diff --git a/app/models/user_callout.rb b/app/models/user_callout.rb index d93fe611538..bb5a9dceaeb 100644 --- a/app/models/user_callout.rb +++ b/app/models/user_callout.rb @@ -7,7 +7,7 @@ class UserCallout < ApplicationRecord gke_cluster_integration: 1, gcp_signup_offer: 2, cluster_security_warning: 3, - gold_trial: 4, # EE-only + ultimate_trial: 4, # EE-only geo_enable_hashed_storage: 5, # EE-only geo_migrate_hashed_storage: 6, # EE-only canary_deployment: 7, # EE-only diff --git a/app/presenters/project_presenter.rb b/app/presenters/project_presenter.rb index a2d25e425da..59a9fb64a36 100644 --- a/app/presenters/project_presenter.rb +++ b/app/presenters/project_presenter.rb @@ -41,7 +41,8 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated contribution_guide_anchor_data, autodevops_anchor_data(show_auto_devops_callout: show_auto_devops_callout), kubernetes_cluster_anchor_data, - gitlab_ci_anchor_data + gitlab_ci_anchor_data, + integrations_anchor_data ].compact.reject(&:is_link).sort_by.with_index { |item, idx| [item.class_modifier ? 0 : 1, idx] } end @@ -57,7 +58,8 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated license_anchor_data, changelog_anchor_data, contribution_guide_anchor_data, - gitlab_ci_anchor_data + gitlab_ci_anchor_data, + integrations_anchor_data ].compact.reject { |item| item.is_link } end @@ -422,6 +424,25 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated private + def integrations_anchor_data + experiment(:repo_integrations_link, project: project) do |e| + e.exclude! unless can?(current_user, :admin_project, project) + + e.use {} # nil control + e.try do + label = statistic_icon('settings') + _('Configure Integrations') + AnchorData.new(false, label, project_settings_integrations_path(project), nil, nil, nil, { + 'track-event': 'click', + 'track-experiment': e.name + }) + end + + e.run # call run so the return value will be the AnchorData (or nil) + + e.track(:view, value: project.id) # track an event for the view, with project id + end + end + def cicd_missing? current_user && can_current_user_push_code? && repository.gitlab_ci_yml.blank? && !auto_devops_enabled? end diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb index cd3db4a0205..2baf8eb5997 100644 --- a/app/services/ci/register_job_service.rb +++ b/app/services/ci/register_job_service.rb @@ -4,7 +4,7 @@ module Ci # This class responsible for assigning # proper pending build to runner on runner API request class RegisterJobService - attr_reader :runner + attr_reader :runner, :metrics Result = Struct.new(:build, :build_json, :valid?) @@ -13,8 +13,18 @@ module Ci @metrics = ::Gitlab::Ci::Queue::Metrics.new(runner) end - # rubocop: disable CodeReuse/ActiveRecord def execute(params = {}) + @metrics.increment_queue_operation(:queue_attempt) + + @metrics.observe_queue_time do + process_queue(params) + end + end + + private + + # rubocop: disable CodeReuse/ActiveRecord + def process_queue(params) builds = if runner.instance_type? builds_for_shared_runner @@ -24,8 +34,6 @@ module Ci builds_for_project_runner end - valid = true - # pick builds that does not have other tags than runner's one builds = builds.matches_tag_ids(runner.tags.ids) @@ -39,14 +47,23 @@ module Ci builds = builds.queued_before(params[:job_age].seconds.ago) end + @metrics.observe_queue_size(-> { builds.to_a.size }) + + valid = true + depth = 0 + builds.each do |build| + depth += 1 + @metrics.increment_queue_operation(:queue_iteration) + result = process_build(build, params) next unless result if result.valid? @metrics.register_success(result.build) + @metrics.observe_queue_depth(:found, depth) - return result + return result # rubocop:disable Cop/AvoidReturnFromBlocks else # The usage of valid: is described in # handling of ActiveRecord::StaleObjectError @@ -54,22 +71,30 @@ module Ci end end + @metrics.increment_queue_operation(:queue_conflict) unless valid + @metrics.observe_queue_depth(:conflict, depth) unless valid + @metrics.observe_queue_depth(:not_found, depth) if valid @metrics.register_failure + Result.new(nil, nil, valid) end # rubocop: enable CodeReuse/ActiveRecord - private - def process_build(build, params) - return unless runner.can_pick?(build) + if runner.can_pick?(build) + @metrics.increment_queue_operation(:build_can_pick) + else + @metrics.increment_queue_operation(:build_not_pick) + + return + end # In case when 2 runners try to assign the same build, second runner will be declined # with StateMachines::InvalidTransition or StaleObjectError when doing run! or save method. if assign_runner!(build, params) present_build!(build) end - rescue StateMachines::InvalidTransition, ActiveRecord::StaleObjectError + rescue ActiveRecord::StaleObjectError # We are looping to find another build that is not conflicting # It also indicates that this build can be picked and passed to runner. # If we don't do it, basically a bunch of runners would be competing for a build @@ -79,8 +104,16 @@ module Ci # In case we hit the concurrency-access lock, # we still have to return 409 in the end, # to make sure that this is properly handled by runner. + @metrics.increment_queue_operation(:build_conflict_lock) + + Result.new(nil, nil, false) + rescue StateMachines::InvalidTransition + @metrics.increment_queue_operation(:build_conflict_transition) + Result.new(nil, nil, false) rescue => ex + @metrics.increment_queue_operation(:build_conflict_exception) + # If an error (e.g. GRPC::DeadlineExceeded) occurred constructing # the result, consider this as a failure to be retried. scheduler_failure!(build) @@ -106,8 +139,12 @@ module Ci failure_reason, _ = pre_assign_runner_checks.find { |_, check| check.call(build, params) } if failure_reason + @metrics.increment_queue_operation(:runner_pre_assign_checks_failed) + build.drop!(failure_reason) else + @metrics.increment_queue_operation(:runner_pre_assign_checks_success) + build.run! end diff --git a/app/views/dashboard/activity.html.haml b/app/views/dashboard/activity.html.haml index 1e93613e978..0ddee68e93f 100644 --- a/app/views/dashboard/activity.html.haml +++ b/app/views/dashboard/activity.html.haml @@ -3,7 +3,7 @@ = content_for :meta_tags do = auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity") -= render_dashboard_gold_trial(current_user) += render_dashboard_ultimate_trial(current_user) - page_title _("Activity") - header_title _("Activity"), activity_dashboard_path diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml index afe4f1b84c2..fdfc2c5adb8 100644 --- a/app/views/dashboard/groups/index.html.haml +++ b/app/views/dashboard/groups/index.html.haml @@ -2,7 +2,7 @@ - page_title _("Groups") - header_title _("Groups"), dashboard_groups_path -= render_dashboard_gold_trial(current_user) += render_dashboard_ultimate_trial(current_user) = render 'dashboard/groups_head' - if params[:filter].blank? && @groups.empty? diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml index b3ee5034204..5a7eb46771b 100644 --- a/app/views/dashboard/issues.html.haml +++ b/app/views/dashboard/issues.html.haml @@ -4,7 +4,7 @@ = content_for :meta_tags do = auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{current_user.name} issues") -= render_dashboard_gold_trial(current_user) += render_dashboard_ultimate_trial(current_user) .page-title-holder.d-flex.align-items-center %h1.page-title= _('Issues') diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml index 2111b66d26e..d47df24b1b9 100644 --- a/app/views/dashboard/merge_requests.html.haml +++ b/app/views/dashboard/merge_requests.html.haml @@ -2,7 +2,7 @@ - page_title _("Merge Requests") - @breadcrumb_link = merge_requests_dashboard_path(assignee_username: current_user.username) -= render_dashboard_gold_trial(current_user) += render_dashboard_ultimate_trial(current_user) .page-title-holder.d-flex.align-items-start.flex-column.flex-sm-row.align-items-sm-center %h1.page-title= _('Merge Requests') diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml index 6f4d53c79a7..1f4bd06aea4 100644 --- a/app/views/dashboard/projects/index.html.haml +++ b/app/views/dashboard/projects/index.html.haml @@ -12,7 +12,7 @@ callouts_feature_id: UserCalloutsHelper::CUSTOMIZE_HOMEPAGE, track_label: 'home_page' } } -= render_dashboard_gold_trial(current_user) += render_dashboard_ultimate_trial(current_user) - page_title _("Projects") - header_title _("Projects"), dashboard_projects_path diff --git a/app/views/dashboard/projects/shared/_common.html.haml b/app/views/dashboard/projects/shared/_common.html.haml index aa55f5a4e9c..17dcb072152 100644 --- a/app/views/dashboard/projects/shared/_common.html.haml +++ b/app/views/dashboard/projects/shared/_common.html.haml @@ -2,7 +2,7 @@ - breadcrumb_title _("Projects") - header_title _("Projects"), dashboard_projects_path -= render_dashboard_gold_trial(current_user) += render_dashboard_ultimate_trial(current_user) = render "projects/last_push" = render 'dashboard/projects_head', project_tab_filter: :starred diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index 9301f24d6a4..d78059b6aed 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -2,7 +2,7 @@ - page_title _("To-Do List") - header_title _("To-Do List"), dashboard_todos_path -= render_dashboard_gold_trial(current_user) += render_dashboard_ultimate_trial(current_user) - add_page_specific_style 'page_bundles/todos' .page-title-holder.d-flex.align-items-center diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml index f36f30d3638..60132818193 100644 --- a/app/views/explore/groups/index.html.haml +++ b/app/views/explore/groups/index.html.haml @@ -2,7 +2,7 @@ - page_title _("Groups") - header_title _("Groups"), dashboard_groups_path -= render_dashboard_gold_trial(current_user) += render_dashboard_ultimate_trial(current_user) - if current_user = render 'dashboard/groups_head' diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml index 44456b6c015..ae59d9c728b 100644 --- a/app/views/explore/projects/index.html.haml +++ b/app/views/explore/projects/index.html.haml @@ -3,7 +3,7 @@ - header_title _("Projects"), dashboard_projects_path - page_canonical_link explore_projects_url -= render_dashboard_gold_trial(current_user) += render_dashboard_ultimate_trial(current_user) - if current_user = render 'dashboard/projects_head', project_tab_filter: :explore diff --git a/app/views/explore/projects/page_out_of_bounds.html.haml b/app/views/explore/projects/page_out_of_bounds.html.haml index 0ee77ffd7d7..c554cce3dc6 100644 --- a/app/views/explore/projects/page_out_of_bounds.html.haml +++ b/app/views/explore/projects/page_out_of_bounds.html.haml @@ -2,7 +2,7 @@ - page_title _("Projects") - header_title _("Projects"), dashboard_projects_path -= render_dashboard_gold_trial(current_user) += render_dashboard_ultimate_trial(current_user) - if current_user = render 'dashboard/projects_head', project_tab_filter: :explore diff --git a/app/views/explore/projects/starred.html.haml b/app/views/explore/projects/starred.html.haml index ec92852ddde..a1f2fea5134 100644 --- a/app/views/explore/projects/starred.html.haml +++ b/app/views/explore/projects/starred.html.haml @@ -2,7 +2,7 @@ - page_title _("Projects") - header_title _("Projects"), dashboard_projects_path -= render_dashboard_gold_trial(current_user) += render_dashboard_ultimate_trial(current_user) - if current_user = render 'dashboard/projects_head', project_tab_filter: :starred diff --git a/app/views/explore/projects/trending.html.haml b/app/views/explore/projects/trending.html.haml index ed508fa2506..e23f63b0064 100644 --- a/app/views/explore/projects/trending.html.haml +++ b/app/views/explore/projects/trending.html.haml @@ -2,7 +2,7 @@ - page_title _("Projects") - header_title _("Projects"), dashboard_projects_path -= render_dashboard_gold_trial(current_user) += render_dashboard_ultimate_trial(current_user) - if current_user = render 'dashboard/projects_head', project_tab_filter: :explore_trending diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml index 0284c779cfa..3853f428447 100644 --- a/app/views/profiles/two_factor_auths/show.html.haml +++ b/app/views/profiles/two_factor_auths/show.html.haml @@ -9,7 +9,7 @@ %h4.gl-mt-0 = _('Register Two-Factor Authenticator') %p - = _('Use an one time password authenticator on your mobile device or computer to enable two-factor authentication (2FA).') + = _('Use a one-time password authenticator on your mobile device or computer to enable two-factor authentication (2FA).') .col-lg-8 - if current_user.two_factor_otp_enabled? %p diff --git a/app/views/projects/artifacts/browse.html.haml b/app/views/projects/artifacts/browse.html.haml index b363f0d4325..3ebac785d55 100644 --- a/app/views/projects/artifacts/browse.html.haml +++ b/app/views/projects/artifacts/browse.html.haml @@ -18,7 +18,7 @@ .tree-controls< = link_to download_project_job_artifacts_path(@project, @build), rel: 'nofollow', download: '', class: 'gl-button btn btn-default download' do - = sprite_icon('download') + = sprite_icon('download', css_class: 'gl-mr-2') Download artifacts archive .tree-content-holder diff --git a/changelogs/unreleased/247636-fix-logging-search-scope.yml b/changelogs/unreleased/247636-fix-logging-search-scope.yml new file mode 100644 index 00000000000..c9e5a7a7a58 --- /dev/null +++ b/changelogs/unreleased/247636-fix-logging-search-scope.yml @@ -0,0 +1,5 @@ +--- +title: 'Search: Log search.scope for the default scope' +merge_request: 54684 +author: +type: fixed diff --git a/changelogs/unreleased/300302-db-constraint-terraform-name.yml b/changelogs/unreleased/300302-db-constraint-terraform-name.yml new file mode 100644 index 00000000000..bbbe52224fb --- /dev/null +++ b/changelogs/unreleased/300302-db-constraint-terraform-name.yml @@ -0,0 +1,5 @@ +--- +title: Added non-null constraint to terraform state name +merge_request: 54940 +author: +type: changed diff --git a/changelogs/unreleased/35579-remove-graphql_logging-feature-flag.yml b/changelogs/unreleased/35579-remove-graphql_logging-feature-flag.yml new file mode 100644 index 00000000000..d6b8caec999 --- /dev/null +++ b/changelogs/unreleased/35579-remove-graphql_logging-feature-flag.yml @@ -0,0 +1,5 @@ +--- +title: Remove graphql_logging feature flag +merge_request: 54984 +author: +type: other diff --git a/changelogs/unreleased/add-space-download-icon.yml b/changelogs/unreleased/add-space-download-icon.yml new file mode 100644 index 00000000000..85012d9bbcb --- /dev/null +++ b/changelogs/unreleased/add-space-download-icon.yml @@ -0,0 +1,5 @@ +--- +title: Add space next to download icon in download artifacts button +merge_request: 54228 +author: Yogi (@yo) +type: changed diff --git a/changelogs/unreleased/ellipsis-expand-state-commits.yml b/changelogs/unreleased/ellipsis-expand-state-commits.yml new file mode 100644 index 00000000000..1e655514f72 --- /dev/null +++ b/changelogs/unreleased/ellipsis-expand-state-commits.yml @@ -0,0 +1,5 @@ +--- +title: Add selected state for ellipsis button in a commit +merge_request: 54754 +author: Yogi (@yo) +type: changed diff --git a/changelogs/unreleased/uusijani-gitlab-uusijani-master-patch-65195.yml b/changelogs/unreleased/uusijani-gitlab-uusijani-master-patch-65195.yml new file mode 100644 index 00000000000..7ed23b2f681 --- /dev/null +++ b/changelogs/unreleased/uusijani-gitlab-uusijani-master-patch-65195.yml @@ -0,0 +1,5 @@ +--- +title: Fixed typo on Two-Factor Authentication page +merge_request: 54565 +author: Jani Uusitalo @uusijani +type: fixed diff --git a/config/feature_flags/development/gitlab_ci_builds_queuing_metrics.yml b/config/feature_flags/development/gitlab_ci_builds_queuing_metrics.yml new file mode 100644 index 00000000000..49dd857f57f --- /dev/null +++ b/config/feature_flags/development/gitlab_ci_builds_queuing_metrics.yml @@ -0,0 +1,8 @@ +--- +name: gitlab_ci_builds_queuing_metrics +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/54909 +rollout_issue_url: +milestone: '13.10' +type: development +group: group::continuous integration +default_enabled: false diff --git a/config/feature_flags/development/graphql_logging.yml b/config/feature_flags/development/graphql_logging.yml deleted file mode 100644 index c3615215048..00000000000 --- a/config/feature_flags/development/graphql_logging.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: graphql_logging -introduced_by_url: https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/27885 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/35579 -milestone: '12.0' -type: development -group: group::project management -default_enabled: true diff --git a/config/feature_flags/experiment/repo_integrations_link.yml b/config/feature_flags/experiment/repo_integrations_link.yml new file mode 100644 index 00000000000..943429a84e7 --- /dev/null +++ b/config/feature_flags/experiment/repo_integrations_link.yml @@ -0,0 +1,8 @@ +--- +name: repo_integrations_link +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/54652/ +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/285154 +milestone: '13.10' +type: experiment +group: group::adoption +default_enabled: false diff --git a/db/migrate/20210224132547_add_null_constraint_to_terraform_state_name.rb b/db/migrate/20210224132547_add_null_constraint_to_terraform_state_name.rb new file mode 100644 index 00000000000..d9f23311cf5 --- /dev/null +++ b/db/migrate/20210224132547_add_null_constraint_to_terraform_state_name.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class AddNullConstraintToTerraformStateName < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + change_column_null :terraform_states, :name, false + end +end diff --git a/db/schema_migrations/20210224132547 b/db/schema_migrations/20210224132547 new file mode 100644 index 00000000000..f38b394f082 --- /dev/null +++ b/db/schema_migrations/20210224132547 @@ -0,0 +1 @@ +ac0f71b427be1fb583474e352ce9468a1270c6e67fa40f9071751a0485f21160
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 6c463a668d2..b829666c1d3 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -17599,7 +17599,7 @@ CREATE TABLE terraform_states ( locked_at timestamp with time zone, locked_by_user_id bigint, uuid character varying(32) NOT NULL, - name character varying(255), + name character varying(255) NOT NULL, versioning_enabled boolean DEFAULT true NOT NULL ); diff --git a/doc/operations/incident_management/integrations.md b/doc/operations/incident_management/integrations.md index 4985772eadc..d5f3643695a 100644 --- a/doc/operations/incident_management/integrations.md +++ b/doc/operations/incident_management/integrations.md @@ -4,7 +4,7 @@ group: Health info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Alert integrations **(FREE)** +# Integrations **(FREE)** > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13203) in GitLab Ultimate 12.4. > - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/42640) to GitLab Free in 12.8. diff --git a/doc/user/project/repository/repository_mirroring.md b/doc/user/project/repository/repository_mirroring.md index d9d9fd8bcd3..98d33b7461e 100644 --- a/doc/user/project/repository/repository_mirroring.md +++ b/doc/user/project/repository/repository_mirroring.md @@ -588,3 +588,9 @@ If you receive an "13:Received RST_STREAM with error code 2" while mirroring to ### 4:Deadline Exceeded When upgrading to GitLab 11.11.8 or newer, a change in how usernames are represented means that you may need to update your mirroring username and password to ensure that `%40` characters are replaced with `@`. + +### Connection blocked because server only allows public key authentication + +As the error indicates, the connection is getting blocked between GitLab and the remote repository. Even if a [TCP Check](../../../administration/raketasks/maintenance.md#check-tcp-connectivity-to-a-remote-site) is successful, you will need to check any networking components in the route from GitLab to the remote Server to ensure there's no blockage. + +For example, we've seen this error when a Firewall was performing a `Deep SSH Inspection` on outgoing packets. diff --git a/doc/user/project/settings/project_access_tokens.md b/doc/user/project/settings/project_access_tokens.md index 590f549577e..cda39508835 100644 --- a/doc/user/project/settings/project_access_tokens.md +++ b/doc/user/project/settings/project_access_tokens.md @@ -78,33 +78,3 @@ the following table. | `write_registry` | Allows write-access (push) to [container registry](../../packages/container_registry/index.md). | | `read_repository` | Allows read-only access (pull) to the repository. | | `write_repository` | Allows read-write access (pull, push) to the repository. | - -### Enable or disable project access tokens - -Project access tokens are deployed behind a feature flag that is **enabled by default**. -[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md) -can disable it for your instance, globally or by project. - -To disable it globally: - -```ruby -Feature.disable(:resource_access_token) -``` - -To disable it for a specific project: - -```ruby -Feature.disable(:resource_access_token, project) -``` - -To enable it globally: - -```ruby -Feature.enable(:resource_access_token) -``` - -To enable it for a specific project: - -```ruby -Feature.enable(:resource_access_token, project) -``` diff --git a/lib/gitlab/ci/queue/metrics.rb b/lib/gitlab/ci/queue/metrics.rb index 441f1b1dca1..aa02dd106d5 100644 --- a/lib/gitlab/ci/queue/metrics.rb +++ b/lib/gitlab/ci/queue/metrics.rb @@ -7,10 +7,34 @@ module Gitlab extend Gitlab::Utils::StrongMemoize QUEUE_DURATION_SECONDS_BUCKETS = [1, 3, 10, 30, 60, 300, 900, 1800, 3600].freeze + QUEUE_DEPTH_TOTAL_BUCKETS = [1, 2, 3, 5, 8, 16, 32, 50, 100, 250, 500, 1000, 2000, 5000].freeze + QUEUE_SIZE_TOTAL_BUCKETS = [1, 5, 10, 50, 100, 500, 1000, 2000, 5000].freeze + QUEUE_ITERATION_DURATION_SECONDS_BUCKETS = [0.1, 0.3, 0.5, 1, 5, 10, 30, 60, 180, 300].freeze + METRICS_SHARD_TAG_PREFIX = 'metrics_shard::' DEFAULT_METRICS_SHARD = 'default' JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET = 5.freeze + OPERATION_COUNTERS = [ + :build_can_pick, + :build_not_pick, + :build_conflict_lock, + :build_conflict_exception, + :build_conflict_transition, + :queue_attempt, + :queue_conflict, + :queue_iteration, + :queue_replication_lag, + :runner_pre_assign_checks_failed, + :runner_pre_assign_checks_success + ].to_set.freeze + + QUEUE_DEPTH_HISTOGRAMS = [ + :found, + :not_found, + :conflict + ].to_set.freeze + attr_reader :runner def initialize(runner) @@ -47,6 +71,43 @@ module Gitlab end # rubocop: enable CodeReuse/ActiveRecord + def increment_queue_operation(operation) + if !Rails.env.production? && !OPERATION_COUNTERS.include?(operation) + raise ArgumentError, "unknown queue operation: #{operation}" + end + + self.class.queue_operations_total.increment(operation: operation) + end + + def observe_queue_depth(queue, size) + return unless Feature.enabled?(:gitlab_ci_builds_queuing_metrics, default_enabled: false) + + if !Rails.env.production? && !QUEUE_DEPTH_HISTOGRAMS.include?(queue) + raise ArgumentError, "unknown queue depth label: #{queue}" + end + + self.class.queue_depth_total.observe({ queue: queue }, size.to_f) + end + + def observe_queue_size(size_proc) + return unless Feature.enabled?(:gitlab_ci_builds_queuing_metrics, default_enabled: false) + + self.class.queue_size_total.observe({}, size_proc.call.to_f) + end + + def observe_queue_time + start_time = ::Gitlab::Metrics::System.monotonic_time + + result = yield + + return result unless Feature.enabled?(:gitlab_ci_builds_queuing_metrics, default_enabled: false) + + seconds = ::Gitlab::Metrics::System.monotonic_time - start_time + self.class.queue_iteration_duration_seconds.observe({}, seconds.to_f) + + result + end + def self.failed_attempt_counter strong_memoize(:failed_attempt_counter) do name = :job_register_attempts_failed_total @@ -75,6 +136,48 @@ module Gitlab Gitlab::Metrics.histogram(name, comment, labels, buckets) end end + + def self.queue_operations_total + strong_memoize(:queue_operations_total) do + name = :gitlab_ci_queue_operations_total + comment = 'Counts all the operations that are happening inside a queue' + + Gitlab::Metrics.counter(name, comment) + end + end + + def self.queue_depth_total + strong_memoize(:queue_depth_total) do + name = :gitlab_ci_queue_depth_total + comment = 'Size of a CI/CD builds queue in relation to the operation result' + labels = {} + buckets = QUEUE_DEPTH_TOTAL_BUCKETS + + Gitlab::Metrics.histogram(name, comment, labels, buckets) + end + end + + def self.queue_size_total + strong_memoize(:queue_size_total) do + name = :gitlab_ci_queue_size_total + comment = 'Size of initialized CI/CD builds queue' + labels = {} + buckets = QUEUE_SIZE_TOTAL_BUCKETS + + Gitlab::Metrics.histogram(name, comment, labels, buckets) + end + end + + def self.queue_iteration_duration_seconds + strong_memoize(:queue_iteration_duration_seconds) do + name = :gitlab_ci_queue_iteration_duration_seconds + comment = 'Time it takes to find a build in CI/CD queue' + labels = {} + buckets = QUEUE_ITERATION_DURATION_SECONDS_BUCKETS + + Gitlab::Metrics.histogram(name, comment, labels, buckets) + end + end end end end diff --git a/lib/gitlab/ci/variables/collection/item.rb b/lib/gitlab/ci/variables/collection/item.rb index d32afee2e99..1b81d6a02e5 100644 --- a/lib/gitlab/ci/variables/collection/item.rb +++ b/lib/gitlab/ci/variables/collection/item.rb @@ -5,13 +5,13 @@ module Gitlab module Variables class Collection class Item + include Gitlab::Utils::StrongMemoize + def initialize(key:, value:, public: true, file: false, masked: false) raise ArgumentError, "`#{key}` must be of type String or nil value, while it was: #{value.class}" unless value.is_a?(String) || value.nil? - @variable = { - key: key, value: value, public: public, file: file, masked: masked - } + @variable = { key: key, value: value, public: public, file: file, masked: masked } end def value @@ -26,6 +26,14 @@ module Gitlab to_runner_variable == self.class.fabricate(other).to_runner_variable end + def depends_on + strong_memoize(:depends_on) do + next unless ExpandVariables.possible_var_reference?(value) + + value.scan(ExpandVariables::VARIABLES_REGEXP).map(&:first) + end + end + ## # If `file: true` has been provided we expose it, otherwise we # don't expose `file` attribute at all (stems from what the runner diff --git a/lib/gitlab/ci/variables/collection/sort.rb b/lib/gitlab/ci/variables/collection/sort.rb index 40f129f62d1..94273ab3d67 100644 --- a/lib/gitlab/ci/variables/collection/sort.rb +++ b/lib/gitlab/ci/variables/collection/sort.rb @@ -27,11 +27,11 @@ module Gitlab strong_memoize(:errors) do # Check for cyclic dependencies and build error message in that case - errors = each_strongly_connected_component.filter_map do |component| + cyclic_vars = each_strongly_connected_component.filter_map do |component| component.map { |v| v[:key] }.inspect if component.size > 1 end - "circular variable reference detected: #{errors.join(', ')}" if errors.any? + "circular variable reference detected: #{cyclic_vars.join(', ')}" if cyclic_vars.any? end end @@ -51,29 +51,11 @@ module Gitlab @collection.each(&block) end - def tsort_each_child(variable, &block) - each_variable_reference(variable.value, &block) - end - - def input_vars - strong_memoize(:input_vars) do - @collection.index_by { |env| env[:key] } - end - end - - def walk_references(value) - return unless ExpandVariables.possible_var_reference?(value) + def tsort_each_child(var_item, &block) + depends_on = var_item.depends_on + return unless depends_on - value.scan(ExpandVariables::VARIABLES_REGEXP) do |var_ref| - yield(input_vars, var_ref.first) - end - end - - def each_variable_reference(value) - walk_references(value) do |vars_hash, ref_var_name| - variable = vars_hash.dig(ref_var_name) - yield variable if variable - end + depends_on.filter_map { |var_ref_name| @collection[var_ref_name] }.each(&block) end end end diff --git a/lib/gitlab/graphql/query_analyzers/logger_analyzer.rb b/lib/gitlab/graphql/query_analyzers/logger_analyzer.rb index 0665ea8b6c9..8acd27869a9 100644 --- a/lib/gitlab/graphql/query_analyzers/logger_analyzer.rb +++ b/lib/gitlab/graphql/query_analyzers/logger_analyzer.rb @@ -9,10 +9,6 @@ module Gitlab FIELD_USAGE_ANALYZER = GraphQL::Analysis::FieldUsage.new { |query, used_fields, used_deprecated_fields| [used_fields, used_deprecated_fields] } ALL_ANALYZERS = [COMPLEXITY_ANALYZER, DEPTH_ANALYZER, FIELD_USAGE_ANALYZER].freeze - def analyze?(query) - Feature.enabled?(:graphql_logging, default_enabled: true) - end - def initial_value(query) variables = process_variables(query.provided_variables) default_initial_values(query).merge({ diff --git a/locale/gitlab.pot b/locale/gitlab.pot index c9fb3b39247..f1838bc3dc6 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -7736,6 +7736,9 @@ msgstr "" msgid "Configure Gitaly timeouts." msgstr "" +msgid "Configure Integrations" +msgstr "" + msgid "Configure Let's Encrypt" msgstr "" @@ -10634,7 +10637,7 @@ msgstr "" msgid "Discover|Security capabilities, integrated into your development lifecycle" msgstr "" -msgid "Discover|See the other features of the %{linkStart}gold plan%{linkEnd}" +msgid "Discover|See the other features of the %{linkStart}ultimate plan%{linkEnd}" msgstr "" msgid "Discover|Start a free trial" @@ -15643,7 +15646,7 @@ msgstr "" msgid "InProductMarketing|Start a GitLab Gold trial today in less than one minute, no credit card required." msgstr "" -msgid "InProductMarketing|Start a free trial of GitLab Gold – no CC required" +msgid "InProductMarketing|Start a free trial of GitLab Ultimate – no CC required" msgstr "" msgid "InProductMarketing|Start a trial" @@ -21097,7 +21100,7 @@ msgstr "" msgid "Only project members will be imported. Group members will be skipped." msgstr "" -msgid "Only projects created under a Gold license are available in Security Dashboards." +msgid "Only projects created under a Ultimate license are available in Security Dashboards." msgstr "" msgid "Only verified users with an email address in any of these domains can be added to the group." @@ -32265,7 +32268,7 @@ msgstr "" msgid "Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab" msgstr "" -msgid "Use an one time password authenticator on your mobile device or computer to enable two-factor authentication (2FA)." +msgid "Use a one-time password authenticator on your mobile device or computer to enable two-factor authentication (2FA)." msgstr "" msgid "Use cURL" diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb index 95cea10f0d0..7a5e5efedf1 100644 --- a/spec/controllers/search_controller_spec.rb +++ b/spec/controllers/search_controller_spec.rb @@ -261,23 +261,29 @@ RSpec.describe SearchController do describe '#append_info_to_payload' do it 'appends search metadata for logging' do - last_payload = nil - original_append_info_to_payload = controller.method(:append_info_to_payload) - - expect(controller).to receive(:append_info_to_payload) do |payload| - original_append_info_to_payload.call(payload) - last_payload = payload + expect(controller).to receive(:append_info_to_payload).and_wrap_original do |method, payload| + method.call(payload) + + expect(payload[:metadata]['meta.search.group_id']).to eq('123') + expect(payload[:metadata]['meta.search.project_id']).to eq('456') + expect(payload[:metadata]).not_to have_key('meta.search.search') + expect(payload[:metadata]['meta.search.scope']).to eq('issues') + expect(payload[:metadata]['meta.search.force_search_results']).to eq('true') + expect(payload[:metadata]['meta.search.filters.confidential']).to eq('true') + expect(payload[:metadata]['meta.search.filters.state']).to eq('true') end get :show, params: { scope: 'issues', search: 'hello world', group_id: '123', project_id: '456', confidential: true, state: true, force_search_results: true } + end + + it 'appends the default scope in meta.search.scope' do + expect(controller).to receive(:append_info_to_payload).and_wrap_original do |method, payload| + method.call(payload) + + expect(payload[:metadata]['meta.search.scope']).to eq('projects') + end - expect(last_payload[:metadata]['meta.search.group_id']).to eq('123') - expect(last_payload[:metadata]['meta.search.project_id']).to eq('456') - expect(last_payload[:metadata]).not_to have_key('meta.search.search') - expect(last_payload[:metadata]['meta.search.scope']).to eq('issues') - expect(last_payload[:metadata]['meta.search.force_search_results']).to eq('true') - expect(last_payload[:metadata]['meta.search.filters.confidential']).to eq('true') - expect(last_payload[:metadata]['meta.search.filters.state']).to eq('true') + get :show, params: { search: 'hello world', group_id: '123', project_id: '456' } end end end diff --git a/spec/experiments/application_experiment_spec.rb b/spec/experiments/application_experiment_spec.rb index 3803fa10ab3..2595512eec3 100644 --- a/spec/experiments/application_experiment_spec.rb +++ b/spec/experiments/application_experiment_spec.rb @@ -82,6 +82,10 @@ RSpec.describe ApplicationExperiment, :experiment do end end + it "can exclude from within the block" do + expect(described_class.new('namespaced/stub') { |e| e.exclude! }).to be_excluded + end + describe "tracking events", :snowplow do it "doesn't track if we shouldn't track" do allow(subject).to receive(:should_track?).and_return(false) diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb index e0d7ad3c133..87b9a6c0e23 100644 --- a/spec/factories/ci/pipelines.rb +++ b/spec/factories/ci/pipelines.rb @@ -40,6 +40,10 @@ FactoryBot.define do end end + trait :created do + status { :created } + end + factory :ci_pipeline do transient { ci_ref_presence { true } } @@ -53,10 +57,6 @@ FactoryBot.define do failure_reason { :config_error } end - trait :created do - status { :created } - end - trait :preparing do status { :preparing } end diff --git a/spec/features/projects/settings/operations_settings_spec.rb b/spec/features/projects/settings/operations_settings_spec.rb index 9a2333cf787..1d9f256a819 100644 --- a/spec/features/projects/settings/operations_settings_spec.rb +++ b/spec/features/projects/settings/operations_settings_spec.rb @@ -89,7 +89,7 @@ RSpec.describe 'Projects > Settings > For a forked project', :js do ) end - it 'successfully fills and submits the form', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/322666' do + it 'successfully fills and submits the form' do visit project_settings_operations_path(project) wait_for_requests @@ -152,7 +152,7 @@ RSpec.describe 'Projects > Settings > For a forked project', :js do end context 'grafana integration settings form' do - it 'successfully fills and completes the form', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/322666' do + it 'successfully fills and completes the form' do visit project_settings_operations_path(project) wait_for_requests diff --git a/spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js b/spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js index 409805fdbf7..8e63b24284b 100644 --- a/spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js +++ b/spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js @@ -2,6 +2,8 @@ import { GlLoadingIcon } from '@gitlab/ui'; import { mount, createLocalVue } from '@vue/test-utils'; import AxiosMockAdapter from 'axios-mock-adapter'; import VueApollo from 'vue-apollo'; +import createHttpIntegrationMutation from 'ee_else_ce/alerts_settings/graphql/mutations/create_http_integration.mutation.graphql'; +import updateHttpIntegrationMutation from 'ee_else_ce/alerts_settings/graphql/mutations/update_http_integration.mutation.graphql'; import createMockApollo from 'helpers/mock_apollo_helper'; import { useMockIntersectionObserver } from 'helpers/mock_dom_observer'; import waitForPromises from 'helpers/wait_for_promises'; @@ -9,14 +11,12 @@ import IntegrationsList from '~/alerts_settings/components/alerts_integrations_l import AlertsSettingsForm from '~/alerts_settings/components/alerts_settings_form.vue'; import AlertsSettingsWrapper from '~/alerts_settings/components/alerts_settings_wrapper.vue'; import { typeSet } from '~/alerts_settings/constants'; -import createHttpIntegrationMutation from '~/alerts_settings/graphql/mutations/create_http_integration.mutation.graphql'; import createPrometheusIntegrationMutation from '~/alerts_settings/graphql/mutations/create_prometheus_integration.mutation.graphql'; import destroyHttpIntegrationMutation from '~/alerts_settings/graphql/mutations/destroy_http_integration.mutation.graphql'; import resetHttpTokenMutation from '~/alerts_settings/graphql/mutations/reset_http_token.mutation.graphql'; import resetPrometheusTokenMutation from '~/alerts_settings/graphql/mutations/reset_prometheus_token.mutation.graphql'; import updateCurrentHttpIntegrationMutation from '~/alerts_settings/graphql/mutations/update_current_http_integration.mutation.graphql'; import updateCurrentPrometheusIntegrationMutation from '~/alerts_settings/graphql/mutations/update_current_prometheus_integration.mutation.graphql'; -import updateHttpIntegrationMutation from '~/alerts_settings/graphql/mutations/update_http_integration.mutation.graphql'; import updatePrometheusIntegrationMutation from '~/alerts_settings/graphql/mutations/update_prometheus_integration.mutation.graphql'; import getIntegrationsQuery from '~/alerts_settings/graphql/queries/get_integrations.query.graphql'; import { diff --git a/spec/frontend/tracking_spec.js b/spec/frontend/tracking_spec.js index a9f33b9a069..726dc0edede 100644 --- a/spec/frontend/tracking_spec.js +++ b/spec/frontend/tracking_spec.js @@ -183,6 +183,7 @@ describe('Tracking', () => { <input class="dropdown" data-track-event="toggle_dropdown"/> <div data-track-event="nested_event"><span class="nested"></span></div> <input data-track-eventbogus="click_bogusinput" data-track-label="_label_" value="_value_"/> + <input data-track-event="click_input3" data-track-experiment="example" value="_value_"/> `); }); @@ -242,6 +243,22 @@ describe('Tracking', () => { expect(eventSpy).toHaveBeenCalledWith('_category_', 'nested_event', {}); }); + + it('brings in experiment data if linked to an experiment', () => { + const data = { + variant: 'candidate', + experiment: 'repo_integrations_link', + key: '2bff73f6bb8cc11156c50a8ba66b9b8b', + }; + + window.gon.global = { experiment: { example: data } }; + document.querySelector('[data-track-event="click_input3"]').click(); + + expect(eventSpy).toHaveBeenCalledWith('_category_', 'click_input3', { + value: '_value_', + context: { schema: 'iglu:com.gitlab/gitlab_experiment/jsonschema/1-0-0', data }, + }); + }); }); describe('tracking page loaded events', () => { diff --git a/spec/lib/gitlab/ci/variables/collection/item_spec.rb b/spec/lib/gitlab/ci/variables/collection/item_spec.rb index 42fbb88cceb..d806c466309 100644 --- a/spec/lib/gitlab/ci/variables/collection/item_spec.rb +++ b/spec/lib/gitlab/ci/variables/collection/item_spec.rb @@ -70,6 +70,43 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Item do end end + describe '#depends_on' do + let(:item) { Gitlab::Ci::Variables::Collection::Item.new(**variable) } + + subject { item.depends_on } + + context 'table tests' do + using RSpec::Parameterized::TableSyntax + + where do + { + "no variable references": { + variable: { key: 'VAR', value: 'something' }, + expected_depends_on: nil + }, + "simple variable reference": { + variable: { key: 'VAR', value: 'something_$VAR2' }, + expected_depends_on: %w(VAR2) + }, + "complex expansion": { + variable: { key: 'VAR', value: 'something_${VAR2}_$VAR3' }, + expected_depends_on: %w(VAR2 VAR3) + }, + "complex expansions for Windows": { + variable: { key: 'variable3', value: 'key%variable%%variable2%' }, + expected_depends_on: %w(variable variable2) + } + } + end + + with_them do + it 'contains referenced variable names' do + is_expected.to eq(expected_depends_on) + end + end + end + end + describe '.fabricate' do it 'supports using a hash' do resource = described_class.fabricate(variable) @@ -140,5 +177,13 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Item do .to eq(key: 'VAR', value: 'value', public: true, file: true, masked: false) end end + + context 'when referencing a variable' do + it '#depends_on contains names of dependencies' do + runner_variable = described_class.new(key: 'CI_VAR', value: '${CI_VAR_2}-123-$CI_VAR_3') + + expect(runner_variable.depends_on).to eq(%w(CI_VAR_2 CI_VAR_3)) + end + end end end diff --git a/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb b/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb index 138765afd8a..8450396284a 100644 --- a/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb +++ b/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb @@ -5,42 +5,6 @@ require 'spec_helper' RSpec.describe Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer do subject { described_class.new } - describe '#analyze?' do - context 'feature flag disabled' do - before do - stub_feature_flags(graphql_logging: false) - end - - it 'disables the analyzer' do - expect(subject.analyze?(anything)).to be_falsey - end - end - - context 'feature flag enabled by default' do - let(:monotonic_time_before) { 42 } - let(:monotonic_time_after) { 500 } - let(:monotonic_time_duration) { monotonic_time_after - monotonic_time_before } - - it 'enables the analyzer' do - expect(subject.analyze?(anything)).to be_truthy - end - - it 'returns a duration in seconds' do - allow(GraphQL::Analysis).to receive(:analyze_query).and_return([4, 2, [[], []]]) - allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(monotonic_time_before, monotonic_time_after) - allow(Gitlab::GraphqlLogger).to receive(:info) - - expected_duration = monotonic_time_duration - memo = subject.initial_value(spy('query')) - - subject.final_value(memo) - - expect(memo).to have_key(:duration_s) - expect(memo[:duration_s]).to eq(expected_duration) - end - end - end - describe '#initial_value' do it 'filters out sensitive variables' do doc = GraphQL.parse <<-GRAPHQL @@ -58,4 +22,24 @@ RSpec.describe Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer do expect(subject.initial_value(query)[:variables]).to eq('{:body=>"[FILTERED]"}') end end + + describe '#final_value' do + let(:monotonic_time_before) { 42 } + let(:monotonic_time_after) { 500 } + let(:monotonic_time_duration) { monotonic_time_after - monotonic_time_before } + + it 'returns a duration in seconds' do + allow(GraphQL::Analysis).to receive(:analyze_query).and_return([4, 2, [[], []]]) + allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(monotonic_time_before, monotonic_time_after) + allow(Gitlab::GraphqlLogger).to receive(:info) + + expected_duration = monotonic_time_duration + memo = subject.initial_value(spy('query')) + + subject.final_value(memo) + + expect(memo).to have_key(:duration_s) + expect(memo[:duration_s]).to eq(expected_duration) + end + end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 4ad7ce70a44..dd816101f0c 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -2584,14 +2584,14 @@ RSpec.describe Ci::Build do end shared_examples 'containing environment variables' do - it { environment_variables.each { |v| is_expected.to include(v) } } + it { is_expected.to include(*environment_variables) } end context 'when no URL was set' do it_behaves_like 'containing environment variables' it 'does not have CI_ENVIRONMENT_URL' do - keys = subject.map { |var| var[:key] } + keys = subject.pluck(:key) expect(keys).not_to include('CI_ENVIRONMENT_URL') end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 7dc84577f40..bc9bd8edc6e 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -11,10 +11,6 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do let_it_be(:namespace) { create_default(:namespace).freeze } let_it_be(:project) { create_default(:project, :repository).freeze } - let(:pipeline) do - create(:ci_empty_pipeline, status: :created, project: project) - end - it_behaves_like 'having unique enum values' it { is_expected.to belong_to(:project) } @@ -53,6 +49,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe 'associations' do + let_it_be(:pipeline) { create(:ci_empty_pipeline, :created) } + it 'has a bidirectional relationship with projects' do expect(described_class.reflect_on_association(:project).has_inverse?).to eq(:all_pipelines) expect(Project.reflect_on_association(:all_pipelines).has_inverse?).to eq(:project) @@ -82,6 +80,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe '#set_status' do + let(:pipeline) { build(:ci_empty_pipeline, :created) } + where(:from_status, :to_status) do from_status_names = described_class.state_machines[:status].states.map(&:name) to_status_names = from_status_names - [:created] # we never want to transition into created @@ -105,6 +105,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe '.processables' do + let_it_be(:pipeline) { create(:ci_empty_pipeline, :created) } + before do create(:ci_build, name: 'build', pipeline: pipeline) create(:ci_bridge, name: 'bridge', pipeline: pipeline) @@ -142,7 +144,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do subject { described_class.for_sha(sha) } let(:sha) { 'abc' } - let!(:pipeline) { create(:ci_pipeline, sha: 'abc') } + + let_it_be(:pipeline) { create(:ci_pipeline, sha: 'abc') } it 'returns the pipeline' do is_expected.to contain_exactly(pipeline) @@ -170,7 +173,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do subject { described_class.for_source_sha(source_sha) } let(:source_sha) { 'abc' } - let!(:pipeline) { create(:ci_pipeline, source_sha: 'abc') } + + let_it_be(:pipeline) { create(:ci_pipeline, source_sha: 'abc') } it 'returns the pipeline' do is_expected.to contain_exactly(pipeline) @@ -228,7 +232,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do subject { described_class.for_branch(branch) } let(:branch) { 'master' } - let!(:pipeline) { create(:ci_pipeline, ref: 'master') } + + let_it_be(:pipeline) { create(:ci_pipeline, ref: 'master') } it 'returns the pipeline' do is_expected.to contain_exactly(pipeline) @@ -247,13 +252,16 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do describe '.ci_sources' do subject { described_class.ci_sources } - let!(:push_pipeline) { create(:ci_pipeline, source: :push) } - let!(:web_pipeline) { create(:ci_pipeline, source: :web) } - let!(:api_pipeline) { create(:ci_pipeline, source: :api) } - let!(:webide_pipeline) { create(:ci_pipeline, source: :webide) } - let!(:child_pipeline) { create(:ci_pipeline, source: :parent_pipeline) } + let(:push_pipeline) { build(:ci_pipeline, source: :push) } + let(:web_pipeline) { build(:ci_pipeline, source: :web) } + let(:api_pipeline) { build(:ci_pipeline, source: :api) } + let(:webide_pipeline) { build(:ci_pipeline, source: :webide) } + let(:child_pipeline) { build(:ci_pipeline, source: :parent_pipeline) } + let(:pipelines) { [push_pipeline, web_pipeline, api_pipeline, webide_pipeline, child_pipeline] } it 'contains pipelines having CI only sources' do + pipelines.map(&:save!) + expect(subject).to contain_exactly(push_pipeline, web_pipeline, api_pipeline) end @@ -387,6 +395,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do describe '#merge_request_ref?' do subject { pipeline.merge_request_ref? } + let(:pipeline) { build(:ci_empty_pipeline, :created) } + it 'calls MergeRequest#merge_request_ref?' do expect(MergeRequest).to receive(:merge_request_ref?).with(pipeline.ref) @@ -624,7 +634,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do describe '#source' do context 'when creating new pipeline' do let(:pipeline) do - build(:ci_empty_pipeline, status: :created, project: project, source: nil) + build(:ci_empty_pipeline, :created, project: project, source: nil) end it "prevents from creating an object" do @@ -633,17 +643,21 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end context 'when updating existing pipeline' do + let(:pipeline) { create(:ci_empty_pipeline, :created) } + before do pipeline.update_attribute(:source, nil) end - it "object is valid" do + it 'object is valid' do expect(pipeline).to be_valid end end end describe '#block' do + let(:pipeline) { create(:ci_empty_pipeline, :created) } + it 'changes pipeline status to manual' do expect(pipeline.block).to be true expect(pipeline.reload).to be_manual @@ -654,7 +668,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do describe '#delay' do subject { pipeline.delay } - let(:pipeline) { build(:ci_pipeline, status: :created) } + let(:pipeline) { build(:ci_pipeline, :created) } it 'changes pipeline status to schedule' do subject @@ -664,6 +678,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe '#valid_commit_sha' do + let(:pipeline) { build_stubbed(:ci_empty_pipeline, :created, project: project) } + context 'commit.sha can not start with 00000000' do before do pipeline.sha = '0' * 40 @@ -677,6 +693,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do describe '#short_sha' do subject { pipeline.short_sha } + let(:pipeline) { build_stubbed(:ci_empty_pipeline, :created) } + it 'has 8 items' do expect(subject.size).to eq(8) end @@ -686,49 +704,58 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do describe '#retried' do subject { pipeline.retried } + let(:pipeline) { create(:ci_empty_pipeline, :created, project: project) } + let!(:build1) { create(:ci_build, pipeline: pipeline, name: 'deploy', retried: true) } + before do - @build1 = create(:ci_build, pipeline: pipeline, name: 'deploy', retried: true) - @build2 = create(:ci_build, pipeline: pipeline, name: 'deploy') + create(:ci_build, pipeline: pipeline, name: 'deploy') end it 'returns old builds' do - is_expected.to contain_exactly(@build1) + is_expected.to contain_exactly(build1) end end describe '#coverage' do - let(:project) { create(:project, build_coverage_regex: "/.*/") } - let(:pipeline) { create(:ci_empty_pipeline, project: project) } + let_it_be_with_reload(:pipeline) { create(:ci_empty_pipeline) } - it "calculates average when there are two builds with coverage" do - create(:ci_build, name: "rspec", coverage: 30, pipeline: pipeline) - create(:ci_build, name: "rubocop", coverage: 40, pipeline: pipeline) - expect(pipeline.coverage).to eq("35.00") - end + context 'with multiple pipelines' do + before_all do + create(:ci_build, name: "rspec", coverage: 30, pipeline: pipeline) + create(:ci_build, name: "rubocop", coverage: 40, pipeline: pipeline) + end - it "calculates average when there are two builds with coverage and one with nil" do - create(:ci_build, name: "rspec", coverage: 30, pipeline: pipeline) - create(:ci_build, name: "rubocop", coverage: 40, pipeline: pipeline) - create(:ci_build, pipeline: pipeline) - expect(pipeline.coverage).to eq("35.00") - end + it "calculates average when there are two builds with coverage" do + expect(pipeline.coverage).to eq("35.00") + end - it "calculates average when there are two builds with coverage and one is retried" do - create(:ci_build, name: "rspec", coverage: 30, pipeline: pipeline) - create(:ci_build, name: "rubocop", coverage: 30, pipeline: pipeline, retried: true) - create(:ci_build, name: "rubocop", coverage: 40, pipeline: pipeline) - expect(pipeline.coverage).to eq("35.00") + it "calculates average when there are two builds with coverage and one with nil" do + create(:ci_build, pipeline: pipeline) + + expect(pipeline.coverage).to eq("35.00") + end + + it "calculates average when there are two builds with coverage and one is retried" do + create(:ci_build, name: "rubocop", coverage: 30, pipeline: pipeline, retried: true) + + expect(pipeline.coverage).to eq("35.00") + end end - it "calculates average when there is one build without coverage" do - FactoryBot.create(:ci_build, pipeline: pipeline) - expect(pipeline.coverage).to be_nil + context 'when there is one build without coverage' do + it "calculates average to nil" do + create(:ci_build, pipeline: pipeline) + + expect(pipeline.coverage).to be_nil + end end end describe '#retryable?' do subject { pipeline.retryable? } + let_it_be(:pipeline) { create(:ci_empty_pipeline, :created, project: project) } + context 'no failed builds' do before do create_build('rspec', 'success') @@ -790,6 +817,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do describe '#predefined_variables' do subject { pipeline.predefined_variables } + let(:pipeline) { build(:ci_empty_pipeline, :created) } + it 'includes all predefined variables in a valid order' do keys = subject.map { |variable| variable[:key] } @@ -816,21 +845,18 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end context 'when merge request is present' do + let_it_be(:assignees) { create_list(:user, 2) } + let_it_be(:milestone) { create(:milestone, project: project) } + let_it_be(:labels) { create_list(:label, 2) } let(:merge_request) do - create(:merge_request, + create(:merge_request, :simple, source_project: project, - source_branch: 'feature', target_project: project, - target_branch: 'master', assignees: assignees, milestone: milestone, labels: labels) end - let(:assignees) { create_list(:user, 2) } - let(:milestone) { create(:milestone, project: project) } - let(:labels) { create_list(:label, 2) } - context 'when pipeline for merge request is created' do let(:pipeline) do create(:ci_pipeline, :detached_merge_request_pipeline, @@ -1016,9 +1042,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe '#protected_ref?' do - before do - pipeline.project = create(:project, :repository) - end + let(:pipeline) { build(:ci_empty_pipeline, :created) } it 'delegates method to project' do expect(pipeline).not_to be_protected_ref @@ -1026,11 +1050,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe '#legacy_trigger' do - let(:trigger_request) { create(:ci_trigger_request) } - - before do - pipeline.trigger_requests << trigger_request - end + let(:trigger_request) { build(:ci_trigger_request) } + let(:pipeline) { build(:ci_empty_pipeline, :created, trigger_requests: [trigger_request]) } it 'returns first trigger request' do expect(pipeline.legacy_trigger).to eq trigger_request @@ -1040,6 +1061,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do describe '#auto_canceled?' do subject { pipeline.auto_canceled? } + let(:pipeline) { build(:ci_empty_pipeline, :created) } + context 'when it is canceled' do before do pipeline.cancel @@ -1047,7 +1070,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do context 'when there is auto_canceled_by' do before do - pipeline.update!(auto_canceled_by: create(:ci_empty_pipeline)) + pipeline.auto_canceled_by = create(:ci_empty_pipeline) end it 'is auto canceled' do @@ -1075,6 +1098,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe 'pipeline stages' do + let(:pipeline) { build(:ci_empty_pipeline, :created) } + describe 'legacy stages' do before do create(:commit_status, pipeline: pipeline, @@ -1180,6 +1205,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do describe '#legacy_stage' do subject { pipeline.legacy_stage('test') } + let(:pipeline) { build(:ci_empty_pipeline, :created) } + context 'with status in stage' do before do create(:commit_status, pipeline: pipeline, stage: 'test') @@ -1202,6 +1229,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe '#stages' do + let(:pipeline) { build(:ci_empty_pipeline, :created) } + before do create(:ci_stage_entity, project: project, pipeline: pipeline, @@ -1256,6 +1285,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe 'state machine' do + let_it_be_with_reload(:pipeline) { create(:ci_empty_pipeline, :created) } let(:current) { Time.current.change(usec: 0) } let(:build) { create_build('build1', queued_at: 0) } let(:build_b) { create_build('build2', queued_at: 0) } @@ -1459,24 +1489,25 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe 'auto merge' do - let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) } - - let(:pipeline) do - create(:ci_pipeline, :running, project: merge_request.source_project, - ref: merge_request.source_branch, - sha: merge_request.diff_head_sha) - end + context 'when auto merge is enabled' do + let_it_be_with_reload(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) } + let_it_be_with_reload(:pipeline) do + create(:ci_pipeline, :running, project: merge_request.source_project, + ref: merge_request.source_branch, + sha: merge_request.diff_head_sha) + end - before do - merge_request.update_head_pipeline - end + before_all do + merge_request.update_head_pipeline + end - %w[succeed! drop! cancel! skip!].each do |action| - context "when the pipeline recieved #{action} event" do - it 'performs AutoMergeProcessWorker' do - expect(AutoMergeProcessWorker).to receive(:perform_async).with(merge_request.id) + %w[succeed! drop! cancel! skip!].each do |action| + context "when the pipeline recieved #{action} event" do + it 'performs AutoMergeProcessWorker' do + expect(AutoMergeProcessWorker).to receive(:perform_async).with(merge_request.id) - pipeline.public_send(action) + pipeline.public_send(action) + end end end end @@ -1628,15 +1659,15 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do context 'multi-project pipelines' do let!(:downstream_project) { create(:project, :repository) } - let!(:upstream_pipeline) { create(:ci_pipeline, project: project) } + let!(:upstream_pipeline) { create(:ci_pipeline) } let!(:downstream_pipeline) { create(:ci_pipeline, :with_job, project: downstream_project) } it_behaves_like 'upstream downstream pipeline' end context 'parent-child pipelines' do - let!(:upstream_pipeline) { create(:ci_pipeline, project: project) } - let!(:downstream_pipeline) { create(:ci_pipeline, :with_job, project: project) } + let!(:upstream_pipeline) { create(:ci_pipeline) } + let!(:downstream_pipeline) { create(:ci_pipeline, :with_job) } it_behaves_like 'upstream downstream pipeline' end @@ -1655,6 +1686,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do describe '#branch?' do subject { pipeline.branch? } + let(:pipeline) { build(:ci_empty_pipeline, :created) } + context 'when ref is not a tag' do before do pipeline.tag = false @@ -1665,16 +1698,12 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end context 'when pipeline is merge request' do - let(:pipeline) do - create(:ci_pipeline, merge_request: merge_request) - end + let(:pipeline) { build(:ci_pipeline, merge_request: merge_request) } let(:merge_request) do - create(:merge_request, + create(:merge_request, :simple, source_project: project, - source_branch: 'feature', - target_project: project, - target_branch: 'master') + target_project: project) end it 'returns false' do @@ -1738,6 +1767,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do context 'when repository exists' do using RSpec::Parameterized::TableSyntax + let_it_be(:pipeline, refind: true) { create(:ci_empty_pipeline) } + where(:tag, :ref, :result) do false | 'master' | true false | 'non-existent-branch' | false @@ -1746,8 +1777,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end with_them do - let(:pipeline) do - create(:ci_empty_pipeline, project: project, tag: tag, ref: ref) + before do + pipeline.update!(tag: tag, ref: ref) end it "correctly detects ref" do @@ -1757,10 +1788,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end context 'when repository does not exist' do - let(:project) { create(:project) } - let(:pipeline) do - create(:ci_empty_pipeline, project: project, ref: 'master') - end + let(:pipeline) { build(:ci_empty_pipeline, ref: 'master', project: build(:project)) } it 'always returns false' do expect(pipeline.ref_exists?).to eq false @@ -1771,7 +1799,6 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do context 'with non-empty project' do let(:pipeline) do create(:ci_pipeline, - project: project, ref: project.default_branch, sha: project.commit.sha) end @@ -1779,14 +1806,12 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do describe '#lazy_ref_commit' do let(:another) do create(:ci_pipeline, - project: project, ref: 'feature', sha: project.commit('feature').sha) end let(:unicode) do create(:ci_pipeline, - project: project, ref: 'ü/unicode/multi-byte') end @@ -1845,6 +1870,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do describe '#manual_actions' do subject { pipeline.manual_actions } + let(:pipeline) { create(:ci_empty_pipeline, :created) } + it 'when none defined' do is_expected.to be_empty end @@ -1871,9 +1898,11 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe '#branch_updated?' do + let(:pipeline) { create(:ci_empty_pipeline, :created) } + context 'when pipeline has before SHA' do before do - pipeline.update_column(:before_sha, 'a1b2c3d4') + pipeline.update!(before_sha: 'a1b2c3d4') end it 'runs on a branch update push' do @@ -1884,7 +1913,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do context 'when pipeline does not have before SHA' do before do - pipeline.update_column(:before_sha, Gitlab::Git::BLANK_SHA) + pipeline.update!(before_sha: Gitlab::Git::BLANK_SHA) end it 'does not run on a branch updating push' do @@ -1894,6 +1923,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe '#modified_paths' do + let(:pipeline) { create(:ci_empty_pipeline, :created) } + context 'when old and new revisions are set' do before do pipeline.update!(before_sha: '1234abcd', sha: '2345bcde') @@ -1910,7 +1941,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do context 'when either old or new revision is missing' do before do - pipeline.update_column(:before_sha, Gitlab::Git::BLANK_SHA) + pipeline.update!(before_sha: Gitlab::Git::BLANK_SHA) end it 'returns nil' do @@ -1924,11 +1955,9 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end let(:merge_request) do - create(:merge_request, + create(:merge_request, :simple, source_project: project, - source_branch: 'feature', - target_project: project, - target_branch: 'master') + target_project: project) end it 'returns merge request modified paths' do @@ -1962,6 +1991,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe '#has_kubernetes_active?' do + let(:pipeline) { create(:ci_empty_pipeline, :created, project: project) } + context 'when kubernetes is active' do context 'when user configured kubernetes from CI/CD > Clusters' do let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } @@ -1983,6 +2014,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do describe '#has_warnings?' do subject { pipeline.has_warnings? } + let_it_be(:pipeline) { create(:ci_empty_pipeline, :created) } + context 'build which is allowed to fail fails' do before do create :ci_build, :success, pipeline: pipeline, name: 'rspec' @@ -2039,6 +2072,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe '#number_of_warnings' do + let_it_be(:pipeline) { create(:ci_empty_pipeline, :created) } + it 'returns the number of warnings' do create(:ci_build, :allowed_to_fail, :failed, pipeline: pipeline, name: 'rubocop') create(:ci_bridge, :allowed_to_fail, :failed, pipeline: pipeline, name: 'rubocop') @@ -2047,7 +2082,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end it 'supports eager loading of the number of warnings' do - pipeline2 = create(:ci_empty_pipeline, status: :created, project: project) + pipeline2 = create(:ci_empty_pipeline, :created) create(:ci_build, :allowed_to_fail, :failed, pipeline: pipeline, name: 'rubocop') create(:ci_build, :allowed_to_fail, :failed, pipeline: pipeline2, name: 'rubocop') @@ -2071,6 +2106,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do subject { pipeline.needs_processing? } + let_it_be(:pipeline) { create(:ci_empty_pipeline, :created) } + where(:processed, :result) do nil | true false | true @@ -2090,122 +2127,107 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end end - shared_context 'with some outdated pipelines' do - before do - create_pipeline(:canceled, 'ref', 'A', project) - create_pipeline(:success, 'ref', 'A', project) - create_pipeline(:failed, 'ref', 'B', project) - create_pipeline(:skipped, 'feature', 'C', project) + context 'with outdated pipelines' do + before_all do + create_pipeline(:canceled, 'ref', 'A') + create_pipeline(:success, 'ref', 'A') + create_pipeline(:failed, 'ref', 'B') + create_pipeline(:skipped, 'feature', 'C') end - def create_pipeline(status, ref, sha, project) + def create_pipeline(status, ref, sha) create( :ci_empty_pipeline, status: status, ref: ref, - sha: sha, - project: project + sha: sha ) end - end - - describe '.newest_first' do - include_context 'with some outdated pipelines' - it 'returns the pipelines from new to old' do - expect(described_class.newest_first.pluck(:status)) - .to eq(%w[skipped failed success canceled]) - end + describe '.newest_first' do + it 'returns the pipelines from new to old' do + expect(described_class.newest_first.pluck(:status)) + .to eq(%w[skipped failed success canceled]) + end - it 'searches limited backlog' do - expect(described_class.newest_first(limit: 1).pluck(:status)) - .to eq(%w[skipped]) + it 'searches limited backlog' do + expect(described_class.newest_first(limit: 1).pluck(:status)) + .to eq(%w[skipped]) + end end - end - - describe '.latest_status' do - include_context 'with some outdated pipelines' - context 'when no ref is specified' do - it 'returns the status of the latest pipeline' do - expect(described_class.latest_status).to eq('skipped') + describe '.latest_status' do + context 'when no ref is specified' do + it 'returns the status of the latest pipeline' do + expect(described_class.latest_status).to eq('skipped') + end end - end - context 'when ref is specified' do - it 'returns the status of the latest pipeline for the given ref' do - expect(described_class.latest_status('ref')).to eq('failed') + context 'when ref is specified' do + it 'returns the status of the latest pipeline for the given ref' do + expect(described_class.latest_status('ref')).to eq('failed') + end end end - end - - describe '.latest_successful_for_ref' do - include_context 'with some outdated pipelines' - let!(:latest_successful_pipeline) do - create_pipeline(:success, 'ref', 'D', project) - end + describe '.latest_successful_for_ref' do + let!(:latest_successful_pipeline) do + create_pipeline(:success, 'ref', 'D') + end - it 'returns the latest successful pipeline' do - expect(described_class.latest_successful_for_ref('ref')) - .to eq(latest_successful_pipeline) + it 'returns the latest successful pipeline' do + expect(described_class.latest_successful_for_ref('ref')) + .to eq(latest_successful_pipeline) + end end - end - - describe '.latest_running_for_ref' do - include_context 'with some outdated pipelines' - let!(:latest_running_pipeline) do - create_pipeline(:running, 'ref', 'D', project) - end + describe '.latest_running_for_ref' do + let!(:latest_running_pipeline) do + create_pipeline(:running, 'ref', 'D') + end - it 'returns the latest running pipeline' do - expect(described_class.latest_running_for_ref('ref')) - .to eq(latest_running_pipeline) + it 'returns the latest running pipeline' do + expect(described_class.latest_running_for_ref('ref')) + .to eq(latest_running_pipeline) + end end - end - describe '.latest_failed_for_ref' do - include_context 'with some outdated pipelines' - - let!(:latest_failed_pipeline) do - create_pipeline(:failed, 'ref', 'D', project) - end + describe '.latest_failed_for_ref' do + let!(:latest_failed_pipeline) do + create_pipeline(:failed, 'ref', 'D') + end - it 'returns the latest failed pipeline' do - expect(described_class.latest_failed_for_ref('ref')) - .to eq(latest_failed_pipeline) + it 'returns the latest failed pipeline' do + expect(described_class.latest_failed_for_ref('ref')) + .to eq(latest_failed_pipeline) + end end - end - describe '.latest_successful_for_sha' do - include_context 'with some outdated pipelines' - - let!(:latest_successful_pipeline) do - create_pipeline(:success, 'ref', 'awesomesha', project) - end + describe '.latest_successful_for_sha' do + let!(:latest_successful_pipeline) do + create_pipeline(:success, 'ref', 'awesomesha') + end - it 'returns the latest successful pipeline' do - expect(described_class.latest_successful_for_sha('awesomesha')) - .to eq(latest_successful_pipeline) + it 'returns the latest successful pipeline' do + expect(described_class.latest_successful_for_sha('awesomesha')) + .to eq(latest_successful_pipeline) + end end - end - - describe '.latest_successful_for_refs' do - include_context 'with some outdated pipelines' - let!(:latest_successful_pipeline1) do - create_pipeline(:success, 'ref1', 'D', project) - end + describe '.latest_successful_for_refs' do + let!(:latest_successful_pipeline1) do + create_pipeline(:success, 'ref1', 'D') + end - let!(:latest_successful_pipeline2) do - create_pipeline(:success, 'ref2', 'D', project) - end + let!(:latest_successful_pipeline2) do + create_pipeline(:success, 'ref2', 'D') + end - it 'returns the latest successful pipeline for both refs' do - refs = %w(ref1 ref2 ref3) + it 'returns the latest successful pipeline for both refs' do + refs = %w(ref1 ref2 ref3) - expect(described_class.latest_successful_for_refs(refs)).to eq({ 'ref1' => latest_successful_pipeline1, 'ref2' => latest_successful_pipeline2 }) + expect(described_class.latest_successful_for_refs(refs)).to eq({ 'ref1' => latest_successful_pipeline1, 'ref2' => latest_successful_pipeline2 }) + end end end @@ -2215,8 +2237,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do :ci_empty_pipeline, status: 'success', ref: 'master', - sha: '123', - project: project + sha: '123' ) end @@ -2225,8 +2246,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do :ci_empty_pipeline, status: 'success', ref: 'develop', - sha: '123', - project: project + sha: '123' ) end @@ -2235,8 +2255,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do :ci_empty_pipeline, status: 'success', ref: 'test', - sha: '456', - project: project + sha: '456' ) end @@ -2333,12 +2352,11 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe '#status', :sidekiq_inline do - let(:build) do - create(:ci_build, :created, pipeline: pipeline, name: 'test') - end - subject { pipeline.reload.status } + let_it_be(:pipeline) { create(:ci_empty_pipeline, :created) } + let(:build) { create(:ci_build, :created, pipeline: pipeline, name: 'test') } + context 'on waiting for resource' do before do allow(build).to receive(:with_resource_group?) { true } @@ -2430,8 +2448,10 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do describe '#detailed_status' do subject { pipeline.detailed_status(user) } + let_it_be(:pipeline) { create(:ci_empty_pipeline, :created) } + context 'when pipeline is created' do - let(:pipeline) { create(:ci_pipeline, status: :created) } + let(:pipeline) { create(:ci_pipeline, :created) } it 'returns detailed status for created pipeline' do expect(subject.text).to eq s_('CiStatusText|created') @@ -2508,6 +2528,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe '#cancelable?' do + let_it_be(:pipeline) { create(:ci_empty_pipeline, :created) } + %i[created running pending].each do |status0| context "when there is a build #{status0}" do before do @@ -2599,7 +2621,9 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe '#cancel_running' do - let(:latest_status) { pipeline.statuses.pluck(:status) } + subject(:latest_status) { pipeline.statuses.pluck(:status) } + + let_it_be(:pipeline) { create(:ci_empty_pipeline, :created) } context 'when there is a running external job and a regular job' do before do @@ -2642,7 +2666,9 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe '#retry_failed' do - let(:latest_status) { pipeline.latest_statuses.pluck(:status) } + subject(:latest_status) { pipeline.latest_statuses.pluck(:status) } + + let_it_be(:pipeline) { create(:ci_empty_pipeline, :created) } before do stub_not_protect_default_branch @@ -2691,11 +2717,12 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe '#execute_hooks' do + let_it_be(:pipeline) { create(:ci_empty_pipeline, :created) } let!(:build_a) { create_build('a', 0) } let!(:build_b) { create_build('b', 0) } let!(:hook) do - create(:project_hook, project: project, pipeline_events: enabled) + create(:project_hook, pipeline_events: enabled) end before do @@ -2721,7 +2748,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end it 'builds hook data once' do - create(:pipelines_email_service, project: project) + create(:pipelines_email_service) expect(Gitlab::DataBuilder::Pipeline).to receive(:build).once.and_call_original @@ -2807,7 +2834,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe "#merge_requests_as_head_pipeline" do - let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: 'a288a022a53a5a944fae87bcec6efc87b7061808') } + let_it_be_with_reload(:pipeline) { create(:ci_empty_pipeline, status: 'created', ref: 'master', sha: 'a288a022a53a5a944fae87bcec6efc87b7061808') } it "returns merge requests whose `diff_head_sha` matches the pipeline's SHA" do allow_next_instance_of(MergeRequest) do |instance| @@ -2819,7 +2846,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end it "doesn't return merge requests whose source branch doesn't match the pipeline's ref" do - create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'master') + create(:merge_request, :simple, source_project: project) expect(pipeline.merge_requests_as_head_pipeline).to be_empty end @@ -2835,7 +2862,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe '#all_merge_requests' do - let(:project) { create(:project) } + let_it_be_with_reload(:project) { create(:project) } + let_it_be(:pipeline) { create(:ci_empty_pipeline, :created, project: project) } shared_examples 'a method that returns all merge requests for a given pipeline' do let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: pipeline_project, ref: 'master') } @@ -2929,10 +2957,9 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe '#related_merge_requests' do - let(:project) { create(:project, :repository) } let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'master') } let(:other_merge_request) { create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'stable') } - let(:branch_pipeline) { create(:ci_pipeline, project: project, ref: 'feature') } + let(:branch_pipeline) { create(:ci_pipeline, ref: 'feature') } let(:merge_pipeline) { create(:ci_pipeline, :detached_merge_request_pipeline, merge_request: merge_request) } context 'for a branch pipeline' do @@ -2969,9 +2996,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe '#open_merge_requests_refs' do - let(:project) { create(:project) } - let(:user) { create(:user) } - let!(:pipeline) { create(:ci_pipeline, user: user, project: project, ref: 'feature') } + let!(:pipeline) { create(:ci_pipeline, user: user, ref: 'feature') } let!(:merge_request) { create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'master') } subject { pipeline.open_merge_requests_refs } @@ -3018,6 +3043,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do describe '#same_family_pipeline_ids' do subject { pipeline.same_family_pipeline_ids.map(&:id) } + let_it_be(:pipeline) { create(:ci_empty_pipeline, :created) } + context 'when pipeline is not child nor parent' do it 'returns just the pipeline id' do expect(subject).to contain_exactly(pipeline.id) @@ -3025,7 +3052,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end context 'when pipeline is child' do - let(:parent) { create(:ci_pipeline, project: project) } + let(:parent) { create(:ci_pipeline) } let!(:pipeline) { create(:ci_pipeline, child_of: parent) } let!(:sibling) { create(:ci_pipeline, child_of: parent) } @@ -3043,7 +3070,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end context 'when pipeline is a child of a child pipeline' do - let(:ancestor) { create(:ci_pipeline, project: project) } + let(:ancestor) { create(:ci_pipeline) } let!(:parent) { create(:ci_pipeline, child_of: ancestor) } let!(:pipeline) { create(:ci_pipeline, child_of: parent) } let!(:cousin_parent) { create(:ci_pipeline, child_of: ancestor) } @@ -3068,10 +3095,10 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do describe '#root_ancestor' do subject { pipeline.root_ancestor } - let_it_be(:pipeline) { create(:ci_pipeline, project: project) } + let_it_be(:pipeline) { create(:ci_pipeline) } context 'when pipeline is child of child pipeline' do - let!(:root_ancestor) { create(:ci_pipeline, project: project) } + let!(:root_ancestor) { create(:ci_pipeline) } let!(:parent_pipeline) { create(:ci_pipeline, child_of: root_ancestor) } let!(:pipeline) { create(:ci_pipeline, child_of: parent_pipeline) } @@ -3106,6 +3133,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe '#stuck?' do + let(:pipeline) { create(:ci_empty_pipeline, :created) } + before do create(:ci_build, :pending, pipeline: pipeline) end @@ -3150,6 +3179,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe '#has_yaml_errors?' do + let(:pipeline) { build_stubbed(:ci_pipeline) } + context 'when yaml_errors is set' do before do pipeline.yaml_errors = 'File not found' @@ -3219,7 +3250,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do context 'when pipeline is not the latest' do before do - create(:ci_pipeline, :success, project: project, ci_ref: pipeline.ci_ref) + create(:ci_pipeline, :success, ci_ref: pipeline.ci_ref) end it 'does not pass ref_status' do @@ -3320,7 +3351,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do describe '#builds_in_self_and_descendants' do subject(:builds) { pipeline.builds_in_self_and_descendants } - let(:pipeline) { create(:ci_pipeline, project: project) } + let(:pipeline) { create(:ci_pipeline) } let!(:build) { create(:ci_build, pipeline: pipeline) } context 'when pipeline is standalone' do @@ -3351,6 +3382,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe '#build_with_artifacts_in_self_and_descendants' do + let_it_be(:pipeline) { create(:ci_pipeline) } let!(:build) { create(:ci_build, name: 'test', pipeline: pipeline) } let(:child_pipeline) { create(:ci_pipeline, child_of: pipeline) } let!(:child_build) { create(:ci_build, :artifacts, name: 'test', pipeline: child_pipeline) } @@ -3369,6 +3401,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe '#find_job_with_archive_artifacts' do + let(:pipeline) { create(:ci_pipeline) } let!(:old_job) { create(:ci_build, name: 'rspec', retried: true, pipeline: pipeline) } let!(:job_without_artifacts) { create(:ci_build, name: 'rspec', pipeline: pipeline) } let!(:expected_job) { create(:ci_build, :artifacts, name: 'rspec', pipeline: pipeline ) } @@ -3382,6 +3415,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe '#latest_builds_with_artifacts' do + let(:pipeline) { create(:ci_pipeline) } let!(:fresh_build) { create(:ci_build, :success, :artifacts, pipeline: pipeline) } let!(:stale_build) { create(:ci_build, :success, :expired, :artifacts, pipeline: pipeline) } @@ -3408,7 +3442,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do describe '#batch_lookup_report_artifact_for_file_type' do context 'with code quality report artifact' do - let(:pipeline) { create(:ci_pipeline, :with_codequality_reports, project: project) } + let(:pipeline) { create(:ci_pipeline, :with_codequality_reports) } it "returns the code quality artifact" do expect(pipeline.batch_lookup_report_artifact_for_file_type(:codequality)).to eq(pipeline.job_artifacts.sample) @@ -3417,24 +3451,26 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe '#latest_report_builds' do + let_it_be(:pipeline) { create(:ci_pipeline, project: project) } + it 'returns build with test artifacts' do - test_build = create(:ci_build, :test_reports, pipeline: pipeline, project: project) - coverage_build = create(:ci_build, :coverage_reports, pipeline: pipeline, project: project) + test_build = create(:ci_build, :test_reports, pipeline: pipeline) + coverage_build = create(:ci_build, :coverage_reports, pipeline: pipeline) create(:ci_build, :artifacts, pipeline: pipeline, project: project) expect(pipeline.latest_report_builds).to contain_exactly(test_build, coverage_build) end it 'filters builds by scope' do - test_build = create(:ci_build, :test_reports, pipeline: pipeline, project: project) - create(:ci_build, :coverage_reports, pipeline: pipeline, project: project) + test_build = create(:ci_build, :test_reports, pipeline: pipeline) + create(:ci_build, :coverage_reports, pipeline: pipeline) expect(pipeline.latest_report_builds(Ci::JobArtifact.test_reports)).to contain_exactly(test_build) end it 'only returns not retried builds' do - test_build = create(:ci_build, :test_reports, pipeline: pipeline, project: project) - create(:ci_build, :test_reports, :retried, pipeline: pipeline, project: project) + test_build = create(:ci_build, :test_reports, pipeline: pipeline) + create(:ci_build, :test_reports, :retried, pipeline: pipeline) expect(pipeline.latest_report_builds).to contain_exactly(test_build) end @@ -3445,17 +3481,17 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do context 'when pipeline has builds with test reports' do before do - create(:ci_build, :test_reports, pipeline: pipeline, project: project) + create(:ci_build, :test_reports, pipeline: pipeline) end context 'when pipeline status is running' do - let(:pipeline) { create(:ci_pipeline, :running, project: project) } + let(:pipeline) { create(:ci_pipeline, :running) } it { is_expected.to be_falsey } end context 'when pipeline status is success' do - let(:pipeline) { create(:ci_pipeline, :success, project: project) } + let(:pipeline) { create(:ci_pipeline, :success) } it { is_expected.to be_truthy } end @@ -3463,20 +3499,20 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do context 'when pipeline does not have builds with test reports' do before do - create(:ci_build, :artifacts, pipeline: pipeline, project: project) + create(:ci_build, :artifacts, pipeline: pipeline) end - let(:pipeline) { create(:ci_pipeline, :success, project: project) } + let(:pipeline) { create(:ci_pipeline, :success) } it { is_expected.to be_falsey } end context 'when retried build has test reports' do before do - create(:ci_build, :retried, :test_reports, pipeline: pipeline, project: project) + create(:ci_build, :retried, :test_reports, pipeline: pipeline) end - let(:pipeline) { create(:ci_pipeline, :success, project: project) } + let(:pipeline) { create(:ci_pipeline, :success) } it { is_expected.to be_falsey } end @@ -3486,13 +3522,13 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do subject { pipeline.has_coverage_reports? } context 'when pipeline has a code coverage artifact' do - let(:pipeline) { create(:ci_pipeline, :with_coverage_report_artifact, :running, project: project) } + let(:pipeline) { create(:ci_pipeline, :with_coverage_report_artifact, :running) } it { expect(subject).to be_truthy } end context 'when pipeline does not have a code coverage artifact' do - let(:pipeline) { create(:ci_pipeline, :success, project: project) } + let(:pipeline) { create(:ci_pipeline, :success) } it { expect(subject).to be_falsey } end @@ -3503,17 +3539,17 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do context 'when pipeline has builds with coverage reports' do before do - create(:ci_build, :coverage_reports, pipeline: pipeline, project: project) + create(:ci_build, :coverage_reports, pipeline: pipeline) end context 'when pipeline status is running' do - let(:pipeline) { create(:ci_pipeline, :running, project: project) } + let(:pipeline) { create(:ci_pipeline, :running) } it { expect(subject).to be_falsey } end context 'when pipeline status is success' do - let(:pipeline) { create(:ci_pipeline, :success, project: project) } + let(:pipeline) { create(:ci_pipeline, :success) } it { expect(subject).to be_truthy } end @@ -3521,10 +3557,10 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do context 'when pipeline does not have builds with coverage reports' do before do - create(:ci_build, :artifacts, pipeline: pipeline, project: project) + create(:ci_build, :artifacts, pipeline: pipeline) end - let(:pipeline) { create(:ci_pipeline, :success, project: project) } + let(:pipeline) { create(:ci_pipeline, :success) } it { expect(subject).to be_falsey } end @@ -3534,13 +3570,13 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do subject { pipeline.has_codequality_mr_diff_report? } context 'when pipeline has a codequality mr diff report' do - let(:pipeline) { create(:ci_pipeline, :with_codequality_mr_diff_report, :running, project: project) } + let(:pipeline) { create(:ci_pipeline, :with_codequality_mr_diff_report, :running) } it { expect(subject).to be_truthy } end context 'when pipeline does not have a codequality mr diff report' do - let(:pipeline) { create(:ci_pipeline, :success, project: project) } + let(:pipeline) { create(:ci_pipeline, :success) } it { expect(subject).to be_falsey } end @@ -3551,17 +3587,17 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do context 'when pipeline has builds with codequality reports' do before do - create(:ci_build, :codequality_reports, pipeline: pipeline, project: project) + create(:ci_build, :codequality_reports, pipeline: pipeline) end context 'when pipeline status is running' do - let(:pipeline) { create(:ci_pipeline, :running, project: project) } + let(:pipeline) { create(:ci_pipeline, :running) } it { expect(subject).to be_falsey } end context 'when pipeline status is success' do - let(:pipeline) { create(:ci_pipeline, :success, project: project) } + let(:pipeline) { create(:ci_pipeline, :success) } it 'can generate a codequality report' do expect(subject).to be_truthy @@ -3581,10 +3617,10 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do context 'when pipeline does not have builds with codequality reports' do before do - create(:ci_build, :artifacts, pipeline: pipeline, project: project) + create(:ci_build, :artifacts, pipeline: pipeline) end - let(:pipeline) { create(:ci_pipeline, :success, project: project) } + let(:pipeline) { create(:ci_pipeline, :success) } it { expect(subject).to be_falsey } end @@ -3593,12 +3629,12 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do describe '#test_report_summary' do subject { pipeline.test_report_summary } - context 'when pipeline has multiple builds with report results' do - let(:pipeline) { create(:ci_pipeline, :success, project: project) } + let(:pipeline) { create(:ci_pipeline, :success) } + context 'when pipeline has multiple builds with report results' do before do - create(:ci_build, :success, :report_results, name: 'rspec', pipeline: pipeline, project: project) - create(:ci_build, :success, :report_results, name: 'java', pipeline: pipeline, project: project) + create(:ci_build, :success, :report_results, name: 'rspec', pipeline: pipeline) + create(:ci_build, :success, :report_results, name: 'java', pipeline: pipeline) end it 'returns test report summary with collected data' do @@ -3616,13 +3652,15 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do describe '#test_reports' do subject { pipeline.test_reports } + let_it_be(:pipeline) { create(:ci_pipeline) } + context 'when pipeline has multiple builds with test reports' do - let!(:build_rspec) { create(:ci_build, :success, name: 'rspec', pipeline: pipeline, project: project) } - let!(:build_java) { create(:ci_build, :success, name: 'java', pipeline: pipeline, project: project) } + let!(:build_rspec) { create(:ci_build, :success, name: 'rspec', pipeline: pipeline) } + let!(:build_java) { create(:ci_build, :success, name: 'java', pipeline: pipeline) } before do - create(:ci_job_artifact, :junit, job: build_rspec, project: project) - create(:ci_job_artifact, :junit_with_ant, job: build_java, project: project) + create(:ci_job_artifact, :junit, job: build_rspec) + create(:ci_job_artifact, :junit_with_ant, job: build_java) end it 'returns test reports with collected data' do @@ -3632,8 +3670,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end context 'when builds are retried' do - let!(:build_rspec) { create(:ci_build, :retried, :success, name: 'rspec', pipeline: pipeline, project: project) } - let!(:build_java) { create(:ci_build, :retried, :success, name: 'java', pipeline: pipeline, project: project) } + let!(:build_rspec) { create(:ci_build, :retried, :success, name: 'rspec', pipeline: pipeline) } + let!(:build_java) { create(:ci_build, :retried, :success, name: 'java', pipeline: pipeline) } it 'does not take retried builds into account' do expect(subject.total_count).to be(0) @@ -3653,13 +3691,15 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do describe '#accessibility_reports' do subject { pipeline.accessibility_reports } + let_it_be(:pipeline) { create(:ci_pipeline) } + context 'when pipeline has multiple builds with accessibility reports' do - let(:build_rspec) { create(:ci_build, :success, name: 'rspec', pipeline: pipeline, project: project) } - let(:build_golang) { create(:ci_build, :success, name: 'golang', pipeline: pipeline, project: project) } + let(:build_rspec) { create(:ci_build, :success, name: 'rspec', pipeline: pipeline) } + let(:build_golang) { create(:ci_build, :success, name: 'golang', pipeline: pipeline) } before do - create(:ci_job_artifact, :accessibility, job: build_rspec, project: project) - create(:ci_job_artifact, :accessibility_without_errors, job: build_golang, project: project) + create(:ci_job_artifact, :accessibility, job: build_rspec) + create(:ci_job_artifact, :accessibility_without_errors, job: build_golang) end it 'returns accessibility report with collected data' do @@ -3670,8 +3710,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end context 'when builds are retried' do - let(:build_rspec) { create(:ci_build, :retried, :success, name: 'rspec', pipeline: pipeline, project: project) } - let(:build_golang) { create(:ci_build, :retried, :success, name: 'golang', pipeline: pipeline, project: project) } + let(:build_rspec) { create(:ci_build, :retried, :success, name: 'rspec', pipeline: pipeline) } + let(:build_golang) { create(:ci_build, :retried, :success, name: 'golang', pipeline: pipeline) } it 'returns empty urls for accessibility reports' do expect(subject.urls).to be_empty @@ -3689,13 +3729,15 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do describe '#coverage_reports' do subject { pipeline.coverage_reports } + let_it_be(:pipeline) { create(:ci_pipeline) } + context 'when pipeline has multiple builds with coverage reports' do - let!(:build_rspec) { create(:ci_build, :success, name: 'rspec', pipeline: pipeline, project: project) } - let!(:build_golang) { create(:ci_build, :success, name: 'golang', pipeline: pipeline, project: project) } + let!(:build_rspec) { create(:ci_build, :success, name: 'rspec', pipeline: pipeline) } + let!(:build_golang) { create(:ci_build, :success, name: 'golang', pipeline: pipeline) } before do - create(:ci_job_artifact, :cobertura, job: build_rspec, project: project) - create(:ci_job_artifact, :coverage_gocov_xml, job: build_golang, project: project) + create(:ci_job_artifact, :cobertura, job: build_rspec) + create(:ci_job_artifact, :coverage_gocov_xml, job: build_golang) end it 'returns coverage reports with collected data' do @@ -3707,8 +3749,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end it 'does not execute N+1 queries' do - single_build_pipeline = create(:ci_empty_pipeline, status: :created, project: project) - single_rspec = create(:ci_build, :success, name: 'rspec', pipeline: single_build_pipeline, project: project) + single_build_pipeline = create(:ci_empty_pipeline, :created) + single_rspec = create(:ci_build, :success, name: 'rspec', pipeline: single_build_pipeline) create(:ci_job_artifact, :cobertura, job: single_rspec, project: project) control = ActiveRecord::QueryRecorder.new { single_build_pipeline.coverage_reports } @@ -3717,8 +3759,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end context 'when builds are retried' do - let!(:build_rspec) { create(:ci_build, :retried, :success, name: 'rspec', pipeline: pipeline, project: project) } - let!(:build_golang) { create(:ci_build, :retried, :success, name: 'golang', pipeline: pipeline, project: project) } + let!(:build_rspec) { create(:ci_build, :retried, :success, name: 'rspec', pipeline: pipeline) } + let!(:build_golang) { create(:ci_build, :retried, :success, name: 'golang', pipeline: pipeline) } it 'does not take retried builds into account' do expect(subject.files).to eql({}) @@ -3736,13 +3778,15 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do describe '#codequality_reports' do subject(:codequality_reports) { pipeline.codequality_reports } + let_it_be(:pipeline) { create(:ci_pipeline) } + context 'when pipeline has multiple builds with codequality reports' do - let(:build_rspec) { create(:ci_build, :success, name: 'rspec', pipeline: pipeline, project: project) } - let(:build_golang) { create(:ci_build, :success, name: 'golang', pipeline: pipeline, project: project) } + let(:build_rspec) { create(:ci_build, :success, name: 'rspec', pipeline: pipeline) } + let(:build_golang) { create(:ci_build, :success, name: 'golang', pipeline: pipeline) } before do - create(:ci_job_artifact, :codequality, job: build_rspec, project: project) - create(:ci_job_artifact, :codequality_without_errors, job: build_golang, project: project) + create(:ci_job_artifact, :codequality, job: build_rspec) + create(:ci_job_artifact, :codequality_without_errors, job: build_golang) end it 'returns codequality report with collected data' do @@ -3750,8 +3794,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end context 'when builds are retried' do - let(:build_rspec) { create(:ci_build, :retried, :success, name: 'rspec', pipeline: pipeline, project: project) } - let(:build_golang) { create(:ci_build, :retried, :success, name: 'golang', pipeline: pipeline, project: project) } + let(:build_rspec) { create(:ci_build, :retried, :success, name: 'rspec', pipeline: pipeline) } + let(:build_golang) { create(:ci_build, :retried, :success, name: 'golang', pipeline: pipeline) } it 'returns a codequality reports without degradations' do expect(codequality_reports.degradations).to be_empty @@ -3767,6 +3811,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe '#total_size' do + let(:pipeline) { create(:ci_pipeline) } let!(:build_job1) { create(:ci_build, pipeline: pipeline, stage_idx: 0) } let!(:build_job2) { create(:ci_build, pipeline: pipeline, stage_idx: 0) } let!(:test_job_failed_and_retried) { create(:ci_build, :failed, :retried, pipeline: pipeline, stage_idx: 1) } @@ -3807,7 +3852,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do context 'when pipeline ref is the default branch of the project' do let(:pipeline) do - build(:ci_empty_pipeline, status: :created, project: project, ref: project.default_branch) + build(:ci_empty_pipeline, :created, project: project, ref: project.default_branch) end it "returns true" do @@ -3817,7 +3862,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do context 'when pipeline ref is not the default branch of the project' do let(:pipeline) do - build(:ci_empty_pipeline, status: :created, project: project, ref: 'another_branch') + build(:ci_empty_pipeline, :created, project: project, ref: 'another_branch') end it "returns false" do @@ -3827,7 +3872,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe '#find_stage_by_name' do - let(:pipeline) { create(:ci_pipeline) } + let_it_be(:pipeline) { create(:ci_pipeline) } let(:stage_name) { 'test' } let(:stage) do @@ -3907,10 +3952,10 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe '#parent_pipeline' do - let_it_be(:project) { create(:project) } + let_it_be_with_reload(:pipeline) { create(:ci_pipeline) } context 'when pipeline is triggered by a pipeline from the same project' do - let_it_be(:upstream_pipeline) { create(:ci_pipeline, project: project) } + let_it_be(:upstream_pipeline) { create(:ci_pipeline) } let_it_be(:pipeline) { create(:ci_pipeline, child_of: upstream_pipeline) } it 'returns the parent pipeline' do @@ -3923,7 +3968,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end context 'when pipeline is triggered by a pipeline from another project' do - let(:pipeline) { create(:ci_pipeline, project: project) } + let(:pipeline) { create(:ci_pipeline) } let!(:upstream_pipeline) { create(:ci_pipeline, project: create(:project), upstream_of: pipeline) } it 'returns nil' do @@ -3950,7 +3995,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do describe '#child_pipelines' do let_it_be(:project) { create(:project) } - let(:pipeline) { create(:ci_pipeline, project: project) } + let_it_be_with_reload(:pipeline) { create(:ci_pipeline, project: project) } context 'when pipeline triggered other pipelines on same project' do let(:downstream_pipeline) { create(:ci_pipeline, project: pipeline.project) } @@ -4004,6 +4049,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe 'upstream status interactions' do + let_it_be_with_reload(:pipeline) { create(:ci_pipeline, :created) } + context 'when a pipeline has an upstream status' do context 'when an upstream status is a bridge' do let(:bridge) { create(:ci_bridge, status: :pending) } @@ -4062,6 +4109,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do describe '#source_ref_path' do subject { pipeline.source_ref_path } + let(:pipeline) { create(:ci_pipeline, :created) } + context 'when pipeline is for a branch' do it { is_expected.to eq(Gitlab::Git::BRANCH_REF_PREFIX + pipeline.source_ref.to_s) } end @@ -4074,13 +4123,15 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end context 'when pipeline is for a tag' do - let(:pipeline) { create(:ci_pipeline, project: project, tag: true) } + let(:pipeline) { create(:ci_pipeline, tag: true) } it { is_expected.to eq(Gitlab::Git::TAG_REF_PREFIX + pipeline.source_ref.to_s) } end end - describe "#builds_with_coverage" do + describe '#builds_with_coverage' do + let_it_be(:pipeline) { create(:ci_pipeline, :created) } + it 'returns builds with coverage only' do rspec = create(:ci_build, name: 'rspec', coverage: 97.1, pipeline: pipeline) jest = create(:ci_build, name: 'jest', coverage: 94.1, pipeline: pipeline) @@ -4104,10 +4155,11 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe '#base_and_ancestors' do - let(:same_project) { false } - subject { pipeline.base_and_ancestors(same_project: same_project) } + let_it_be(:pipeline) { create(:ci_pipeline, :created) } + let(:same_project) { false } + context 'when pipeline is not child nor parent' do it 'returns just the pipeline itself' do expect(subject).to contain_exactly(pipeline) @@ -4115,8 +4167,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end context 'when pipeline is child' do - let(:parent) { create(:ci_pipeline, project: pipeline.project) } - let(:sibling) { create(:ci_pipeline, project: pipeline.project) } + let(:parent) { create(:ci_pipeline) } + let(:sibling) { create(:ci_pipeline) } before do create_source_pipeline(parent, pipeline) @@ -4129,7 +4181,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end context 'when pipeline is parent' do - let(:child) { create(:ci_pipeline, project: pipeline.project) } + let(:child) { create(:ci_pipeline) } before do create_source_pipeline(pipeline, child) @@ -4141,8 +4193,9 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end context 'when pipeline is a child of a child pipeline' do - let(:ancestor) { create(:ci_pipeline, project: pipeline.project) } - let(:parent) { create(:ci_pipeline, project: pipeline.project) } + let_it_be(:pipeline) { create(:ci_pipeline, :created) } + let(:ancestor) { create(:ci_pipeline) } + let(:parent) { create(:ci_pipeline) } before do create_source_pipeline(ancestor, parent) @@ -4155,6 +4208,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end context 'when pipeline is a triggered pipeline' do + let_it_be(:pipeline) { create(:ci_pipeline, :created) } let(:upstream) { create(:ci_pipeline, project: create(:project)) } before do @@ -4178,8 +4232,10 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe 'reset_ancestor_bridges!' do + let_it_be(:pipeline) { create(:ci_pipeline, :created) } + context 'when the pipeline is a child pipeline and the bridge is depended' do - let!(:parent_pipeline) { create(:ci_pipeline, project: project) } + let!(:parent_pipeline) { create(:ci_pipeline) } let!(:bridge) { create_bridge(parent_pipeline, pipeline, true) } it 'marks source bridge as pending' do @@ -4203,7 +4259,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end context 'when the pipeline is a child pipeline and the bridge is not depended' do - let!(:parent_pipeline) { create(:ci_pipeline, project: project) } + let!(:parent_pipeline) { create(:ci_pipeline) } let!(:bridge) { create_bridge(parent_pipeline, pipeline, false) } it 'does not touch source bridge' do @@ -4239,6 +4295,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe 'test failure history processing' do + let(:pipeline) { build(:ci_pipeline, :created) } + it 'performs the service asynchronously when the pipeline is completed' do service = double @@ -4250,21 +4308,23 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe '#latest_test_report_builds' do + let_it_be(:pipeline) { create(:ci_pipeline, :created) } + it 'returns pipeline builds with test report artifacts' do - test_build = create(:ci_build, :test_reports, pipeline: pipeline, project: project) + test_build = create(:ci_build, :test_reports, pipeline: pipeline) create(:ci_build, :artifacts, pipeline: pipeline, project: project) expect(pipeline.latest_test_report_builds).to contain_exactly(test_build) end it 'preloads project on each build to avoid N+1 queries' do - create(:ci_build, :test_reports, pipeline: pipeline, project: project) + create(:ci_build, :test_reports, pipeline: pipeline) control_count = ActiveRecord::QueryRecorder.new do pipeline.latest_test_report_builds.map(&:project).map(&:full_path) end - multi_build_pipeline = create(:ci_empty_pipeline, status: :created, project: project) + multi_build_pipeline = create(:ci_empty_pipeline, :created) create(:ci_build, :test_reports, pipeline: multi_build_pipeline, project: project) create(:ci_build, :test_reports, pipeline: multi_build_pipeline, project: project) @@ -4274,30 +4334,32 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe '#builds_with_failed_tests' do + let_it_be(:pipeline) { create(:ci_pipeline, :created) } + it 'returns pipeline builds with test report artifacts' do - failed_build = create(:ci_build, :failed, :test_reports, pipeline: pipeline, project: project) - create(:ci_build, :success, :test_reports, pipeline: pipeline, project: project) + failed_build = create(:ci_build, :failed, :test_reports, pipeline: pipeline) + create(:ci_build, :success, :test_reports, pipeline: pipeline) expect(pipeline.builds_with_failed_tests).to contain_exactly(failed_build) end it 'supports limiting the number of builds to fetch' do - create(:ci_build, :failed, :test_reports, pipeline: pipeline, project: project) - create(:ci_build, :failed, :test_reports, pipeline: pipeline, project: project) + create(:ci_build, :failed, :test_reports, pipeline: pipeline) + create(:ci_build, :failed, :test_reports, pipeline: pipeline) expect(pipeline.builds_with_failed_tests(limit: 1).count).to eq(1) end it 'preloads project on each build to avoid N+1 queries' do - create(:ci_build, :failed, :test_reports, pipeline: pipeline, project: project) + create(:ci_build, :failed, :test_reports, pipeline: pipeline) control_count = ActiveRecord::QueryRecorder.new do pipeline.builds_with_failed_tests.map(&:project).map(&:full_path) end - multi_build_pipeline = create(:ci_empty_pipeline, status: :created, project: project) - create(:ci_build, :failed, :test_reports, pipeline: multi_build_pipeline, project: project) - create(:ci_build, :failed, :test_reports, pipeline: multi_build_pipeline, project: project) + multi_build_pipeline = create(:ci_empty_pipeline, :created) + create(:ci_build, :failed, :test_reports, pipeline: multi_build_pipeline) + create(:ci_build, :failed, :test_reports, pipeline: multi_build_pipeline) expect { multi_build_pipeline.builds_with_failed_tests.map(&:project).map(&:full_path) } .not_to exceed_query_limit(control_count) diff --git a/spec/presenters/project_presenter_spec.rb b/spec/presenters/project_presenter_spec.rb index f78aa61529a..ff8bb820cc5 100644 --- a/spec/presenters/project_presenter_spec.rb +++ b/spec/presenters/project_presenter_spec.rb @@ -602,6 +602,38 @@ RSpec.describe ProjectPresenter do end end + describe 'experiment(:repo_integrations_link)' do + context 'when enabled' do + before do + stub_experiments(repo_integrations_link: :candidate) + end + + it 'includes a button to configure integrations for maintainers' do + project.add_maintainer(user) + + expect(empty_repo_statistics_buttons.map(&:label)).to include( + a_string_including('Configure Integration') + ) + end + + it 'does not include a button if not a maintainer' do + expect(empty_repo_statistics_buttons.map(&:label)).not_to include( + a_string_including('Configure Integration') + ) + end + end + + context 'when disabled' do + it 'does not include a button' do + project.add_maintainer(user) + + expect(empty_repo_statistics_buttons.map(&:label)).not_to include( + a_string_including('Configure Integration') + ) + end + end + end + context 'for a developer' do before do project.add_developer(user) diff --git a/spec/views/admin/application_settings/_package_registry.html.haml_spec.rb b/spec/views/admin/application_settings/_package_registry.html.haml_spec.rb index 405e86ee89f..e0aa2fc8d56 100644 --- a/spec/views/admin/application_settings/_package_registry.html.haml_spec.rb +++ b/spec/views/admin/application_settings/_package_registry.html.haml_spec.rb @@ -48,18 +48,18 @@ RSpec.describe 'admin/application_settings/_package_registry' do end context 'with multiple plans' do - let_it_be(:plan) { create(:plan, name: 'Gold') } - let_it_be(:gold_plan_limits) { create(:plan_limits, :with_package_file_sizes, plan: plan) } + let_it_be(:plan) { create(:plan, name: 'Ultimate') } + let_it_be(:ultimate_plan_limits) { create(:plan_limits, :with_package_file_sizes, plan: plan) } before do - assign(:plans, [default_plan_limits.plan, gold_plan_limits.plan]) + assign(:plans, [default_plan_limits.plan, ultimate_plan_limits.plan]) end it 'displays the plan name when there is more than one plan' do subject expect(page).to have_content('Default') - expect(page).to have_content('Gold') + expect(page).to have_content('Ultimate') end end end diff --git a/vendor/project_templates/learn_gitlab_gold_trial.tar.gz b/vendor/project_templates/learn_gitlab_ultimate_trial.tar.gz Binary files differindex 2ec47956706..2ec47956706 100644 --- a/vendor/project_templates/learn_gitlab_gold_trial.tar.gz +++ b/vendor/project_templates/learn_gitlab_ultimate_trial.tar.gz |