Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
		
							parent
							
								
									aadb3204ea
								
							
						
					
					
						commit
						04befb368f
					
				| 
						 | 
				
			
			@ -55,7 +55,7 @@ export default {
 | 
			
		|||
    <div>
 | 
			
		||||
      <div class="gl-mb-1">
 | 
			
		||||
        <gl-link :href="href" class="gl-font-weight-bold gl-text-gray-900!">{{ fullName }}</gl-link>
 | 
			
		||||
        <gl-badge v-if="isOwner" variant="info">{{ s__('Runner|Owner') }}</gl-badge>
 | 
			
		||||
        <gl-badge v-if="isOwner" variant="info">{{ s__('Runners|Owner') }}</gl-badge>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div v-if="description">{{ description }}</div>
 | 
			
		||||
    </div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -122,7 +122,7 @@ export default {
 | 
			
		|||
    onError(error) {
 | 
			
		||||
      this.deleting = false;
 | 
			
		||||
      const { message } = error;
 | 
			
		||||
      const title = sprintf(s__('Runner|Runner %{runnerName} failed to delete'), {
 | 
			
		||||
      const title = sprintf(s__('Runners|Runner %{runnerName} failed to delete'), {
 | 
			
		||||
        runnerName: this.runnerName,
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -72,12 +72,19 @@ export default {
 | 
			
		|||
    },
 | 
			
		||||
    setBaseImageSize() {
 | 
			
		||||
      const { contentImg } = this.$refs;
 | 
			
		||||
      if (!contentImg || contentImg.offsetHeight === 0 || contentImg.offsetWidth === 0) return;
 | 
			
		||||
 | 
			
		||||
      if (!contentImg) return;
 | 
			
		||||
      if (contentImg.offsetHeight === 0 || contentImg.offsetWidth === 0) {
 | 
			
		||||
        this.baseImageSize = {
 | 
			
		||||
          height: contentImg.naturalHeight,
 | 
			
		||||
          width: contentImg.naturalWidth,
 | 
			
		||||
        };
 | 
			
		||||
      } else {
 | 
			
		||||
        this.baseImageSize = {
 | 
			
		||||
          height: contentImg.offsetHeight,
 | 
			
		||||
          width: contentImg.offsetWidth,
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      this.onResize({ width: this.baseImageSize.width, height: this.baseImageSize.height });
 | 
			
		||||
    },
 | 
			
		||||
    setImageNaturalScale() {
 | 
			
		||||
| 
						 | 
				
			
			@ -96,6 +103,11 @@ export default {
 | 
			
		|||
 | 
			
		||||
      const { height, width } = this.baseImageSize;
 | 
			
		||||
 | 
			
		||||
      this.imageStyle = {
 | 
			
		||||
        width: `${width}px`,
 | 
			
		||||
        height: `${height}px`,
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      this.$parent.$emit(
 | 
			
		||||
        'setMaxScale',
 | 
			
		||||
        Math.round(((height + width) / (naturalHeight + naturalWidth)) * 100) / 100,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -144,7 +144,7 @@ export default {
 | 
			
		|||
          />
 | 
			
		||||
        </span>
 | 
			
		||||
      </div>
 | 
			
		||||
      <gl-intersection-observer @appear="onAppear">
 | 
			
		||||
      <gl-intersection-observer class="gl-flex-grow-1" @appear="onAppear">
 | 
			
		||||
        <gl-loading-icon v-if="showLoadingSpinner" size="lg" />
 | 
			
		||||
        <gl-icon
 | 
			
		||||
          v-else-if="showImageErrorIcon"
 | 
			
		||||
| 
						 | 
				
			
			@ -156,7 +156,7 @@ export default {
 | 
			
		|||
          v-show="showImage"
 | 
			
		||||
          :src="imageLink"
 | 
			
		||||
          :alt="filename"
 | 
			
		||||
          class="gl-display-block gl-mx-auto gl-max-w-full gl-max-h-full design-img"
 | 
			
		||||
          class="gl-display-block gl-mx-auto gl-max-w-full gl-max-h-full gl-w-auto design-img"
 | 
			
		||||
          data-qa-selector="design_image"
 | 
			
		||||
          :data-qa-filename="filename"
 | 
			
		||||
          :data-testid="`design-img-${id}`"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,16 +5,32 @@ export default {
 | 
			
		|||
      hasChildren: false,
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  updated() {
 | 
			
		||||
    this.hasChildren = this.checkSlots();
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    this.hasChildren = this.checkSlots();
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    checkSlots() {
 | 
			
		||||
      return this.$scopedSlots.default?.()?.some((c) => c.elm?.innerText);
 | 
			
		||||
    const setHasChildren = () => {
 | 
			
		||||
      this.hasChildren = Boolean(this.$el.innerText.trim());
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Set initial.
 | 
			
		||||
    setHasChildren();
 | 
			
		||||
 | 
			
		||||
    if (!this.hasChildren) {
 | 
			
		||||
      // Observe children changed.
 | 
			
		||||
      this.observer = new MutationObserver(() => {
 | 
			
		||||
        setHasChildren();
 | 
			
		||||
 | 
			
		||||
        if (this.hasChildren) {
 | 
			
		||||
          this.observer.disconnect();
 | 
			
		||||
          this.observer = undefined;
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      this.observer.observe(this.$el, { childList: true, subtree: true });
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  beforeUnmount() {
 | 
			
		||||
    if (this.observer) {
 | 
			
		||||
      this.observer.disconnect();
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -293,7 +293,10 @@ export default {
 | 
			
		|||
      return this.isWidgetPresent(WIDGET_TYPE_NOTES);
 | 
			
		||||
    },
 | 
			
		||||
    fetchByIid() {
 | 
			
		||||
      return this.glFeatures.useIidInWorkItemsPath && parseBoolean(getParameterByName('iid_path'));
 | 
			
		||||
      return (
 | 
			
		||||
        (this.glFeatures.useIidInWorkItemsPath && parseBoolean(getParameterByName('iid_path'))) ||
 | 
			
		||||
        false
 | 
			
		||||
      );
 | 
			
		||||
    },
 | 
			
		||||
    queryVariables() {
 | 
			
		||||
      return this.fetchByIid
 | 
			
		||||
| 
						 | 
				
			
			@ -572,7 +575,6 @@ export default {
 | 
			
		|||
        @error="updateError = $event"
 | 
			
		||||
      />
 | 
			
		||||
      <work-item-created-updated
 | 
			
		||||
        v-if="workItemsMvcEnabled"
 | 
			
		||||
        :work-item-id="workItem.id"
 | 
			
		||||
        :work-item-iid="workItemIid"
 | 
			
		||||
        :full-path="fullPath"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,7 @@ module Ci
 | 
			
		|||
    include Ci::HasRunnerExecutor
 | 
			
		||||
    include IgnorableColumns
 | 
			
		||||
 | 
			
		||||
    ignore_column :machine_xid, remove_with: '15.10', remove_after: '2022-03-22'
 | 
			
		||||
    ignore_column :machine_xid, remove_with: '15.11', remove_after: '2022-03-22'
 | 
			
		||||
 | 
			
		||||
    belongs_to :runner
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,20 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
module Enums
 | 
			
		||||
  class PackageMetadata
 | 
			
		||||
    PURL_TYPES = {
 | 
			
		||||
      composer: 1,
 | 
			
		||||
      conan: 2,
 | 
			
		||||
      gem: 3,
 | 
			
		||||
      golang: 4,
 | 
			
		||||
      maven: 5,
 | 
			
		||||
      npm: 6,
 | 
			
		||||
      nuget: 7,
 | 
			
		||||
      pypi: 8
 | 
			
		||||
    }.with_indifferent_access.freeze
 | 
			
		||||
 | 
			
		||||
    def self.purl_types
 | 
			
		||||
      PURL_TYPES
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -2,6 +2,36 @@
 | 
			
		|||
 | 
			
		||||
module Ci
 | 
			
		||||
  class ArchiveTraceService
 | 
			
		||||
    include ::Gitlab::ExclusiveLeaseHelpers
 | 
			
		||||
 | 
			
		||||
    EXCLUSIVE_LOCK_KEY = 'archive_trace_service:batch_execute:lock'
 | 
			
		||||
    LOCK_TIMEOUT = 56.minutes
 | 
			
		||||
    LOOP_TIMEOUT = 55.minutes
 | 
			
		||||
    LOOP_LIMIT = 2000
 | 
			
		||||
    BATCH_SIZE = 100
 | 
			
		||||
 | 
			
		||||
    # rubocop: disable CodeReuse/ActiveRecord
 | 
			
		||||
    def batch_execute(worker_name:)
 | 
			
		||||
      start_time = Time.current
 | 
			
		||||
      in_lock(EXCLUSIVE_LOCK_KEY, ttl: LOCK_TIMEOUT, retries: 1) do
 | 
			
		||||
        Ci::Build.with_stale_live_trace.find_each(batch_size: BATCH_SIZE).with_index do |build, index|
 | 
			
		||||
          break if Time.current - start_time > LOOP_TIMEOUT
 | 
			
		||||
 | 
			
		||||
          if index > LOOP_LIMIT
 | 
			
		||||
            Sidekiq.logger.warn(class: worker_name, message: 'Loop limit reached.', job_id: build.id)
 | 
			
		||||
            break
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
          begin
 | 
			
		||||
            execute(build, worker_name: worker_name)
 | 
			
		||||
          rescue StandardError
 | 
			
		||||
            next
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
    # rubocop: enable CodeReuse/ActiveRecord
 | 
			
		||||
 | 
			
		||||
    def execute(job, worker_name:)
 | 
			
		||||
      unless job.trace.archival_attempts_available?
 | 
			
		||||
        Sidekiq.logger.warn(class: worker_name, message: 'The job is out of archival attempts.', job_id: job.id)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
- add_to_breadcrumbs _('Runners'), admin_runners_path
 | 
			
		||||
- breadcrumb_title s_('Runner|New')
 | 
			
		||||
- breadcrumb_title s_('Runners|New')
 | 
			
		||||
- page_title s_('Runners|Create an instance runner')
 | 
			
		||||
 | 
			
		||||
#js-admin-new-runner{ data: {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,16 +9,22 @@ module Ci
 | 
			
		|||
    include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
 | 
			
		||||
 | 
			
		||||
    feature_category :continuous_integration
 | 
			
		||||
    deduplicate :until_executed, including_scheduled: true
 | 
			
		||||
 | 
			
		||||
    # rubocop: disable CodeReuse/ActiveRecord
 | 
			
		||||
    def perform
 | 
			
		||||
      # Archive stale live traces which still resides in redis or database
 | 
			
		||||
      # This could happen when Ci::ArchiveTraceWorker sidekiq jobs were lost by receiving SIGKILL
 | 
			
		||||
      # More details in https://gitlab.com/gitlab-org/gitlab-foss/issues/36791
 | 
			
		||||
 | 
			
		||||
      if Feature.enabled?(:deduplicate_archive_traces_cron_worker)
 | 
			
		||||
        Ci::ArchiveTraceService.new.batch_execute(worker_name: self.class.name)
 | 
			
		||||
      else
 | 
			
		||||
        Ci::Build.with_stale_live_trace.find_each(batch_size: 100) do |build|
 | 
			
		||||
          Ci::ArchiveTraceService.new.execute(build, worker_name: self.class.name)
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
    # rubocop: enable CodeReuse/ActiveRecord
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,8 @@
 | 
			
		|||
---
 | 
			
		||||
name: deduplicate_archive_traces_cron_worker
 | 
			
		||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/110305
 | 
			
		||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/389632
 | 
			
		||||
milestone: '15.9'
 | 
			
		||||
type: development
 | 
			
		||||
group: group::pipeline execution
 | 
			
		||||
default_enabled: false
 | 
			
		||||
| 
						 | 
				
			
			@ -60,3 +60,12 @@ ISO3166::Data.register(
 | 
			
		|||
  currency_code: "UAH",
 | 
			
		||||
  start_of_week: "monday"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
# Updating the display name of Taiwan, from `Taiwan, Province of China` to `Taiwan`
 | 
			
		||||
# See issue: https://gitlab.com/gitlab-org/gitlab/-/issues/349333
 | 
			
		||||
ISO3166::Data.register(
 | 
			
		||||
  ISO3166::Data.new('TW')
 | 
			
		||||
               .call
 | 
			
		||||
               .deep_symbolize_keys
 | 
			
		||||
               .merge({ name: 'Taiwan' })
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
---
 | 
			
		||||
table_name: pm_checkpoints
 | 
			
		||||
classes:
 | 
			
		||||
- PackageMetadata::Checkpoint
 | 
			
		||||
feature_categories:
 | 
			
		||||
- license_compliance
 | 
			
		||||
description: Tracks position of last synced file.
 | 
			
		||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/109713
 | 
			
		||||
milestone: '15.9'
 | 
			
		||||
gitlab_schema: gitlab_pm
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,19 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class AddPackageMetadataCheckpoints < Gitlab::Database::Migration[2.1]
 | 
			
		||||
  def up
 | 
			
		||||
    create_table :pm_checkpoints, id: false do |t|
 | 
			
		||||
      t.integer :sequence, null: false
 | 
			
		||||
      t.timestamps_with_timezone
 | 
			
		||||
      t.integer :purl_type, null: false, primary_key: true
 | 
			
		||||
      t.integer :chunk, null: false, limit: 2
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    change_column(:pm_checkpoints, :purl_type, :integer, limit: 2)
 | 
			
		||||
    drop_sequence(:pm_checkpoints, :purl_type, 'pm_checkpoints_purl_type_seq')
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def down
 | 
			
		||||
    drop_table :pm_checkpoints
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
e5498ebd6ea0c18271078236a4f64b447fa5c55318b92c04f12a66834a38f67d
 | 
			
		||||
| 
						 | 
				
			
			@ -19816,6 +19816,14 @@ CREATE SEQUENCE plans_id_seq
 | 
			
		|||
 | 
			
		||||
ALTER SEQUENCE plans_id_seq OWNED BY plans.id;
 | 
			
		||||
 | 
			
		||||
CREATE TABLE pm_checkpoints (
 | 
			
		||||
    sequence integer NOT NULL,
 | 
			
		||||
    created_at timestamp with time zone NOT NULL,
 | 
			
		||||
    updated_at timestamp with time zone NOT NULL,
 | 
			
		||||
    purl_type smallint NOT NULL,
 | 
			
		||||
    chunk smallint NOT NULL
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
CREATE TABLE pm_licenses (
 | 
			
		||||
    id bigint NOT NULL,
 | 
			
		||||
    spdx_identifier text NOT NULL,
 | 
			
		||||
| 
						 | 
				
			
			@ -27045,6 +27053,9 @@ ALTER TABLE ONLY plan_limits
 | 
			
		|||
ALTER TABLE ONLY plans
 | 
			
		||||
    ADD CONSTRAINT plans_pkey PRIMARY KEY (id);
 | 
			
		||||
 | 
			
		||||
ALTER TABLE ONLY pm_checkpoints
 | 
			
		||||
    ADD CONSTRAINT pm_checkpoints_pkey PRIMARY KEY (purl_type);
 | 
			
		||||
 | 
			
		||||
ALTER TABLE ONLY pm_licenses
 | 
			
		||||
    ADD CONSTRAINT pm_licenses_pkey PRIMARY KEY (id);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -777,6 +777,7 @@ gantt
 | 
			
		|||
    Introduce auto-partitioning mechanisms :5_1, 2023-09-01, 120d
 | 
			
		||||
    New partitions are being created automatically :milestone, part3, 2023-12-01, 1min
 | 
			
		||||
    Partitioning is made available on self-managed :milestone, part4, 2024-01-01, 1min
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Conclusions
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -352,6 +352,13 @@ scope.
 | 
			
		|||
| GitLab Runner    | `15.9` | Label Prometheus metrics with unique system ID. |
 | 
			
		||||
| GitLab Runner    | `15.8` | Prepare `register` command to fail if runner server-side configuration options are passed together with a new `glrt-` token. |
 | 
			
		||||
 | 
			
		||||
### Stage 2a - Prepare GitLab Runner Helm Chart and GitLab Runner Operator
 | 
			
		||||
 | 
			
		||||
| Component        | Milestone | Issue | Changes |
 | 
			
		||||
|------------------|----------:|-------|---------|
 | 
			
		||||
|GitLab Runner Helm Chart| `%15.10` | Update the Runner Helm Chart to support registration with the authentication token. |
 | 
			
		||||
|GitLab Runner Operator| `%15.10` | Update the Runner Operator to support registration with the authentication token. |
 | 
			
		||||
 | 
			
		||||
### Stage 3 - Database changes
 | 
			
		||||
 | 
			
		||||
| Component        | Milestone | Changes |
 | 
			
		||||
| 
						 | 
				
			
			@ -368,14 +375,18 @@ scope.
 | 
			
		|||
| GitLab Rails app | `%15.9` | Use runner token + `system_id` JSON parameters in `POST /jobs/request` request in the [heartbeat request](https://gitlab.com/gitlab-org/gitlab/blob/c73c96a8ffd515295842d72a3635a8ae873d688c/lib/api/ci/helpers/runner.rb#L14-20) to update the `ci_runner_machines` cache/table. |
 | 
			
		||||
| GitLab Rails app | `%15.9` | [Feature flag] Enable runner creation workflow (`create_runner_workflow`). |
 | 
			
		||||
| GitLab Rails app | `%15.9` | Implement `create_{instance|group|project}_runner` permissions. |
 | 
			
		||||
| GitLab Rails app | `%15.10` | Rename `ci_runner_machines.machine_xid` column to `system_xid` to be consistent with `system_id` passed in APIs. |
 | 
			
		||||
| GitLab Rails app | `%15.9` | Rename `ci_runner_machines.machine_xid` column to `system_xid` to be consistent with `system_id` passed in APIs. |
 | 
			
		||||
| GitLab Rails app | `%15.10` | Drop `ci_runner_machines.machine_xid` column. |
 | 
			
		||||
| GitLab Rails app | `%15.11` | Remove the ignore rule for `ci_runner_machines.machine_xid` column. |
 | 
			
		||||
 | 
			
		||||
### Stage 4 - New UI
 | 
			
		||||
### Stage 4 - Create runners from the UI
 | 
			
		||||
 | 
			
		||||
| Component        | Milestone | Changes |
 | 
			
		||||
|------------------|----------:|---------|
 | 
			
		||||
| GitLab Rails app | `%15.9` | Implement new GraphQL user-authenticated API to create a new runner. |
 | 
			
		||||
| GitLab Rails app | `%15.9` | [Add prefix to newly generated runner authentication tokens](https://gitlab.com/gitlab-org/gitlab/-/issues/383198). |
 | 
			
		||||
| GitLab Rails app | `%15.10` | Return token and runner ID information from `/runners/verify` REST endpoint. |
 | 
			
		||||
| GitLab Runner    | `%15.10` | [Modify register command to allow new flow with glrt- prefixed authentication tokens](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/29613). |
 | 
			
		||||
| GitLab Rails app | `%15.10` | Implement UI to create new runner. |
 | 
			
		||||
| GitLab Rails app | `%15.10` | GraphQL changes to `CiRunner` type. |
 | 
			
		||||
| GitLab Rails app | `%15.10` | UI changes to runner details view (listing of platform, architecture, IP address, etc.) (?) |
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -113,22 +113,20 @@ NOTE:
 | 
			
		|||
To protect, update, or unprotect an environment, you must have at least the
 | 
			
		||||
Maintainer role.
 | 
			
		||||
 | 
			
		||||
### Optional settings
 | 
			
		||||
 | 
			
		||||
#### Allow self-approval
 | 
			
		||||
### Allow self-approval **(PREMIUM)**
 | 
			
		||||
 | 
			
		||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/381418) in GitLab 15.8.
 | 
			
		||||
 | 
			
		||||
By default, a user who triggered a deployment pipeline can't self-approve the deployment jobs.
 | 
			
		||||
You can allow the self-approval by the following settings:
 | 
			
		||||
By default, the user who triggers a deployment pipeline can't also approve the deployment job.
 | 
			
		||||
To allow self-approval of a deployment job:
 | 
			
		||||
 | 
			
		||||
1. On the top bar, select **Main menu > Projects** and find your project.
 | 
			
		||||
1. On the left sidebar, select **Settings > CI/CD**.
 | 
			
		||||
1. Expand **Protected environments**.
 | 
			
		||||
1. From the **Approval options**, check **Allow pipeline triggerer to approve deployment**.
 | 
			
		||||
1. From the **Approval options**, select the **Allow pipeline triggerer to approve deployment** checkbox.
 | 
			
		||||
 | 
			
		||||
By enabling this, when a pipeline runs, deployment jobs will automatically be approved in the pipeline
 | 
			
		||||
if the triggerer is allowed to approve, otherwise nothing happens.
 | 
			
		||||
When a pipeline runs, deployment jobs are automatically approved in the pipeline if the user who
 | 
			
		||||
triggered the deployment is allowed to approve.
 | 
			
		||||
 | 
			
		||||
## Approve or reject a deployment
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -152,6 +152,15 @@ Use uppercase for **Alpha**. For example: **The XYZ feature is in Alpha.** or **
 | 
			
		|||
You might also want to link to [this section](../../../policy/alpha-beta-support.md#alpha-features)
 | 
			
		||||
in the handbook when writing about Alpha features.
 | 
			
		||||
 | 
			
		||||
## analytics
 | 
			
		||||
 | 
			
		||||
Use lowercase for **analytics** and its variations, like **contribution analytics** and **issue analytics**.
 | 
			
		||||
However, if the UI has different capitalization, make the documentation match the UI.
 | 
			
		||||
 | 
			
		||||
For example:
 | 
			
		||||
 | 
			
		||||
- You can view merge request analytics for a project. They are displayed on the Merge Request Analytics dashboard.
 | 
			
		||||
 | 
			
		||||
## and/or
 | 
			
		||||
 | 
			
		||||
Instead of **and/or**, use **or** or rewrite the sentence to spell out both options.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -183,4 +183,4 @@ update your agent configuration file with the location of these projects.
 | 
			
		|||
WARNING:
 | 
			
		||||
The project with the agent's
 | 
			
		||||
configuration file can be private or public. Other projects with Kubernetes manifests must be public. Support for private manifest projects is tracked
 | 
			
		||||
in [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/283885).
 | 
			
		||||
in [this epic](https://gitlab.com/groups/gitlab-org/-/epics/7704).
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -97,7 +97,7 @@ After you set up your identity provider to work with GitLab, you must configure
 | 
			
		|||

 | 
			
		||||
 | 
			
		||||
NOTE:
 | 
			
		||||
The certificate [fingerprint algorithm](../../../integration/saml.md#configure-saml-on-your-idp) must be in SHA1. When configuring the identity provider (such as [Google Workspace](#google-workspace-setup-notes)), use a secure signature algorithm.
 | 
			
		||||
The certificate [fingerprint algorithm](../../../integration/saml.md#configure-saml-on-your-idp) must be in SHA1. When configuring the identity provider (such as [Google Workspace](#set-up-google-workspace)), use a secure signature algorithm.
 | 
			
		||||
 | 
			
		||||
### Additional configuration information
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -240,37 +240,38 @@ If using [Group Sync](#group-sync), customize the name of the group claim to mat
 | 
			
		|||
 | 
			
		||||
See our [example configuration page](example_saml_config.md#azure-active-directory).
 | 
			
		||||
 | 
			
		||||
### Google Workspace setup notes
 | 
			
		||||
### Set up Google Workspace
 | 
			
		||||
 | 
			
		||||
Follow the Google Workspace documentation on
 | 
			
		||||
[setting up SSO with Google as your identity provider](https://support.google.com/a/answer/6087519?hl=en)
 | 
			
		||||
with the notes below for consideration.
 | 
			
		||||
1. [Set up SSO with Google as your identity provider](https://support.google.com/a/answer/6087519?hl=en).
 | 
			
		||||
   The following GitLab settings correspond to the Google Workspace fields.
 | 
			
		||||
 | 
			
		||||
| GitLab setting                       | Google Workspace field |
 | 
			
		||||
|:-------------------------------------|:-----------------------|
 | 
			
		||||
| Identifier                           | Entity ID              |
 | 
			
		||||
| Assertion consumer service URL       | ACS URL                |
 | 
			
		||||
| GitLab single sign-on URL            | Start URL              |
 | 
			
		||||
| Identity provider single sign-on URL | SSO URL                |
 | 
			
		||||
   | GitLab setting                       | Google Workspace field |
 | 
			
		||||
   |:-------------------------------------|:-----------------------|
 | 
			
		||||
   | Identifier                           | **Entity ID**          |
 | 
			
		||||
   | Assertion consumer service URL       | **ACS URL**            |
 | 
			
		||||
   | GitLab single sign-on URL            | **Start URL**          |
 | 
			
		||||
   | Identity provider single sign-on URL | **SSO URL**            |
 | 
			
		||||
 | 
			
		||||
NOTE:
 | 
			
		||||
Google Workspace displays a SHA256 fingerprint. To retrieve the SHA1 fingerprint required by GitLab for [configuring SAML](#configure-gitlab), download the certificate and calculate
 | 
			
		||||
the SHA1 certificate fingerprint using this sample command: `openssl x509 -noout -fingerprint -sha1 -inform pem -in "GoogleIDPCertificate-domain.com.pem"`.
 | 
			
		||||
1. Google Workspace displays a SHA256 fingerprint. To retrieve the SHA1 fingerprint
 | 
			
		||||
   required by GitLab to [configure SAML](#configure-gitlab):
 | 
			
		||||
   1. Download the certificate.
 | 
			
		||||
   1. Run this command:
 | 
			
		||||
 | 
			
		||||
The recommended attributes and claims settings are:
 | 
			
		||||
      ```shell
 | 
			
		||||
      openssl x509 -noout -fingerprint -sha1 -inform pem -in "GoogleIDPCertificate-domain.com.pem"
 | 
			
		||||
      ```
 | 
			
		||||
 | 
			
		||||
- **Primary email** set to `email`.
 | 
			
		||||
- **First name** set to `first_name`.
 | 
			
		||||
- **Last name** set to `last_name`.
 | 
			
		||||
1. Set these values:
 | 
			
		||||
   - For **Primary email**: `email`.
 | 
			
		||||
   - For **First name**: `first_name`.
 | 
			
		||||
   - For **Last name**: `last_name`.
 | 
			
		||||
   - For **Name ID format**: `EMAIL`.
 | 
			
		||||
   - For **NameID**: `Basic Information > Primary email`.
 | 
			
		||||
 | 
			
		||||
For NameID, the following settings are recommended:
 | 
			
		||||
On the GitLab SAML SSO page, when you select **Verify SAML Configuration**, disregard
 | 
			
		||||
the warning that recommends setting the **NameID** format to `persistent`.
 | 
			
		||||
 | 
			
		||||
- **Name ID format** is set to `EMAIL`.
 | 
			
		||||
- **NameID** set to `Basic Information > Primary email`.
 | 
			
		||||
 | 
			
		||||
When selecting **Verify SAML Configuration** on the GitLab SAML SSO page, disregard the warning recommending setting the NameID format to "persistent".
 | 
			
		||||
 | 
			
		||||
See our [example configuration page](example_saml_config.md#google-workspace).
 | 
			
		||||
For details, see the [example configuration page](example_saml_config.md#google-workspace).
 | 
			
		||||
 | 
			
		||||
### Okta setup notes
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 9.0 KiB  | 
| 
						 | 
				
			
			@ -86,13 +86,28 @@ Visual Studio Code:
 | 
			
		|||
- From the GitLab interface:
 | 
			
		||||
  1. Go to the project's overview page.
 | 
			
		||||
  1. Select **Clone**.
 | 
			
		||||
  1. Under either the **HTTPS** or **SSH** method, select **Clone with Visual Studio Code**.
 | 
			
		||||
  1. Under **Open in your IDE**, select **Visual Studio Code (SSH)** or **Visual Studio Code (HTTPS)**.
 | 
			
		||||
  1. Select a folder to clone the project into.
 | 
			
		||||
 | 
			
		||||
     After Visual Studio Code clones your project, it opens the folder.
 | 
			
		||||
- From Visual Studio Code, with the [extension](vscode.md) installed, use the
 | 
			
		||||
  extension's [`Git: Clone` command](https://marketplace.visualstudio.com/items?itemName=GitLab.gitlab-workflow#clone-gitlab-projects).
 | 
			
		||||
 | 
			
		||||
### Clone and open in IntelliJ IDEA
 | 
			
		||||
 | 
			
		||||
All projects can be cloned into [IntelliJ IDEA](https://www.jetbrains.com/idea/)
 | 
			
		||||
from the GitLab user interface.
 | 
			
		||||
 | 
			
		||||
Prerequisites:
 | 
			
		||||
 | 
			
		||||
- The [Jetbrains Toolbox App](https://www.jetbrains.com/toolbox-app/) must be also be installed.
 | 
			
		||||
 | 
			
		||||
To do this:
 | 
			
		||||
 | 
			
		||||
1. Go to the project's overview page.
 | 
			
		||||
1. Select **Clone**.
 | 
			
		||||
1. Under **Open in your IDE**, select **IntelliJ IDEA (SSH)** or **IntelliJ IDEA (HTTPS)**.
 | 
			
		||||
 | 
			
		||||
## Download the code in a repository
 | 
			
		||||
 | 
			
		||||
> Support for [including Git LFS blobs](../../../topics/git/lfs#lfs-objects-in-project-archives) was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/15079) in GitLab 13.5.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -87,7 +87,7 @@ called `my-project` under your username, the project is created at `https://gitl
 | 
			
		|||
To view your personal projects:
 | 
			
		||||
 | 
			
		||||
1. On the top bar, select **Main menu > Projects > View all projects**.
 | 
			
		||||
1. In the **Your projects** tab, select **Personal**.
 | 
			
		||||
1. In the **Yours** tab, select **Personal**.
 | 
			
		||||
 | 
			
		||||
## Delete a project
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,7 +17,7 @@ module Gitlab
 | 
			
		|||
        end
 | 
			
		||||
 | 
			
		||||
        def favicon
 | 
			
		||||
          'favicon_pending'
 | 
			
		||||
          'favicon_status_pending'
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def group
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,7 +14,7 @@ namespace :tw do
 | 
			
		|||
      end
 | 
			
		||||
 | 
			
		||||
      def directory
 | 
			
		||||
        @directory ||= File.dirname(path)
 | 
			
		||||
        @directory ||= "#{File.dirname(path)}/"
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -123,15 +123,19 @@ namespace :tw do
 | 
			
		|||
      mappings << DocumentOwnerMapping.new(relative_file, writer) if document.has_a_valid_group?
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    transformed_mappings = mappings.map do |mapping|
 | 
			
		||||
      if mapping.writer_owns_directory?(mappings)
 | 
			
		||||
        DocumentOwnerMapping.new(mapping.directory, mapping.writer)
 | 
			
		||||
      else
 | 
			
		||||
        DocumentOwnerMapping.new(mapping.path, mapping.writer)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    deduplicated_mappings = Set.new
 | 
			
		||||
 | 
			
		||||
    mappings.each do |mapping|
 | 
			
		||||
      if mapping.writer_owns_directory?(mappings)
 | 
			
		||||
        deduplicated_mappings.add("#{mapping.directory}/ #{mapping.writer}")
 | 
			
		||||
      else
 | 
			
		||||
        deduplicated_mappings.add("#{mapping.path} #{mapping.writer}")
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
    transformed_mappings
 | 
			
		||||
      .reject { |mapping| transformed_mappings.any? { |m| m.path == mapping.directory && m.writer == mapping.writer } }
 | 
			
		||||
      .each { |mapping| deduplicated_mappings.add("#{mapping.path} #{mapping.writer}") }
 | 
			
		||||
 | 
			
		||||
    new_docs_owners = deduplicated_mappings.sort.join("\n")
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8735,6 +8735,9 @@ msgstr ""
 | 
			
		|||
msgid "Choose your framework"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
msgid "Ci config already present"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
msgid "CiCdAnalytics|Date range: %{range}"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -11600,7 +11603,7 @@ msgstr ""
 | 
			
		|||
msgid "Couldn't find event type filters where audit event type(s): %{missing_filters}"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
msgid "Country"
 | 
			
		||||
msgid "Country / Region"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
msgid "Counts"
 | 
			
		||||
| 
						 | 
				
			
			@ -31826,7 +31829,7 @@ msgstr ""
 | 
			
		|||
msgid "Please select a Jira project"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
msgid "Please select a country"
 | 
			
		||||
msgid "Please select a country / region"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
msgid "Please select a group"
 | 
			
		||||
| 
						 | 
				
			
			@ -33083,6 +33086,9 @@ msgstr ""
 | 
			
		|||
msgid "Project milestone"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
msgid "Project must have default branch"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
msgid "Project name"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -36891,6 +36897,9 @@ msgstr ""
 | 
			
		|||
msgid "Runners|Never expires"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
msgid "Runners|New"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
msgid "Runners|New group runners can be registered"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -37004,6 +37013,9 @@ msgstr ""
 | 
			
		|||
msgid "Runners|Runner %{name} was deleted"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
msgid "Runners|Runner %{runnerName} failed to delete"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
msgid "Runners|Runner Registration"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -37255,15 +37267,6 @@ msgstr ""
 | 
			
		|||
msgid "Runners|shared"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
msgid "Runner|New"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
msgid "Runner|Owner"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
msgid "Runner|Runner %{runnerName} failed to delete"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
msgid "Running"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,4 +24,26 @@ RSpec.describe 'User views issue designs', :js, feature_category: :design_manage
 | 
			
		|||
 | 
			
		||||
    expect(page).to have_selector('.js-design-image')
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  context 'when svg file is loaded in design detail' do
 | 
			
		||||
    let_it_be(:file) { Rails.root.join('spec/fixtures/svg_without_attr.svg') }
 | 
			
		||||
    let_it_be(:design) { create(:design, :with_file, filename: 'svg_without_attr.svg', file: file, issue: issue) }
 | 
			
		||||
 | 
			
		||||
    before do
 | 
			
		||||
      visit designs_project_issue_path(
 | 
			
		||||
        project,
 | 
			
		||||
        issue,
 | 
			
		||||
        { vueroute: design.filename }
 | 
			
		||||
      )
 | 
			
		||||
      wait_for_requests
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'check if svg is loading' do
 | 
			
		||||
      expect(page).to have_selector(
 | 
			
		||||
        ".js-design-image > img[alt='svg_without_attr.svg']",
 | 
			
		||||
        count: 1,
 | 
			
		||||
        visible: :hidden
 | 
			
		||||
      )
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,17 @@
 | 
			
		|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
			
		||||
<svg viewBox="0 0 50 48" fill="none" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
  <!-- Generator: Sketch 3.3.2 (12043) - http://www.bohemiancoding.com/sketch -->
 | 
			
		||||
  <title>Slice 1</title>
 | 
			
		||||
  <desc>Created with Sketch.</desc>
 | 
			
		||||
  <script>alert('FAIL')</script>
 | 
			
		||||
  <defs></defs>
 | 
			
		||||
  <path d="m49.014 19-.067-.18-6.784-17.696a1.792 1.792 0 0 0-3.389.182l-4.579 14.02H15.651l-4.58-14.02a1.795 1.795 0 0 0-3.388-.182l-6.78 17.7-.071.175A12.595 12.595 0 0 0 5.01 33.556l.026.02.057.044 10.32 7.734 5.12 3.87 3.11 2.351a2.102 2.102 0 0 0 2.535 0l3.11-2.352 5.12-3.869 10.394-7.779.029-.022a12.595 12.595 0 0 0 4.182-14.554Z"
 | 
			
		||||
        fill="#E24329"/>
 | 
			
		||||
  <path d="m49.014 19-.067-.18a22.88 22.88 0 0 0-9.12 4.103L24.931 34.187l9.485 7.167 10.393-7.779.03-.022a12.595 12.595 0 0 0 4.175-14.554Z"
 | 
			
		||||
        fill="#FC6D26"/>
 | 
			
		||||
  <path d="m15.414 41.354 5.12 3.87 3.11 2.351a2.102 2.102 0 0 0 2.535 0l3.11-2.352 5.12-3.869-9.484-7.167-9.51 7.167Z"
 | 
			
		||||
        fill="#FCA326"/>
 | 
			
		||||
  <path d="M10.019 22.923a22.86 22.86 0 0 0-9.117-4.1L.832 19A12.595 12.595 0 0 0 5.01 33.556l.026.02.057.044 10.32 7.734 9.491-7.167L10.02 22.923Z"
 | 
			
		||||
        fill="#FC6D26"/>
 | 
			
		||||
</svg>
 | 
			
		||||
 | 
			
		||||
| 
		 After Width: | Height: | Size: 1.2 KiB  | 
| 
						 | 
				
			
			@ -1,5 +1,19 @@
 | 
			
		|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
 | 
			
		||||
 | 
			
		||||
exports[`Design management large image component renders SVG with proper height and width 1`] = `
 | 
			
		||||
<div
 | 
			
		||||
  class="gl-mx-auto gl-my-auto js-design-image"
 | 
			
		||||
>
 | 
			
		||||
  <!---->
 | 
			
		||||
   
 | 
			
		||||
  <img
 | 
			
		||||
    alt="test"
 | 
			
		||||
    class="mh-100 img-fluid"
 | 
			
		||||
    src="mockImage.svg"
 | 
			
		||||
  />
 | 
			
		||||
</div>
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
exports[`Design management large image component renders image 1`] = `
 | 
			
		||||
<div
 | 
			
		||||
  class="gl-mx-auto gl-my-auto js-design-image"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -42,6 +42,16 @@ describe('Design management large image component', () => {
 | 
			
		|||
    expect(wrapper.element).toMatchSnapshot();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('renders SVG with proper height and width', () => {
 | 
			
		||||
    createComponent({
 | 
			
		||||
      isLoading: false,
 | 
			
		||||
      image: 'mockImage.svg',
 | 
			
		||||
      name: 'test',
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    expect(wrapper.element).toMatchSnapshot();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('sets correct classes and styles if imageStyle is set', async () => {
 | 
			
		||||
    createComponent(
 | 
			
		||||
      {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,12 +21,14 @@ exports[`Design management list item component with notes renders item with mult
 | 
			
		|||
  >
 | 
			
		||||
    <!---->
 | 
			
		||||
     
 | 
			
		||||
    <gl-intersection-observer-stub>
 | 
			
		||||
    <gl-intersection-observer-stub
 | 
			
		||||
      class="gl-flex-grow-1"
 | 
			
		||||
    >
 | 
			
		||||
      <!---->
 | 
			
		||||
       
 | 
			
		||||
      <img
 | 
			
		||||
        alt="test"
 | 
			
		||||
        class="gl-display-block gl-mx-auto gl-max-w-full gl-max-h-full design-img"
 | 
			
		||||
        class="gl-display-block gl-mx-auto gl-max-w-full gl-max-h-full gl-w-auto design-img"
 | 
			
		||||
        data-qa-filename="test"
 | 
			
		||||
        data-qa-selector="design_image"
 | 
			
		||||
        data-testid="design-img-1"
 | 
			
		||||
| 
						 | 
				
			
			@ -98,12 +100,14 @@ exports[`Design management list item component with notes renders item with sing
 | 
			
		|||
  >
 | 
			
		||||
    <!---->
 | 
			
		||||
     
 | 
			
		||||
    <gl-intersection-observer-stub>
 | 
			
		||||
    <gl-intersection-observer-stub
 | 
			
		||||
      class="gl-flex-grow-1"
 | 
			
		||||
    >
 | 
			
		||||
      <!---->
 | 
			
		||||
       
 | 
			
		||||
      <img
 | 
			
		||||
        alt="test"
 | 
			
		||||
        class="gl-display-block gl-mx-auto gl-max-w-full gl-max-h-full design-img"
 | 
			
		||||
        class="gl-display-block gl-mx-auto gl-max-w-full gl-max-h-full gl-w-auto design-img"
 | 
			
		||||
        data-qa-filename="test"
 | 
			
		||||
        data-qa-selector="design_image"
 | 
			
		||||
        data-testid="design-img-1"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -767,17 +767,10 @@ describe('WorkItemDetail component', () => {
 | 
			
		|||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('does not render created/updated by default', async () => {
 | 
			
		||||
  it('renders created/updated', async () => {
 | 
			
		||||
    createComponent();
 | 
			
		||||
    await waitForPromises();
 | 
			
		||||
 | 
			
		||||
    expect(findCreatedUpdated().exists()).toBe(false);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('renders created/updated when the work_items_mvc flag is on', async () => {
 | 
			
		||||
    createComponent({ workItemsMvcEnabled: true });
 | 
			
		||||
    await waitForPromises();
 | 
			
		||||
 | 
			
		||||
    expect(findCreatedUpdated().exists()).toBe(true);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,15 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
RSpec.describe 'countries', feature_category: :onboarding do
 | 
			
		||||
  it 'configures locals to EN' do
 | 
			
		||||
    expect(ISO3166.configuration.locales).to eq([:en])
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  it 'initialises Ukraine with custom country name' do
 | 
			
		||||
    expect(ISO3166::Country['UA'].data["name"]).to be('Ukraine (except the Crimea, Donetsk, and Luhansk regions)')
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  it 'initialises Taiwan with custom country name' do
 | 
			
		||||
    expect(ISO3166::Country['TW'].data["name"]).to be('Taiwan')
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -131,7 +131,7 @@ RSpec.describe Gitlab::Ci::Status::Bridge::Factory, feature_category: :continuou
 | 
			
		|||
      expect(status.text).to eq 'waiting'
 | 
			
		||||
      expect(status.group).to eq 'waiting-for-resource'
 | 
			
		||||
      expect(status.icon).to eq 'status_pending'
 | 
			
		||||
      expect(status.favicon).to eq 'favicon_pending'
 | 
			
		||||
      expect(status.favicon).to eq 'favicon_status_pending'
 | 
			
		||||
      expect(status.illustration).to include(:image, :size, :title)
 | 
			
		||||
      expect(status).not_to have_details
 | 
			
		||||
    end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ RSpec.describe Gitlab::Ci::Status::WaitingForResource do
 | 
			
		|||
  end
 | 
			
		||||
 | 
			
		||||
  describe '#favicon' do
 | 
			
		||||
    it { expect(subject.favicon).to eq 'favicon_pending' }
 | 
			
		||||
    it { expect(subject.favicon).to eq 'favicon_status_pending' }
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe '#group' do
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,8 @@ RSpec.describe ReleaseHighlight, :clean_gitlab_redis_cache, feature_category: :r
 | 
			
		|||
  let(:fixture_dir_glob) { Dir.glob(File.join(Rails.root, 'spec', 'fixtures', 'whats_new', '*.yml')).grep(/\d*_(\d*_\d*)\.yml$/) }
 | 
			
		||||
 | 
			
		||||
  before do
 | 
			
		||||
    allow(Dir).to receive(:glob).with(Rails.root.join('data', 'whats_new', '*.yml')).and_return(fixture_dir_glob)
 | 
			
		||||
    allow(Dir).to receive(:glob).and_call_original
 | 
			
		||||
    allow(Dir).to receive(:glob).with(described_class.whats_new_path).and_return(fixture_dir_glob)
 | 
			
		||||
    Gitlab::CurrentSettings.update!(whats_new_variant: ApplicationSetting.whats_new_variants[:all_tiers])
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,7 @@
 | 
			
		|||
 | 
			
		||||
require 'spec_helper'
 | 
			
		||||
 | 
			
		||||
RSpec.describe Ci::ArchiveTraceService, '#execute' do
 | 
			
		||||
RSpec.describe Ci::ArchiveTraceService, '#execute', feature_category: :continuous_integration do
 | 
			
		||||
  subject { described_class.new.execute(job, worker_name: Ci::ArchiveTraceWorker.name) }
 | 
			
		||||
 | 
			
		||||
  context 'when job is finished' do
 | 
			
		||||
| 
						 | 
				
			
			@ -192,4 +192,69 @@ RSpec.describe Ci::ArchiveTraceService, '#execute' do
 | 
			
		|||
      expect(job.trace_metadata.archival_attempts).to eq(1)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe '#batch_execute' do
 | 
			
		||||
    subject { described_class.new.batch_execute(worker_name: Ci::ArchiveTraceWorker.name) }
 | 
			
		||||
 | 
			
		||||
    let_it_be_with_reload(:job) { create(:ci_build, :success, :trace_live, finished_at: 1.day.ago) }
 | 
			
		||||
    let_it_be_with_reload(:job2) { create(:ci_build, :success, :trace_live, finished_at: 1.day.ago) }
 | 
			
		||||
 | 
			
		||||
    it 'archives multiple traces' do
 | 
			
		||||
      expect { subject }.not_to raise_error
 | 
			
		||||
 | 
			
		||||
      expect(job.reload.job_artifacts_trace).to be_exist
 | 
			
		||||
      expect(job2.reload.job_artifacts_trace).to be_exist
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'processes traces independently' do
 | 
			
		||||
      allow_next_instance_of(Gitlab::Ci::Trace) do |instance|
 | 
			
		||||
        orig_method = instance.method(:archive!)
 | 
			
		||||
        allow(instance).to receive(:archive!) do
 | 
			
		||||
          raise('Unexpected error') if instance.job.id == job.id
 | 
			
		||||
 | 
			
		||||
          orig_method.call
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      expect { subject }.not_to raise_error
 | 
			
		||||
 | 
			
		||||
      expect(job.reload.job_artifacts_trace).to be_nil
 | 
			
		||||
      expect(job2.reload.job_artifacts_trace).to be_exist
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'when timeout is reached' do
 | 
			
		||||
      before do
 | 
			
		||||
        stub_const("#{described_class}::LOOP_TIMEOUT", 0.seconds)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it 'stops executing traces' do
 | 
			
		||||
        expect { subject }.not_to raise_error
 | 
			
		||||
 | 
			
		||||
        expect(job.reload.job_artifacts_trace).to be_nil
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'when loop limit is reached' do
 | 
			
		||||
      before do
 | 
			
		||||
        stub_const("#{described_class}::LOOP_LIMIT", -1)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it 'skips archiving' do
 | 
			
		||||
        expect(job.trace).not_to receive(:archive!)
 | 
			
		||||
 | 
			
		||||
        subject
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it 'stops executing traces' do
 | 
			
		||||
        expect(Sidekiq.logger).to receive(:warn).with(
 | 
			
		||||
          class: Ci::ArchiveTraceWorker.name,
 | 
			
		||||
          message: "Loop limit reached.",
 | 
			
		||||
          job_id: job.id)
 | 
			
		||||
 | 
			
		||||
        expect { subject }.not_to raise_error
 | 
			
		||||
 | 
			
		||||
        expect(job.reload.job_artifacts_trace).to be_nil
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,7 @@
 | 
			
		|||
 | 
			
		||||
require 'spec_helper'
 | 
			
		||||
 | 
			
		||||
RSpec.describe Ci::ArchiveTracesCronWorker do
 | 
			
		||||
RSpec.describe Ci::ArchiveTracesCronWorker, feature_category: :continuous_integration do
 | 
			
		||||
  subject { described_class.new.perform }
 | 
			
		||||
 | 
			
		||||
  let(:finished_at) { 1.day.ago }
 | 
			
		||||
| 
						 | 
				
			
			@ -34,14 +34,28 @@ RSpec.describe Ci::ArchiveTracesCronWorker do
 | 
			
		|||
 | 
			
		||||
    it_behaves_like 'archives trace'
 | 
			
		||||
 | 
			
		||||
    it 'executes service' do
 | 
			
		||||
    it 'batch_execute service' do
 | 
			
		||||
      expect_next_instance_of(Ci::ArchiveTraceService) do |instance|
 | 
			
		||||
        expect(instance).to receive(:execute).with(build, anything)
 | 
			
		||||
        expect(instance).to receive(:batch_execute).with(worker_name: "Ci::ArchiveTracesCronWorker")
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      subject
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context "with FF deduplicate_archive_traces_cron_worker false" do
 | 
			
		||||
      before do
 | 
			
		||||
        stub_feature_flags(deduplicate_archive_traces_cron_worker: false)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it 'calls execute service' do
 | 
			
		||||
        expect_next_instance_of(Ci::ArchiveTraceService) do |instance|
 | 
			
		||||
          expect(instance).to receive(:execute).with(build, worker_name: "Ci::ArchiveTracesCronWorker")
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        subject
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'when the job finished recently' do
 | 
			
		||||
      let(:finished_at) { 1.hour.ago }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue