Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-08-16 09:09:08 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-08-16 09:09:08 +0300
commit80f5d0d15f8d7ced767651978fb016072003f376 (patch)
tree8ddbe65b30cf5f2d2b6543bac7f7508ba259b9ec
parent2a3313dc5ec97ba732681fe034de900215807ac3 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/runner/components/search_tokens/tag_token.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue22
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue20
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue16
-rw-r--r--app/graphql/types/merge_request_sort_enum.rb2
-rw-r--r--app/helpers/sorting_helper.rb10
-rw-r--r--app/helpers/sorting_titles_values_helper.rb24
-rw-r--r--app/models/concerns/issuable.rb2
-rw-r--r--app/models/merge_request.rb18
-rw-r--r--app/views/shared/issuable/_sort_dropdown.html.haml1
-rw-r--r--doc/administration/gitaly/faq.md2
-rw-r--r--doc/administration/gitaly/index.md16
-rw-r--r--doc/administration/gitaly/praefect.md133
-rw-r--r--doc/administration/operations/moving_repositories.md126
-rw-r--r--doc/administration/repository_storage_paths.md2
-rw-r--r--doc/api/graphql/reference/index.md2
-rw-r--r--doc/api/group_repository_storage_moves.md2
-rw-r--r--doc/api/project_repository_storage_moves.md2
-rw-r--r--doc/api/snippet_repository_storage_moves.md2
-rw-r--r--locale/gitlab.pot12
-rwxr-xr-xscripts/api/cancel_pipeline.rb30
-rw-r--r--scripts/api/default_options.rb11
-rwxr-xr-xscripts/api/download_job_artifact.rb19
-rwxr-xr-xscripts/api/get_job_id.rb46
-rwxr-xr-xscripts/api/play_job.rb64
-rw-r--r--scripts/rspec_helpers.sh10
-rw-r--r--spec/features/merge_requests/user_lists_merge_requests_spec.rb32
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js24
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js12
-rw-r--r--spec/graphql/resolvers/merge_requests_resolver_spec.rb23
-rw-r--r--spec/models/merge_request_spec.rb38
-rw-r--r--spec/requests/api/graphql/project/merge_requests_spec.rb40
33 files changed, 435 insertions, 338 deletions
diff --git a/app/assets/javascripts/runner/components/search_tokens/tag_token.vue b/app/assets/javascripts/runner/components/search_tokens/tag_token.vue
index 0c69072f06a..51fae60b6b7 100644
--- a/app/assets/javascripts/runner/components/search_tokens/tag_token.vue
+++ b/app/assets/javascripts/runner/components/search_tokens/tag_token.vue
@@ -28,11 +28,6 @@ export default {
};
},
methods: {
- fnCurrentTokenValue(data) {
- // By default, values are transformed with `toLowerCase`
- // however, runner tags are case sensitive.
- return data;
- },
getTagsOptions(search) {
// TODO This should be implemented via a GraphQL API
// The API should
@@ -72,7 +67,6 @@ export default {
:config="config"
:suggestions-loading="loading"
:suggestions="tags"
- :fn-current-token-value="fnCurrentTokenValue"
:recent-suggestions-storage-key="config.recentTokenValuesStorageKey"
@fetch-suggestions="fetchTags"
v-on="$listeners"
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue
index a25a19a006c..ae5d3965de1 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue
@@ -31,19 +31,25 @@ export default {
data() {
return {
authors: this.config.initialAuthors || [],
- defaultAuthors: this.config.defaultAuthors || [DEFAULT_LABEL_ANY],
- preloadedAuthors: this.config.preloadedAuthors || [],
loading: false,
};
},
+ computed: {
+ defaultAuthors() {
+ return this.config.defaultAuthors || [DEFAULT_LABEL_ANY];
+ },
+ preloadedAuthors() {
+ return this.config.preloadedAuthors || [];
+ },
+ },
methods: {
- getActiveAuthor(authors, currentValue) {
- return authors.find((author) => author.username.toLowerCase() === currentValue);
+ getActiveAuthor(authors, data) {
+ return authors.find((author) => author.username.toLowerCase() === data.toLowerCase());
},
getAvatarUrl(author) {
return author.avatarUrl || author.avatar_url;
},
- fetchAuthorBySearchTerm(searchTerm) {
+ fetchAuthors(searchTerm) {
this.loading = true;
const fetchPromise = this.config.fetchPath
? this.config.fetchAuthors(this.config.fetchPath, searchTerm)
@@ -76,11 +82,11 @@ export default {
:active="active"
:suggestions-loading="loading"
:suggestions="authors"
- :fn-active-token-value="getActiveAuthor"
+ :get-active-token-value="getActiveAuthor"
:default-suggestions="defaultAuthors"
:preloaded-suggestions="preloadedAuthors"
:recent-suggestions-storage-key="config.recentSuggestionsStorageKey"
- @fetch-suggestions="fetchAuthorBySearchTerm"
+ @fetch-suggestions="fetchAuthors"
v-on="$listeners"
>
<template #view="{ viewTokenProps: { inputValue, activeTokenValue } }">
@@ -91,7 +97,7 @@ export default {
shape="circle"
class="gl-mr-2"
/>
- <span>{{ activeTokenValue ? activeTokenValue.name : inputValue }}</span>
+ {{ activeTokenValue ? activeTokenValue.name : inputValue }}
</template>
<template #suggestions-list="{ suggestions }">
<gl-filtered-search-suggestion
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue
index 102c513c145..172b5c402f6 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue
@@ -42,12 +42,10 @@ export default {
required: false,
default: () => [],
},
- fnActiveTokenValue: {
+ getActiveTokenValue: {
type: Function,
required: false,
- default: (suggestions, currentTokenValue) => {
- return suggestions.find(({ value }) => value === currentTokenValue);
- },
+ default: (suggestions, data) => suggestions.find(({ value }) => value === data),
},
defaultSuggestions: {
type: Array,
@@ -69,11 +67,6 @@ export default {
required: false,
default: 'id',
},
- fnCurrentTokenValue: {
- type: Function,
- required: false,
- default: null,
- },
},
data() {
return {
@@ -81,7 +74,6 @@ export default {
recentSuggestions: this.recentSuggestionsStorageKey
? getRecentlyUsedSuggestions(this.recentSuggestionsStorageKey)
: [],
- loading: false,
};
},
computed: {
@@ -94,14 +86,8 @@ export default {
preloadedTokenIds() {
return this.preloadedSuggestions.map((tokenValue) => tokenValue[this.valueIdentifier]);
},
- currentTokenValue() {
- if (this.fnCurrentTokenValue) {
- return this.fnCurrentTokenValue(this.value.data);
- }
- return this.value.data.toLowerCase();
- },
activeTokenValue() {
- return this.fnActiveTokenValue(this.suggestions, this.currentTokenValue);
+ return this.getActiveTokenValue(this.suggestions, this.value.data);
},
/**
* Return all the suggestions when searchKey is present
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue
index 4d08f81fee9..ae514c47068 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue
@@ -33,14 +33,18 @@ export default {
data() {
return {
labels: this.config.initialLabels || [],
- defaultLabels: this.config.defaultLabels || DEFAULT_LABELS,
loading: false,
};
},
+ computed: {
+ defaultLabels() {
+ return this.config.defaultLabels || DEFAULT_LABELS;
+ },
+ },
methods: {
- getActiveLabel(labels, currentValue) {
+ getActiveLabel(labels, data) {
return labels.find(
- (label) => this.getLabelName(label).toLowerCase() === stripQuotes(currentValue),
+ (label) => this.getLabelName(label).toLowerCase() === stripQuotes(data).toLowerCase(),
);
},
/**
@@ -68,7 +72,7 @@ export default {
}
return {};
},
- fetchLabelBySearchTerm(searchTerm) {
+ fetchLabels(searchTerm) {
this.loading = true;
this.config
.fetchLabels(searchTerm)
@@ -98,10 +102,10 @@ export default {
:active="active"
:suggestions-loading="loading"
:suggestions="labels"
- :fn-active-token-value="getActiveLabel"
+ :get-active-token-value="getActiveLabel"
:default-suggestions="defaultLabels"
:recent-suggestions-storage-key="config.recentSuggestionsStorageKey"
- @fetch-suggestions="fetchLabelBySearchTerm"
+ @fetch-suggestions="fetchLabels"
v-on="$listeners"
>
<template
diff --git a/app/graphql/types/merge_request_sort_enum.rb b/app/graphql/types/merge_request_sort_enum.rb
index 92a71998d91..d75eae6abc4 100644
--- a/app/graphql/types/merge_request_sort_enum.rb
+++ b/app/graphql/types/merge_request_sort_enum.rb
@@ -7,5 +7,7 @@ module Types
value 'MERGED_AT_ASC', 'Merge time by ascending order.', value: :merged_at_asc
value 'MERGED_AT_DESC', 'Merge time by descending order.', value: :merged_at_desc
+ value 'CLOSED_AT_ASC', 'Closed time by ascending order.', value: :closed_at_asc
+ value 'CLOSED_AT_DESC', 'Closed time by descending order.', value: :closed_at_desc
end
end
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index da32dfb0b9b..7fa85d143f7 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -3,6 +3,7 @@
module SortingHelper
include SortingTitlesValuesHelper
+ # rubocop: disable Metrics/AbcSize
def sort_options_hash
{
sort_value_created_date => sort_title_created_date,
@@ -29,6 +30,9 @@ module SortingHelper
sort_value_merged_date => sort_title_merged_date,
sort_value_merged_recently => sort_title_merged_recently,
sort_value_merged_earlier => sort_title_merged_earlier,
+ sort_value_closed_date => sort_title_closed_date,
+ sort_value_closed_recently => sort_title_closed_recently,
+ sort_value_closed_earlier => sort_title_closed_earlier,
sort_value_upvotes => sort_title_upvotes,
sort_value_contacted_date => sort_title_contacted_date,
sort_value_relative_position => sort_title_relative_position,
@@ -36,6 +40,7 @@ module SortingHelper
sort_value_expire_date => sort_title_expire_date
}
end
+ # rubocop: enable Metrics/AbcSize
def projects_sort_options_hash
use_old_sorting = Feature.disabled?(:project_list_filter_bar) || current_controller?('admin/projects')
@@ -182,6 +187,7 @@ module SortingHelper
sort_value_milestone_later => sort_value_milestone,
sort_value_due_date_later => sort_value_due_date,
sort_value_merged_recently => sort_value_merged_date,
+ sort_value_closed_recently => sort_value_closed_date,
sort_value_least_popular => sort_value_popularity
}
end
@@ -196,6 +202,8 @@ module SortingHelper
sort_value_due_date_soon => sort_value_due_date_later,
sort_value_merged_date => sort_value_merged_recently,
sort_value_merged_earlier => sort_value_merged_recently,
+ sort_value_closed_date => sort_value_closed_recently,
+ sort_value_closed_earlier => sort_value_closed_recently,
sort_value_popularity => sort_value_least_popular,
sort_value_most_popular => sort_value_least_popular
}.merge(issuable_sort_option_overrides)
@@ -216,7 +224,7 @@ module SortingHelper
def sort_direction_icon(sort_value)
case sort_value
- when sort_value_milestone, sort_value_due_date, sort_value_merged_date, /_asc\z/
+ when sort_value_milestone, sort_value_due_date, sort_value_merged_date, sort_value_closed_date, /_asc\z/
'sort-lowest'
else
'sort-highest'
diff --git a/app/helpers/sorting_titles_values_helper.rb b/app/helpers/sorting_titles_values_helper.rb
index 9b839f4e9bc..f4117d690f3 100644
--- a/app/helpers/sorting_titles_values_helper.rb
+++ b/app/helpers/sorting_titles_values_helper.rb
@@ -38,6 +38,18 @@ module SortingTitlesValuesHelper
s_('SortOptions|Merged earlier')
end
+ def sort_title_closed_date
+ s_('SortOptions|Closed date')
+ end
+
+ def sort_title_closed_recently
+ s_('SortOptions|Closed recently')
+ end
+
+ def sort_title_closed_earlier
+ s_('SortOptions|Closed earlier')
+ end
+
def sort_title_largest_group
s_('SortOptions|Largest group')
end
@@ -199,6 +211,18 @@ module SortingTitlesValuesHelper
'merged_at_asc'
end
+ def sort_value_closed_date
+ 'closed_at'
+ end
+
+ def sort_value_closed_recently
+ 'closed_at_desc'
+ end
+
+ def sort_value_closed_earlier
+ 'closed_at_asc'
+ end
+
def sort_value_largest_group
'storage_size_desc'
end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index d5e2e63402f..9b3f517cdee 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -374,6 +374,8 @@ module Issuable
grouping_columns << milestone_table[:due_date]
elsif %w(merged_at_desc merged_at_asc).include?(sort)
grouping_columns << MergeRequest::Metrics.arel_table[:merged_at]
+ elsif %w(closed_at_desc closed_at_asc).include?(sort)
+ grouping_columns << MergeRequest::Metrics.arel_table[:closed_at]
end
grouping_columns
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 7ca83d1d68c..a090ac87cc9 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -329,16 +329,16 @@ class MergeRequest < ApplicationRecord
where("target_branch LIKE ?", ApplicationRecord.sanitize_sql_like(wildcard_branch_name).tr('*', '%'))
end
scope :by_target_branch, ->(branch_name) { where(target_branch: branch_name) }
- scope :order_merged_at, ->(direction) do
+ scope :order_by_metric, ->(metric, direction) do
reverse_direction = { 'ASC' => 'DESC', 'DESC' => 'ASC' }
reversed_direction = reverse_direction[direction] || raise("Unknown sort direction was given: #{direction}")
order = Gitlab::Pagination::Keyset::Order.build([
Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
- attribute_name: 'merge_request_metrics_merged_at',
- column_expression: MergeRequest::Metrics.arel_table[:merged_at],
- order_expression: Gitlab::Database.nulls_last_order('merge_request_metrics.merged_at', direction),
- reversed_order_expression: Gitlab::Database.nulls_first_order('merge_request_metrics.merged_at', reversed_direction),
+ attribute_name: "merge_request_metrics_#{metric}",
+ column_expression: MergeRequest::Metrics.arel_table[metric],
+ order_expression: Gitlab::Database.nulls_last_order("merge_request_metrics.#{metric}", direction),
+ reversed_order_expression: Gitlab::Database.nulls_first_order("merge_request_metrics.#{metric}", reversed_direction),
order_direction: direction,
nullable: :nulls_last,
distinct: false,
@@ -353,8 +353,10 @@ class MergeRequest < ApplicationRecord
order.apply_cursor_conditions(join_metrics).order(order)
end
- scope :order_merged_at_asc, -> { order_merged_at('ASC') }
- scope :order_merged_at_desc, -> { order_merged_at('DESC') }
+ scope :order_merged_at_asc, -> { order_by_metric(:merged_at, 'ASC') }
+ scope :order_merged_at_desc, -> { order_by_metric(:merged_at, 'DESC') }
+ scope :order_closed_at_asc, -> { order_by_metric(:latest_closed_at, 'ASC') }
+ scope :order_closed_at_desc, -> { order_by_metric(:latest_closed_at, 'DESC') }
scope :preload_source_project, -> { preload(:source_project) }
scope :preload_target_project, -> { preload(:target_project) }
scope :preload_routables, -> do
@@ -452,7 +454,9 @@ class MergeRequest < ApplicationRecord
def self.sort_by_attribute(method, excluded_labels: [])
case method.to_s
when 'merged_at', 'merged_at_asc' then order_merged_at_asc
+ when 'closed_at', 'closed_at_asc' then order_closed_at_asc
when 'merged_at_desc' then order_merged_at_desc
+ when 'closed_at_desc' then order_closed_at_desc
else
super
end
diff --git a/app/views/shared/issuable/_sort_dropdown.html.haml b/app/views/shared/issuable/_sort_dropdown.html.haml
index caf271e9ee9..f5bf010e4db 100644
--- a/app/views/shared/issuable/_sort_dropdown.html.haml
+++ b/app/views/shared/issuable/_sort_dropdown.html.haml
@@ -19,6 +19,7 @@
= sortable_item(sort_title_popularity, page_filter_path(sort: sort_value_popularity), sort_title)
= sortable_item(sort_title_label_priority, page_filter_path(sort: sort_value_label_priority), sort_title)
= sortable_item(sort_title_merged_date, page_filter_path(sort: sort_value_merged_date), sort_title) if viewing_merge_requests
+ = sortable_item(sort_title_closed_date, page_filter_path(sort: sort_value_closed_date), sort_title) if viewing_merge_requests
= sortable_item(sort_title_relative_position, page_filter_path(sort: sort_value_relative_position), sort_title) if viewing_issues
= render_if_exists('shared/ee/issuable/sort_dropdown', viewing_issues: viewing_issues, sort_title: sort_title)
= issuable_sort_direction_button(sort_value)
diff --git a/doc/administration/gitaly/faq.md b/doc/administration/gitaly/faq.md
index 2712432f6dc..c7ecaa020e0 100644
--- a/doc/administration/gitaly/faq.md
+++ b/doc/administration/gitaly/faq.md
@@ -35,7 +35,7 @@ For more information, see:
## Are there instructions for migrating to Gitaly Cluster?
-Yes! For more information, see [Migrate to Gitaly Cluster](praefect.md#migrate-to-gitaly-cluster).
+Yes! For more information, see [Migrate to Gitaly Cluster](index.md#migrate-to-gitaly-cluster).
## What are some repository storage recommendations?
diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md
index 86c0ce392f2..bca83e903ac 100644
--- a/doc/administration/gitaly/index.md
+++ b/doc/administration/gitaly/index.md
@@ -306,6 +306,18 @@ For configuration information, see [Configure replication factor](praefect.md#co
For more information on configuring Gitaly Cluster, see [Configure Gitaly Cluster](praefect.md).
+### Migrate to Gitaly Cluster
+
+Whether migrating to Gitaly Cluster because of [NFS support deprecation](index.md#nfs-deprecation-notice)
+or to move from single Gitaly nodes, the basic process involves:
+
+1. Create the required storage. Refer to
+ [repository storage recommendations](faq.md#what-are-some-repository-storage-recommendations).
+1. Create and configure [Gitaly Cluster](praefect.md).
+1. [Move the repositories](../operations/moving_repositories.md#move-repositories). To migrate to
+ Gitaly Cluster, existing repositories stored outside Gitaly Cluster must be moved. There is no
+ automatic migration but the moves can be scheduled with the GitLab API.
+
## Monitor Gitaly and Gitaly Cluster
You can use the available logs and [Prometheus metrics](../monitoring/prometheus/index.md) to
@@ -389,7 +401,7 @@ The following are useful queries for monitoring Gitaly:
{enforced="true",status="ok"} 4424.985419441742
```
- There may also be other numbers with rate 0, but you only need to take note of the non-zero numbers.
+ There may also be other numbers with rate 0, but you only have to take note of the non-zero numbers.
The only non-zero number should have `enforced="true",status="ok"`. If you have other non-zero
numbers, something is wrong in your configuration.
@@ -560,7 +572,7 @@ Additional information:
GitLab recommends:
- Creating a [Gitaly Cluster](#gitaly-cluster) as soon as possible.
-- [Moving your repositories](praefect.md#migrate-to-gitaly-cluster) from NFS-based storage to Gitaly
+- [Moving your repositories](#migrate-to-gitaly-cluster) from NFS-based storage to Gitaly
Cluster.
We welcome your feedback on this process. You can:
diff --git a/doc/administration/gitaly/praefect.md b/doc/administration/gitaly/praefect.md
index 6a794dba4f9..4af7f1a58a5 100644
--- a/doc/administration/gitaly/praefect.md
+++ b/doc/administration/gitaly/praefect.md
@@ -432,7 +432,7 @@ On the **Praefect** node:
WARNING:
If you have data on an already existing storage called
`default`, you should configure the virtual storage with another name and
- [migrate the data to the Gitaly Cluster storage](#migrate-to-gitaly-cluster)
+ [migrate the data to the Gitaly Cluster storage](index.md#migrate-to-gitaly-cluster)
afterwards.
Replace `PRAEFECT_INTERNAL_TOKEN` with a strong secret, which is used by
@@ -896,7 +896,7 @@ Particular attention should be shown to:
WARNING:
If you have existing data stored on the default Gitaly storage,
- you should [migrate the data your Gitaly Cluster storage](#migrate-to-gitaly-cluster)
+ you should [migrate the data your Gitaly Cluster storage](index.md#migrate-to-gitaly-cluster)
first.
```ruby
@@ -1574,132 +1574,3 @@ sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.t
- Replace the placeholder `<virtual-storage>` with the virtual storage containing the Gitaly node storage to be checked.
- Replace the placeholder `<up-to-date-storage>` with the Gitaly storage name containing up to date repositories.
- Replace the placeholder `<outdated-storage>` with the Gitaly storage name containing outdated repositories.
-
-## Migrate to Gitaly Cluster
-
-Whether migrating to Gitaly Cluster because of [NFS support deprecation](index.md#nfs-deprecation-notice)
-or to move from single Gitaly nodes, the basic process involves:
-
-1. Create the required storage.
-1. Create and configure Gitaly Cluster.
-1. [Move the repositories](#move-repositories).
-
-When creating the storage, see some
-[repository storage recommendations](faq.md#what-are-some-repository-storage-recommendations).
-
-### Move Repositories
-
-WARNING:
-To move repositories into a Gitaly Cluster in GitLab versions 13.12 to 14.1, you must
-[enable the `gitaly_replicate_repository_direct_fetch` feature flag](../feature_flags.md).
-
-To migrate to Gitaly Cluster, existing repositories stored outside Gitaly Cluster must be
-moved. There is no automatic migration but the moves can be scheduled with the GitLab API.
-
-GitLab repositories can be associated with projects, groups, and snippets. Each of these types
-have a separate API to schedule the respective repositories to move. To move all repositories
-on a GitLab instance, each of these types must be scheduled to move for each storage.
-
-Each repository is made read-only for the duration of the move. The repository is not writable
-until the move has completed.
-
-After creating and configuring Gitaly Cluster:
-
-1. Ensure all storages are accessible to the GitLab instance. In this example, these are
- `<original_storage_name>` and `<cluster_storage_name>`.
-1. [Configure repository storage weights](../repository_storage_paths.md#configure-where-new-repositories-are-stored)
- so that the Gitaly Cluster receives all new projects. This stops new projects from being created
- on existing Gitaly nodes while the migration is in progress.
-1. Schedule repository moves for:
- - [Projects](#bulk-schedule-project-moves).
- - [Snippets](#bulk-schedule-snippet-moves).
- - [Groups](#bulk-schedule-group-moves). **(PREMIUM SELF)**
-
-#### Bulk schedule project moves
-
-1. [Schedule repository storage moves for all projects on a storage shard](../../api/project_repository_storage_moves.md#schedule-repository-storage-moves-for-all-projects-on-a-storage-shard) using the API. For example:
-
- ```shell
- curl --request POST --header "Private-Token: <your_access_token>" \
- --header "Content-Type: application/json" \
- --data '{"source_storage_name":"<original_storage_name>","destination_storage_name":"<cluster_storage_name>"}' \
- "https://gitlab.example.com/api/v4/project_repository_storage_moves"
- ```
-
-1. [Query the most recent repository moves](../../api/project_repository_storage_moves.md#retrieve-all-project-repository-storage-moves)
- using the API. The query indicates either:
- - The moves have completed successfully. The `state` field is `finished`.
- - The moves are in progress. Re-query the repository move until it completes successfully.
- - The moves have failed. Most failures are temporary and are solved by rescheduling the move.
-
-1. After the moves are complete, [query projects](../../api/projects.md#list-all-projects)
- using the API to confirm that all projects have moved. No projects should be returned
- with `repository_storage` field set to the old storage.
-
- ```shell
- curl --header "Private-Token: <your_access_token>" --header "Content-Type: application/json" \
- "https://gitlab.example.com/api/v4/projects?repository_storage=<original_storage_name>"
- ```
-
- Alternatively use [the rails console](../operations/rails_console.md) to
- confirm that all projects have moved. Run the following in the rails console:
-
- ```ruby
- ProjectRepository.for_repository_storage('<original_storage_name>')
- ```
-
-1. Repeat for each storage as required.
-
-#### Bulk schedule snippet moves
-
-1. [Schedule repository storage moves for all snippets on a storage shard](../../api/snippet_repository_storage_moves.md#schedule-repository-storage-moves-for-all-snippets-on-a-storage-shard) using the API. For example:
-
- ```shell
- curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
- --header "Content-Type: application/json" \
- --data '{"source_storage_name":"<original_storage_name>","destination_storage_name":"<cluster_storage_name>"}' \
- "https://gitlab.example.com/api/v4/snippet_repository_storage_moves"
- ```
-
-1. [Query the most recent repository moves](../../api/snippet_repository_storage_moves.md#retrieve-all-snippet-repository-storage-moves)
- using the API. The query indicates either:
- - The moves have completed successfully. The `state` field is `finished`.
- - The moves are in progress. Re-query the repository move until it completes successfully.
- - The moves have failed. Most failures are temporary and are solved by rescheduling the move.
-
-1. After the moves are complete, use [the rails console](../operations/rails_console.md) to
- confirm that all snippets have moved. No snippets should be returned for the original
- storage. Run the following in the rails console:
-
- ```ruby
- SnippetRepository.for_repository_storage('<original_storage_name>')
- ```
-
-1. Repeat for each storage as required.
-
-#### Bulk schedule group moves **(PREMIUM SELF)**
-
-1. [Schedule repository storage moves for all groups on a storage shard](../../api/group_repository_storage_moves.md#schedule-repository-storage-moves-for-all-groups-on-a-storage-shard) using the API.
-
- ```shell
- curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
- --header "Content-Type: application/json" \
- --data '{"source_storage_name":"<original_storage_name>","destination_storage_name":"<cluster_storage_name>"}' \
- "https://gitlab.example.com/api/v4/group_repository_storage_moves"
- ```
-
-1. [Query the most recent repository moves](../../api/group_repository_storage_moves.md#retrieve-all-group-repository-storage-moves)
- using the API. The query indicates either:
- - The moves have completed successfully. The `state` field is `finished`.
- - The moves are in progress. Re-query the repository move until it completes successfully.
- - The moves have failed. Most failures are temporary and are solved by rescheduling the move.
-
-1. After the moves are complete, use [the rails console](../operations/rails_console.md) to
- confirm that all groups have moved. No groups should be returned for the original
- storage. Run the following in the rails console:
-
- ```ruby
- GroupWikiRepository.for_repository_storage('<original_storage_name>')
- ```
-
-1. Repeat for each storage as required.
diff --git a/doc/administration/operations/moving_repositories.md b/doc/administration/operations/moving_repositories.md
index fba20da9aea..765cf64e735 100644
--- a/doc/administration/operations/moving_repositories.md
+++ b/doc/administration/operations/moving_repositories.md
@@ -7,8 +7,7 @@ type: reference
# Moving repositories managed by GitLab **(FREE SELF)**
-Sometimes you need to move all repositories managed by GitLab to
-another file system or another server.
+You can move all repositories managed by GitLab to another file system or another server.
## Moving data within a GitLab instance
@@ -28,7 +27,128 @@ For more information, see:
querying and scheduling snippet repository moves.
- [The API documentation](../../api/group_repository_storage_moves.md) details the endpoints for
querying and scheduling group repository moves **(PREMIUM SELF)**.
-- [Migrate to Gitaly Cluster](../gitaly/praefect.md#migrate-to-gitaly-cluster).
+- [Migrate to Gitaly Cluster](../gitaly/index.md#migrate-to-gitaly-cluster).
+
+### Move Repositories
+
+GitLab repositories can be associated with projects, groups, and snippets. Each of these types
+have a separate API to schedule the respective repositories to move. To move all repositories
+on a GitLab instance, each of these types must be scheduled to move for each storage.
+
+WARNING:
+To move repositories into a [Gitaly Cluster](../gitaly/index.md#gitaly-cluster) in GitLab versions
+13.12 to 14.1, you must [enable the `gitaly_replicate_repository_direct_fetch` feature flag](../feature_flags.md).
+
+Each repository is made read-only for the duration of the move. The repository is not writable
+until the move has completed.
+
+To move repositories:
+
+1. Ensure all storages are accessible to the GitLab instance. In this example, these are
+ `<original_storage_name>` and `<cluster_storage_name>`.
+1. [Configure repository storage weights](../repository_storage_paths.md#configure-where-new-repositories-are-stored)
+ so that the new storages receives all new projects. This stops new projects from being created
+ on existing storages while the migration is in progress.
+1. Schedule repository moves for:
+ - [Projects](#bulk-schedule-project-moves).
+ - [Snippets](#bulk-schedule-snippet-moves).
+ - [Groups](#bulk-schedule-group-moves). **(PREMIUM SELF)**
+
+### Bulk schedule project moves
+
+Use the API to schedule project moves:
+
+1. [Schedule repository storage moves for all projects on a storage shard](../../api/project_repository_storage_moves.md#schedule-repository-storage-moves-for-all-projects-on-a-storage-shard)
+ using the API. For example:
+
+ ```shell
+ curl --request POST --header "Private-Token: <your_access_token>" \
+ --header "Content-Type: application/json" \
+ --data '{"source_storage_name":"<original_storage_name>","destination_storage_name":"<cluster_storage_name>"}' \
+ "https://gitlab.example.com/api/v4/project_repository_storage_moves"
+ ```
+
+1. [Query the most recent repository moves](../../api/project_repository_storage_moves.md#retrieve-all-project-repository-storage-moves)
+ using the API. The response indicates either:
+ - The moves have completed successfully. The `state` field is `finished`.
+ - The moves are in progress. Re-query the repository move until it completes successfully.
+ - The moves have failed. Most failures are temporary and are solved by rescheduling the move.
+
+1. After the moves are complete, use the API to [query projects](../../api/projects.md#list-all-projects) and confirm that all projects have moved. None of the projects should be returned with the
+ `repository_storage` field set to the old storage. For example:
+
+ ```shell
+ curl --header "Private-Token: <your_access_token>" --header "Content-Type: application/json" \
+ "https://gitlab.example.com/api/v4/projects?repository_storage=<original_storage_name>"
+ ```
+
+ Alternatively use [the rails console](../operations/rails_console.md) to confirm that all
+ projects have moved. Run the following in the rails console:
+
+ ```ruby
+ ProjectRepository.for_repository_storage('<original_storage_name>')
+ ```
+
+1. Repeat for each storage as required.
+
+### Bulk schedule snippet moves
+
+Use the API to schedule snippet moves:
+
+1. [Schedule repository storage moves for all snippets on a storage shard](../../api/snippet_repository_storage_moves.md#schedule-repository-storage-moves-for-all-snippets-on-a-storage-shard). For example:
+
+ ```shell
+ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
+ --header "Content-Type: application/json" \
+ --data '{"source_storage_name":"<original_storage_name>","destination_storage_name":"<cluster_storage_name>"}' \
+ "https://gitlab.example.com/api/v4/snippet_repository_storage_moves"
+ ```
+
+1. [Query the most recent repository moves](../../api/snippet_repository_storage_moves.md#retrieve-all-snippet-repository-storage-moves)
+The response indicates either:
+ - The moves have completed successfully. The `state` field is `finished`.
+ - The moves are in progress. Re-query the repository move until it completes successfully.
+ - The moves have failed. Most failures are temporary and are solved by rescheduling the move.
+
+1. After the moves are complete, use [the rails console](../operations/rails_console.md) to confirm
+ that all snippets have moved. No snippets should be returned for the original storage. Run the
+ following in the rails console:
+
+ ```ruby
+ SnippetRepository.for_repository_storage('<original_storage_name>')
+ ```
+
+1. Repeat for each storage as required.
+
+### Bulk schedule group moves **(PREMIUM SELF)**
+
+Use the API to schedule group moves:
+
+1. [Schedule repository storage moves for all groups on a storage shard](../../api/group_repository_storage_moves.md#schedule-repository-storage-moves-for-all-groups-on-a-storage-shard)
+. For example:
+
+ ```shell
+ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
+ --header "Content-Type: application/json" \
+ --data '{"source_storage_name":"<original_storage_name>","destination_storage_name":"<cluster_storage_name>"}' \
+ "https://gitlab.example.com/api/v4/group_repository_storage_moves"
+ ```
+
+1. [Query the most recent repository moves](../../api/group_repository_storage_moves.md#retrieve-all-group-repository-storage-moves)
+. The response indicates either:
+ - The moves have completed successfully. The `state` field is `finished`.
+ - The moves are in progress. Re-query the repository move until it completes successfully.
+ - The moves have failed. Most failures are temporary and are solved by rescheduling the move.
+
+1. After the moves are complete, use [the rails console](../operations/rails_console.md) to confirm
+ that all groups have moved. No groups should be returned for the original storage. Run the
+ following in the rails console:
+
+ ```ruby
+ GroupWikiRepository.for_repository_storage('<original_storage_name>')
+ ```
+
+1. Repeat for each storage as required.
## Migrating to another GitLab instance
diff --git a/doc/administration/repository_storage_paths.md b/doc/administration/repository_storage_paths.md
index 68f351e737a..be1a3a56e4f 100644
--- a/doc/administration/repository_storage_paths.md
+++ b/doc/administration/repository_storage_paths.md
@@ -156,4 +156,4 @@ paths, the more often it is chosen. That is,
## Move repositories
To move a repository to a different repository storage (for example, from `default` to `storage2`), use the
-same process as [migrating to Gitaly Cluster](gitaly/praefect.md#migrate-to-gitaly-cluster).
+same process as [migrating to Gitaly Cluster](gitaly/index.md#migrate-to-gitaly-cluster).
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 3f9fd5c1d34..8fb90ab6960 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -15251,6 +15251,8 @@ Values for sorting merge requests.
| Value | Description |
| ----- | ----------- |
+| <a id="mergerequestsortclosed_at_asc"></a>`CLOSED_AT_ASC` | Closed time by ascending order. |
+| <a id="mergerequestsortclosed_at_desc"></a>`CLOSED_AT_DESC` | Closed time by descending order. |
| <a id="mergerequestsortcreated_asc"></a>`CREATED_ASC` | Created at ascending order. |
| <a id="mergerequestsortcreated_desc"></a>`CREATED_DESC` | Created at descending order. |
| <a id="mergerequestsortlabel_priority_asc"></a>`LABEL_PRIORITY_ASC` | Label priority by ascending order. |
diff --git a/doc/api/group_repository_storage_moves.md b/doc/api/group_repository_storage_moves.md
index a54f50da46b..a893bffb1f5 100644
--- a/doc/api/group_repository_storage_moves.md
+++ b/doc/api/group_repository_storage_moves.md
@@ -10,7 +10,7 @@ type: reference
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/53016) in GitLab 13.9.
Group repositories can be moved between storages. This API can help you when
-[migrating to Gitaly Cluster](../administration/gitaly/praefect.md#migrate-to-gitaly-cluster), for
+[migrating to Gitaly Cluster](../administration/gitaly/index.md#migrate-to-gitaly-cluster), for
example, or to migrate a [group wiki](../user/project/wiki/index.md#group-wikis).
As group repository storage moves are processed, they transition through different states. Values
diff --git a/doc/api/project_repository_storage_moves.md b/doc/api/project_repository_storage_moves.md
index fe2750fa4bf..ebb15e1c1d6 100644
--- a/doc/api/project_repository_storage_moves.md
+++ b/doc/api/project_repository_storage_moves.md
@@ -10,7 +10,7 @@ type: reference
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/31285) in GitLab 13.0.
Project repositories including wiki and design repositories can be moved between storages. This can be useful when
-[migrating to Gitaly Cluster](../administration/gitaly/praefect.md#migrate-to-gitaly-cluster),
+[migrating to Gitaly Cluster](../administration/gitaly/index.md#migrate-to-gitaly-cluster),
for example.
As project repository storage moves are processed, they transition through different states. Values
diff --git a/doc/api/snippet_repository_storage_moves.md b/doc/api/snippet_repository_storage_moves.md
index 9951e073c39..a73542c8505 100644
--- a/doc/api/snippet_repository_storage_moves.md
+++ b/doc/api/snippet_repository_storage_moves.md
@@ -10,7 +10,7 @@ type: reference
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49228) in GitLab 13.8.
Snippet repositories can be moved between storages. This can be useful when
-[migrating to Gitaly Cluster](../administration/gitaly/praefect.md#migrate-to-gitaly-cluster), for
+[migrating to Gitaly Cluster](../administration/gitaly/index.md#migrate-to-gitaly-cluster), for
example.
As snippet repository storage moves are processed, they transition through different states. Values
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index c73780024ae..d0a4d9feca4 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -10534,6 +10534,9 @@ msgid_plural "DastSiteValidation|This will affect %d other profiles targeting th
msgstr[0] ""
msgstr[1] ""
+msgid "DastSiteValidation|To run an active scan, validate your target site. All site profiles that share the same base URL share the same validation status."
+msgstr ""
+
msgid "DastSiteValidation|Validate"
msgstr ""
@@ -31228,6 +31231,15 @@ msgstr ""
msgid "SortOptions|Blocking"
msgstr ""
+msgid "SortOptions|Closed date"
+msgstr ""
+
+msgid "SortOptions|Closed earlier"
+msgstr ""
+
+msgid "SortOptions|Closed recently"
+msgstr ""
+
msgid "SortOptions|Created date"
msgstr ""
diff --git a/scripts/api/cancel_pipeline.rb b/scripts/api/cancel_pipeline.rb
index f3aea90f54f..2de50dcee80 100755
--- a/scripts/api/cancel_pipeline.rb
+++ b/scripts/api/cancel_pipeline.rb
@@ -1,40 +1,32 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
-require 'rubygems'
require 'gitlab'
require 'optparse'
-require_relative 'get_job_id'
+require_relative 'default_options'
class CancelPipeline
- DEFAULT_OPTIONS = {
- project: ENV['CI_PROJECT_ID'],
- pipeline_id: ENV['CI_PIPELINE_ID'],
- # Default to "CI scripts API usage" at https://gitlab.com/gitlab-org/gitlab/-/settings/access_tokens
- api_token: ENV['PROJECT_TOKEN_FOR_CI_SCRIPTS_API_USAGE']
- }.freeze
-
def initialize(options)
@project = options.delete(:project)
@pipeline_id = options.delete(:pipeline_id)
- Gitlab.configure do |config|
- config.endpoint = 'https://gitlab.com/api/v4'
- config.private_token = options.delete(:api_token)
- end
+ @client = Gitlab.client(
+ endpoint: options.delete(:endpoint) || API::DEFAULT_OPTIONS[:endpoint],
+ private_token: options.delete(:api_token)
+ )
end
def execute
- Gitlab.cancel_pipeline(project, pipeline_id)
+ client.cancel_pipeline(project, pipeline_id)
end
private
- attr_reader :project, :pipeline_id
+ attr_reader :project, :pipeline_id, :client
end
if $0 == __FILE__
- options = CancelPipeline::DEFAULT_OPTIONS.dup
+ options = API::DEFAULT_OPTIONS.dup
OptionParser.new do |opts|
opts.on("-p", "--project PROJECT", String, "Project where to find the job (defaults to $CI_PROJECT_ID)") do |value|
@@ -45,10 +37,14 @@ if $0 == __FILE__
options[:pipeline_id] = value
end
- opts.on("-t", "--api-token API_TOKEN", String, "A value API token with the `read_api` scope") do |value|
+ opts.on("-t", "--api-token API_TOKEN", String, "A value API token with the `api` scope") do |value|
options[:api_token] = value
end
+ opts.on("-E", "--endpoint ENDPOINT", String, "The API endpoint for the API token. (defaults to $CI_API_V4_URL and fallback to https://gitlab.com/api/v4)") do |value|
+ options[:endpoint] = value
+ end
+
opts.on("-h", "--help", "Prints this help") do
puts opts
exit
diff --git a/scripts/api/default_options.rb b/scripts/api/default_options.rb
new file mode 100644
index 00000000000..70fb9683733
--- /dev/null
+++ b/scripts/api/default_options.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module API
+ DEFAULT_OPTIONS = {
+ project: ENV['CI_PROJECT_ID'],
+ pipeline_id: ENV['CI_PIPELINE_ID'],
+ # Default to "CI scripts API usage" at https://gitlab.com/gitlab-org/gitlab/-/settings/access_tokens
+ api_token: ENV['PROJECT_TOKEN_FOR_CI_SCRIPTS_API_USAGE'],
+ endpoint: ENV['CI_API_V4_URL'] || 'https://gitlab.com/api/v4'
+ }.freeze
+end
diff --git a/scripts/api/download_job_artifact.rb b/scripts/api/download_job_artifact.rb
index d725de3f1b6..23202ad3912 100755
--- a/scripts/api/download_job_artifact.rb
+++ b/scripts/api/download_job_artifact.rb
@@ -1,31 +1,26 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
-require 'rubygems'
require 'optparse'
require 'fileutils'
require 'uri'
require 'cgi'
require 'net/http'
+require_relative 'default_options'
class ArtifactFinder
- DEFAULT_OPTIONS = {
- project: ENV['CI_PROJECT_ID'],
- # Default to "CI scripts API usage" at https://gitlab.com/gitlab-org/gitlab/-/settings/access_tokens
- api_token: ENV['PROJECT_TOKEN_FOR_CI_SCRIPTS_API_USAGE']
- }.freeze
-
def initialize(options)
@project = options.delete(:project)
@job_id = options.delete(:job_id)
@api_token = options.delete(:api_token)
+ @endpoint = options.delete(:endpoint) || API::DEFAULT_OPTIONS[:endpoint]
@artifact_path = options.delete(:artifact_path)
warn "No API token given." unless api_token
end
def execute
- url = "https://gitlab.com/api/v4/projects/#{CGI.escape(project)}/jobs/#{job_id}/artifacts"
+ url = "#{endpoint}/projects/#{CGI.escape(project)}/jobs/#{job_id}/artifacts"
if artifact_path
FileUtils.mkdir_p(File.dirname(artifact_path))
@@ -37,7 +32,7 @@ class ArtifactFinder
private
- attr_reader :project, :job_id, :api_token, :artifact_path
+ attr_reader :project, :job_id, :api_token, :endpoint, :artifact_path
def fetch(uri_str, limit = 10)
raise 'Too many HTTP redirects' if limit == 0
@@ -66,7 +61,7 @@ class ArtifactFinder
end
if $0 == __FILE__
- options = ArtifactFinder::DEFAULT_OPTIONS.dup
+ options = API::DEFAULT_OPTIONS.dup
OptionParser.new do |opts|
opts.on("-p", "--project PROJECT", String, "Project where to find the job (defaults to $CI_PROJECT_ID)") do |value|
@@ -85,6 +80,10 @@ if $0 == __FILE__
options[:api_token] = value
end
+ opts.on("-E", "--endpoint ENDPOINT", String, "The API endpoint for the API token. (defaults to $CI_API_V4_URL and fallback to https://gitlab.com/api/v4)") do |value|
+ options[:endpoint] = value
+ end
+
opts.on("-h", "--help", "Prints this help") do
puts opts
exit
diff --git a/scripts/api/get_job_id.rb b/scripts/api/get_job_id.rb
index f6f1f326225..166c9198951 100755
--- a/scripts/api/get_job_id.rb
+++ b/scripts/api/get_job_id.rb
@@ -1,19 +1,15 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
-require 'rubygems'
require 'gitlab'
require 'optparse'
+require_relative 'default_options'
class JobFinder
- DEFAULT_OPTIONS = {
- project: ENV['CI_PROJECT_ID'],
- pipeline_id: ENV['CI_PIPELINE_ID'],
- pipeline_query: {},
- job_query: {},
- # Default to "CI scripts API usage" at https://gitlab.com/gitlab-org/gitlab/-/settings/access_tokens
- api_token: ENV['PROJECT_TOKEN_FOR_CI_SCRIPTS_API_USAGE']
- }.freeze
+ DEFAULT_OPTIONS = API::DEFAULT_OPTIONS.merge(
+ pipeline_query: {}.freeze,
+ job_query: {}.freeze
+ ).freeze
def initialize(options)
@project = options.delete(:project)
@@ -28,10 +24,10 @@ class JobFinder
warn "No API token given." if api_token.empty?
- Gitlab.configure do |config|
- config.endpoint = 'https://gitlab.com/api/v4'
- config.private_token = api_token
- end
+ @client = Gitlab.client(
+ endpoint: options.delete(:endpoint) || DEFAULT_OPTIONS[:endpoint],
+ private_token: api_token
+ )
end
def execute
@@ -40,13 +36,13 @@ class JobFinder
private
- attr_reader :project, :pipeline_query, :job_query, :pipeline_id, :job_name, :artifact_path
+ attr_reader :project, :pipeline_query, :job_query, :pipeline_id, :job_name, :artifact_path, :client
def find_job_with_artifact
return if artifact_path.nil?
- Gitlab.pipelines(project, pipeline_query_params).auto_paginate do |pipeline|
- Gitlab.pipeline_jobs(project, pipeline.id, job_query_params).auto_paginate do |job|
+ client.pipelines(project, pipeline_query_params).auto_paginate do |pipeline|
+ client.pipeline_jobs(project, pipeline.id, job_query_params).auto_paginate do |job|
return job if found_job_with_artifact?(job) # rubocop:disable Cop/AvoidReturnFromBlocks
end
end
@@ -57,8 +53,8 @@ class JobFinder
def find_job_with_filtered_pipelines
return if pipeline_query.empty?
- Gitlab.pipelines(project, pipeline_query_params).auto_paginate do |pipeline|
- Gitlab.pipeline_jobs(project, pipeline.id, job_query_params).auto_paginate do |job|
+ client.pipelines(project, pipeline_query_params).auto_paginate do |pipeline|
+ client.pipeline_jobs(project, pipeline.id, job_query_params).auto_paginate do |job|
return job if found_job_by_name?(job) # rubocop:disable Cop/AvoidReturnFromBlocks
end
end
@@ -69,7 +65,7 @@ class JobFinder
def find_job_in_pipeline
return unless pipeline_id
- Gitlab.pipeline_jobs(project, pipeline_id, job_query_params).auto_paginate do |job|
+ client.pipeline_jobs(project, pipeline_id, job_query_params).auto_paginate do |job|
return job if found_job_by_name?(job) # rubocop:disable Cop/AvoidReturnFromBlocks
end
@@ -77,7 +73,7 @@ class JobFinder
end
def found_job_with_artifact?(job)
- artifact_url = "https://gitlab.com/api/v4/projects/#{CGI.escape(project)}/jobs/#{job.id}/artifacts/#{artifact_path}"
+ artifact_url = "#{client.endpoint}/projects/#{CGI.escape(project)}/jobs/#{job.id}/artifacts/#{artifact_path}"
response = HTTParty.head(artifact_url) # rubocop:disable Gitlab/HTTParty
response.success?
end
@@ -108,11 +104,13 @@ if $0 == __FILE__
end
opts.on("-q", "--pipeline-query pipeline_query", String, "Query to pass to the Pipeline API request") do |value|
- options[:pipeline_query].merge!(Hash[*value.split('=')])
+ options[:pipeline_query] =
+ options[:pipeline_query].merge(Hash[*value.split('=')])
end
opts.on("-Q", "--job-query job_query", String, "Query to pass to the Job API request") do |value|
- options[:job_query].merge!(Hash[*value.split('=')])
+ options[:job_query] =
+ options[:job_query].merge(Hash[*value.split('=')])
end
opts.on("-j", "--job-name job_name", String, "A job name that needs to exist in the found pipeline") do |value|
@@ -127,6 +125,10 @@ if $0 == __FILE__
options[:api_token] = value
end
+ opts.on("-E", "--endpoint ENDPOINT", String, "The API endpoint for the API token. (defaults to $CI_API_V4_URL and fallback to https://gitlab.com/api/v4)") do |value|
+ options[:endpoint] = value
+ end
+
opts.on("-h", "--help", "Prints this help") do
puts opts
exit
diff --git a/scripts/api/play_job.rb b/scripts/api/play_job.rb
deleted file mode 100755
index 2c5cc75619d..00000000000
--- a/scripts/api/play_job.rb
+++ /dev/null
@@ -1,64 +0,0 @@
-#!/usr/bin/env ruby
-# frozen_string_literal: true
-
-require 'rubygems'
-require 'gitlab'
-require 'optparse'
-require_relative 'get_job_id'
-
-class PlayJob
- DEFAULT_OPTIONS = {
- project: ENV['CI_PROJECT_ID'],
- pipeline_id: ENV['CI_PIPELINE_ID'],
- # Default to "CI scripts API usage" at https://gitlab.com/gitlab-org/gitlab/-/settings/access_tokens
- api_token: ENV['PROJECT_TOKEN_FOR_CI_SCRIPTS_API_USAGE']
- }.freeze
-
- def initialize(options)
- @options = options
-
- Gitlab.configure do |config|
- config.endpoint = 'https://gitlab.com/api/v4'
- config.private_token = options.fetch(:api_token)
- end
- end
-
- def execute
- job = JobFinder.new(options.slice(:project, :api_token, :pipeline_id, :job_name).merge(scope: 'manual')).execute
-
- Gitlab.job_play(project, job.id)
- end
-
- private
-
- attr_reader :options
-
- def project
- options[:project]
- end
-end
-
-if $0 == __FILE__
- options = PlayJob::DEFAULT_OPTIONS.dup
-
- OptionParser.new do |opts|
- opts.on("-p", "--project PROJECT", String, "Project where to find the job (defaults to $CI_PROJECT_ID)") do |value|
- options[:project] = value
- end
-
- opts.on("-j", "--job-name JOB_NAME", String, "A job name that needs to exist in the found pipeline") do |value|
- options[:job_name] = value
- end
-
- opts.on("-t", "--api-token API_TOKEN", String, "A value API token with the `read_api` scope") do |value|
- options[:api_token] = value
- end
-
- opts.on("-h", "--help", "Prints this help") do
- puts opts
- exit
- end
- end.parse!
-
- PlayJob.new(options).execute
-end
diff --git a/scripts/rspec_helpers.sh b/scripts/rspec_helpers.sh
index a788c3417c2..0714ecfce80 100644
--- a/scripts/rspec_helpers.sh
+++ b/scripts/rspec_helpers.sh
@@ -10,14 +10,14 @@ function retrieve_tests_metadata() {
local test_metadata_job_id
# Ruby
- test_metadata_job_id=$(scripts/api/get_job_id.rb --project "${project_path}" -q "status=success" -q "ref=${artifact_branch}" -q "username=gitlab-bot" -Q "scope=success" --job-name "update-tests-metadata")
+ test_metadata_job_id=$(scripts/api/get_job_id.rb --endpoint "https://gitlab.com/api/v4" --project "${project_path}" -q "status=success" -q "ref=${artifact_branch}" -q "username=gitlab-bot" -Q "scope=success" --job-name "update-tests-metadata")
if [[ ! -f "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" ]]; then
- scripts/api/download_job_artifact.rb --project "${project_path}" --job-id "${test_metadata_job_id}" --artifact-path "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" || echo "{}" > "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}"
+ scripts/api/download_job_artifact.rb --endpoint "https://gitlab.com/api/v4" --project "${project_path}" --job-id "${test_metadata_job_id}" --artifact-path "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" || echo "{}" > "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}"
fi
if [[ ! -f "${FLAKY_RSPEC_SUITE_REPORT_PATH}" ]]; then
- scripts/api/download_job_artifact.rb --project "${project_path}" --job-id "${test_metadata_job_id}" --artifact-path "${FLAKY_RSPEC_SUITE_REPORT_PATH}" || echo "{}" > "${FLAKY_RSPEC_SUITE_REPORT_PATH}"
+ scripts/api/download_job_artifact.rb --endpoint "https://gitlab.com/api/v4" --project "${project_path}" --job-id "${test_metadata_job_id}" --artifact-path "${FLAKY_RSPEC_SUITE_REPORT_PATH}" || echo "{}" > "${FLAKY_RSPEC_SUITE_REPORT_PATH}"
fi
}
@@ -48,10 +48,10 @@ function retrieve_tests_mapping() {
local artifact_branch="master"
local test_metadata_with_mapping_job_id
- test_metadata_with_mapping_job_id=$(scripts/api/get_job_id.rb --project "${project_path}" -q "status=success" -q "ref=${artifact_branch}" -q "username=gitlab-bot" -Q "scope=success" --job-name "update-tests-metadata" --artifact-path "${RSPEC_PACKED_TESTS_MAPPING_PATH}.gz")
+ test_metadata_with_mapping_job_id=$(scripts/api/get_job_id.rb --endpoint "https://gitlab.com/api/v4" --project "${project_path}" -q "status=success" -q "ref=${artifact_branch}" -q "username=gitlab-bot" -Q "scope=success" --job-name "update-tests-metadata" --artifact-path "${RSPEC_PACKED_TESTS_MAPPING_PATH}.gz")
if [[ ! -f "${RSPEC_PACKED_TESTS_MAPPING_PATH}" ]]; then
- (scripts/api/download_job_artifact.rb --project "${project_path}" --job-id "${test_metadata_with_mapping_job_id}" --artifact-path "${RSPEC_PACKED_TESTS_MAPPING_PATH}.gz" && gzip -d "${RSPEC_PACKED_TESTS_MAPPING_PATH}.gz") || echo "{}" > "${RSPEC_PACKED_TESTS_MAPPING_PATH}"
+ (scripts/api/download_job_artifact.rb --endpoint "https://gitlab.com/api/v4" --project "${project_path}" --job-id "${test_metadata_with_mapping_job_id}" --artifact-path "${RSPEC_PACKED_TESTS_MAPPING_PATH}.gz" && gzip -d "${RSPEC_PACKED_TESTS_MAPPING_PATH}.gz") || echo "{}" > "${RSPEC_PACKED_TESTS_MAPPING_PATH}"
fi
scripts/unpack-test-mapping "${RSPEC_PACKED_TESTS_MAPPING_PATH}" "${RSPEC_TESTS_MAPPING_PATH}"
diff --git a/spec/features/merge_requests/user_lists_merge_requests_spec.rb b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
index ab6242784fe..f96717970bf 100644
--- a/spec/features/merge_requests/user_lists_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
@@ -23,7 +23,7 @@ RSpec.describe 'Merge requests > User lists merge requests' do
milestone: create(:milestone, project: project, due_date: '2013-12-11'),
created_at: 1.minute.ago,
updated_at: 1.minute.ago)
- @fix.metrics.update_column(:merged_at, 10.seconds.ago)
+ @fix.metrics.update!(merged_at: 10.seconds.ago, latest_closed_at: 10.seconds.ago)
@markdown = create(:merge_request,
title: 'markdown',
@@ -34,7 +34,7 @@ RSpec.describe 'Merge requests > User lists merge requests' do
milestone: create(:milestone, project: project, due_date: '2013-12-12'),
created_at: 2.minutes.ago,
updated_at: 2.minutes.ago)
- @markdown.metrics.update_column(:merged_at, 50.seconds.ago)
+ @markdown.metrics.update!(merged_at: 10.minutes.ago, latest_closed_at: 10.seconds.ago)
@merge_test = create(:merge_request,
title: 'merge-test',
@@ -42,7 +42,15 @@ RSpec.describe 'Merge requests > User lists merge requests' do
source_branch: 'merge-test',
created_at: 3.minutes.ago,
updated_at: 10.seconds.ago)
- @merge_test.metrics.update_column(:merged_at, 10.seconds.ago)
+ @merge_test.metrics.update!(merged_at: 10.seconds.ago, latest_closed_at: 10.seconds.ago)
+
+ @feature = create(:merge_request,
+ title: 'feature',
+ source_project: project,
+ source_branch: 'feautre',
+ created_at: 2.minutes.ago,
+ updated_at: 1.minute.ago)
+ @feature.metrics.update!(merged_at: 10.seconds.ago, latest_closed_at: 10.minutes.ago)
end
context 'merge request reviewers' do
@@ -71,9 +79,10 @@ RSpec.describe 'Merge requests > User lists merge requests' do
expect(current_path).to eq(project_merge_requests_path(project))
expect(page).to have_content 'merge-test'
+ expect(page).to have_content 'feature'
expect(page).not_to have_content 'fix'
expect(page).not_to have_content 'markdown'
- expect(count_merge_requests).to eq(1)
+ expect(count_merge_requests).to eq(2)
end
it 'filters on a specific assignee' do
@@ -90,28 +99,35 @@ RSpec.describe 'Merge requests > User lists merge requests' do
expect(first_merge_request).to include('fix')
expect(last_merge_request).to include('merge-test')
- expect(count_merge_requests).to eq(3)
+ expect(count_merge_requests).to eq(4)
end
it 'sorts by last updated' do
visit_merge_requests(project, sort: sort_value_recently_updated)
expect(first_merge_request).to include('merge-test')
- expect(count_merge_requests).to eq(3)
+ expect(count_merge_requests).to eq(4)
end
it 'sorts by milestone' do
visit_merge_requests(project, sort: sort_value_milestone)
expect(first_merge_request).to include('fix')
- expect(count_merge_requests).to eq(3)
+ expect(count_merge_requests).to eq(4)
end
it 'sorts by merged at' do
visit_merge_requests(project, sort: sort_value_merged_date)
expect(first_merge_request).to include('markdown')
- expect(count_merge_requests).to eq(3)
+ expect(count_merge_requests).to eq(4)
+ end
+
+ it 'sorts by closed at' do
+ visit_merge_requests(project, sort: sort_value_closed_date)
+
+ expect(first_merge_request).to include('feature')
+ expect(count_merge_requests).to eq(4)
end
it 'filters on one label and sorts by due date' do
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js
index 74f579e77ed..d3e1bfef561 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js
@@ -86,7 +86,7 @@ describe('AuthorToken', () => {
});
describe('methods', () => {
- describe('fetchAuthorBySearchTerm', () => {
+ describe('fetchAuthors', () => {
beforeEach(() => {
wrapper = createComponent();
});
@@ -155,7 +155,7 @@ describe('AuthorToken', () => {
expect(baseTokenEl.exists()).toBe(true);
expect(baseTokenEl.props()).toMatchObject({
suggestions: mockAuthors,
- fnActiveTokenValue: wrapper.vm.getActiveAuthor,
+ getActiveTokenValue: wrapper.vm.getActiveAuthor,
});
});
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js
index cd6ffd679d0..c746cb7749a 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js
@@ -53,7 +53,6 @@ const mockProps = {
suggestionsLoading: false,
defaultSuggestions: DEFAULT_LABELS,
recentSuggestionsStorageKey: mockStorageKey,
- fnCurrentTokenValue: jest.fn(),
};
function createComponent({
@@ -99,31 +98,20 @@ describe('BaseToken', () => {
});
describe('computed', () => {
- describe('currentTokenValue', () => {
- it('calls `fnCurrentTokenValue` when it is provided', () => {
- // We're disabling lint to trigger computed prop execution for this test.
- // eslint-disable-next-line no-unused-vars
- const { currentTokenValue } = wrapper.vm;
-
- expect(wrapper.vm.fnCurrentTokenValue).toHaveBeenCalledWith(`"${mockRegularLabel.title}"`);
- });
- });
-
describe('activeTokenValue', () => {
- it('calls `fnActiveTokenValue` when it is provided', async () => {
- const mockFnActiveTokenValue = jest.fn();
+ it('calls `getActiveTokenValue` when it is provided', async () => {
+ const mockGetActiveTokenValue = jest.fn();
wrapper.setProps({
- fnActiveTokenValue: mockFnActiveTokenValue,
- fnCurrentTokenValue: undefined,
+ getActiveTokenValue: mockGetActiveTokenValue,
});
await wrapper.vm.$nextTick();
- expect(mockFnActiveTokenValue).toHaveBeenCalledTimes(1);
- expect(mockFnActiveTokenValue).toHaveBeenCalledWith(
+ expect(mockGetActiveTokenValue).toHaveBeenCalledTimes(1);
+ expect(mockGetActiveTokenValue).toHaveBeenCalledWith(
mockLabels,
- `"${mockRegularLabel.title.toLowerCase()}"`,
+ `"${mockRegularLabel.title}"`,
);
});
});
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js
index ec9458f64d2..e5ffbd41afa 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js
@@ -98,11 +98,11 @@ describe('LabelToken', () => {
});
});
- describe('fetchLabelBySearchTerm', () => {
+ describe('fetchLabels', () => {
it('calls `config.fetchLabels` with provided searchTerm param', () => {
jest.spyOn(wrapper.vm.config, 'fetchLabels');
- wrapper.vm.fetchLabelBySearchTerm('foo');
+ wrapper.vm.fetchLabels('foo');
expect(wrapper.vm.config.fetchLabels).toHaveBeenCalledWith('foo');
});
@@ -110,7 +110,7 @@ describe('LabelToken', () => {
it('sets response to `labels` when request is succesful', () => {
jest.spyOn(wrapper.vm.config, 'fetchLabels').mockResolvedValue(mockLabels);
- wrapper.vm.fetchLabelBySearchTerm('foo');
+ wrapper.vm.fetchLabels('foo');
return waitForPromises().then(() => {
expect(wrapper.vm.labels).toEqual(mockLabels);
@@ -120,7 +120,7 @@ describe('LabelToken', () => {
it('calls `createFlash` with flash error message when request fails', () => {
jest.spyOn(wrapper.vm.config, 'fetchLabels').mockRejectedValue({});
- wrapper.vm.fetchLabelBySearchTerm('foo');
+ wrapper.vm.fetchLabels('foo');
return waitForPromises().then(() => {
expect(createFlash).toHaveBeenCalledWith({
@@ -132,7 +132,7 @@ describe('LabelToken', () => {
it('sets `loading` to false when request completes', () => {
jest.spyOn(wrapper.vm.config, 'fetchLabels').mockRejectedValue({});
- wrapper.vm.fetchLabelBySearchTerm('foo');
+ wrapper.vm.fetchLabels('foo');
return waitForPromises().then(() => {
expect(wrapper.vm.loading).toBe(false);
@@ -160,7 +160,7 @@ describe('LabelToken', () => {
expect(baseTokenEl.exists()).toBe(true);
expect(baseTokenEl.props()).toMatchObject({
suggestions: mockLabels,
- fnActiveTokenValue: wrapper.vm.getActiveLabel,
+ getActiveTokenValue: wrapper.vm.getActiveLabel,
});
});
diff --git a/spec/graphql/resolvers/merge_requests_resolver_spec.rb b/spec/graphql/resolvers/merge_requests_resolver_spec.rb
index aec6c6c6708..64ee0d4f9cc 100644
--- a/spec/graphql/resolvers/merge_requests_resolver_spec.rb
+++ b/spec/graphql/resolvers/merge_requests_resolver_spec.rb
@@ -303,6 +303,29 @@ RSpec.describe Resolvers::MergeRequestsResolver do
expect { resolve_mr(project, sort: :merged_at_desc, labels: %w[a b]) }.not_to raise_error
end
end
+
+ context 'when sorting by closed at' do
+ before do
+ merge_request_1.metrics.update!(latest_closed_at: 10.days.ago)
+ merge_request_3.metrics.update!(latest_closed_at: 5.days.ago)
+ end
+
+ it 'sorts merge requests ascending' do
+ expect(resolve_mr(project, sort: :closed_at_asc))
+ .to match_array(mrs)
+ .and be_sorted(->(mr) { [closed_at(mr), -mr.id] })
+ end
+
+ it 'sorts merge requests descending' do
+ expect(resolve_mr(project, sort: :closed_at_desc))
+ .to match_array(mrs)
+ .and be_sorted(->(mr) { [-closed_at(mr), -mr.id] })
+ end
+
+ def closed_at(mr)
+ nils_last(mr.metrics.latest_closed_at)
+ end
+ end
end
end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index edd543854cb..4a8a2909891 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -80,6 +80,24 @@ RSpec.describe MergeRequest, factory_default: :keep do
end
end
+ describe '.order_closed_at_asc' do
+ let_it_be(:older_mr) { create(:merge_request, :closed_last_month) }
+ let_it_be(:newer_mr) { create(:merge_request, :closed_last_month) }
+
+ it 'returns MRs ordered by closed_at ascending' do
+ expect(described_class.order_closed_at_asc).to eq([older_mr, newer_mr])
+ end
+ end
+
+ describe '.order_closed_at_desc' do
+ let_it_be(:older_mr) { create(:merge_request, :closed_last_month) }
+ let_it_be(:newer_mr) { create(:merge_request, :closed_last_month) }
+
+ it 'returns MRs ordered by closed_at descending' do
+ expect(described_class.order_closed_at_desc).to eq([newer_mr, older_mr])
+ end
+ end
+
describe '.with_jira_issue_keys' do
let_it_be(:mr_with_jira_title) { create(:merge_request, :unique_branches, title: 'Fix TEST-123') }
let_it_be(:mr_with_jira_description) { create(:merge_request, :unique_branches, description: 'this closes TEST-321') }
@@ -577,6 +595,26 @@ RSpec.describe MergeRequest, factory_default: :keep do
expect(merge_requests).to eq([newer_mr, older_mr])
end
end
+
+ context 'closed_at' do
+ let_it_be(:older_mr) { create(:merge_request, :closed_last_month) }
+ let_it_be(:newer_mr) { create(:merge_request, :closed_last_month) }
+
+ it 'sorts asc' do
+ merge_requests = described_class.sort_by_attribute(:closed_at_asc)
+ expect(merge_requests).to eq([older_mr, newer_mr])
+ end
+
+ it 'sorts desc' do
+ merge_requests = described_class.sort_by_attribute(:closed_at_desc)
+ expect(merge_requests).to eq([newer_mr, older_mr])
+ end
+
+ it 'sorts asc when its closed_at' do
+ merge_requests = described_class.sort_by_attribute(:closed_at)
+ expect(merge_requests).to eq([older_mr, newer_mr])
+ end
+ end
end
describe 'time to merge calculations' do
diff --git a/spec/requests/api/graphql/project/merge_requests_spec.rb b/spec/requests/api/graphql/project/merge_requests_spec.rb
index 7fc1ef05fa7..1b0405be09c 100644
--- a/spec/requests/api/graphql/project/merge_requests_spec.rb
+++ b/spec/requests/api/graphql/project/merge_requests_spec.rb
@@ -422,6 +422,46 @@ RSpec.describe 'getting merge request listings nested in a project' do
end
end
end
+
+ context 'when sorting by closed_at DESC' do
+ let(:sort_param) { :CLOSED_AT_DESC }
+ let(:expected_results) do
+ [
+ merge_request_b,
+ merge_request_d,
+ merge_request_c,
+ merge_request_e,
+ merge_request_a
+ ].map { |mr| global_id_of(mr) }
+ end
+
+ before do
+ five_days_ago = 5.days.ago
+
+ merge_request_d.metrics.update!(latest_closed_at: five_days_ago)
+
+ # same merged_at, the second order column will decide (merge_request.id)
+ merge_request_c.metrics.update!(latest_closed_at: five_days_ago)
+
+ merge_request_b.metrics.update!(latest_closed_at: 1.day.ago)
+ end
+
+ it_behaves_like 'sorted paginated query' do
+ let(:first_param) { 2 }
+ end
+
+ context 'when last parameter is given' do
+ let(:params) { graphql_args(sort: sort_param, last: 2) }
+ let(:page_info) { nil }
+
+ it 'takes the last 2 records' do
+ query = pagination_query(params)
+ post_graphql(query, current_user: current_user)
+
+ expect(results.map { |item| item["id"] }).to eq(expected_results.last(2))
+ end
+ end
+ end
end
context 'when only the count is requested' do