Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
81c0f29ad9
commit
b3c9b2468d
29 changed files with 353 additions and 103 deletions
|
@ -12,6 +12,7 @@ import { redirectTo } from '~/lib/utils/url_utility';
|
||||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||||
import WebIdeLink from '~/vue_shared/components/web_ide_link.vue';
|
import WebIdeLink from '~/vue_shared/components/web_ide_link.vue';
|
||||||
import CodeIntelligence from '~/code_navigation/components/app.vue';
|
import CodeIntelligence from '~/code_navigation/components/app.vue';
|
||||||
|
import LineHighlighter from '~/blob/line_highlighter';
|
||||||
import getRefMixin from '../mixins/get_ref';
|
import getRefMixin from '../mixins/get_ref';
|
||||||
import blobInfoQuery from '../queries/blob_info.query.graphql';
|
import blobInfoQuery from '../queries/blob_info.query.graphql';
|
||||||
import userInfoQuery from '../queries/user_info.query.graphql';
|
import userInfoQuery from '../queries/user_info.query.graphql';
|
||||||
|
@ -192,6 +193,7 @@ export default {
|
||||||
|
|
||||||
window.requestIdleCallback(() => {
|
window.requestIdleCallback(() => {
|
||||||
this.isRenderingLegacyTextViewer = false;
|
this.isRenderingLegacyTextViewer = false;
|
||||||
|
new LineHighlighter(); // eslint-disable-line no-new
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.legacyRichViewer = html;
|
this.legacyRichViewer = html;
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlIcon, GlSafeHtmlDirective } from '@gitlab/ui';
|
import { GlIcon, GlSafeHtmlDirective } from '@gitlab/ui';
|
||||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
|
||||||
import LineHighlighter from '~/blob/line_highlighter';
|
|
||||||
import { HIGHLIGHT_CLASS_NAME } from './constants';
|
import { HIGHLIGHT_CLASS_NAME } from './constants';
|
||||||
import ViewerMixin from './mixins';
|
import ViewerMixin from './mixins';
|
||||||
|
|
||||||
|
@ -13,7 +11,7 @@ export default {
|
||||||
directives: {
|
directives: {
|
||||||
SafeHtml: GlSafeHtmlDirective,
|
SafeHtml: GlSafeHtmlDirective,
|
||||||
},
|
},
|
||||||
mixins: [ViewerMixin, glFeatureFlagsMixin()],
|
mixins: [ViewerMixin],
|
||||||
inject: ['blobHash'],
|
inject: ['blobHash'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -21,21 +19,14 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
refactorBlobViewerEnabled() {
|
|
||||||
return this.glFeatures.refactorBlobViewer;
|
|
||||||
},
|
|
||||||
|
|
||||||
lineNumbers() {
|
lineNumbers() {
|
||||||
return this.content.split('\n').length;
|
return this.content.split('\n').length;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.refactorBlobViewerEnabled) {
|
|
||||||
// This line will be removed once we start using highlight.js on the frontend (https://gitlab.com/groups/gitlab-org/-/epics/7146)
|
|
||||||
new LineHighlighter(); // eslint-disable-line no-new
|
|
||||||
} else {
|
|
||||||
const { hash } = window.location;
|
const { hash } = window.location;
|
||||||
if (hash) this.scrollToLine(hash, true);
|
if (hash) {
|
||||||
|
this.scrollToLine(hash, true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
|
@ -27,7 +27,11 @@ class UsersController < ApplicationController
|
||||||
check_rate_limit!(:username_exists, scope: request.ip)
|
check_rate_limit!(:username_exists, scope: request.ip)
|
||||||
end
|
end
|
||||||
|
|
||||||
feature_category :users
|
feature_category :users, [:show, :activity, :groups, :projects, :contributed, :starred,
|
||||||
|
:followers, :following, :calendar, :calendar_activities,
|
||||||
|
:exists, :activity, :follow, :unfollow, :ssh_keys, :gpg_keys]
|
||||||
|
|
||||||
|
feature_category :snippets, [:snippets]
|
||||||
|
|
||||||
def show
|
def show
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
|
|
|
@ -17,6 +17,7 @@ class ContainerRepository < ApplicationRecord
|
||||||
SKIPPABLE_MIGRATION_STATES = (ABORTABLE_MIGRATION_STATES + %w[import_aborted]).freeze
|
SKIPPABLE_MIGRATION_STATES = (ABORTABLE_MIGRATION_STATES + %w[import_aborted]).freeze
|
||||||
|
|
||||||
MIGRATION_PHASE_1_STARTED_AT = Date.new(2021, 11, 4).freeze
|
MIGRATION_PHASE_1_STARTED_AT = Date.new(2021, 11, 4).freeze
|
||||||
|
MIGRATION_PHASE_1_ENDED_AT = Date.new(2022, 01, 23).freeze
|
||||||
|
|
||||||
TooManyImportsError = Class.new(StandardError)
|
TooManyImportsError = Class.new(StandardError)
|
||||||
|
|
||||||
|
@ -58,8 +59,8 @@ class ContainerRepository < ApplicationRecord
|
||||||
scope :import_in_process, -> { where(migration_state: %w[pre_importing pre_import_done importing]) }
|
scope :import_in_process, -> { where(migration_state: %w[pre_importing pre_import_done importing]) }
|
||||||
|
|
||||||
scope :recently_done_migration_step, -> do
|
scope :recently_done_migration_step, -> do
|
||||||
where(migration_state: %w[import_done pre_import_done import_aborted])
|
where(migration_state: %w[import_done pre_import_done import_aborted import_skipped])
|
||||||
.order(Arel.sql('GREATEST(migration_pre_import_done_at, migration_import_done_at, migration_aborted_at) DESC'))
|
.order(Arel.sql('GREATEST(migration_pre_import_done_at, migration_import_done_at, migration_aborted_at, migration_skipped_at) DESC'))
|
||||||
end
|
end
|
||||||
|
|
||||||
scope :ready_for_import, -> do
|
scope :ready_for_import, -> do
|
||||||
|
@ -160,7 +161,7 @@ class ContainerRepository < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
before_transition %i[pre_importing import_aborted] => :pre_import_done do |container_repository|
|
before_transition any => :pre_import_done do |container_repository|
|
||||||
container_repository.migration_pre_import_done_at = Time.zone.now
|
container_repository.migration_pre_import_done_at = Time.zone.now
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -217,6 +218,13 @@ class ContainerRepository < ApplicationRecord
|
||||||
).exists?
|
).exists?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.all_migrated?
|
||||||
|
# check that the set of non migrated repositories is empty
|
||||||
|
where(created_at: ...MIGRATION_PHASE_1_ENDED_AT)
|
||||||
|
.where.not(migration_state: 'import_done')
|
||||||
|
.empty?
|
||||||
|
end
|
||||||
|
|
||||||
def self.with_enabled_policy
|
def self.with_enabled_policy
|
||||||
joins('INNER JOIN container_expiration_policies ON container_repositories.project_id = container_expiration_policies.project_id')
|
joins('INNER JOIN container_expiration_policies ON container_repositories.project_id = container_expiration_policies.project_id')
|
||||||
.where(container_expiration_policies: { enabled: true })
|
.where(container_expiration_policies: { enabled: true })
|
||||||
|
@ -359,7 +367,7 @@ class ContainerRepository < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def last_import_step_done_at
|
def last_import_step_done_at
|
||||||
[migration_pre_import_done_at, migration_import_done_at, migration_aborted_at].compact.max
|
[migration_pre_import_done_at, migration_import_done_at, migration_aborted_at, migration_skipped_at].compact.max
|
||||||
end
|
end
|
||||||
|
|
||||||
def external_import_status
|
def external_import_status
|
||||||
|
@ -456,7 +464,7 @@ class ContainerRepository < ApplicationRecord
|
||||||
next if self.created_at.before?(MIGRATION_PHASE_1_STARTED_AT)
|
next if self.created_at.before?(MIGRATION_PHASE_1_STARTED_AT)
|
||||||
next unless gitlab_api_client.supports_gitlab_api?
|
next unless gitlab_api_client.supports_gitlab_api?
|
||||||
|
|
||||||
gitlab_api_client.repository_details(self.path, with_size: true)['size_bytes']
|
gitlab_api_client.repository_details(self.path, sizing: :self)['size_bytes']
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1063,6 +1063,17 @@ class Project < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def container_repositories_size
|
||||||
|
strong_memoize(:container_repositories_size) do
|
||||||
|
next unless Gitlab.com?
|
||||||
|
next 0 if container_repositories.empty?
|
||||||
|
next unless container_repositories.all_migrated?
|
||||||
|
next unless ContainerRegistry::GitlabApiClient.supports_gitlab_api?
|
||||||
|
|
||||||
|
ContainerRegistry::GitlabApiClient.deduplicated_size(full_path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def has_container_registry_tags?
|
def has_container_registry_tags?
|
||||||
return @images if defined?(@images)
|
return @images if defined?(@images)
|
||||||
|
|
||||||
|
|
|
@ -9,14 +9,12 @@
|
||||||
- link_start = '<a href="%{url}">'.html_safe % { url: help_page_path('development/snowplow/index') }
|
- link_start = '<a href="%{url}">'.html_safe % { url: help_page_path('development/snowplow/index') }
|
||||||
= html_escape(_('Configure %{link} to track events. %{link_start}Learn more.%{link_end}')) % { link: link_to('Snowplow', 'https://snowplowanalytics.com/', target: '_blank', rel: 'noopener noreferrer').html_safe, link_start: link_start, link_end: '</a>'.html_safe }
|
= html_escape(_('Configure %{link} to track events. %{link_start}Learn more.%{link_end}')) % { link: link_to('Snowplow', 'https://snowplowanalytics.com/', target: '_blank', rel: 'noopener noreferrer').html_safe, link_start: link_start, link_end: '</a>'.html_safe }
|
||||||
.settings-content
|
.settings-content
|
||||||
= form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-snowplow-settings'), html: { class: 'fieldset-form', id: 'snowplow-settings' } do |f|
|
= gitlab_ui_form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-snowplow-settings'), html: { class: 'fieldset-form', id: 'snowplow-settings' } do |f|
|
||||||
= form_errors(@application_setting) if expanded
|
= form_errors(@application_setting) if expanded
|
||||||
|
|
||||||
%fieldset
|
%fieldset
|
||||||
.form-group
|
.form-group
|
||||||
.form-check
|
= f.gitlab_ui_checkbox_component :snowplow_enabled, _('Enable Snowplow tracking'), checkbox_options: { data: { qa_selector: 'snowplow_enabled_checkbox' } }
|
||||||
= f.check_box :snowplow_enabled, class: 'form-check-input', data: { qa_selector: 'snowplow_enabled_checkbox' }
|
|
||||||
= f.label :snowplow_enabled, _('Enable Snowplow tracking'), class: 'form-check-label'
|
|
||||||
.form-group
|
.form-group
|
||||||
= f.label :snowplow_collector_hostname, _('Collector hostname'), class: 'label-light'
|
= f.label :snowplow_collector_hostname, _('Collector hostname'), class: 'label-light'
|
||||||
= f.text_field :snowplow_collector_hostname, class: 'form-control gl-form-input', placeholder: 'snowplow.example.com'
|
= f.text_field :snowplow_collector_hostname, class: 'form-control gl-form-input', placeholder: 'snowplow.example.com'
|
||||||
|
|
|
@ -16,20 +16,14 @@
|
||||||
|
|
||||||
|
|
||||||
.settings-content
|
.settings-content
|
||||||
= form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-sourcegraph-settings'), html: { class: 'fieldset-form', id: 'sourcegraph-settings' } do |f|
|
= gitlab_ui_form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-sourcegraph-settings'), html: { class: 'fieldset-form', id: 'sourcegraph-settings' } do |f|
|
||||||
= form_errors(@application_setting)
|
= form_errors(@application_setting)
|
||||||
|
|
||||||
%fieldset
|
%fieldset
|
||||||
.form-group
|
.form-group
|
||||||
.form-check
|
= f.gitlab_ui_checkbox_component :sourcegraph_enabled, s_('SourcegraphAdmin|Enable Sourcegraph')
|
||||||
= f.check_box :sourcegraph_enabled, class: 'form-check-input'
|
|
||||||
= f.label :sourcegraph_enabled, s_('SourcegraphAdmin|Enable Sourcegraph'), class: 'form-check-label'
|
|
||||||
.form-group
|
.form-group
|
||||||
.form-check
|
= f.gitlab_ui_checkbox_component :sourcegraph_public_only, s_('SourcegraphAdmin|Block on private and internal projects'), help_text: s_('SourcegraphAdmin|Only public projects have code intelligence enabled and communicate with Sourcegraph.')
|
||||||
= f.check_box :sourcegraph_public_only, class: 'form-check-input'
|
|
||||||
= f.label :sourcegraph_public_only, s_('SourcegraphAdmin|Block on private and internal projects'), class: 'form-check-label'
|
|
||||||
.form-text.text-muted
|
|
||||||
= s_('SourcegraphAdmin|Only public projects have code intelligence enabled and communicate with Sourcegraph.')
|
|
||||||
.form-group
|
.form-group
|
||||||
= f.label :sourcegraph_url, s_('SourcegraphAdmin|Sourcegraph URL'), class: 'label-bold'
|
= f.label :sourcegraph_url, s_('SourcegraphAdmin|Sourcegraph URL'), class: 'label-bold'
|
||||||
= f.text_field :sourcegraph_url, class: 'form-control gl-form-input', placeholder: s_('SourcegraphAdmin|https://sourcegraph.example.com')
|
= f.text_field :sourcegraph_url, class: 'form-control gl-form-input', placeholder: s_('SourcegraphAdmin|https://sourcegraph.example.com')
|
||||||
|
|
|
@ -23,7 +23,8 @@
|
||||||
- else
|
- else
|
||||||
= link_to 'discussion', target_url
|
= link_to 'discussion', target_url
|
||||||
|
|
||||||
- if include_stylesheet_link && discussion&.diff_discussion? && discussion.on_text?
|
- if discussion&.diff_discussion? && discussion.on_text?
|
||||||
|
- if include_stylesheet_link
|
||||||
= content_for :head do
|
= content_for :head do
|
||||||
= stylesheet_link_tag 'mailers/highlighted_diff_email'
|
= stylesheet_link_tag 'mailers/highlighted_diff_email'
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,7 @@
|
||||||
|
|
||||||
.row
|
.row
|
||||||
.col-lg-12
|
.col-lg-12
|
||||||
.gl-alert.gl-alert-info{ role: 'alert' }
|
= render Pajamas::AlertComponent.new(dismissible: false) do
|
||||||
= sprite_icon('information-o', css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
|
|
||||||
.gl-alert-body
|
.gl-alert-body
|
||||||
= s_('AlertSettings|You can now set up alert endpoints for manually configured Prometheus instances in the Alerts section on the Operations settings page. Alert endpoint fields on this page have been deprecated.')
|
= s_('AlertSettings|You can now set up alert endpoints for manually configured Prometheus instances in the Alerts section on the Operations settings page. Alert endpoint fields on this page have been deprecated.')
|
||||||
.gl-alert-actions
|
.gl-alert-actions
|
||||||
|
|
|
@ -14,8 +14,10 @@ module Packages
|
||||||
|
|
||||||
artifact.destroy!
|
artifact.destroy!
|
||||||
rescue StandardError
|
rescue StandardError
|
||||||
|
unless artifact&.destroyed?
|
||||||
artifact&.update_column(:status, :error)
|
artifact&.update_column(:status, :error)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
after_destroy
|
after_destroy
|
||||||
end
|
end
|
||||||
|
|
|
@ -82,7 +82,7 @@ module ContainerRegistry
|
||||||
def waiting_time_passed?
|
def waiting_time_passed?
|
||||||
delay = migration.enqueue_waiting_time
|
delay = migration.enqueue_waiting_time
|
||||||
return true if delay == 0
|
return true if delay == 0
|
||||||
return true unless last_step_completed_repository
|
return true unless last_step_completed_repository&.last_import_step_done_at
|
||||||
|
|
||||||
last_step_completed_repository.last_import_step_done_at < Time.zone.now - delay
|
last_step_completed_repository.last_import_step_done_at < Time.zone.now - delay
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddNonMigratedIndexToContainerRepositories < Gitlab::Database::Migration[1.0]
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
# follow up issue: https://gitlab.com/gitlab-org/gitlab/-/issues/358407
|
||||||
|
INDEX_NAME = 'tmp_idx_container_repos_on_non_migrated'
|
||||||
|
MIGRATION_PHASE_1_ENDED_AT = '2022-01-23'
|
||||||
|
|
||||||
|
def up
|
||||||
|
add_concurrent_index :container_repositories,
|
||||||
|
[:project_id, :id],
|
||||||
|
name: INDEX_NAME,
|
||||||
|
where: "migration_state != 'import_done' AND created_at < '#{MIGRATION_PHASE_1_ENDED_AT}'"
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
remove_concurrent_index_by_name :container_repositories, INDEX_NAME
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,24 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class UpdateIndexOnGreatedDoneAtOnContainerRepositories < Gitlab::Database::Migration[1.0]
|
||||||
|
OLD_INDEX_NAME = 'index_container_repositories_on_greatest_done_at'
|
||||||
|
NEW_INDEX_NAME = 'index_container_repositories_on_greatest_completed_at'
|
||||||
|
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
def up
|
||||||
|
add_concurrent_index :container_repositories,
|
||||||
|
'GREATEST(migration_pre_import_done_at, migration_import_done_at, migration_aborted_at, migration_skipped_at)',
|
||||||
|
where: "migration_state IN ('import_done', 'pre_import_done', 'import_aborted', 'import_skipped')",
|
||||||
|
name: NEW_INDEX_NAME
|
||||||
|
remove_concurrent_index_by_name :container_repositories, OLD_INDEX_NAME
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
add_concurrent_index :container_repositories,
|
||||||
|
'GREATEST(migration_pre_import_done_at, migration_import_done_at, migration_aborted_at)',
|
||||||
|
where: "migration_state IN ('import_done', 'pre_import_done', 'import_aborted')",
|
||||||
|
name: OLD_INDEX_NAME
|
||||||
|
remove_concurrent_index_by_name :container_repositories, NEW_INDEX_NAME
|
||||||
|
end
|
||||||
|
end
|
1
db/schema_migrations/20220405125459
Normal file
1
db/schema_migrations/20220405125459
Normal file
|
@ -0,0 +1 @@
|
||||||
|
c4dcb2b2e1262d63c56e171796f1cb6fb76d4b7dc090cf585f17a451c2fa784f
|
1
db/schema_migrations/20220408135815
Normal file
1
db/schema_migrations/20220408135815
Normal file
|
@ -0,0 +1 @@
|
||||||
|
01d8ab924e8c76b54d316ba94089eabea28999e4ce747e6c51803e1ea97b37df
|
|
@ -27308,7 +27308,7 @@ CREATE INDEX index_composer_cache_files_where_namespace_id_is_null ON packages_c
|
||||||
|
|
||||||
CREATE INDEX index_container_expiration_policies_on_next_run_at_and_enabled ON container_expiration_policies USING btree (next_run_at, enabled);
|
CREATE INDEX index_container_expiration_policies_on_next_run_at_and_enabled ON container_expiration_policies USING btree (next_run_at, enabled);
|
||||||
|
|
||||||
CREATE INDEX index_container_repositories_on_greatest_done_at ON container_repositories USING btree (GREATEST(migration_pre_import_done_at, migration_import_done_at, migration_aborted_at)) WHERE (migration_state = ANY (ARRAY['import_done'::text, 'pre_import_done'::text, 'import_aborted'::text]));
|
CREATE INDEX index_container_repositories_on_greatest_completed_at ON container_repositories USING btree (GREATEST(migration_pre_import_done_at, migration_import_done_at, migration_aborted_at, migration_skipped_at)) WHERE (migration_state = ANY (ARRAY['import_done'::text, 'pre_import_done'::text, 'import_aborted'::text, 'import_skipped'::text]));
|
||||||
|
|
||||||
CREATE INDEX index_container_repositories_on_migration_state_import_done_at ON container_repositories USING btree (migration_state, migration_import_done_at);
|
CREATE INDEX index_container_repositories_on_migration_state_import_done_at ON container_repositories USING btree (migration_state, migration_import_done_at);
|
||||||
|
|
||||||
|
@ -29646,6 +29646,8 @@ CREATE INDEX tmp_gitlab_subscriptions_max_seats_used_migration ON gitlab_subscri
|
||||||
|
|
||||||
CREATE INDEX tmp_gitlab_subscriptions_max_seats_used_migration_2 ON gitlab_subscriptions USING btree (id) WHERE ((start_date < '2021-08-02'::date) AND (max_seats_used <> 0) AND (max_seats_used > seats_in_use) AND (max_seats_used > seats));
|
CREATE INDEX tmp_gitlab_subscriptions_max_seats_used_migration_2 ON gitlab_subscriptions USING btree (id) WHERE ((start_date < '2021-08-02'::date) AND (max_seats_used <> 0) AND (max_seats_used > seats_in_use) AND (max_seats_used > seats));
|
||||||
|
|
||||||
|
CREATE INDEX tmp_idx_container_repos_on_non_migrated ON container_repositories USING btree (project_id, id) WHERE ((migration_state <> 'import_done'::text) AND (created_at < '2022-01-23 00:00:00'::timestamp without time zone));
|
||||||
|
|
||||||
CREATE INDEX tmp_index_ci_job_artifacts_on_id_where_trace_and_expire_at ON ci_job_artifacts USING btree (id) WHERE ((file_type = 3) AND (expire_at = ANY (ARRAY['2021-04-22 00:00:00+00'::timestamp with time zone, '2021-05-22 00:00:00+00'::timestamp with time zone, '2021-06-22 00:00:00+00'::timestamp with time zone, '2022-01-22 00:00:00+00'::timestamp with time zone, '2022-02-22 00:00:00+00'::timestamp with time zone, '2022-03-22 00:00:00+00'::timestamp with time zone, '2022-04-22 00:00:00+00'::timestamp with time zone])));
|
CREATE INDEX tmp_index_ci_job_artifacts_on_id_where_trace_and_expire_at ON ci_job_artifacts USING btree (id) WHERE ((file_type = 3) AND (expire_at = ANY (ARRAY['2021-04-22 00:00:00+00'::timestamp with time zone, '2021-05-22 00:00:00+00'::timestamp with time zone, '2021-06-22 00:00:00+00'::timestamp with time zone, '2022-01-22 00:00:00+00'::timestamp with time zone, '2022-02-22 00:00:00+00'::timestamp with time zone, '2022-03-22 00:00:00+00'::timestamp with time zone, '2022-04-22 00:00:00+00'::timestamp with time zone])));
|
||||||
|
|
||||||
CREATE INDEX tmp_index_container_repositories_on_id_migration_state ON container_repositories USING btree (id, migration_state);
|
CREATE INDEX tmp_index_container_repositories_on_id_migration_state ON container_repositories USING btree (id, migration_state);
|
||||||
|
|
|
@ -76,7 +76,14 @@ To avoid this scenario:
|
||||||
1. Select the **Skip outdated deployment jobs** checkbox.
|
1. Select the **Skip outdated deployment jobs** checkbox.
|
||||||
1. Select **Save changes**.
|
1. Select **Save changes**.
|
||||||
|
|
||||||
Older deployment jobs are skipped when a new deployment starts.
|
When a new deployment starts, older deployment jobs are skipped. Skipped jobs are labeled:
|
||||||
|
|
||||||
|
- `forward deployment failure` in the pipeline view.
|
||||||
|
- `The deployment job is older than the previously succeeded deployment job, and therefore cannot be run`
|
||||||
|
when viewing the completed job.
|
||||||
|
|
||||||
|
Job age is determined by the job start time, not the commit time, so a newer commit
|
||||||
|
can be skipped in some circumstances.
|
||||||
|
|
||||||
For more information, see [Deployment safety](../environments/deployment_safety.md).
|
For more information, see [Deployment safety](../environments/deployment_safety.md).
|
||||||
|
|
||||||
|
|
|
@ -37,14 +37,24 @@ module ContainerRegistry
|
||||||
class << self
|
class << self
|
||||||
private
|
private
|
||||||
|
|
||||||
def with_dummy_client(return_value_if_disabled: nil)
|
def with_dummy_client(return_value_if_disabled: nil, token_config: { type: :full_access_token, path: nil })
|
||||||
registry_config = Gitlab.config.registry
|
registry_config = Gitlab.config.registry
|
||||||
unless registry_config.enabled && registry_config.api_url.present?
|
unless registry_config.enabled && registry_config.api_url.present?
|
||||||
return return_value_if_disabled
|
return return_value_if_disabled
|
||||||
end
|
end
|
||||||
|
|
||||||
token = Auth::ContainerRegistryAuthenticationService.access_token([], [])
|
yield new(registry_config.api_url, token: token_from(token_config))
|
||||||
yield new(registry_config.api_url, token: token)
|
end
|
||||||
|
|
||||||
|
def token_from(config)
|
||||||
|
case config[:type]
|
||||||
|
when :full_access_token
|
||||||
|
Auth::ContainerRegistryAuthenticationService.access_token([], [])
|
||||||
|
when :nested_repositories_token
|
||||||
|
return unless config[:path]
|
||||||
|
|
||||||
|
Auth::ContainerRegistryAuthenticationService.pull_nested_repositories_access_token(config[:path])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,12 @@ module ContainerRegistry
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.deduplicated_size(path)
|
||||||
|
with_dummy_client(token_config: { type: :nested_repositories_token, path: path }) do |client|
|
||||||
|
client.repository_details(path, sizing: :self_with_descendants)['size_bytes']
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs-gitlab/api.md#compliance-check
|
# https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs-gitlab/api.md#compliance-check
|
||||||
def supports_gitlab_api?
|
def supports_gitlab_api?
|
||||||
strong_memoize(:supports_gitlab_api) do
|
strong_memoize(:supports_gitlab_api) do
|
||||||
|
@ -78,10 +84,10 @@ module ContainerRegistry
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def repository_details(path, with_size: false)
|
def repository_details(path, sizing: nil)
|
||||||
with_token_faraday do |faraday_client|
|
with_token_faraday do |faraday_client|
|
||||||
req = faraday_client.get("/gitlab/v1/repositories/#{path}/") do |req|
|
req = faraday_client.get("/gitlab/v1/repositories/#{path}/") do |req|
|
||||||
req.params['size'] = 'self' if with_size
|
req.params['size'] = sizing if sizing
|
||||||
end
|
end
|
||||||
|
|
||||||
break {} unless req.success?
|
break {} unless req.success?
|
||||||
|
|
|
@ -39298,6 +39298,9 @@ msgstr ""
|
||||||
msgid "To access this domain create a new DNS record"
|
msgid "To access this domain create a new DNS record"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "To activate your trial, we need additional details from you."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "To add a custom suffix, set up a Service Desk email address. %{linkStart}Learn more.%{linkEnd}"
|
msgid "To add a custom suffix, set up a Service Desk email address. %{linkStart}Learn more.%{linkEnd}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -39307,6 +39310,9 @@ msgstr ""
|
||||||
msgid "To ask someone to look at a merge request, select %{strongStart}Request attention%{strongEnd}. Select again to remove the request."
|
msgid "To ask someone to look at a merge request, select %{strongStart}Request attention%{strongEnd}. Select again to remove the request."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "To complete registration, we need additional details from you."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "To confirm, type %{phrase_code}"
|
msgid "To confirm, type %{phrase_code}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ import { redirectTo } from '~/lib/utils/url_utility';
|
||||||
import { isLoggedIn } from '~/lib/utils/common_utils';
|
import { isLoggedIn } from '~/lib/utils/common_utils';
|
||||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||||
import httpStatusCodes from '~/lib/utils/http_status';
|
import httpStatusCodes from '~/lib/utils/http_status';
|
||||||
|
import LineHighlighter from '~/blob/line_highlighter';
|
||||||
import {
|
import {
|
||||||
simpleViewerMock,
|
simpleViewerMock,
|
||||||
richViewerMock,
|
richViewerMock,
|
||||||
|
@ -39,6 +40,7 @@ import {
|
||||||
jest.mock('~/repository/components/blob_viewers');
|
jest.mock('~/repository/components/blob_viewers');
|
||||||
jest.mock('~/lib/utils/url_utility');
|
jest.mock('~/lib/utils/url_utility');
|
||||||
jest.mock('~/lib/utils/common_utils');
|
jest.mock('~/lib/utils/common_utils');
|
||||||
|
jest.mock('~/blob/line_highlighter');
|
||||||
|
|
||||||
let wrapper;
|
let wrapper;
|
||||||
let mockResolver;
|
let mockResolver;
|
||||||
|
@ -173,20 +175,30 @@ describe('Blob content viewer component', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('legacy viewers', () => {
|
describe('legacy viewers', () => {
|
||||||
|
const legacyViewerUrl = 'some_file.js?format=json&viewer=simple';
|
||||||
|
const fileType = 'text';
|
||||||
|
const highlightJs = false;
|
||||||
|
|
||||||
it('loads a legacy viewer when a the fileType is text and the highlightJs feature is turned off', async () => {
|
it('loads a legacy viewer when a the fileType is text and the highlightJs feature is turned off', async () => {
|
||||||
await createComponent({
|
await createComponent({
|
||||||
blob: { ...simpleViewerMock, fileType: 'text', highlightJs: false },
|
blob: { ...simpleViewerMock, fileType, highlightJs },
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mockAxios.history.get).toHaveLength(1);
|
expect(mockAxios.history.get).toHaveLength(1);
|
||||||
expect(mockAxios.history.get[0].url).toEqual('some_file.js?format=json&viewer=simple');
|
expect(mockAxios.history.get[0].url).toBe(legacyViewerUrl);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('loads a legacy viewer when a viewer component is not available', async () => {
|
it('loads a legacy viewer when a viewer component is not available', async () => {
|
||||||
await createComponent({ blob: { ...simpleViewerMock, fileType: 'unknown' } });
|
await createComponent({ blob: { ...simpleViewerMock, fileType: 'unknown' } });
|
||||||
|
|
||||||
expect(mockAxios.history.get).toHaveLength(1);
|
expect(mockAxios.history.get).toHaveLength(1);
|
||||||
expect(mockAxios.history.get[0].url).toEqual('some_file.js?format=json&viewer=simple');
|
expect(mockAxios.history.get[0].url).toBe(legacyViewerUrl);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loads the LineHighlighter', async () => {
|
||||||
|
mockAxios.onGet(legacyViewerUrl).replyOnce(httpStatusCodes.OK, 'test');
|
||||||
|
await createComponent({ blob: { ...simpleViewerMock, fileType, highlightJs } });
|
||||||
|
expect(LineHighlighter).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,9 +2,6 @@ import { shallowMount } from '@vue/test-utils';
|
||||||
import { nextTick } from 'vue';
|
import { nextTick } from 'vue';
|
||||||
import { HIGHLIGHT_CLASS_NAME } from '~/vue_shared/components/blob_viewers/constants';
|
import { HIGHLIGHT_CLASS_NAME } from '~/vue_shared/components/blob_viewers/constants';
|
||||||
import SimpleViewer from '~/vue_shared/components/blob_viewers/simple_viewer.vue';
|
import SimpleViewer from '~/vue_shared/components/blob_viewers/simple_viewer.vue';
|
||||||
import LineHighlighter from '~/blob/line_highlighter';
|
|
||||||
|
|
||||||
jest.mock('~/blob/line_highlighter');
|
|
||||||
|
|
||||||
describe('Blob Simple Viewer component', () => {
|
describe('Blob Simple Viewer component', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
@ -30,20 +27,6 @@ describe('Blob Simple Viewer component', () => {
|
||||||
wrapper.destroy();
|
wrapper.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('refactorBlobViewer feature flag', () => {
|
|
||||||
it('loads the LineHighlighter if refactorBlobViewer is enabled', () => {
|
|
||||||
createComponent('', false, { refactorBlobViewer: true });
|
|
||||||
|
|
||||||
expect(LineHighlighter).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not load the LineHighlighter if refactorBlobViewer is disabled', () => {
|
|
||||||
createComponent('', false, { refactorBlobViewer: false });
|
|
||||||
|
|
||||||
expect(LineHighlighter).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not fail if content is empty', () => {
|
it('does not fail if content is empty', () => {
|
||||||
const spy = jest.spyOn(window.console, 'error');
|
const spy = jest.spyOn(window.console, 'error');
|
||||||
createComponent('');
|
createComponent('');
|
||||||
|
|
|
@ -174,31 +174,26 @@ RSpec.describe ContainerRegistry::GitlabApiClient do
|
||||||
describe '#repository_details' do
|
describe '#repository_details' do
|
||||||
let(:path) { 'namespace/path/to/repository' }
|
let(:path) { 'namespace/path/to/repository' }
|
||||||
let(:response) { { foo: :bar, this: :is_a_test } }
|
let(:response) { { foo: :bar, this: :is_a_test } }
|
||||||
let(:with_size) { true }
|
|
||||||
|
|
||||||
subject { client.repository_details(path, with_size: with_size) }
|
subject { client.repository_details(path, sizing: sizing) }
|
||||||
|
|
||||||
|
[:self, :self_with_descendants, nil].each do |size_type|
|
||||||
|
context "with sizing #{size_type}" do
|
||||||
|
let(:sizing) { size_type }
|
||||||
|
|
||||||
context 'with size' do
|
|
||||||
before do
|
before do
|
||||||
stub_repository_details(path, with_size: with_size, respond_with: response)
|
stub_repository_details(path, sizing: sizing, respond_with: response)
|
||||||
end
|
end
|
||||||
|
|
||||||
it { is_expected.to eq(response.stringify_keys.deep_transform_values(&:to_s)) }
|
it { is_expected.to eq(response.stringify_keys.deep_transform_values(&:to_s)) }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'without_size' do
|
|
||||||
let(:with_size) { false }
|
|
||||||
|
|
||||||
before do
|
|
||||||
stub_repository_details(path, with_size: with_size, respond_with: response)
|
|
||||||
end
|
|
||||||
|
|
||||||
it { is_expected.to eq(response.stringify_keys.deep_transform_values(&:to_s)) }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with non successful response' do
|
context 'with non successful response' do
|
||||||
|
let(:sizing) { nil }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
stub_repository_details(path, with_size: with_size, status_code: 404)
|
stub_repository_details(path, sizing: sizing, status_code: 404)
|
||||||
end
|
end
|
||||||
|
|
||||||
it { is_expected.to eq({}) }
|
it { is_expected.to eq({}) }
|
||||||
|
@ -263,6 +258,54 @@ RSpec.describe ContainerRegistry::GitlabApiClient do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '.deduplicated_size' do
|
||||||
|
let(:path) { 'foo/bar' }
|
||||||
|
let(:response) { { 'size_bytes': 555 } }
|
||||||
|
let(:registry_enabled) { true }
|
||||||
|
|
||||||
|
subject { described_class.deduplicated_size(path) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_container_registry_config(enabled: registry_enabled, api_url: registry_api_url, key: 'spec/fixtures/x509_certificate_pk.key')
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with successful response' do
|
||||||
|
before do
|
||||||
|
expect(Auth::ContainerRegistryAuthenticationService).to receive(:pull_nested_repositories_access_token).with(path).and_return(token)
|
||||||
|
stub_repository_details(path, sizing: :self_with_descendants, status_code: 200, respond_with: response)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { is_expected.to eq(555) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with unsuccessful response' do
|
||||||
|
before do
|
||||||
|
expect(Auth::ContainerRegistryAuthenticationService).to receive(:pull_nested_repositories_access_token).with(path).and_return(token)
|
||||||
|
stub_repository_details(path, sizing: :self_with_descendants, status_code: 404, respond_with: response)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { is_expected.to eq(nil) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with the registry disabled' do
|
||||||
|
let(:registry_enabled) { false }
|
||||||
|
|
||||||
|
it { is_expected.to eq(nil) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a nil path' do
|
||||||
|
let(:path) { nil }
|
||||||
|
let(:token) { nil }
|
||||||
|
|
||||||
|
before do
|
||||||
|
expect(Auth::ContainerRegistryAuthenticationService).not_to receive(:pull_nested_repositories_access_token)
|
||||||
|
stub_repository_details(path, sizing: :self_with_descendants, status_code: 401, respond_with: response)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { is_expected.to eq(nil) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def stub_pre_import(path, status_code, pre:)
|
def stub_pre_import(path, status_code, pre:)
|
||||||
import_type = pre ? 'pre' : 'final'
|
import_type = pre ? 'pre' : 'final'
|
||||||
stub_request(:put, "#{registry_api_url}/gitlab/v1/import/#{path}/?import_type=#{import_type}")
|
stub_request(:put, "#{registry_api_url}/gitlab/v1/import/#{path}/?import_type=#{import_type}")
|
||||||
|
@ -303,11 +346,15 @@ RSpec.describe ContainerRegistry::GitlabApiClient do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def stub_repository_details(path, with_size: true, status_code: 200, respond_with: {})
|
def stub_repository_details(path, sizing: nil, status_code: 200, respond_with: {})
|
||||||
url = "#{registry_api_url}/gitlab/v1/repositories/#{path}/"
|
url = "#{registry_api_url}/gitlab/v1/repositories/#{path}/"
|
||||||
url += "?size=self" if with_size
|
url += "?size=#{sizing}" if sizing
|
||||||
|
|
||||||
|
headers = { 'Accept' => described_class::JSON_TYPE }
|
||||||
|
headers['Authorization'] = "bearer #{token}" if token
|
||||||
|
|
||||||
stub_request(:get, url)
|
stub_request(:get, url)
|
||||||
.with(headers: { 'Accept' => described_class::JSON_TYPE, 'Authorization' => "bearer #{token}" })
|
.with(headers: headers)
|
||||||
.to_return(status: status_code, body: respond_with.to_json, headers: { 'Content-Type' => described_class::JSON_TYPE })
|
.to_return(status: status_code, body: respond_with.to_json, headers: { 'Content-Type' => described_class::JSON_TYPE })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -652,7 +652,7 @@ RSpec.describe ContainerRepository, :aggregate_failures do
|
||||||
context 'supports gitlab api on .com with a recent repository' do
|
context 'supports gitlab api on .com with a recent repository' do
|
||||||
before do
|
before do
|
||||||
expect(repository.gitlab_api_client).to receive(:supports_gitlab_api?).and_return(true)
|
expect(repository.gitlab_api_client).to receive(:supports_gitlab_api?).and_return(true)
|
||||||
expect(repository.gitlab_api_client).to receive(:repository_details).with(repository.path, with_size: true).and_return(response)
|
expect(repository.gitlab_api_client).to receive(:repository_details).with(repository.path, sizing: :self).and_return(response)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with a size_bytes field' do
|
context 'with a size_bytes field' do
|
||||||
|
@ -1076,6 +1076,43 @@ RSpec.describe ContainerRepository, :aggregate_failures do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '.all_migrated?' do
|
||||||
|
let_it_be(:project) { create(:project) }
|
||||||
|
|
||||||
|
subject { project.container_repositories.all_migrated? }
|
||||||
|
|
||||||
|
context 'with no repositories' do
|
||||||
|
it { is_expected.to be_truthy }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with only recent repositories' do
|
||||||
|
let_it_be(:container_repository1) { create(:container_repository, project: project) }
|
||||||
|
let_it_be_with_reload(:container_repository2) { create(:container_repository, project: project) }
|
||||||
|
|
||||||
|
it { is_expected.to be_truthy }
|
||||||
|
|
||||||
|
context 'with one old non migrated repository' do
|
||||||
|
before do
|
||||||
|
container_repository2.update!(created_at: described_class::MIGRATION_PHASE_1_ENDED_AT - 3.months)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { is_expected.to be_falsey }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with one old migrated repository' do
|
||||||
|
before do
|
||||||
|
container_repository2.update!(
|
||||||
|
created_at: described_class::MIGRATION_PHASE_1_ENDED_AT - 3.months,
|
||||||
|
migration_state: 'import_done',
|
||||||
|
migration_import_done_at: Time.zone.now
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { is_expected.to be_truthy }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '.with_enabled_policy' do
|
describe '.with_enabled_policy' do
|
||||||
let_it_be(:repository) { create(:container_repository) }
|
let_it_be(:repository) { create(:container_repository) }
|
||||||
let_it_be(:repository2) { create(:container_repository) }
|
let_it_be(:repository2) { create(:container_repository) }
|
||||||
|
@ -1271,11 +1308,12 @@ RSpec.describe ContainerRepository, :aggregate_failures do
|
||||||
let_it_be(:import_done_repository) { create(:container_repository, :import_done, migration_pre_import_done_at: 3.days.ago, migration_import_done_at: 2.days.ago) }
|
let_it_be(:import_done_repository) { create(:container_repository, :import_done, migration_pre_import_done_at: 3.days.ago, migration_import_done_at: 2.days.ago) }
|
||||||
let_it_be(:import_aborted_repository) { create(:container_repository, :import_aborted, migration_pre_import_done_at: 5.days.ago, migration_aborted_at: 1.day.ago) }
|
let_it_be(:import_aborted_repository) { create(:container_repository, :import_aborted, migration_pre_import_done_at: 5.days.ago, migration_aborted_at: 1.day.ago) }
|
||||||
let_it_be(:pre_import_done_repository) { create(:container_repository, :pre_import_done, migration_pre_import_done_at: 1.hour.ago) }
|
let_it_be(:pre_import_done_repository) { create(:container_repository, :pre_import_done, migration_pre_import_done_at: 1.hour.ago) }
|
||||||
|
let_it_be(:import_skipped_repository) { create(:container_repository, :import_skipped, migration_skipped_at: 90.minutes.ago) }
|
||||||
|
|
||||||
subject { described_class.recently_done_migration_step }
|
subject { described_class.recently_done_migration_step }
|
||||||
|
|
||||||
it 'returns completed imports by done_at date' do
|
it 'returns completed imports by done_at date' do
|
||||||
expect(subject.to_a).to eq([pre_import_done_repository, import_aborted_repository, import_done_repository])
|
expect(subject.to_a).to eq([pre_import_done_repository, import_skipped_repository, import_aborted_repository, import_done_repository])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1296,13 +1334,15 @@ RSpec.describe ContainerRepository, :aggregate_failures do
|
||||||
describe '#last_import_step_done_at' do
|
describe '#last_import_step_done_at' do
|
||||||
let_it_be(:aborted_at) { Time.zone.now - 1.hour }
|
let_it_be(:aborted_at) { Time.zone.now - 1.hour }
|
||||||
let_it_be(:pre_import_done_at) { Time.zone.now - 2.hours }
|
let_it_be(:pre_import_done_at) { Time.zone.now - 2.hours }
|
||||||
|
let_it_be(:skipped_at) { Time.zone.now - 3.hours }
|
||||||
|
|
||||||
subject { repository.last_import_step_done_at }
|
subject { repository.last_import_step_done_at }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
repository.update_columns(
|
repository.update_columns(
|
||||||
migration_pre_import_done_at: pre_import_done_at,
|
migration_pre_import_done_at: pre_import_done_at,
|
||||||
migration_aborted_at: aborted_at
|
migration_aborted_at: aborted_at,
|
||||||
|
migration_skipped_at: skipped_at
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -2715,6 +2715,39 @@ RSpec.describe Project, factory_default: :keep do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#container_repositories_size' do
|
||||||
|
let(:project) { build(:project) }
|
||||||
|
|
||||||
|
subject { project.container_repositories_size }
|
||||||
|
|
||||||
|
context 'on gitlab.com' do
|
||||||
|
where(:no_container_repositories, :all_migrated, :gitlab_api_supported, :returned_size, :expected_result) do
|
||||||
|
true | nil | nil | nil | 0
|
||||||
|
false | false | nil | nil | nil
|
||||||
|
false | true | false | nil | nil
|
||||||
|
false | true | true | 555 | 555
|
||||||
|
false | true | true | nil | nil
|
||||||
|
end
|
||||||
|
|
||||||
|
with_them do
|
||||||
|
before do
|
||||||
|
stub_container_registry_config(enabled: true, api_url: 'http://container-registry', key: 'spec/fixtures/x509_certificate_pk.key')
|
||||||
|
allow(Gitlab).to receive(:com?).and_return(true)
|
||||||
|
allow(project.container_repositories).to receive(:empty?).and_return(no_container_repositories)
|
||||||
|
allow(project.container_repositories).to receive(:all_migrated?).and_return(all_migrated)
|
||||||
|
allow(ContainerRegistry::GitlabApiClient).to receive(:supports_gitlab_api?).and_return(gitlab_api_supported)
|
||||||
|
allow(ContainerRegistry::GitlabApiClient).to receive(:deduplicated_size).with(project.full_path).and_return(returned_size)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { is_expected.to eq(expected_result) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'not on gitlab.com' do
|
||||||
|
it { is_expected.to eq(nil) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '#container_registry_enabled=' do
|
describe '#container_registry_enabled=' do
|
||||||
let_it_be_with_reload(:project) { create(:project) }
|
let_it_be_with_reload(:project) { create(:project) }
|
||||||
|
|
||||||
|
|
|
@ -86,6 +86,13 @@ module CycleAnalyticsHelpers
|
||||||
wait_for_stages_to_load(ready_selector)
|
wait_for_stages_to_load(ready_selector)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def select_value_stream(value_stream_name)
|
||||||
|
toggle_value_stream_dropdown
|
||||||
|
|
||||||
|
page.find('[data-testid="dropdown-value-streams"]').all('li button').find { |item| item.text == value_stream_name.to_s }.click
|
||||||
|
wait_for_requests
|
||||||
|
end
|
||||||
|
|
||||||
def toggle_dropdown(field)
|
def toggle_dropdown(field)
|
||||||
page.within("[data-testid*='#{field}']") do
|
page.within("[data-testid*='#{field}']") do
|
||||||
find('.dropdown-toggle').click
|
find('.dropdown-toggle').click
|
||||||
|
|
|
@ -8,8 +8,8 @@ RSpec.shared_context 'container registry client stubs' do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def stub_container_registry_gitlab_api_repository_details(client, path:, size_bytes:)
|
def stub_container_registry_gitlab_api_repository_details(client, path:, size_bytes:, sizing: :self)
|
||||||
allow(client).to receive(:repository_details).with(path, with_size: true).and_return('size_bytes' => size_bytes)
|
allow(client).to receive(:repository_details).with(path, sizing: sizing).and_return('size_bytes' => size_bytes)
|
||||||
end
|
end
|
||||||
|
|
||||||
def stub_container_registry_gitlab_api_network_error(client_method: :supports_gitlab_api?)
|
def stub_container_registry_gitlab_api_network_error(client_method: :supports_gitlab_api?)
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe ContainerRegistry::Migration::EnqueuerWorker, :aggregate_failures, :clean_gitlab_redis_shared_state do
|
RSpec.describe ContainerRegistry::Migration::EnqueuerWorker, :aggregate_failures, :clean_gitlab_redis_shared_state do
|
||||||
|
using RSpec::Parameterized::TableSyntax
|
||||||
include ExclusiveLeaseHelpers
|
include ExclusiveLeaseHelpers
|
||||||
|
|
||||||
let_it_be_with_reload(:container_repository) { create(:container_repository, created_at: 2.days.ago) }
|
let_it_be_with_reload(:container_repository) { create(:container_repository, created_at: 2.days.ago) }
|
||||||
|
@ -131,9 +132,17 @@ RSpec.describe ContainerRegistry::Migration::EnqueuerWorker, :aggregate_failures
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'too soon before previous completed import step' do
|
context 'too soon before previous completed import step' do
|
||||||
|
where(:state, :timestamp) do
|
||||||
|
:import_done | :migration_import_done_at
|
||||||
|
:pre_import_done | :migration_pre_import_done_at
|
||||||
|
:import_aborted | :migration_aborted_at
|
||||||
|
:import_skipped | :migration_skipped_at
|
||||||
|
end
|
||||||
|
|
||||||
|
with_them do
|
||||||
before do
|
before do
|
||||||
create(:container_repository, :import_done, migration_import_done_at: 1.minute.ago)
|
|
||||||
allow(ContainerRegistry::Migration).to receive(:enqueue_waiting_time).and_return(1.hour)
|
allow(ContainerRegistry::Migration).to receive(:enqueue_waiting_time).and_return(1.hour)
|
||||||
|
create(:container_repository, state, timestamp => 1.minute.ago)
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'no action' do
|
it_behaves_like 'no action' do
|
||||||
|
@ -143,6 +152,18 @@ RSpec.describe ContainerRegistry::Migration::EnqueuerWorker, :aggregate_failures
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when last completed repository has nil timestamps' do
|
||||||
|
before do
|
||||||
|
allow(ContainerRegistry::Migration).to receive(:enqueue_waiting_time).and_return(1.hour)
|
||||||
|
create(:container_repository, migration_state: 'import_done')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'continues to try the next import' do
|
||||||
|
expect { subject }.to change { container_repository.reload.migration_state }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when an aborted import is available' do
|
context 'when an aborted import is available' do
|
||||||
let_it_be(:aborted_repository) { create(:container_repository, :import_aborted) }
|
let_it_be(:aborted_repository) { create(:container_repository, :import_aborted) }
|
||||||
|
|
||||||
|
|
|
@ -43,9 +43,10 @@ RSpec.describe Packages::CleanupPackageFileWorker do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with an error during the destroy' do
|
context 'with a package file to destroy' do
|
||||||
let_it_be(:package_file) { create(:package_file, :pending_destruction) }
|
let_it_be(:package_file) { create(:package_file, :pending_destruction) }
|
||||||
|
|
||||||
|
context 'with an error during the destroy' do
|
||||||
before do
|
before do
|
||||||
expect(worker).to receive(:log_metadata).and_raise('Error!')
|
expect(worker).to receive(:log_metadata).and_raise('Error!')
|
||||||
end
|
end
|
||||||
|
@ -56,6 +57,25 @@ RSpec.describe Packages::CleanupPackageFileWorker do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when trying to destroy a destroyed record' do
|
||||||
|
before do
|
||||||
|
allow_next_found_instance_of(Packages::PackageFile) do |package_file|
|
||||||
|
destroy_method = package_file.method(:destroy!)
|
||||||
|
|
||||||
|
allow(package_file).to receive(:destroy!) do
|
||||||
|
destroy_method.call
|
||||||
|
|
||||||
|
raise 'Error!'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'handles the error' do
|
||||||
|
expect { subject }.to change { Packages::PackageFile.count }.by(-1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'removing the last package file' do
|
context 'removing the last package file' do
|
||||||
let_it_be(:package_file) { create(:package_file, :pending_destruction, package: package) }
|
let_it_be(:package_file) { create(:package_file, :pending_destruction, package: package) }
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue