Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-05-31 06:09:47 +00:00
parent 96fb7f03bd
commit 3a5eccd3d0
14 changed files with 151 additions and 456 deletions

View File

@ -1067,11 +1067,7 @@
"type": "string",
"markdownDescription": "Determines the strategy for downloading and updating the cache. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#cachepolicy)",
"default": "pull-push",
"enum": [
"pull",
"push",
"pull-push"
]
"pattern": "pull-push|pull|push|\\$\\w{1,255}"
},
"unprotect": {
"type": "boolean",

View File

@ -13,7 +13,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
around_action :allow_gitaly_ref_name_caching
after_action :track_viewed_diffs_events, only: [:diffs_batch]
after_action :track_viewed_diffs_events, only: [:diffs_batch, :diff_for_path]
urgency :low, [
:show,

View File

@ -6,7 +6,7 @@ class PopulateReleasesAccessLevelFromRepository < Gitlab::Database::Migration[2.
disable_ddl_transaction!
def up
update_column_in_batches(
update_column_in_batches( # rubocop: disable Migration/UpdateColumnInBatches
:project_features,
:releases_access_level,
Arel.sql('repository_access_level')

View File

@ -261,6 +261,39 @@ cache:
key: $CI_JOB_NAME
```
### Use a variable to control a job's cache policy
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/371480) in GitLab 16.1.
To reduce duplication of jobs where the only difference is the pull policy, you can use a [CI/CD variable](../variables/index.md).
For example:
```yaml
conditional-policy:
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
variables:
POLICY: pull-push
- if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
variables:
POLICY: pull
stage: build
cache:
key: gems
policy: $POLICY
paths:
- vendor/bundle
script:
- echo "This job pulls and pushes the cache depending on the branch"
- echo "Downloading dependencies..."
```
In this example, the job's cache policy is:
- `pull-push` for changes to the default branch.
- `pull` for changes to other branches.
### Cache Node.js dependencies
If your project uses [npm](https://www.npmjs.com/) to install Node.js

View File

@ -28,6 +28,7 @@ There are two places defined variables can be used. On the:
| [`artifacts:name`](../yaml/index.md#artifactsname) | yes | Runner | The variable expansion is made by GitLab Runner's shell environment. |
| [`before_script`](../yaml/index.md#before_script) | yes | Script execution shell | The variable expansion is made by the [execution shell environment](#execution-shell-environment) |
| [`cache:key`](../yaml/index.md#cachekey) | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism). |
| [`cache:policy`](../yaml/index.md#cachepolicy) | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism). |
| [`environment:name`](../yaml/index.md#environmentname) | yes | GitLab | Similar to `environment:url`, but the variables expansion doesn't support the following:<br/><br/>- `CI_ENVIRONMENT_*` variables.<br/>- [Persisted variables](#persisted-variables). |
| [`environment:url`](../yaml/index.md#environmenturl) | yes | GitLab | The variable expansion is made by the [internal variable expansion mechanism](#gitlab-internal-variable-expansion-mechanism) in GitLab.<br/><br/>Supported are all variables defined for a job (project/group variables, variables from `.gitlab-ci.yml`, variables from triggers, variables from pipeline schedules).<br/><br/>Not supported are variables defined in the GitLab Runner `config.toml` and variables created in the job's `script`. |
| [`environment:auto_stop_in`](../yaml/index.md#environmentauto_stop_in)| yes | GitLab | The variable expansion is made by the [internal variable expansion mechanism](#gitlab-internal-variable-expansion-mechanism) in GitLab.<br/><br/> The value of the variable being substituted should be a period of time in a human readable natural language form. See [possible inputs](../yaml/index.md#environmentauto_stop_in) for more information.|

View File

@ -1453,6 +1453,7 @@ Must be used with `cache: paths`, or nothing is cached.
- `pull`
- `push`
- `pull-push` (default)
- [CI/CD variables](../variables/where_variables_can_be_used.md#gitlab-ciyml-file).
**Example of `cache:policy`**:
@ -1480,6 +1481,10 @@ faster-test-job:
- echo "Running tests..."
```
**Related topics**:
- You can [use a variable to control a job's cache policy](../caching/index.md#use-a-variable-to-control-a-jobs-cache-policy).
#### `cache:fallback_keys`
Use `cache:fallback_keys` to specify a list of keys to try to restore cache from

View File

@ -14,7 +14,6 @@ Because of the many registration paths and multiple verification stages, identit
Before you enable these features, ensure [hard email confirmation](../security/user_email_confirmation.md) is enabled and [Arkose](../integration/arkose.md#configuration) is configured properly.
| Feature flag name | Description |
|---------|-------------|
| `identity_verification` | Turns on email verification for all registration paths |

View File

@ -300,6 +300,40 @@ All UI strings should be prepared for translation by following our [internationa
The strings should use the integration name as [namespace](../i18n/externalization.md#namespaces), for example, `s_('FooBarIntegration|My string')`.
## Deprecate and remove an integration
To remove an integration, you must first deprecate the integration. For more information,
see the [feature deprecation guidelines](../../development/deprecation_guidelines/index.md).
### Deprecate an integration
You must announce any deprecation [no later than the third milestone preceding intended removal](../../development/deprecation_guidelines/index.md#when-can-a-feature-be-deprecated).
To deprecate an integration:
- [Add a deprecation entry](../../development/deprecation_guidelines/index.md#update-the-deprecations-and-removals-documentation-pages).
- [Mark the integration documentation as deprecated](../../development/documentation/versions.md#deprecate-a-page-or-topic).
- Optional. To prevent any new project-level records from
being created, add the integration to `Project#disabled_integrations` (see [example merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114835)).
### Remove an integration
To safely remove an integration, you must stage the removal across two milestones.
In the major milestone of intended removal (M.0), disable the integration and delete the records from the database:
- Remove the integration from `Integration::INTEGRATION_NAMES`.
- Delete the integration model's `#execute` and `#test` methods (if defined), but keep the model.
- Add a post-migration to delete the integration records from PostgreSQL (see [example merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114721)).
- [Add a removal entry](../../development/deprecation_guidelines/index.md#update-the-deprecations-and-removals-documentation-pages).
- [Mark the integration documentation as removed](../../development/documentation/versions.md#remove-a-page).
- [Update the integration API documentation](../../api/integrations.md).
In the next minor release (M.1):
- Remove the integration's model and any remaining code.
- Close any issues, merge requests, and epics that have the integration's label (`~Integration::<name>`).
- Delete the integration's label (`~Integration::<name>`) from `gitlab-org`.
## Ongoing migrations and refactorings
Developers should be aware that the Integrations team is in the process of

View File

@ -10,7 +10,7 @@ module Gitlab
include ::Gitlab::Config::Entry::Attributable
ALLOWED_KEYS = %i[key untracked paths when policy unprotect fallback_keys].freeze
ALLOWED_POLICY = %w[pull-push push pull].freeze
ALLOWED_POLICY = /pull-push|push|pull|\$\w{1,255}*/
DEFAULT_POLICY = 'pull-push'
ALLOWED_WHEN = %w[on_success on_failure always].freeze
DEFAULT_WHEN = 'on_success'
@ -18,9 +18,9 @@ module Gitlab
validations do
validates :config, type: Hash, allowed_keys: ALLOWED_KEYS
validates :policy, type: String, allow_blank: true, inclusion: {
in: ALLOWED_POLICY,
message: "should be one of: #{ALLOWED_POLICY.join(', ')}"
validates :policy, type: String, allow_blank: true, format: {
with: ALLOWED_POLICY,
message: "should be a variable or one of: pull-push, push, pull"
}
with_options allow_nil: true do

View File

@ -8,7 +8,7 @@ code_quality:
variables:
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: ""
CODE_QUALITY_IMAGE_TAG: "0.94.0"
CODE_QUALITY_IMAGE_TAG: "0.96.0"
CODE_QUALITY_IMAGE: "$CI_TEMPLATE_REGISTRY_HOST/gitlab-org/ci-cd/codequality:$CODE_QUALITY_IMAGE_TAG"
needs: []
script:

View File

@ -29,6 +29,59 @@ RSpec.describe Projects::MergeRequests::DiffsController, feature_category: :code
end
end
shared_examples 'diff tracking' do
it 'tracks mr_diffs event' do
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
.to receive(:track_mr_diffs_action)
.with(merge_request: merge_request)
method_call
end
context 'when DNT is enabled' do
before do
stub_do_not_track('1')
end
it 'does not track any mr_diffs event' do
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
.not_to receive(:track_mr_diffs_action)
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
.not_to receive(:track_mr_diffs_single_file_action)
method_call
end
end
context 'when user has view_diffs_file_by_file set to false' do
before do
user.update!(view_diffs_file_by_file: false)
end
it 'does not track single_file_diffs events' do
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
.not_to receive(:track_mr_diffs_single_file_action)
method_call
end
end
context 'when user has view_diffs_file_by_file set to true' do
before do
user.update!(view_diffs_file_by_file: true)
end
it 'tracks single_file_diffs events' do
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
.to receive(:track_mr_diffs_single_file_action)
.with(merge_request: merge_request, user: user)
method_call
end
end
end
shared_examples 'forked project with submodules' do
render_views
@ -327,6 +380,10 @@ RSpec.describe Projects::MergeRequests::DiffsController, feature_category: :code
context 'when the merge request exists' do
context 'when the user can view the merge request' do
context 'when the path exists in the diff' do
include_examples 'diff tracking' do
let(:method_call) { diff_for_path(old_path: existing_path, new_path: existing_path) }
end
it 'enables diff notes' do
diff_for_path(old_path: existing_path, new_path: existing_path)
@ -399,6 +456,10 @@ RSpec.describe Projects::MergeRequests::DiffsController, feature_category: :code
end
shared_examples_for 'successful request' do
include_examples 'diff tracking' do
let(:method_call) { subject }
end
it 'returns success' do
subject
@ -414,57 +475,6 @@ RSpec.describe Projects::MergeRequests::DiffsController, feature_category: :code
subject
end
it 'tracks mr_diffs event' do
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
.to receive(:track_mr_diffs_action)
.with(merge_request: merge_request)
subject
end
context 'when DNT is enabled' do
before do
stub_do_not_track('1')
end
it 'does not track any mr_diffs event' do
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
.not_to receive(:track_mr_diffs_action)
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
.not_to receive(:track_mr_diffs_single_file_action)
subject
end
end
context 'when user has view_diffs_file_by_file set to false' do
before do
user.update!(view_diffs_file_by_file: false)
end
it 'does not track single_file_diffs events' do
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
.not_to receive(:track_mr_diffs_single_file_action)
subject
end
end
context 'when user has view_diffs_file_by_file set to true' do
before do
user.update!(view_diffs_file_by_file: true)
end
it 'tracks single_file_diffs events' do
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
.to receive(:track_mr_diffs_single_file_action)
.with(merge_request: merge_request, user: user)
subject
end
end
end
def collection_arguments(pagination_data = {})

View File

@ -82,6 +82,7 @@ jest.mock('~/lib/utils/url_utility', () => ({
describe('GroupRunnersApp', () => {
let wrapper;
const showToast = jest.fn();
const findRunnerStats = () => wrapper.findComponent(RunnerStats);
const findRunnerActionsCell = () => wrapper.findComponent(RunnerActionsCell);
@ -123,6 +124,11 @@ describe('GroupRunnersApp', () => {
staleTimeoutSecs,
...provide,
},
mocks: {
$toast: {
show: showToast,
},
},
...options,
});
@ -250,8 +256,6 @@ describe('GroupRunnersApp', () => {
});
describe('Single runner row', () => {
let showToast;
const { webUrl, editUrl, node } = mockGroupRunnersEdges[0];
const { id: graphqlId, shortSha, jobExecutionStatus } = node;
const id = getIdFromGraphQLId(graphqlId);
@ -260,7 +264,6 @@ describe('GroupRunnersApp', () => {
beforeEach(async () => {
await createComponent({ mountFn: mountExtended });
showToast = jest.spyOn(wrapper.vm.$root.$toast, 'show');
});
it('Shows job status and links to jobs', () => {

View File

@ -82,6 +82,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Cache do
'pull-push' | 'pull-push'
'push' | 'push'
'pull' | 'pull'
'$VARIABLE' | '$VARIABLE'
'unknown' | 'unknown' # invalid
end
@ -145,6 +146,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Cache do
'pull-push' | true
'push' | true
'pull' | true
'$VARIABLE' | true
'unknown' | false
end
@ -280,7 +282,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Cache do
let(:config) { { policy: 'unknown' } }
it 'returns error' do
is_expected.to include('cache policy should be one of: pull-push, push, pull')
is_expected.to include('cache policy should be a variable or one of: pull-push, push, pull')
end
end

View File

@ -239,391 +239,3 @@ RSpec.shared_examples 'value stream analytics flow metrics deploymentCount examp
it_behaves_like 'validation on Time arguments'
end
RSpec.shared_examples 'value stream analytics flow metrics leadTime examples' do
let_it_be(:milestone) { create(:milestone, group: group) }
let_it_be(:label) { create(:group_label, group: group) }
let_it_be(:author) { create(:user) }
let_it_be(:assignee) { create(:user) }
let_it_be(:issue1) do
create(:issue, project: project1, author: author, created_at: 17.days.ago, closed_at: 12.days.ago)
end
let_it_be(:issue2) do
create(:issue, project: project2, author: author, created_at: 16.days.ago, closed_at: 13.days.ago)
end
let_it_be(:issue3) do
create(:labeled_issue,
project: project1,
labels: [label],
author: author,
milestone: milestone,
assignees: [assignee],
created_at: 14.days.ago,
closed_at: 11.days.ago)
end
let_it_be(:issue4) do
create(:labeled_issue,
project: project2,
labels: [label],
assignees: [assignee],
created_at: 20.days.ago,
closed_at: 15.days.ago)
end
before do
Analytics::CycleAnalytics::DataLoaderService.new(group: group, model: Issue).execute
end
let(:query) do
<<~QUERY
query($path: ID!, $assigneeUsernames: [String!], $authorUsername: String, $milestoneTitle: String, $labelNames: [String!], $from: Time!, $to: Time!) {
#{context}(fullPath: $path) {
flowMetrics {
leadTime(assigneeUsernames: $assigneeUsernames, authorUsername: $authorUsername, milestoneTitle: $milestoneTitle, labelNames: $labelNames, from: $from, to: $to) {
value
unit
identifier
title
links {
label
url
}
}
}
}
}
QUERY
end
let(:variables) do
{
path: full_path,
from: 21.days.ago.iso8601,
to: 10.days.ago.iso8601
}
end
subject(:result) do
post_graphql(query, current_user: current_user, variables: variables)
graphql_data.dig(context.to_s, 'flowMetrics', 'leadTime')
end
it 'returns the correct value' do
expect(result).to match(a_hash_including({
'identifier' => 'lead_time',
'unit' => n_('day', 'days', 4),
'value' => 4,
'title' => _('Lead Time'),
'links' => [
{ 'label' => s_('ValueStreamAnalytics|Dashboard'), 'url' => match(/issues_analytics/) },
{ 'label' => s_('ValueStreamAnalytics|Go to docs'), 'url' => match(/definitions/) }
]
}))
end
context 'when the user is not authorized' do
let(:current_user) { create(:user) }
it 'returns nil' do
expect(result).to eq(nil)
end
end
context 'when outside of the date range' do
let(:variables) do
{
path: full_path,
from: 30.days.ago.iso8601,
to: 25.days.ago.iso8601
}
end
it 'returns 0 count' do
expect(result).to match(a_hash_including({ 'value' => nil }))
end
end
context 'with all filters' do
let(:variables) do
{
path: full_path,
assigneeUsernames: [assignee.username],
labelNames: [label.title],
authorUsername: author.username,
milestoneTitle: milestone.title,
from: 20.days.ago.iso8601,
to: 10.days.ago.iso8601
}
end
it 'returns filtered count' do
expect(result).to match(a_hash_including({ 'value' => 3 }))
end
end
end
RSpec.shared_examples 'value stream analytics flow metrics cycleTime examples' do
let_it_be(:milestone) { create(:milestone, group: group) }
let_it_be(:label) { create(:group_label, group: group) }
let_it_be(:author) { create(:user) }
let_it_be(:assignee) { create(:user) }
let_it_be(:issue1) do
create(:issue, project: project1, author: author, closed_at: 12.days.ago).tap do |issue|
issue.metrics.update!(first_mentioned_in_commit_at: 17.days.ago)
end
end
let_it_be(:issue2) do
create(:issue, project: project2, author: author, closed_at: 13.days.ago).tap do |issue|
issue.metrics.update!(first_mentioned_in_commit_at: 16.days.ago)
end
end
let_it_be(:issue3) do
create(:labeled_issue,
project: project1,
labels: [label],
author: author,
milestone: milestone,
assignees: [assignee],
closed_at: 11.days.ago).tap do |issue|
issue.metrics.update!(first_mentioned_in_commit_at: 14.days.ago)
end
end
let_it_be(:issue4) do
create(:labeled_issue,
project: project2,
labels: [label],
assignees: [assignee],
closed_at: 15.days.ago).tap do |issue|
issue.metrics.update!(first_mentioned_in_commit_at: 20.days.ago)
end
end
before do
Analytics::CycleAnalytics::DataLoaderService.new(group: group, model: Issue).execute
end
let(:query) do
<<~QUERY
query($path: ID!, $assigneeUsernames: [String!], $authorUsername: String, $milestoneTitle: String, $labelNames: [String!], $from: Time!, $to: Time!) {
#{context}(fullPath: $path) {
flowMetrics {
cycleTime(assigneeUsernames: $assigneeUsernames, authorUsername: $authorUsername, milestoneTitle: $milestoneTitle, labelNames: $labelNames, from: $from, to: $to) {
value
unit
identifier
title
links {
label
url
}
}
}
}
}
QUERY
end
let(:variables) do
{
path: full_path,
from: 21.days.ago.iso8601,
to: 10.days.ago.iso8601
}
end
subject(:result) do
post_graphql(query, current_user: current_user, variables: variables)
graphql_data.dig(context.to_s, 'flowMetrics', 'cycleTime')
end
it 'returns the correct value' do
expect(result).to eq({
'identifier' => 'cycle_time',
'unit' => n_('day', 'days', 4),
'value' => 4,
'title' => _('Cycle Time'),
'links' => []
})
end
context 'when the user is not authorized' do
let(:current_user) { create(:user) }
it 'returns nil' do
expect(result).to eq(nil)
end
end
context 'when outside of the date range' do
let(:variables) do
{
path: full_path,
from: 30.days.ago.iso8601,
to: 25.days.ago.iso8601
}
end
it 'returns 0 count' do
expect(result).to match(a_hash_including({ 'value' => nil }))
end
end
context 'with all filters' do
let(:variables) do
{
path: full_path,
assigneeUsernames: [assignee.username],
labelNames: [label.title],
authorUsername: author.username,
milestoneTitle: milestone.title,
from: 20.days.ago.iso8601,
to: 10.days.ago.iso8601
}
end
it 'returns filtered count' do
expect(result).to match(a_hash_including({ 'value' => 3 }))
end
end
end
RSpec.shared_examples 'value stream analytics flow metrics issuesCompleted examples' do
let_it_be(:milestone) { create(:milestone, group: group) }
let_it_be(:label) { create(:group_label, group: group) }
let_it_be(:author) { create(:user) }
let_it_be(:assignee) { create(:user) }
# we don't care about opened date, only closed date.
let_it_be(:issue1) do
create(:issue, project: project1, author: author, created_at: 17.days.ago, closed_at: 12.days.ago)
end
let_it_be(:issue2) do
create(:issue, project: project2, author: author, created_at: 16.days.ago, closed_at: 13.days.ago)
end
let_it_be(:issue3) do
create(:labeled_issue,
project: project1,
labels: [label],
author: author,
milestone: milestone,
assignees: [assignee],
created_at: 14.days.ago,
closed_at: 11.days.ago)
end
let_it_be(:issue4) do
create(:labeled_issue,
project: project2,
labels: [label],
assignees: [assignee],
created_at: 20.days.ago,
closed_at: 15.days.ago)
end
before do
Analytics::CycleAnalytics::DataLoaderService.new(group: group, model: Issue).execute
end
let(:query) do
<<~QUERY
query($path: ID!, $assigneeUsernames: [String!], $authorUsername: String, $milestoneTitle: String, $labelNames: [String!], $from: Time!, $to: Time!) {
#{context}(fullPath: $path) {
flowMetrics {
issuesCompletedCount(assigneeUsernames: $assigneeUsernames, authorUsername: $authorUsername, milestoneTitle: $milestoneTitle, labelNames: $labelNames, from: $from, to: $to) {
value
unit
identifier
title
links {
label
url
}
}
}
}
}
QUERY
end
let(:variables) do
{
path: full_path,
from: 21.days.ago.iso8601,
to: 10.days.ago.iso8601
}
end
subject(:result) do
post_graphql(query, current_user: current_user, variables: variables)
graphql_data.dig(context.to_s, 'flowMetrics', 'issuesCompletedCount')
end
it 'returns the correct value' do
expect(result).to match(a_hash_including({
'identifier' => 'issues_completed',
'unit' => n_('issue', 'issues', 4),
'value' => 4,
'title' => _('Issues Completed'),
'links' => [
{ 'label' => s_('ValueStreamAnalytics|Dashboard'), 'url' => match(/issues_analytics/) },
{ 'label' => s_('ValueStreamAnalytics|Go to docs'), 'url' => match(/definitions/) }
]
}))
end
context 'when the user is not authorized' do
let(:current_user) { create(:user) }
it 'returns nil' do
expect(result).to eq(nil)
end
end
context 'when outside of the date range' do
let(:variables) do
{
path: full_path,
from: 30.days.ago.iso8601,
to: 25.days.ago.iso8601
}
end
it 'returns 0 count' do
expect(result).to match(a_hash_including({ 'value' => 0.0 }))
end
end
context 'with all filters' do
let(:variables) do
{
path: full_path,
assigneeUsernames: [assignee.username],
labelNames: [label.title],
authorUsername: author.username,
milestoneTitle: milestone.title,
from: 20.days.ago.iso8601,
to: 10.days.ago.iso8601
}
end
it 'returns filtered count' do
expect(result).to match(a_hash_including({ 'value' => 1.0 }))
end
end
end