package should be used when comparing large structs in tests. It makes it possible to output a specific diff where the two structs differ, rather than seeing the whole of both structs printed out in the test logs. Here is a small example:
+The [go-cmp](https://github.com/google/go-cmp) package should be used when comparing large structs in tests. It makes it possible to output a specific diff where the two structs differ, rather than seeing the whole of both structs printed out in the test logs. Here is a small example:
```golang
package main
diff --git a/doc/development/testing_guide/end_to_end/running_tests_that_require_special_setup.md b/doc/development/testing_guide/end_to_end/running_tests_that_require_special_setup.md
index 797a0b2dd72..ef3e0624395 100644
--- a/doc/development/testing_guide/end_to_end/running_tests_that_require_special_setup.md
+++ b/doc/development/testing_guide/end_to_end/running_tests_that_require_special_setup.md
@@ -496,12 +496,12 @@ Tests that are tagged with `:mobile` can be run against specified mobile devices
Running directly against an environment like staging is not recommended because Sauce Labs test logs expose credentials. Therefore, it is best practice and the default to use a tunnel.
-Tunnel installation instructions are here [https://docs.saucelabs.com/secure-connections/sauce-connect/installation]. To start the tunnel, after following the installation above, copy the run command in Sauce Labs > Tunnels (must be logged in to Sauce Labs with the credentials found in 1Password) and run in terminal.
+For tunnel installation instructions, read [Sauce Connect Proxy Installation](https://docs.saucelabs.com/secure-connections/sauce-connect/installation). To start the tunnel, after following the installation above, copy the run command in Sauce Labs > Tunnels (must be logged in to Sauce Labs with the credentials found in 1Password) and run in terminal.
NOTE:
It is highly recommended to use `GITLAB_QA_ACCESS_TOKEN` to speed up tests and reduce flakiness.
-`QA_REMOTE_MOBILE_DEVICE_NAME` can be any device name listed in [https://saucelabs.com/platform/supported-browsers-devices] under Emulators/simulators and the latest versions of Android or iOS. `QA_BROWSER` must be set to `safari` for iOS devices and `chrome` for Android devices.
+`QA_REMOTE_MOBILE_DEVICE_NAME` can be any device name listed in [Supported browsers and devices](https://saucelabs.com/platform/supported-browsers-devices) under Emulators/simulators and the latest versions of Android or iOS. `QA_BROWSER` must be set to `safari` for iOS devices and `chrome` for Android devices.
1. To test against a local instance with a tunnel running, in `gitlab/qa` run:
diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md
index 5d412d89186..75f52808de4 100644
--- a/doc/user/application_security/dependency_scanning/index.md
+++ b/doc/user/application_security/dependency_scanning/index.md
@@ -510,7 +510,7 @@ The following variables allow configuration of global dependency scanning settin
| `ADDITIONAL_CA_CERT_BUNDLE` | Bundle of CA certs to trust. The bundle of certificates provided here is also used by other tools during the scanning process, such as `git`, `yarn`, or `npm`. See [Using a custom SSL CA certificate authority](#using-a-custom-ssl-ca-certificate-authority) for more details. |
| `DS_EXCLUDED_ANALYZERS` | Specify the analyzers (by name) to exclude from Dependency Scanning. For more information, see [Dependency Scanning Analyzers](analyzers.md). |
| `DS_DEFAULT_ANALYZERS` | ([**DEPRECATED - use `DS_EXCLUDED_ANALYZERS` instead**](https://gitlab.com/gitlab-org/gitlab/-/issues/287691)) Override the names of the official default images. For more information, see [Dependency Scanning Analyzers](analyzers.md). |
-| `DS_EXCLUDED_PATHS` | Exclude vulnerabilities from output based on the paths. A comma-separated list of patterns. Patterns can be globs, or file or folder paths (for example, `doc,spec`). Parent directories also match patterns. Default: `"spec, test, tests, tmp"`. |
+| `DS_EXCLUDED_PATHS` | Exclude files and directories from the scan based on the paths. A comma-separated list of patterns. Patterns can be globs, or file or folder paths (for example, `doc,spec`). Parent directories also match patterns. Default: `"spec, test, tests, tmp"`. |
| `SECURE_ANALYZERS_PREFIX` | Override the name of the Docker registry providing the official default images (proxy). Read more about [customizing analyzers](analyzers.md). |
| `SECURE_LOG_LEVEL` | Set the minimum logging level. Messages of this logging level or higher are output. From highest to lowest severity, the logging levels are: `fatal`, `error`, `warn`, `info`, `debug`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/10880) in GitLab 13.1. Default: `info`. |
diff --git a/doc/user/application_security/policies/index.md b/doc/user/application_security/policies/index.md
index 10bd04cbc3f..03eee2ad180 100644
--- a/doc/user/application_security/policies/index.md
+++ b/doc/user/application_security/policies/index.md
@@ -313,9 +313,10 @@ rule in the defined policy are met.
| Field | Type | Possible values | Description |
|-------|------|-----------------|-------------|
-| `scan` | `string` | `dast`, `secret_detection`, `sast` | The action's type. |
+| `scan` | `string` | `dast`, `secret_detection`, `sast`, `container_scanning`, `cluster_image_scanning` | The action's type. |
| `site_profile` | `string` | Name of the selected [DAST site profile](../dast/index.md#site-profile). | The DAST site profile to execute the DAST scan. This field should only be set if `scan` type is `dast`. |
| `scanner_profile` | `string` or `null` | Name of the selected [DAST scanner profile](../dast/index.md#scanner-profile). | The DAST scanner profile to execute the DAST scan. This field should only be set if `scan` type is `dast`.|
+| `variables` | `object` | | Set of variables applied and enforced for the selected scan. The object's key is the variable name with a value provided as a string. |
Note the following:
@@ -379,6 +380,9 @@ scan_execution_policy:
- main
actions:
- scan: secret_detection
+ - scan: sast
+ variables:
+ SAST_EXCLUDED_ANALYZERS: brakeman
- scan: container_scanning
- name: Enforce Cluster Image Scanning on production-cluster every 24h
description: This policy enforces Cluster Image Scanning scan to run every 24 hours
@@ -406,7 +410,8 @@ In this example:
`release/v1.2.1`), DAST scans run with `Scanner Profile A` and `Site Profile B`.
- DAST and secret detection scans run every 10 minutes. The DAST scan runs with `Scanner Profile C`
and `Site Profile D`.
-- Secret detection and container scanning scans run for every pipeline executed on the `main` branch.
+- Secret detection, container scanning, and SAST scans run for every pipeline executed on the `main`
+ branch. The SAST scan runs with the `SAST_EXCLUDED_ANALYZER` variable set to `"brakeman"`.
- Cluster Image Scanning scan runs every 24h. The scan runs on the `production-cluster` cluster and fetches vulnerabilities
from the container with the name `database` configured for deployment with the name `production-application` in the `production-namespace` namespace.
diff --git a/doc/user/project/integrations/bamboo.md b/doc/user/project/integrations/bamboo.md
index 58cfd8c3a2f..38de8d9f1af 100644
--- a/doc/user/project/integrations/bamboo.md
+++ b/doc/user/project/integrations/bamboo.md
@@ -4,59 +4,66 @@ group: Integrations
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
-# Atlassian Bamboo Service **(FREE)**
+# Atlassian Bamboo integration **(FREE)**
-GitLab provides integration with Atlassian Bamboo for continuous integration.
-When configured, pushes to a project trigger a build in Bamboo automatically.
-Merge requests also display CI/CD status showing whether the build is pending,
-failed, or completed successfully. It also provides a link to the Bamboo build
-page for more information.
+You can automatically trigger builds in Atlassian Bamboo when you push changes
+to your project in GitLab.
-Bamboo doesn't quite provide the same features as a traditional build system when
-it comes to accepting webhooks and commit data. There are a few things that
-need to be configured in a Bamboo build plan before GitLab can integrate.
+When this integration is configured, merge requests also display the following information:
-## Setup
+- A CI/CD status that shows if the build is pending, failed, or has completed successfully.
+- A link to the Bamboo build page for more information.
-### Complete these steps in Bamboo
+Bamboo doesn't provide the same features as a traditional build system when
+accepting webhooks and commit data. You must configure a Bamboo
+build plan before you configure the integration in GitLab.
-1. Navigate to a Bamboo build plan and choose **Configure plan** from the **Actions**
- dropdown.
+## Configure Bamboo
+
+1. In Bamboo, go to a build plan and choose **Actions > Configure plan**.
1. Select the **Triggers** tab.
-1. Click **Add trigger**.
-1. Enter a description such as **GitLab trigger**.
-1. Choose **Repository triggers the build when changes are committed**.
+1. Select **Add trigger**.
+1. Enter a description like `GitLab trigger`.
+1. Select **Repository triggers the build when changes are committed**.
1. Select the checkbox for one or more repositories.
-1. Enter the GitLab IP address in the **Trigger IP addresses** box. This is a
- list of IP addresses that are allowed to trigger Bamboo builds.
+1. Enter the GitLab IP address in **Trigger IP addresses**. These IP addresses
+ are allowed to trigger Bamboo builds.
1. Save the trigger.
-1. In the left pane, select a build stage. If you have multiple build stages
- you want to select the last stage that contains the Git checkout task.
+1. In the left pane, select a build stage. If you have multiple build stages,
+ select the last stage that contains the Git checkout task.
1. Select the **Miscellaneous** tab.
-1. Under **Pattern Match Labeling** put `${bamboo.repository.revision.number}`
- in the **Labels** box.
-1. Save
+1. Under **Pattern Match Labeling** enter `${bamboo.repository.revision.number}`
+ in **Labels**.
+1. Select **Save**.
-Bamboo is now ready to accept triggers from GitLab. Next, set up the Bamboo
-service in GitLab.
+Bamboo is ready to accept triggers from GitLab. Next, set up the Bamboo
+integration in GitLab.
-### Complete these steps in GitLab
+## Configure GitLab
-1. Navigate to the project you want to configure to trigger builds.
-1. Navigate to the [Integrations page](overview.md#accessing-integrations)
-1. Click **Atlassian Bamboo**.
-1. Ensure that the **Active** toggle is enabled.
-1. Enter the base URL of your Bamboo server. `https://bamboo.example.com`
-1. Enter the build key from your Bamboo build plan. Build keys are typically made
- up from the Project Key and Plan Key that are set on project/plan creation and
- separated with a dash (`-`), for example **PROJ-PLAN**. This is a short, all
- uppercase identifier that is unique. When viewing a plan in Bamboo, the
- build key is also shown in the browser URL, for example `https://bamboo.example.com/browse/PROJ-PLAN`.
-1. If necessary, enter username and password for a Bamboo user that has
+1. On the top bar, select **Menu > Projects** and find your project.
+1. On the left sidebar, select **Settings > Integrations**.
+1. Select **Atlassian Bamboo**.
+1. Ensure the **Active** checkbox is selected.
+1. Enter the base URL of your Bamboo server. For example, `https://bamboo.example.com`.
+1. Enter the [build key](#identify-the-bamboo-build-plan-build-key) from your Bamboo
+ build plan.
+1. If necessary, enter a username and password for a Bamboo user that has
access to trigger the build plan. Leave these fields blank if you do not require
authentication.
-1. Save or optionally click **Test Settings**. **Test Settings**
- actually triggers a build in Bamboo.
+1. Optional. To test the configuration and trigger a build in Bamboo,
+ select **Test Settings**.
+1. Select **Save changes**.
+
+### Identify the Bamboo build plan build key
+
+A build key is a unique identifier typically made up from the project key and
+plan key.
+Build keys are short, all uppercase, and separated with a dash (`-`),
+for example `PROJ-PLAN`.
+
+The build key is included in the browser URL when you view a plan in
+Bamboo. For example, `https://bamboo.example.com/browse/PROJ-PLAN`.
## Troubleshooting
diff --git a/lib/tasks/gitlab/background_migrations.rake b/lib/tasks/gitlab/background_migrations.rake
index c978a2807ca..c7f3d003f9f 100644
--- a/lib/tasks/gitlab/background_migrations.rake
+++ b/lib/tasks/gitlab/background_migrations.rake
@@ -2,6 +2,7 @@
namespace :gitlab do
namespace :background_migrations do
+ desc 'Synchronously finish executing a batched background migration'
task :finalize, [:job_class_name, :table_name, :column_name, :job_arguments] => :environment do |_, args|
[:job_class_name, :table_name, :column_name, :job_arguments].each do |argument|
unless args[argument]
@@ -19,5 +20,23 @@ namespace :gitlab do
puts "Done.".color(:green)
end
+
+ desc 'Display the status of batched background migrations'
+ task status: :environment do
+ statuses = Gitlab::Database::BackgroundMigration::BatchedMigration.statuses
+ max_status_length = statuses.keys.map(&:length).max
+ format_string = "%-#{max_status_length}s | %s\n"
+
+ Gitlab::Database::BackgroundMigration::BatchedMigration.find_each(batch_size: 100) do |migration|
+ identification_fields = [
+ migration.job_class_name,
+ migration.table_name,
+ migration.column_name,
+ migration.job_arguments.to_json
+ ].join(',')
+
+ printf(format_string, migration.status, identification_fields)
+ end
+ end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 291eabe8e36..cd1f5967ce6 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -11531,6 +11531,9 @@ msgstr ""
msgid "DependencyProxy|Cached %{time}"
msgstr ""
+msgid "DependencyProxy|Clear the Dependency Proxy cache automatically"
+msgstr ""
+
msgid "DependencyProxy|Contains %{count} blobs of images (%{size})"
msgstr ""
@@ -11555,6 +11558,9 @@ msgstr ""
msgid "DependencyProxy|Image list"
msgstr ""
+msgid "DependencyProxy|Storage settings"
+msgstr ""
+
msgid "DependencyProxy|The Dependency Proxy is disabled. %{docLinkStart}Learn how to enable it%{docLinkEnd}."
msgstr ""
@@ -11564,6 +11570,9 @@ msgstr ""
msgid "DependencyProxy|To see the image prefix and what is in the cache, visit the %{linkStart}Dependency Proxy%{linkEnd}"
msgstr ""
+msgid "DependencyProxy|When enabled, images older than 90 days will be removed from the cache."
+msgstr ""
+
msgid "Depends on %d merge request being merged"
msgid_plural "Depends on %d merge requests being merged"
msgstr[0] ""
diff --git a/spec/experiments/application_experiment_spec.rb b/spec/experiments/application_experiment_spec.rb
index de5328b31e4..20936977612 100644
--- a/spec/experiments/application_experiment_spec.rb
+++ b/spec/experiments/application_experiment_spec.rb
@@ -306,6 +306,26 @@ RSpec.describe ApplicationExperiment, :experiment do
end
end
+ context "when nesting experiments" do
+ before do
+ stub_experiments(top: :control, nested: :control)
+ end
+
+ it "doesn't raise an exception" do
+ expect { experiment(:top) { |e| e.control { experiment(:nested) { } } } }.not_to raise_error
+ end
+
+ it "tracks an event", :snowplow do
+ experiment(:top) { |e| e.control { experiment(:nested) { } } }
+
+ expect(Gitlab::Tracking).to have_received(:event).with( # rubocop:disable RSpec/ExpectGitlabTracking
+ 'top',
+ 'nested',
+ hash_including(label: 'nested')
+ )
+ end
+ end
+
context "when caching" do
let(:cache) { Gitlab::Experiment::Configuration.cache }
diff --git a/spec/factories/gitlab/database/background_migration/batched_migrations.rb b/spec/factories/gitlab/database/background_migration/batched_migrations.rb
index de57e0c1565..79b4447b76e 100644
--- a/spec/factories/gitlab/database/background_migration/batched_migrations.rb
+++ b/spec/factories/gitlab/database/background_migration/batched_migrations.rb
@@ -12,5 +12,13 @@ FactoryBot.define do
sequence(:job_arguments) { |n| [["column_#{n}"], ["column_#{n}_convert_to_bigint"]] }
total_tuple_count { 10_000 }
pause_ms { 100 }
+
+ trait :finished do
+ status { :finished }
+ end
+
+ trait :failed do
+ status { :failed }
+ end
end
end
diff --git a/spec/frontend/jobs/components/job_sidebar_details_container_spec.js b/spec/frontend/jobs/components/job_sidebar_details_container_spec.js
index ad0368555fa..cc9a5e4ee25 100644
--- a/spec/frontend/jobs/components/job_sidebar_details_container_spec.js
+++ b/spec/frontend/jobs/components/job_sidebar_details_container_spec.js
@@ -56,7 +56,7 @@ describe('Job Sidebar Details Container', () => {
beforeEach(createWrapper);
it.each([
- ['duration', 'Duration: 6 seconds'],
+ ['duration', 'Elapsed time: 6 seconds'],
['erased_at', 'Erased: 3 weeks ago'],
['finished_at', 'Finished: 3 weeks ago'],
['queued', 'Queued: 9 seconds'],
@@ -86,6 +86,15 @@ describe('Job Sidebar Details Container', () => {
expect(findAllDetailsRow()).toHaveLength(7);
});
+
+ describe('duration row', () => {
+ it('renders all the details components', async () => {
+ createWrapper();
+ await store.dispatch('receiveJobSuccess', job);
+
+ expect(findAllDetailsRow().at(0).text()).toBe('Duration: 6 seconds');
+ });
+ });
});
describe('timeout', () => {
diff --git a/spec/frontend/packages_and_registries/settings/group/components/__snapshots__/settings_titles_spec.js.snap b/spec/frontend/packages_and_registries/settings/group/components/__snapshots__/settings_titles_spec.js.snap
index f2087733d2b..5b56cb7f74e 100644
--- a/spec/frontend/packages_and_registries/settings/group/components/__snapshots__/settings_titles_spec.js.snap
+++ b/spec/frontend/packages_and_registries/settings/group/components/__snapshots__/settings_titles_spec.js.snap
@@ -3,7 +3,7 @@
exports[`settings_titles renders properly 1`] = `
foo
diff --git a/spec/frontend/packages_and_registries/settings/group/components/dependency_proxy_settings_spec.js b/spec/frontend/packages_and_registries/settings/group/components/dependency_proxy_settings_spec.js
index a75549cd764..f6c1d212b51 100644
--- a/spec/frontend/packages_and_registries/settings/group/components/dependency_proxy_settings_spec.js
+++ b/spec/frontend/packages_and_registries/settings/group/components/dependency_proxy_settings_spec.js
@@ -13,14 +13,21 @@ import {
} from '~/packages_and_registries/settings/group/constants';
import updateDependencyProxySettings from '~/packages_and_registries/settings/group/graphql/mutations/update_dependency_proxy_settings.mutation.graphql';
+import updateDependencyProxyImageTtlGroupPolicy from '~/packages_and_registries/settings/group/graphql/mutations/update_dependency_proxy_image_ttl_group_policy.mutation.graphql';
import getGroupPackagesSettingsQuery from '~/packages_and_registries/settings/group/graphql/queries/get_group_packages_settings.query.graphql';
import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue';
-import { updateGroupDependencyProxySettingsOptimisticResponse } from '~/packages_and_registries/settings/group/graphql/utils/optimistic_responses';
+import SettingsTitles from '~/packages_and_registries/settings/group/components/settings_titles.vue';
+import {
+ updateGroupDependencyProxySettingsOptimisticResponse,
+ updateDependencyProxyImageTtlGroupPolicyOptimisticResponse,
+} from '~/packages_and_registries/settings/group/graphql/utils/optimistic_responses';
import {
dependencyProxySettings as dependencyProxySettingsMock,
+ dependencyProxyImageTtlPolicy as dependencyProxyImageTtlPolicyMock,
dependencyProxySettingMutationMock,
groupPackageSettingsMock,
- dependencyProxySettingMutationErrorMock,
+ mutationErrorMock,
+ dependencyProxyUpdateTllPolicyMutationMock,
} from '../mock_data';
jest.mock('~/flash');
@@ -31,6 +38,8 @@ const localVue = createLocalVue();
describe('DependencyProxySettings', () => {
let wrapper;
let apolloProvider;
+ let updateSettingsMutationResolver;
+ let updateTtlPoliciesMutationResolver;
const defaultProvide = {
defaultExpanded: false,
@@ -42,11 +51,14 @@ describe('DependencyProxySettings', () => {
const mountComponent = ({
provide = defaultProvide,
- mutationResolver = jest.fn().mockResolvedValue(dependencyProxySettingMutationMock()),
isLoading = false,
dependencyProxySettings = dependencyProxySettingsMock(),
+ dependencyProxyImageTtlPolicy = dependencyProxyImageTtlPolicyMock(),
} = {}) => {
- const requestHandlers = [[updateDependencyProxySettings, mutationResolver]];
+ const requestHandlers = [
+ [updateDependencyProxySettings, updateSettingsMutationResolver],
+ [updateDependencyProxyImageTtlGroupPolicy, updateTtlPoliciesMutationResolver],
+ ];
apolloProvider = createMockApollo(requestHandlers);
@@ -56,6 +68,7 @@ describe('DependencyProxySettings', () => {
provide,
propsData: {
dependencyProxySettings,
+ dependencyProxyImageTtlPolicy,
isLoading,
},
stubs: {
@@ -66,14 +79,26 @@ describe('DependencyProxySettings', () => {
});
};
+ beforeEach(() => {
+ updateSettingsMutationResolver = jest
+ .fn()
+ .mockResolvedValue(dependencyProxySettingMutationMock());
+ updateTtlPoliciesMutationResolver = jest
+ .fn()
+ .mockResolvedValue(dependencyProxyUpdateTllPolicyMutationMock());
+ });
+
afterEach(() => {
wrapper.destroy();
});
const findSettingsBlock = () => wrapper.findComponent(SettingsBlock);
+ const findSettingsTitles = () => wrapper.findComponent(SettingsTitles);
const findDescription = () => wrapper.findByTestId('description');
const findDescriptionLink = () => wrapper.findByTestId('description-link');
- const findToggle = () => wrapper.findComponent(GlToggle);
+ const findEnableProxyToggle = () => wrapper.findByTestId('dependency-proxy-setting-toggle');
+ const findEnableTtlPoliciesToggle = () =>
+ wrapper.findByTestId('dependency-proxy-ttl-policies-toggle');
const findToggleHelpLink = () => wrapper.findByTestId('toggle-help-link');
const fillApolloCache = () => {
@@ -86,10 +111,6 @@ describe('DependencyProxySettings', () => {
});
};
- const emitSettingsUpdate = (value = false) => {
- findToggle().vm.$emit('change', value);
- };
-
it('renders a settings block', () => {
mountComponent();
@@ -127,8 +148,8 @@ describe('DependencyProxySettings', () => {
it('exists', () => {
mountComponent();
- expect(findToggle().props()).toMatchObject({
- label: component.i18n.label,
+ expect(findEnableProxyToggle().props()).toMatchObject({
+ label: component.i18n.enabledProxyLabel,
});
});
@@ -138,13 +159,13 @@ describe('DependencyProxySettings', () => {
});
it('has the help prop correctly set', () => {
- expect(findToggle().props()).toMatchObject({
+ expect(findEnableProxyToggle().props()).toMatchObject({
help: component.i18n.enabledProxyHelpText,
});
});
it('has help text with a link', () => {
- expect(findToggle().text()).toContain(
+ expect(findEnableProxyToggle().text()).toContain(
'To see the image prefix and what is in the cache, visit the Dependency Proxy',
);
expect(findToggleHelpLink().attributes()).toMatchObject({
@@ -161,7 +182,7 @@ describe('DependencyProxySettings', () => {
});
it('has the help prop set to empty', () => {
- expect(findToggle().props()).toMatchObject({
+ expect(findEnableProxyToggle().props()).toMatchObject({
help: '',
});
});
@@ -172,13 +193,38 @@ describe('DependencyProxySettings', () => {
});
});
- describe('settings update', () => {
+ describe('storage settings', () => {
+ it('the component has the settings title', () => {
+ mountComponent();
+
+ expect(findSettingsTitles().props()).toMatchObject({
+ title: component.i18n.storageSettingsTitle,
+ });
+ });
+
+ describe('enable proxy ttl policies', () => {
+ it('exists', () => {
+ mountComponent();
+
+ expect(findEnableTtlPoliciesToggle().props()).toMatchObject({
+ label: component.i18n.ttlPolicyEnabledLabel,
+ help: component.i18n.ttlPolicyEnabledHelpText,
+ });
+ });
+ });
+ });
+
+ describe.each`
+ toggleName | toggleFinder | localErrorMock | optimisticResponse
+ ${'enable proxy'} | ${findEnableProxyToggle} | ${dependencyProxySettingMutationMock} | ${updateGroupDependencyProxySettingsOptimisticResponse}
+ ${'enable ttl policies'} | ${findEnableTtlPoliciesToggle} | ${dependencyProxyUpdateTllPolicyMutationMock} | ${updateDependencyProxyImageTtlGroupPolicyOptimisticResponse}
+ `('$toggleName settings update ', ({ optimisticResponse, toggleFinder, localErrorMock }) => {
describe('success state', () => {
it('emits a success event', async () => {
mountComponent();
fillApolloCache();
- emitSettingsUpdate();
+ toggleFinder().vm.$emit('change', false);
await waitForPromises();
@@ -190,26 +236,28 @@ describe('DependencyProxySettings', () => {
fillApolloCache();
- expect(findToggle().props('value')).toBe(true);
+ expect(toggleFinder().props('value')).toBe(true);
- emitSettingsUpdate();
+ toggleFinder().vm.$emit('change', false);
- expect(updateGroupDependencyProxySettingsOptimisticResponse).toHaveBeenCalledWith({
- enabled: false,
- });
+ expect(optimisticResponse).toHaveBeenCalledWith(
+ expect.objectContaining({
+ enabled: false,
+ }),
+ );
});
});
describe('errors', () => {
it('mutation payload with root level errors', async () => {
- const mutationResolver = jest
- .fn()
- .mockResolvedValue(dependencyProxySettingMutationErrorMock);
- mountComponent({ mutationResolver });
+ updateSettingsMutationResolver = jest.fn().mockResolvedValue(mutationErrorMock);
+ updateTtlPoliciesMutationResolver = jest.fn().mockResolvedValue(mutationErrorMock);
+
+ mountComponent();
fillApolloCache();
- emitSettingsUpdate();
+ toggleFinder().vm.$emit('change', false);
await waitForPromises();
@@ -217,14 +265,16 @@ describe('DependencyProxySettings', () => {
});
it.each`
- type | mutationResolver
- ${'local'} | ${jest.fn().mockResolvedValue(dependencyProxySettingMutationMock({ errors: ['foo'] }))}
+ type | mutationResolverMock
+ ${'local'} | ${jest.fn().mockResolvedValue(localErrorMock({ errors: ['foo'] }))}
${'network'} | ${jest.fn().mockRejectedValue()}
- `('mutation payload with $type error', async ({ mutationResolver }) => {
- mountComponent({ mutationResolver });
+ `('mutation payload with $type error', async ({ mutationResolverMock }) => {
+ updateSettingsMutationResolver = mutationResolverMock;
+ updateTtlPoliciesMutationResolver = mutationResolverMock;
+ mountComponent();
fillApolloCache();
- emitSettingsUpdate();
+ toggleFinder().vm.$emit('change', false);
await waitForPromises();
@@ -234,10 +284,16 @@ describe('DependencyProxySettings', () => {
});
describe('when isLoading is true', () => {
- it('disables enable toggle', () => {
+ it('disables enable proxy toggle', () => {
mountComponent({ isLoading: true });
- expect(findToggle().props('disabled')).toBe(true);
+ expect(findEnableProxyToggle().props('disabled')).toBe(true);
+ });
+
+ it('disables enable ttl policies toggle', () => {
+ mountComponent({ isLoading: true });
+
+ expect(findEnableTtlPoliciesToggle().props('disabled')).toBe(true);
});
});
});
diff --git a/spec/frontend/packages_and_registries/settings/group/components/group_settings_app_spec.js b/spec/frontend/packages_and_registries/settings/group/components/group_settings_app_spec.js
index e4d62bc6a6e..933dac7f5a8 100644
--- a/spec/frontend/packages_and_registries/settings/group/components/group_settings_app_spec.js
+++ b/spec/frontend/packages_and_registries/settings/group/components/group_settings_app_spec.js
@@ -10,7 +10,12 @@ import DependencyProxySettings from '~/packages_and_registries/settings/group/co
import component from '~/packages_and_registries/settings/group/components/group_settings_app.vue';
import getGroupPackagesSettingsQuery from '~/packages_and_registries/settings/group/graphql/queries/get_group_packages_settings.query.graphql';
-import { groupPackageSettingsMock, packageSettings, dependencyProxySettings } from '../mock_data';
+import {
+ groupPackageSettingsMock,
+ packageSettings,
+ dependencyProxySettings,
+ dependencyProxyImageTtlPolicy,
+} from '../mock_data';
jest.mock('~/flash');
@@ -66,11 +71,17 @@ describe('Group Settings App', () => {
await nextTick();
};
+ const packageSettingsProps = { packageSettings: packageSettings() };
+ const dependencyProxyProps = {
+ dependencyProxySettings: dependencyProxySettings(),
+ dependencyProxyImageTtlPolicy: dependencyProxyImageTtlPolicy(),
+ };
+
describe.each`
- finder | entityProp | entityValue | successMessage | errorMessage
- ${findPackageSettings} | ${'packageSettings'} | ${packageSettings()} | ${'Settings saved successfully'} | ${'An error occurred while saving the settings'}
- ${findDependencyProxySettings} | ${'dependencyProxySettings'} | ${dependencyProxySettings()} | ${'Setting saved successfully'} | ${'An error occurred while saving the setting'}
- `('settings blocks', ({ finder, entityProp, entityValue, successMessage, errorMessage }) => {
+ finder | entitySpecificProps | successMessage | errorMessage
+ ${findPackageSettings} | ${packageSettingsProps} | ${'Settings saved successfully'} | ${'An error occurred while saving the settings'}
+ ${findDependencyProxySettings} | ${dependencyProxyProps} | ${'Setting saved successfully'} | ${'An error occurred while saving the setting'}
+ `('settings blocks', ({ finder, entitySpecificProps, successMessage, errorMessage }) => {
beforeEach(() => {
mountComponent();
return waitForApolloQueryAndRender();
@@ -83,7 +94,7 @@ describe('Group Settings App', () => {
it('binds the correctProps', () => {
expect(finder().props()).toMatchObject({
isLoading: false,
- [entityProp]: entityValue,
+ ...entitySpecificProps,
});
});
diff --git a/spec/frontend/packages_and_registries/settings/group/components/settings_titles_spec.js b/spec/frontend/packages_and_registries/settings/group/components/settings_titles_spec.js
index a61edad8685..fcfad4b42b8 100644
--- a/spec/frontend/packages_and_registries/settings/group/components/settings_titles_spec.js
+++ b/spec/frontend/packages_and_registries/settings/group/components/settings_titles_spec.js
@@ -4,15 +4,19 @@ import SettingsTitles from '~/packages_and_registries/settings/group/components/
describe('settings_titles', () => {
let wrapper;
- const mountComponent = () => {
+ const defaultProps = {
+ title: 'foo',
+ subTitle: 'bar',
+ };
+
+ const mountComponent = (propsData = defaultProps) => {
wrapper = shallowMount(SettingsTitles, {
- propsData: {
- title: 'foo',
- subTitle: 'bar',
- },
+ propsData,
});
};
+ const findSubTitle = () => wrapper.find('p');
+
afterEach(() => {
wrapper.destroy();
});
@@ -22,4 +26,10 @@ describe('settings_titles', () => {
expect(wrapper.element).toMatchSnapshot();
});
+
+ it('does not render the subtitle paragraph when no subtitle is passed', () => {
+ mountComponent({ title: defaultProps.title });
+
+ expect(findSubTitle().exists()).toBe(false);
+ });
});
diff --git a/spec/frontend/packages_and_registries/settings/group/graphl/utils/cache_update_spec.js b/spec/frontend/packages_and_registries/settings/group/graphl/utils/cache_update_spec.js
index 9d8504a1124..a5b571a0241 100644
--- a/spec/frontend/packages_and_registries/settings/group/graphl/utils/cache_update_spec.js
+++ b/spec/frontend/packages_and_registries/settings/group/graphl/utils/cache_update_spec.js
@@ -17,6 +17,13 @@ describe('Package and Registries settings group cache updates', () => {
},
};
+ const updateDependencyProxyImageTtlGroupPolicyPayload = {
+ dependencyProxyImageTtlPolicy: {
+ enabled: false,
+ ttl: 45,
+ },
+ };
+
const cacheMock = {
group: {
packageSettings: {
@@ -26,6 +33,10 @@ describe('Package and Registries settings group cache updates', () => {
dependencyProxySetting: {
enabled: true,
},
+ dependencyProxyImageTtlPolicy: {
+ enabled: true,
+ ttl: 45,
+ },
},
};
@@ -42,15 +53,26 @@ describe('Package and Registries settings group cache updates', () => {
});
describe.each`
- updateNamespacePackageSettings | updateDependencyProxySettings
- ${updateNamespacePackageSettingsPayload} | ${updateDependencyProxySettingsPayload}
- ${undefined} | ${updateDependencyProxySettingsPayload}
- ${updateNamespacePackageSettingsPayload} | ${undefined}
- ${undefined} | ${undefined}
+ updateNamespacePackageSettings | updateDependencyProxySettings | updateDependencyProxyImageTtlGroupPolicy
+ ${updateNamespacePackageSettingsPayload} | ${updateDependencyProxySettingsPayload} | ${undefined}
+ ${undefined} | ${updateDependencyProxySettingsPayload} | ${undefined}
+ ${updateNamespacePackageSettingsPayload} | ${undefined} | ${undefined}
+ ${undefined} | ${undefined} | ${updateDependencyProxyImageTtlGroupPolicyPayload}
+ ${undefined} | ${undefined} | ${undefined}
`(
'updateGroupPackageSettings',
- ({ updateNamespacePackageSettings, updateDependencyProxySettings }) => {
- const payload = { data: { updateNamespacePackageSettings, updateDependencyProxySettings } };
+ ({
+ updateNamespacePackageSettings,
+ updateDependencyProxySettings,
+ updateDependencyProxyImageTtlGroupPolicy,
+ }) => {
+ const payload = {
+ data: {
+ updateNamespacePackageSettings,
+ updateDependencyProxySettings,
+ updateDependencyProxyImageTtlGroupPolicy,
+ },
+ };
it('calls readQuery', () => {
updateGroupPackageSettings('foo')(client, payload);
expect(client.readQuery).toHaveBeenCalledWith(queryAndVariables);
@@ -65,6 +87,7 @@ describe('Package and Registries settings group cache updates', () => {
...cacheMock.group,
...payload.data.updateNamespacePackageSettings,
...payload.data.updateDependencyProxySettings,
+ ...payload.data.updateDependencyProxyImageTtlGroupPolicy,
},
},
});
diff --git a/spec/frontend/packages_and_registries/settings/group/graphl/utils/optimistic_responses_spec.js b/spec/frontend/packages_and_registries/settings/group/graphl/utils/optimistic_responses_spec.js
index debeb9aa89c..b4efda3e7b2 100644
--- a/spec/frontend/packages_and_registries/settings/group/graphl/utils/optimistic_responses_spec.js
+++ b/spec/frontend/packages_and_registries/settings/group/graphl/utils/optimistic_responses_spec.js
@@ -1,6 +1,7 @@
import {
updateGroupPackagesSettingsOptimisticResponse,
updateGroupDependencyProxySettingsOptimisticResponse,
+ updateDependencyProxyImageTtlGroupPolicyOptimisticResponse,
} from '~/packages_and_registries/settings/group/graphql/utils/optimistic_responses';
describe('Optimistic responses', () => {
@@ -38,4 +39,22 @@ describe('Optimistic responses', () => {
`);
});
});
+
+ describe('updateDependencyProxyImageTtlGroupPolicyOptimisticResponse', () => {
+ it('returns the correct structure', () => {
+ expect(updateDependencyProxyImageTtlGroupPolicyOptimisticResponse({ foo: 'bar' }))
+ .toMatchInlineSnapshot(`
+ Object {
+ "__typename": "Mutation",
+ "updateDependencyProxyImageTtlGroupPolicy": Object {
+ "__typename": "UpdateDependencyProxyImageTtlGroupPolicyPayload",
+ "dependencyProxyImageTtlPolicy": Object {
+ "foo": "bar",
+ },
+ "errors": Array [],
+ },
+ }
+ `);
+ });
+ });
});
diff --git a/spec/frontend/packages_and_registries/settings/group/mock_data.js b/spec/frontend/packages_and_registries/settings/group/mock_data.js
index c74bebf1664..d53446de910 100644
--- a/spec/frontend/packages_and_registries/settings/group/mock_data.js
+++ b/spec/frontend/packages_and_registries/settings/group/mock_data.js
@@ -10,6 +10,12 @@ export const dependencyProxySettings = (extend) => ({
...extend,
});
+export const dependencyProxyImageTtlPolicy = (extend) => ({
+ ttl: 90,
+ enabled: true,
+ ...extend,
+});
+
export const groupPackageSettingsMock = {
data: {
group: {
@@ -17,6 +23,7 @@ export const groupPackageSettingsMock = {
fullPath: 'foo_group_path',
packageSettings: packageSettings(),
dependencyProxySetting: dependencyProxySettings(),
+ dependencyProxyImageTtlPolicy: dependencyProxyImageTtlPolicy(),
},
},
};
@@ -46,6 +53,16 @@ export const dependencyProxySettingMutationMock = (override) => ({
},
});
+export const dependencyProxyUpdateTllPolicyMutationMock = (override) => ({
+ data: {
+ updateDependencyProxyImageTtlGroupPolicy: {
+ dependencyProxyImageTtlPolicy: dependencyProxyImageTtlPolicy(),
+ errors: [],
+ ...override,
+ },
+ },
+});
+
export const groupPackageSettingsMutationErrorMock = {
errors: [
{
@@ -70,7 +87,8 @@ export const groupPackageSettingsMutationErrorMock = {
},
],
};
-export const dependencyProxySettingMutationErrorMock = {
+
+export const mutationErrorMock = {
errors: [
{
message: 'Some error',
diff --git a/spec/tasks/gitlab/background_migrations_rake_spec.rb b/spec/tasks/gitlab/background_migrations_rake_spec.rb
new file mode 100644
index 00000000000..079b4d3aea8
--- /dev/null
+++ b/spec/tasks/gitlab/background_migrations_rake_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require 'rake_helper'
+
+RSpec.describe 'gitlab:background_migrations namespace rake tasks' do
+ before do
+ Rake.application.rake_require 'tasks/gitlab/background_migrations'
+ end
+
+ describe 'finalize' do
+ subject(:finalize_task) { run_rake_task('gitlab:background_migrations:finalize', *arguments) }
+
+ context 'without the proper arguments' do
+ let(:arguments) { %w[CopyColumnUsingBackgroundMigrationJob events id] }
+
+ it 'exits without finalizing the migration' do
+ expect(Gitlab::Database::BackgroundMigration::BatchedMigrationRunner).not_to receive(:finalize)
+
+ expect { finalize_task }.to output(/Must specify job_arguments as an argument/).to_stdout
+ .and raise_error(SystemExit) { |error| expect(error.status).to eq(1) }
+ end
+ end
+
+ context 'with the proper arguments' do
+ let(:arguments) { %w[CopyColumnUsingBackgroundMigrationJob events id [["id1"\,"id2"]]] }
+
+ it 'finalizes the matching migration' do
+ expect(Gitlab::Database::BackgroundMigration::BatchedMigrationRunner).to receive(:finalize)
+ .with('CopyColumnUsingBackgroundMigrationJob', 'events', 'id', [%w[id1 id2]])
+
+ expect { finalize_task }.to output(/Done/).to_stdout
+ end
+ end
+ end
+
+ describe 'status' do
+ subject(:status_task) { run_rake_task('gitlab:background_migrations:status') }
+
+ it 'outputs the status of background migrations' do
+ migration1 = create(:batched_background_migration, :finished, job_arguments: [%w[id1 id2]])
+ migration2 = create(:batched_background_migration, :failed, job_arguments: [])
+
+ expect { status_task }.to output(<<~OUTPUT).to_stdout
+ finished | #{migration1.job_class_name},#{migration1.table_name},#{migration1.column_name},[["id1","id2"]]
+ failed | #{migration2.job_class_name},#{migration2.table_name},#{migration2.column_name},[]
+ OUTPUT
+ end
+ end
+end