From 1760f824bb6bcac278e95493805dff70a5b2a75b Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Tue, 15 Feb 2022 21:12:52 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- GITALY_SERVER_VERSION | 2 +- .../vue_shared/components/markdown/header.vue | 257 +++++++++--------- .../stylesheets/framework/markdown_area.scss | 21 ++ app/helpers/storage_helper.rb | 38 +++ app/models/container_repository.rb | 2 +- app/models/namespace.rb | 6 + app/models/users/group_callout.rb | 6 +- app/views/admin/runners/edit.html.haml | 2 +- .../runner_read_only_admin_view.yml | 2 +- .../monitoring/prometheus/index.md | 62 +++++ doc/api/container_registry.md | 3 +- doc/api/graphql/reference/index.md | 4 +- doc/user/clusters/agent/install/index.md | 3 +- doc/user/clusters/agent/repository.md | 11 + locale/gitlab.pot | 3 + package.json | 2 +- qa/qa/page/component/design_management.rb | 2 +- .../merge_request/user_posts_notes_spec.rb | 4 +- .../packages_list_app_spec.js.snap | 4 +- .../pages/__snapshots__/list_spec.js.snap | 4 +- .../components/markdown/field_spec.js | 31 +-- .../components/markdown/header_spec.js | 34 ++- spec/helpers/storage_helper_spec.rb | 83 ++++++ spec/models/container_repository_spec.rb | 49 ++++ spec/models/namespace_spec.rb | 9 + yarn.lock | 8 +- 26 files changed, 476 insertions(+), 176 deletions(-) diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index fd29e16738c..abd21e1f044 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -a67a6fdd96ba690d57c919f9a042dceebab2832e +ac32a7f07a26a15fb59ea897ee7150f98910903d diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue index 3ed9de6c133..e2b6579a841 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/header.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue @@ -1,9 +1,9 @@ diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index c6e52c13e83..7731ec751c9 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -67,6 +67,27 @@ } } } + + .gl-tabs-nav { + @include media-breakpoint-down(xs) { + .nav-item { + flex: 1; + border-bottom: 1px solid $border-color; + } + + .gl-tab-nav-item { + padding-top: $gl-padding-4; + padding-bottom: $gl-padding-8; + } + + .md-header-toolbar { + width: 100%; + display: flex; + flex-wrap: wrap; + margin-top: $gl-padding-8; + } + } + } } .md-header-tab { diff --git a/app/helpers/storage_helper.rb b/app/helpers/storage_helper.rb index a60143db739..34ba66db444 100644 --- a/app/helpers/storage_helper.rb +++ b/app/helpers/storage_helper.rb @@ -23,4 +23,42 @@ module StorageHelper _("Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / Pipeline Artifacts: %{counter_pipeline_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets} / Packages: %{counter_packages} / Uploads: %{counter_uploads}") % counters end + + def storage_enforcement_banner_info(namespace) + return if namespace.paid? + return unless namespace.storage_enforcement_date && namespace.storage_enforcement_date >= Date.today + return if user_dismissed_storage_enforcement_banner?(namespace) + + { + text: html_escape_once(s_("UsageQuota|From %{storage_enforcement_date} storage limits will apply to this namespace. " \ + "View and manage your usage in %{strong_start}Group Settings > Usage quotas%{strong_end}.")).html_safe % + { storage_enforcement_date: namespace.storage_enforcement_date, strong_start: "".html_safe, strong_end: "".html_safe }, + variant: 'warning', + callouts_path: group_callouts_path, + callouts_feature_name: storage_enforcement_banner_user_callouts_feature_name(namespace), + learn_more_link: link_to(_('Learn more.'), help_page_path('/'), rel: 'noopener noreferrer', target: '_blank') # TBD: https://gitlab.com/gitlab-org/gitlab/-/issues/350632 + } + end + + private + + def storage_enforcement_banner_user_callouts_feature_name(namespace) + "storage_enforcement_banner_#{storage_enforcement_banner_threshold(namespace)}_enforcement_threshold" + end + + def storage_enforcement_banner_threshold(namespace) + days_to_enforcement_date = (namespace.storage_enforcement_date - Date.today) + + return :first if days_to_enforcement_date > 30 + return :second if days_to_enforcement_date > 15 && days_to_enforcement_date <= 30 + return :third if days_to_enforcement_date > 7 && days_to_enforcement_date <= 15 + return :fourth if days_to_enforcement_date > 0 && days_to_enforcement_date <= 7 + end + + def user_dismissed_storage_enforcement_banner?(namespace) + return false unless current_user + + current_user.dismissed_callout_for_group?(feature_name: storage_enforcement_banner_user_callouts_feature_name(namespace), + group: namespace) + end end diff --git a/app/models/container_repository.rb b/app/models/container_repository.rb index 38bd8143bf7..1f123cb0244 100644 --- a/app/models/container_repository.rb +++ b/app/models/container_repository.rb @@ -79,7 +79,7 @@ class ContainerRepository < ApplicationRecord ) end - state_machine :migration_state, initial: :default do + state_machine :migration_state, initial: :default, use_transactions: false do state :pre_importing do validates :migration_pre_import_started_at, presence: true validates :migration_pre_import_done_at, presence: false diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 8faaf557252..5c55f4d3def 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -513,6 +513,12 @@ class Namespace < ApplicationRecord Feature.enabled?(:create_project_namespace_on_project_create, self, default_enabled: :yaml) end + def storage_enforcement_date + # should return something like Date.new(2022, 02, 03) + # TBD: https://gitlab.com/gitlab-org/gitlab/-/issues/350632 + nil + end + private def expire_child_caches diff --git a/app/models/users/group_callout.rb b/app/models/users/group_callout.rb index faa5130e6ec..0dc449719ab 100644 --- a/app/models/users/group_callout.rb +++ b/app/models/users/group_callout.rb @@ -10,7 +10,11 @@ module Users enum feature_name: { invite_members_banner: 1, - approaching_seat_count_threshold: 2 # EE-only + approaching_seat_count_threshold: 2, # EE-only + storage_enforcement_banner_first_enforcement_threshold: 43, + storage_enforcement_banner_second_enforcement_threshold: 44, + storage_enforcement_banner_third_enforcement_threshold: 45, + storage_enforcement_banner_fourth_enforcement_threshold: 46 } validates :group, presence: true diff --git a/app/views/admin/runners/edit.html.haml b/app/views/admin/runners/edit.html.haml index 0257983016c..b65fead49ab 100644 --- a/app/views/admin/runners/edit.html.haml +++ b/app/views/admin/runners/edit.html.haml @@ -1,7 +1,7 @@ - add_page_specific_style 'page_bundles/ci_status' - runner_name = "##{@runner.id} (#{@runner.short_sha})" -- if Feature.enabled?(:runner_read_only_admin_view) +- if Feature.enabled?(:runner_read_only_admin_view, default_enabled: :yaml) - breadcrumb_title _('Edit') - page_title _('Edit'), runner_name - add_to_breadcrumbs _('Runners'), admin_runners_path diff --git a/config/feature_flags/development/runner_read_only_admin_view.yml b/config/feature_flags/development/runner_read_only_admin_view.yml index 8053c31df6a..7a412e70dd9 100644 --- a/config/feature_flags/development/runner_read_only_admin_view.yml +++ b/config/feature_flags/development/runner_read_only_admin_view.yml @@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/350164 milestone: '14.7' type: development group: group::runner -default_enabled: false +default_enabled: true diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md index acdcdb41dca..26e4ba0399d 100644 --- a/doc/administration/monitoring/prometheus/index.md +++ b/doc/administration/monitoring/prometheus/index.md @@ -295,6 +295,36 @@ To use an external Prometheus server: 1. Reload the Prometheus server. +### Configure the storage retention size + +Prometheus has several custom flags to configure local storage: + +- `storage.tsdb.retention.time`: when to remove old data. Defaults to `15d`. Overrides + `storage.tsdb.retention` if this flag is set to anything other than the default. +- `storage.tsdb.retention.size`: [EXPERIMENTAL] the maximum number of bytes of storage blocks to + retain. The oldest data is removed first. Defaults to `0` (disabled). This flag is experimental + and may change in future releases. Units supported: `B`, `KB`, `MB`, `GB`, `TB`, `PB`, `EB`. For + example, `512MB`. + +To configure the storage retention size: + +1. Edit `/etc/gitlab/gitlab.rb`: + + ```ruby + prometheus['flags'] = { + 'storage.tsdb.path' => "/var/opt/gitlab/prometheus/data", + 'storage.tsdb.retention.time' => "7d", + 'storage.tsdb.retention.size' => "2GB", + 'config.file' => "/var/opt/gitlab/prometheus/prometheus.yml" + } + ``` + +1. Reconfigure GitLab: + + ```shell + sudo gitlab-ctl reconfigure + ``` + ## Viewing performance metrics You can visit `http://localhost:9090` for the dashboard that Prometheus offers by default. @@ -402,3 +432,35 @@ To disable the monitoring of Kubernetes: 1. Save the file and [reconfigure GitLab](../../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect. + +### Troubleshooting + +## `/var/opt/gitlab/prometheus` consumes too much disk space + +If you are **not** using Prometheus monitoring: + +1. [Disable Prometheus](index.md#configuring-prometheus). +1. Delete the data under `/var/opt/gitlab/prometheus`. + +If you are using Prometheus monitoring: + +1. Stop Prometheus (deleting data while it's running can cause data corruption): + + ```shell + gitlab-ctl stop prometheus + ``` + +1. Delete the data under `/var/opt/gitlab/prometheus/data`. +1. Start the service again: + + ```shell + gitlab-ctl start prometheus + ``` + +1. Verify the service is up and running: + + ```shell + gitlab-ctl status prometheus + ``` + +1. Optional. [Configure the storage retention size](index.md#configure-the-storage-retention-size). diff --git a/doc/api/container_registry.md b/doc/api/container_registry.md index 0a1983b6b33..8fc7c4dc4fe 100644 --- a/doc/api/container_registry.md +++ b/doc/api/container_registry.md @@ -372,7 +372,8 @@ DELETE /projects/:id/registry/repositories/:repository_id/tags | `keep_n` | integer | no | The amount of latest tags of given name to keep. | | `older_than` | string | no | Tags to delete that are older than the given time, written in human readable form `1h`, `1d`, `1month`. | -This API call performs the following operations: +This API returns [HTTP response status code 202](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202) +if successful, and performs the following operations: - It orders all tags by creation date. The creation date is the time of the manifest creation, not the time of tag push. diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index bb12816e0fe..6d3115e29af 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -1143,7 +1143,7 @@ Input type: `ConfigureSecretDetectionInput` ### `Mutation.corpusCreate` -Available only when feature flag `corpus_management` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. +Available only when feature flag `corpus_management` is enabled. This flag is enabled by default. Input type: `CorpusCreateInput` @@ -13467,7 +13467,7 @@ Represents vulnerability finding of a security report on the pipeline. | `containerExpirationPolicy` | [`ContainerExpirationPolicy`](#containerexpirationpolicy) | Container expiration policy of the project. | | `containerRegistryEnabled` | [`Boolean`](#boolean) | Indicates if Container Registry is enabled for the current user. | | `containerRepositoriesCount` | [`Int!`](#int) | Number of container repositories in the project. | -| `corpuses` | [`CoverageFuzzingCorpusConnection`](#coveragefuzzingcorpusconnection) | Find corpuses of the project. Available only when feature flag `corpus_management` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. (see [Connections](#connections)) | +| `corpuses` | [`CoverageFuzzingCorpusConnection`](#coveragefuzzingcorpusconnection) | Find corpuses of the project. Available only when feature flag `corpus_management` is enabled. This flag is enabled by default. (see [Connections](#connections)) | | `createdAt` | [`Time`](#time) | Timestamp of the project creation. | | `dastScannerProfiles` | [`DastScannerProfileConnection`](#dastscannerprofileconnection) | DAST scanner profiles associated with the project. (see [Connections](#connections)) | | `dastSiteProfiles` | [`DastSiteProfileConnection`](#dastsiteprofileconnection) | DAST Site Profiles associated with the project. (see [Connections](#connections)) | diff --git a/doc/user/clusters/agent/install/index.md b/doc/user/clusters/agent/install/index.md index 10e4ab932a7..a400b264587 100644 --- a/doc/user/clusters/agent/install/index.md +++ b/doc/user/clusters/agent/install/index.md @@ -6,7 +6,8 @@ info: To determine the technical writer assigned to the Stage/Group associated w # Install the GitLab Agent **(FREE)** -> [Moved](https://gitlab.com/groups/gitlab-org/-/epics/6290) from GitLab Premium to GitLab Free in 14.5. +> - [Moved](https://gitlab.com/groups/gitlab-org/-/epics/6290) from GitLab Premium to GitLab Free in 14.5. +> - [Introduced](https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent/-/merge_requests/594) multi-arch images in GitLab 14.8. The first multi-arch release is `v14.8.1`. It supports AMD64 and ARM64 architectures. To connect a cluster to GitLab, you need to install the GitLab Agent onto your cluster. diff --git a/doc/user/clusters/agent/repository.md b/doc/user/clusters/agent/repository.md index a26991f7492..a9d9fd1c13d 100644 --- a/doc/user/clusters/agent/repository.md +++ b/doc/user/clusters/agent/repository.md @@ -239,6 +239,17 @@ SPDY protocol. We [plan to add support for these features](https://gitlab.com/gitlab-org/gitlab/-/issues/346248) in a future version of GitLab. +### `kubectl` requires TLS + +`kubectl` would never send credentials over an unencrypted connection. Self-managed users should ensure that their +GitLab instance is configured with TLS for the CI/CD tunnel feature to work. Trying to use it without TLS +would produce errors: + +```shell +$ kubectl get pods +error: You must be logged in to the server (the server has asked for the client to provide credentials) +``` + ## Use impersonation to restrict project and group access **(PREMIUM)** > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/345014) in GitLab 14.5. diff --git a/locale/gitlab.pot b/locale/gitlab.pot index c2495a600d1..30b9c7bb07e 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -39153,6 +39153,9 @@ msgstr "" msgid "UsageQuota|File attachments and smaller design graphics." msgstr "" +msgid "UsageQuota|From %{storage_enforcement_date} storage limits will apply to this namespace. View and manage your usage in %{strong_start}Group Settings > Usage quotas%{strong_end}." +msgstr "" + msgid "UsageQuota|Git repository." msgstr "" diff --git a/package.json b/package.json index 177f53c406a..83b80c337c7 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "@gitlab/at.js": "1.5.7", "@gitlab/favicon-overlay": "2.0.0", "@gitlab/svgs": "2.5.0", - "@gitlab/ui": "36.1.0", + "@gitlab/ui": "36.6.0", "@gitlab/visual-review-tools": "1.6.1", "@rails/actioncable": "6.1.4-6", "@rails/ujs": "6.1.4-6", diff --git a/qa/qa/page/component/design_management.rb b/qa/qa/page/component/design_management.rb index 1f5620e30c7..73ba5713bda 100644 --- a/qa/qa/page/component/design_management.rb +++ b/qa/qa/page/component/design_management.rb @@ -55,7 +55,7 @@ module QA # wait for the "Save comment" button to disappear saved = has_no_element?(:save_comment_button) - raise ExpectationNotMet, %q(There was a problem while adding the annotation) unless saved + raise RSpec::Expectations::ExpectationNotMetError, %q(There was a problem while adding the annotation) unless saved end def add_design(design_file_path) diff --git a/spec/features/merge_request/user_posts_notes_spec.rb b/spec/features/merge_request/user_posts_notes_spec.rb index 393108d8407..1779567624c 100644 --- a/spec/features/merge_request/user_posts_notes_spec.rb +++ b/spec/features/merge_request/user_posts_notes_spec.rb @@ -133,7 +133,7 @@ RSpec.describe 'Merge request > User posts notes', :js do describe 'when previewing a note' do it 'shows the toolbar buttons when editing a note' do page.within('.js-main-target-form') do - expect(page).to have_css('.md-header-toolbar.active') + expect(page).to have_css('.md-header-toolbar') end end @@ -141,7 +141,7 @@ RSpec.describe 'Merge request > User posts notes', :js do wait_for_requests find('.js-md-preview-button').click page.within('.js-main-target-form') do - expect(page).not_to have_css('.md-header-toolbar.active') + expect(page).not_to have_css('.md-header-toolbar') end end end diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/__snapshots__/packages_list_app_spec.js.snap b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/__snapshots__/packages_list_app_spec.js.snap index a6f3e00fde1..d82af8f9e63 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/__snapshots__/packages_list_app_spec.js.snap +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/__snapshots__/packages_list_app_spec.js.snap @@ -38,8 +38,8 @@ exports[`packages_list_app renders 1`] = ` class="gl-font-size-h-display gl-line-height-36 h4" > - There are no packages yet - + There are no packages yet +

- There are no packages yet - + There are no packages yet +

{ afterEach(() => { subject.destroy(); - subject = null; axiosMock.restore(); }); function createSubject(lines = []) { // We actually mount a wrapper component so that we can force Vue to rerender classes in order to test a regression // caused by mixing Vanilla JS and Vue. - subject = mount( + subject = mountExtended( { components: { MarkdownField, @@ -72,8 +71,8 @@ describe('Markdown field component', () => { ); } - const getPreviewLink = () => subject.find('.nav-links .js-preview-link'); - const getWriteLink = () => subject.find('.nav-links .js-write-link'); + const getPreviewLink = () => subject.findByTestId('preview-tab'); + const getWriteLink = () => subject.findByTestId('write-tab'); const getMarkdownButton = () => subject.find('.js-md'); const getAllMarkdownButtons = () => subject.findAll('.js-md'); const getVideo = () => subject.find('video'); @@ -107,15 +106,15 @@ describe('Markdown field component', () => { it('sets preview link as active', async () => { previewLink = getPreviewLink(); - previewLink.trigger('click'); + previewLink.vm.$emit('click', { target: {} }); await nextTick(); - expect(previewLink.element.parentNode.classList.contains('active')).toBeTruthy(); + expect(previewLink.element.children[0].classList.contains('active')).toBe(true); }); it('shows preview loading text', async () => { previewLink = getPreviewLink(); - previewLink.trigger('click'); + previewLink.vm.$emit('click', { target: {} }); await nextTick(); expect(subject.find('.md-preview-holder').element.textContent.trim()).toContain('Loading…'); @@ -126,7 +125,7 @@ describe('Markdown field component', () => { previewLink = getPreviewLink(); - previewLink.trigger('click'); + previewLink.vm.$emit('click', { target: {} }); await axios.waitFor(markdownPreviewPath); expect(subject.find('.md-preview-holder').element.innerHTML).toContain(previewHTML); @@ -135,7 +134,7 @@ describe('Markdown field component', () => { it('calls video.pause() on comment input when isSubmitting is changed to true', async () => { previewLink = getPreviewLink(); - previewLink.trigger('click'); + previewLink.vm.$emit('click', { target: {} }); await axios.waitFor(markdownPreviewPath); const video = getVideo(); @@ -151,19 +150,19 @@ describe('Markdown field component', () => { writeLink = getWriteLink(); previewLink = getPreviewLink(); - writeLink.trigger('click'); + writeLink.vm.$emit('click', { target: {} }); await nextTick(); assertMarkdownTabs(true, writeLink, previewLink, subject); - writeLink.trigger('click'); + writeLink.vm.$emit('click', { target: {} }); await nextTick(); assertMarkdownTabs(true, writeLink, previewLink, subject); - previewLink.trigger('click'); + previewLink.vm.$emit('click', { target: {} }); await nextTick(); assertMarkdownTabs(false, writeLink, previewLink, subject); - previewLink.trigger('click'); + previewLink.vm.$emit('click', { target: {} }); await nextTick(); assertMarkdownTabs(false, writeLink, previewLink, subject); diff --git a/spec/frontend/vue_shared/components/markdown/header_spec.js b/spec/frontend/vue_shared/components/markdown/header_spec.js index 70ee72b7e2c..93ce3935fab 100644 --- a/spec/frontend/vue_shared/components/markdown/header_spec.js +++ b/spec/frontend/vue_shared/components/markdown/header_spec.js @@ -1,21 +1,25 @@ -import { shallowMount } from '@vue/test-utils'; import $ from 'jquery'; import { nextTick } from 'vue'; +import { GlTabs } from '@gitlab/ui'; import HeaderComponent from '~/vue_shared/components/markdown/header.vue'; import ToolbarButton from '~/vue_shared/components/markdown/toolbar_button.vue'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; describe('Markdown field header component', () => { let wrapper; const createWrapper = (props) => { - wrapper = shallowMount(HeaderComponent, { + wrapper = shallowMountExtended(HeaderComponent, { propsData: { previewMarkdown: false, ...props, }, + stubs: { GlTabs }, }); }; + const findWriteTab = () => wrapper.findByTestId('write-tab'); + const findPreviewTab = () => wrapper.findByTestId('preview-tab'); const findToolbarButtons = () => wrapper.findAll(ToolbarButton); const findToolbarButtonByProp = (prop, value) => findToolbarButtons() @@ -34,7 +38,6 @@ describe('Markdown field header component', () => { afterEach(() => { wrapper.destroy(); - wrapper = null; }); describe('markdown header buttons', () => { @@ -75,23 +78,26 @@ describe('Markdown field header component', () => { }); }); - it('renders `write` link as active when previewMarkdown is false', () => { - expect(wrapper.find('li:nth-child(1)').classes()).toContain('active'); + it('activates `write` tab when previewMarkdown is false', () => { + expect(findWriteTab().attributes('active')).toBe('true'); + expect(findPreviewTab().attributes('active')).toBeUndefined(); }); - it('renders `preview` link as active when previewMarkdown is true', () => { + it('activates `preview` tab when previewMarkdown is true', () => { createWrapper({ previewMarkdown: true }); - expect(wrapper.find('li:nth-child(2)').classes()).toContain('active'); + expect(findWriteTab().attributes('active')).toBeUndefined(); + expect(findPreviewTab().attributes('active')).toBe('true'); }); - it('emits toggle markdown event when clicking preview', async () => { - wrapper.find('.js-preview-link').trigger('click'); + it('emits toggle markdown event when clicking preview tab', async () => { + const eventData = { target: {} }; + findPreviewTab().vm.$emit('click', eventData); await nextTick(); expect(wrapper.emitted('preview-markdown').length).toEqual(1); - wrapper.find('.js-write-link').trigger('click'); + findWriteTab().vm.$emit('click', eventData); await nextTick(); expect(wrapper.emitted('write-markdown').length).toEqual(1); @@ -109,12 +115,10 @@ describe('Markdown field header component', () => { }); it('blurs preview link after click', () => { - const link = wrapper.find('li:nth-child(2) button'); - jest.spyOn(HTMLElement.prototype, 'blur').mockImplementation(); + const target = { blur: jest.fn() }; + findPreviewTab().vm.$emit('click', { target }); - link.trigger('click'); - - expect(link.element.blur).toHaveBeenCalled(); + expect(target.blur).toHaveBeenCalled(); }); it('renders markdown table template', () => { diff --git a/spec/helpers/storage_helper_spec.rb b/spec/helpers/storage_helper_spec.rb index d0646b30161..82b78ed831c 100644 --- a/spec/helpers/storage_helper_spec.rb +++ b/spec/helpers/storage_helper_spec.rb @@ -50,4 +50,87 @@ RSpec.describe StorageHelper do expect(helper.storage_counters_details(namespace_stats)).to eq(message) end end + + describe "storage_enforcement_banner" do + let_it_be_with_refind(:current_user) { create(:user) } + let_it_be(:free_group) { create(:group) } + let_it_be(:paid_group) { create(:group) } + + before do + allow(helper).to receive(:current_user) { current_user } + allow(Gitlab).to receive(:com?).and_return(true) + allow(paid_group).to receive(:paid?).and_return(true) + end + + describe "#storage_enforcement_banner_info" do + it 'returns nil when namespace is not free' do + expect(storage_enforcement_banner_info(paid_group)).to be(nil) + end + + it 'returns nil when storage_enforcement_date is not set' do + allow(free_group).to receive(:storage_enforcement_date).and_return(nil) + + expect(storage_enforcement_banner_info(free_group)).to be(nil) + end + + it 'returns a hash when storage_enforcement_date is set' do + storage_enforcement_date = Date.today + 30 + allow(free_group).to receive(:storage_enforcement_date).and_return(storage_enforcement_date) + + expect(storage_enforcement_banner_info(free_group)).to eql({ + text: "From #{storage_enforcement_date} storage limits will apply to this namespace. View and manage your usage in Group Settings > Usage quotas.", + variant: 'warning', + callouts_feature_name: 'storage_enforcement_banner_second_enforcement_threshold', + callouts_path: '/-/users/group_callouts', + learn_more_link: 'Learn more.' + }) + end + + context 'when storage_enforcement_date is set and dismissed callout exists' do + before do + create(:group_callout, + user: current_user, + group_id: free_group.id, + feature_name: 'storage_enforcement_banner_second_enforcement_threshold') + storage_enforcement_date = Date.today + 30 + allow(free_group).to receive(:storage_enforcement_date).and_return(storage_enforcement_date) + end + + it { expect(storage_enforcement_banner_info(free_group)).to be(nil) } + end + + context 'callouts_feature_name' do + let(:days_from_now) { 45 } + + subject do + storage_enforcement_date = Date.today + days_from_now + allow(free_group).to receive(:storage_enforcement_date).and_return(storage_enforcement_date) + + storage_enforcement_banner_info(free_group)[:callouts_feature_name] + end + + it 'returns first callouts_feature_name' do + is_expected.to eq('storage_enforcement_banner_first_enforcement_threshold') + end + + context 'returns second callouts_feature_name' do + let(:days_from_now) { 20 } + + it { is_expected.to eq('storage_enforcement_banner_second_enforcement_threshold') } + end + + context 'returns third callouts_feature_name' do + let(:days_from_now) { 13 } + + it { is_expected.to eq('storage_enforcement_banner_third_enforcement_threshold') } + end + + context 'returns fourth callouts_feature_name' do + let(:days_from_now) { 3 } + + it { is_expected.to eq('storage_enforcement_banner_fourth_enforcement_threshold') } + end + end + end + end end diff --git a/spec/models/container_repository_spec.rb b/spec/models/container_repository_spec.rb index 3524c810ff4..7c0ae51223b 100644 --- a/spec/models/container_repository_spec.rb +++ b/spec/models/container_repository_spec.rb @@ -339,6 +339,55 @@ RSpec.describe ContainerRepository, :aggregate_failures do end end + context 'when triggering registry API requests' do + let(:repository_state) { nil } + let(:repository) { create(:container_repository, repository_state) } + + shared_examples 'a state machine configured with use_transactions: false' do + it 'executes the registry API request outside of a transaction', :delete do + expect(repository).to receive(:save).and_call_original do + expect(ApplicationRecord.connection.transaction_open?).to be true + end + + expect(repository).to receive(:try_import) do + expect(ApplicationRecord.connection.transaction_open?).to be false + end + + subject + end + end + + context 'when responding to a start_pre_import event' do + subject { repository.start_pre_import } + + it_behaves_like 'a state machine configured with use_transactions: false' + end + + context 'when responding to a retry_pre_import event' do + let(:repository_state) { :import_aborted } + + subject { repository.retry_pre_import } + + it_behaves_like 'a state machine configured with use_transactions: false' + end + + context 'when responding to a start_import event' do + let(:repository_state) { :pre_import_done } + + subject { repository.start_import } + + it_behaves_like 'a state machine configured with use_transactions: false' + end + + context 'when responding to a retry_import event' do + let(:repository_state) { :import_aborted } + + subject { repository.retry_import } + + it_behaves_like 'a state machine configured with use_transactions: false' + end + end + describe '#retry_aborted_migration' do subject { repository.retry_aborted_migration } diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index b6ab6be0ba2..481cae6031b 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -2219,4 +2219,13 @@ RSpec.describe Namespace do end end end + + describe 'storage_enforcement_date' do + let_it_be(:namespace) { create(:group) } + + # Date TBD: https://gitlab.com/gitlab-org/gitlab/-/issues/350632 + it 'returns false' do + expect(namespace.storage_enforcement_date).to be(nil) + end + end end diff --git a/yarn.lock b/yarn.lock index a10a05ebae0..bf90af5d776 100644 --- a/yarn.lock +++ b/yarn.lock @@ -986,10 +986,10 @@ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-2.5.0.tgz#e0569916fa858462b1801cc90ef8dd9706a12e96" integrity sha512-cH/EBs//wdkH6kG+kDpvRCIl63/A8JgjAhBJ+ZWucPgtNCDD6x6RDMGdQrxSqhYwcCKDoLStfcxmblBkuiSRXQ== -"@gitlab/ui@36.1.0": - version "36.1.0" - resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-36.1.0.tgz#1cd3d74fabd429a5ff5086eb1f6b4db22506e1b3" - integrity sha512-hTSG1l12AX+2SuGu+04bTc3lt1xE4FXej7O1UIrGELo197GfnpfnQM76/+JK0+b1w8vHw5MODBlt/c536dgaVg== +"@gitlab/ui@36.6.0": + version "36.6.0" + resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-36.6.0.tgz#902ec76623de3b46d450fbe2074d00a39a58d61c" + integrity sha512-hHuknkt4KTQVPEA8t+Cg29hocqMUv4bYfVH7Hinj3qFaIK32zMKUGQ2P/w5BG8R+cP9PTjw+WxNYc4WpRPpcUw== dependencies: "@babel/standalone" "^7.0.0" bootstrap-vue "2.20.1"