Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-12-07 21:14:42 +00:00
parent 551b3bfd7e
commit eab843a2f5
30 changed files with 493 additions and 135 deletions

View File

@ -20,6 +20,9 @@ export default {
duration() {
return timeIntervalInWords(this.job.duration);
},
durationTitle() {
return this.job.finished_at ? __('Duration') : __('Elapsed time');
},
erasedAt() {
return this.timeFormatted(this.job.erased_at);
},
@ -76,7 +79,7 @@ export default {
<template>
<div v-if="shouldRenderBlock">
<detail-row v-if="job.duration" :value="duration" title="Duration" />
<detail-row v-if="job.duration" :value="duration" :title="durationTitle" />
<detail-row
v-if="job.finished_at"
:value="finishedAt"

View File

@ -2,9 +2,14 @@
import { GlToggle, GlSprintf, GlLink } from '@gitlab/ui';
import { s__ } from '~/locale';
import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue';
import SettingsTitles from '~/packages_and_registries/settings/group/components/settings_titles.vue';
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 { updateGroupPackageSettings } from '~/packages_and_registries/settings/group/graphql/utils/cache_update';
import { updateGroupDependencyProxySettingsOptimisticResponse } from '~/packages_and_registries/settings/group/graphql/utils/optimistic_responses';
import {
updateGroupDependencyProxySettingsOptimisticResponse,
updateDependencyProxyImageTtlGroupPolicyOptimisticResponse,
} from '~/packages_and_registries/settings/group/graphql/utils/optimistic_responses';
import {
DEPENDENCY_PROXY_HEADER,
@ -19,14 +24,20 @@ export default {
GlSprintf,
GlLink,
SettingsBlock,
SettingsTitles,
},
i18n: {
DEPENDENCY_PROXY_HEADER,
DEPENDENCY_PROXY_SETTINGS_DESCRIPTION,
label: s__('DependencyProxy|Enable Dependency Proxy'),
enabledProxyLabel: s__('DependencyProxy|Enable Dependency Proxy'),
enabledProxyHelpText: s__(
'DependencyProxy|To see the image prefix and what is in the cache, visit the %{linkStart}Dependency Proxy%{linkEnd}',
),
storageSettingsTitle: s__('DependencyProxy|Storage settings'),
ttlPolicyEnabledLabel: s__('DependencyProxy|Clear the Dependency Proxy cache automatically'),
ttlPolicyEnabledHelpText: s__(
'DependencyProxy|When enabled, images older than 90 days will be removed from the cache.',
),
},
links: {
DEPENDENCY_PROXY_DOCS_PATH,
@ -37,6 +48,10 @@ export default {
type: Object,
required: true,
},
dependencyProxyImageTtlPolicy: {
type: Object,
required: true,
},
isLoading: {
type: Boolean,
required: false,
@ -52,29 +67,35 @@ export default {
this.updateSettings({ enabled });
},
},
ttlEnabled: {
get() {
return this.dependencyProxyImageTtlPolicy.enabled;
},
set(enabled) {
const payload = {
enabled,
ttl: 90, // hardocded TTL for the MVC version
};
this.updateDependencyProxyImageTtlGroupPolicy(payload);
},
},
helpText() {
return this.enabled ? this.$options.i18n.enabledProxyHelpText : '';
},
},
methods: {
async updateSettings(payload) {
mutationVariables(payload) {
return {
input: {
groupPath: this.groupPath,
...payload,
},
};
},
async executeMutation(config, resource) {
try {
const { data } = await this.$apollo.mutate({
mutation: updateDependencyProxySettings,
variables: {
input: {
groupPath: this.groupPath,
...payload,
},
},
update: updateGroupPackageSettings(this.groupPath),
optimisticResponse: updateGroupDependencyProxySettingsOptimisticResponse({
...this.dependencyProxySettings,
...payload,
}),
});
if (data.updateDependencyProxySettings?.errors?.length > 0) {
const { data } = await this.$apollo.mutate(config);
if (data[resource]?.errors.length > 0) {
throw new Error();
} else {
this.$emit('success');
@ -83,6 +104,32 @@ export default {
this.$emit('error');
}
},
async updateSettings(payload) {
const apolloConfig = {
mutation: updateDependencyProxySettings,
variables: this.mutationVariables(payload),
update: updateGroupPackageSettings(this.groupPath),
optimisticResponse: updateGroupDependencyProxySettingsOptimisticResponse({
...this.dependencyProxySettings,
...payload,
}),
};
this.executeMutation(apolloConfig, 'updateDependencyProxySettings');
},
async updateDependencyProxyImageTtlGroupPolicy(payload) {
const apolloConfig = {
mutation: updateDependencyProxyImageTtlGroupPolicy,
variables: this.mutationVariables(payload),
update: updateGroupPackageSettings(this.groupPath),
optimisticResponse: updateDependencyProxyImageTtlGroupPolicyOptimisticResponse({
...this.dependencyProxyImageTtlPolicy,
...payload,
}),
};
this.executeMutation(apolloConfig, 'updateDependencyProxyImageTtlGroupPolicy');
},
},
};
</script>
@ -111,7 +158,7 @@ export default {
<gl-toggle
v-model="enabled"
:disabled="isLoading"
:label="$options.i18n.label"
:label="$options.i18n.enabledProxyLabel"
:help="helpText"
data-qa-selector="dependency_proxy_setting_toggle"
data-testid="dependency-proxy-setting-toggle"
@ -128,6 +175,15 @@ export default {
</span>
</template>
</gl-toggle>
<settings-titles :title="$options.i18n.storageSettingsTitle" class="gl-my-6" />
<gl-toggle
v-model="ttlEnabled"
:disabled="isLoading"
:label="$options.i18n.ttlPolicyEnabledLabel"
:help="$options.i18n.ttlPolicyEnabledHelpText"
data-testid="dependency-proxy-ttl-policies-toggle"
/>
</div>
</template>
</settings-block>

View File

@ -37,6 +37,9 @@ export default {
dependencyProxySettings() {
return this.group?.dependencyProxySetting || {};
},
dependencyProxyImageTtlPolicy() {
return this.group?.dependencyProxyImageTtlPolicy || {};
},
isLoading() {
return this.$apollo.queries.group.loading;
},
@ -82,6 +85,7 @@ export default {
<dependency-proxy-settings
v-if="dependencyProxyAvailable"
:dependency-proxy-settings="dependencyProxySettings"
:dependency-proxy-image-ttl-policy="dependencyProxyImageTtlPolicy"
:is-loading="isLoading"
@success="handleSuccess"
@error="handleError"

View File

@ -8,7 +8,8 @@ export default {
},
subTitle: {
type: String,
required: true,
required: false,
default: '',
},
},
};
@ -16,10 +17,10 @@ export default {
<template>
<div>
<h5 class="gl-border-b-solid gl-border-b-1 gl-border-gray-200">
<h5 class="gl-border-b-solid gl-border-b-1 gl-border-gray-200 gl-pb-3">
{{ title }}
</h5>
<p>{{ subTitle }}</p>
<p v-if="subTitle">{{ subTitle }}</p>
<slot></slot>
</div>
</template>

View File

@ -0,0 +1,11 @@
mutation updateDependencyProxyImageTtlGroupPolicy(
$input: UpdateDependencyProxyImageTtlGroupPolicyInput!
) {
updateDependencyProxyImageTtlGroupPolicy(input: $input) {
dependencyProxyImageTtlPolicy {
enabled
ttl
}
errors
}
}

View File

@ -4,6 +4,10 @@ query getGroupPackagesSettings($fullPath: ID!) {
dependencyProxySetting {
enabled
}
dependencyProxyImageTtlPolicy {
ttl
enabled
}
packageSettings {
mavenDuplicatesAllowed
mavenDuplicateExceptionRegex

View File

@ -19,6 +19,11 @@ export const updateGroupPackageSettings = (fullPath) => (client, { data: updated
...updatedData.updateDependencyProxySettings.dependencyProxySetting,
};
}
if (updatedData.updateDependencyProxyImageTtlGroupPolicy) {
draftState.group.dependencyProxyImageTtlPolicy = {
...updatedData.updateDependencyProxyImageTtlGroupPolicy.dependencyProxyImageTtlPolicy,
};
}
});
client.writeQuery({

View File

@ -21,3 +21,15 @@ export const updateGroupDependencyProxySettingsOptimisticResponse = (changes) =>
},
},
});
export const updateDependencyProxyImageTtlGroupPolicyOptimisticResponse = (changes) => ({
// eslint-disable-next-line @gitlab/require-i18n-strings
__typename: 'Mutation',
updateDependencyProxyImageTtlGroupPolicy: {
__typename: 'UpdateDependencyProxyImageTtlGroupPolicyPayload',
errors: [],
dependencyProxyImageTtlPolicy: {
...changes,
},
},
});

View File

@ -57,6 +57,10 @@ class ApplicationExperiment < Gitlab::Experiment # rubocop:disable Gitlab/Namesp
Digest::MD5.hexdigest(ingredients.join('|'))
end
def nest_experiment(other)
instance_exec(:nested, { label: other.name }, &Configuration.tracking_behavior)
end
private
def feature_flag_name

View File

@ -70,15 +70,13 @@
.card
.card-header
= _('Projects')
%span.badge.badge-pill
#{@group.projects.count}
= gl_badge_tag @group.projects.count
%ul.content-list
- @projects.each do |project|
%li
%strong
= link_to project.full_name, [:admin, project]
%span.badge.badge-pill
= storage_counter(project.statistics.storage_size)
= gl_badge_tag storage_counter(project.statistics.storage_size)
%span.float-right.light
%span.monospace= project.full_path + '.git'
- unless @projects.size < Kaminari.config.default_per_page
@ -90,15 +88,13 @@
.card
.card-header
= _('Projects shared with %{group_name}') % { group_name: @group.name }
%span.badge.badge-pill
#{shared_projects.size}
= gl_badge_tag shared_projects.size
%ul.content-list
- shared_projects.each do |project|
%li
%strong
= link_to project.full_name, [:admin, project]
%span.badge.badge-pill
= storage_counter(project.statistics.storage_size)
= gl_badge_tag storage_counter(project.statistics.storage_size)
%span.float-right.light
%span.monospace= project.full_path + '.git'
@ -126,7 +122,7 @@
.card
.card-header
= html_escape(_("%{group_name} group members")) % { group_name: "<strong>#{html_escape(@group.name)}</strong>".html_safe }
%span.badge.badge-pill= @group.users_count
= gl_badge_tag @group.users_count
= render 'shared/members/manage_access_button', path: group_group_members_path(@group)
%ul.content-list.group-users-list.content-list.members-list
= render partial: 'shared/members/member',

View File

@ -9,8 +9,7 @@
= s_('AdminProjects|Delete')
.stats
%span.badge.badge-pill
= storage_counter(project.statistics&.storage_size)
= gl_badge_tag storage_counter(project.statistics&.storage_size)
= render_if_exists 'admin/projects/archived', project: project
.title
= link_to(admin_project_path(project)) do

View File

@ -8,7 +8,7 @@
= f.check_box :auto_devops_enabled, class: 'form-check-input', checked: group.auto_devops_enabled?
= f.label :auto_devops_enabled, class: 'form-check-label' do
%strong= s_('GroupSettings|Default to Auto DevOps pipeline for all projects within this group')
%span.badge.badge-info#auto-devops-badge= badge_for_auto_devops_scope(group)
= gl_badge_tag badge_for_auto_devops_scope(group), variant: :info
.form-text.text-muted
= s_('GroupSettings|The Auto DevOps pipeline runs if no alternative CI configuration file is found.')
= link_to _('Learn more.'), help_page_path('topics/autodevops/index.md'), target: '_blank'

View File

@ -452,7 +452,7 @@ If the scanner report is small, less than 35 lines, then feel free to [inline th
#### Test Diffs
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:
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

View File

@ -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:

View File

@ -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`. |

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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] ""

View File

@ -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 }

View File

@ -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

View File

@ -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', () => {

View File

@ -3,7 +3,7 @@
exports[`settings_titles renders properly 1`] = `
<div>
<h5
class="gl-border-b-solid gl-border-b-1 gl-border-gray-200"
class="gl-border-b-solid gl-border-b-1 gl-border-gray-200 gl-pb-3"
>
foo

View File

@ -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);
});
});
});

View File

@ -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,
});
});

View File

@ -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);
});
});

View File

@ -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,
},
},
});

View File

@ -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 [],
},
}
`);
});
});
});

View File

@ -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',

View File

@ -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