Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
69d62eae98
commit
b654eb44a8
31 changed files with 312 additions and 154 deletions
|
@ -6,10 +6,12 @@ import {
|
|||
GlSafeHtmlDirective as SafeHtml,
|
||||
} from '@gitlab/ui';
|
||||
import { mapActions } from 'vuex';
|
||||
import { __ } from '~/locale';
|
||||
import { __, s__ } from '~/locale';
|
||||
import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import UserNameWithStatus from '../../sidebar/components/assignees/user_name_with_status.vue';
|
||||
|
||||
import { NOTEABLE_TYPE_MAPPING } from '../constants';
|
||||
|
||||
export default {
|
||||
safeHtmlConfig: { ADD_TAGS: ['gl-emoji'] },
|
||||
components: {
|
||||
|
@ -45,6 +47,11 @@ export default {
|
|||
required: false,
|
||||
default: null,
|
||||
},
|
||||
noteableType: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
includeToggle: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
|
@ -103,6 +110,15 @@ export default {
|
|||
authorName() {
|
||||
return this.author.name;
|
||||
},
|
||||
noteConfidentialityTooltip() {
|
||||
if (
|
||||
this.noteableType === NOTEABLE_TYPE_MAPPING.Issue ||
|
||||
this.noteableType === NOTEABLE_TYPE_MAPPING.MergeRequest
|
||||
) {
|
||||
return s__('Notes|This comment is confidential and only visible to project members');
|
||||
}
|
||||
return s__('Notes|This comment is confidential and only visible to group members');
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.emojiTitle = this.emojiElement ? this.emojiElement.getAttribute('title') : '';
|
||||
|
@ -226,7 +242,7 @@ export default {
|
|||
data-testid="confidentialIndicator"
|
||||
name="eye-slash"
|
||||
:size="16"
|
||||
:title="s__('Notes|This comment is confidential and only visible to project members')"
|
||||
:title="noteConfidentialityTooltip"
|
||||
class="gl-ml-1 gl-text-orange-700 align-middle"
|
||||
/>
|
||||
<slot name="extra-controls"></slot>
|
||||
|
|
|
@ -432,6 +432,7 @@ export default {
|
|||
:created-at="note.created_at"
|
||||
:note-id="note.id"
|
||||
:is-confidential="note.confidential"
|
||||
:noteable-type="noteableType"
|
||||
>
|
||||
<template #note-header-info>
|
||||
<slot name="note-header-info"></slot>
|
||||
|
|
|
@ -32,6 +32,7 @@ class GraphqlController < ApplicationController
|
|||
before_action :set_user_last_activity
|
||||
before_action :track_vs_code_usage
|
||||
before_action :track_jetbrains_usage
|
||||
before_action :track_gitlab_cli_usage
|
||||
before_action :disable_query_limiting
|
||||
before_action :limit_query_size
|
||||
|
||||
|
@ -143,6 +144,11 @@ class GraphqlController < ApplicationController
|
|||
.track_api_request_when_trackable(user_agent: request.user_agent, user: current_user)
|
||||
end
|
||||
|
||||
def track_gitlab_cli_usage
|
||||
Gitlab::UsageDataCounters::GitLabCliActivityUniqueCounter
|
||||
.track_api_request_when_trackable(user_agent: request.user_agent, user: current_user)
|
||||
end
|
||||
|
||||
def execute_multiplex
|
||||
GitlabSchema.multiplex(multiplex_queries, context: context)
|
||||
end
|
||||
|
|
|
@ -10,7 +10,7 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
include RecordUserLastActivity
|
||||
|
||||
ISSUES_EXCEPT_ACTIONS = %i[index calendar new create bulk_update import_csv export_csv service_desk].freeze
|
||||
SET_ISSUABLES_INDEX_ONLY_ACTIONS = %i[calendar service_desk].freeze
|
||||
SET_ISSUABLES_INDEX_ONLY_ACTIONS = %i[index calendar service_desk].freeze
|
||||
|
||||
prepend_before_action(only: [:index]) { authenticate_sessionless_user!(:rss) }
|
||||
prepend_before_action(only: [:calendar]) { authenticate_sessionless_user!(:ics) }
|
||||
|
@ -22,7 +22,9 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
before_action :issue, unless: ->(c) { ISSUES_EXCEPT_ACTIONS.include?(c.action_name.to_sym) }
|
||||
after_action :log_issue_show, unless: ->(c) { ISSUES_EXCEPT_ACTIONS.include?(c.action_name.to_sym) }
|
||||
|
||||
before_action :set_issuables_index, if: ->(c) { SET_ISSUABLES_INDEX_ONLY_ACTIONS.include?(c.action_name.to_sym) }
|
||||
before_action :set_issuables_index, if: ->(c) {
|
||||
SET_ISSUABLES_INDEX_ONLY_ACTIONS.include?(c.action_name.to_sym) && !vue_issues_list?
|
||||
}
|
||||
|
||||
# Allow write(create) issue
|
||||
before_action :authorize_create_issue!, only: [:new, :create]
|
||||
|
@ -71,10 +73,9 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
attr_accessor :vulnerability_id
|
||||
|
||||
def index
|
||||
if html_request? && Feature.enabled?(:vue_issues_list, project&.group, default_enabled: :yaml)
|
||||
if vue_issues_list?
|
||||
set_sort_order
|
||||
else
|
||||
set_issuables_index
|
||||
@issues = @issuables
|
||||
end
|
||||
|
||||
|
@ -248,6 +249,12 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
|
||||
protected
|
||||
|
||||
def vue_issues_list?
|
||||
action_name.to_sym == :index &&
|
||||
html_request? &&
|
||||
Feature.enabled?(:vue_issues_list, project&.group, default_enabled: :yaml)
|
||||
end
|
||||
|
||||
def sorting_field
|
||||
Issue::SORTING_PREFERENCE_FIELD
|
||||
end
|
||||
|
|
|
@ -6,9 +6,8 @@ module Releases
|
|||
#
|
||||
# order_by - only ordering by released_at is supported
|
||||
# filter by tag - currently not supported
|
||||
# include_subgroups - always true for group releases finder
|
||||
class GroupReleasesFinder
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
attr_reader :parent, :current_user, :params
|
||||
|
||||
def initialize(parent, current_user = nil, params = {})
|
||||
|
@ -25,45 +24,26 @@ module Releases
|
|||
def execute(preload: true)
|
||||
return Release.none unless Ability.allowed?(current_user, :read_release, parent)
|
||||
|
||||
releases = get_releases(preload: preload)
|
||||
|
||||
releases = get_releases
|
||||
releases.preloaded if preload
|
||||
paginate_releases(releases)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def include_subgroups?
|
||||
params.fetch(:include_subgroups, false)
|
||||
end
|
||||
|
||||
def accessible_projects_scope
|
||||
if include_subgroups?
|
||||
Project.for_group_and_its_subgroups(parent)
|
||||
else
|
||||
parent.projects
|
||||
end
|
||||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def get_releases(preload: true)
|
||||
def get_releases
|
||||
Gitlab::Pagination::Keyset::InOperatorOptimization::QueryBuilder.new(
|
||||
scope: releases_scope(preload: preload),
|
||||
array_scope: accessible_projects_scope.select(:id),
|
||||
scope: releases_scope,
|
||||
array_scope: Project.for_group_and_its_subgroups(parent).select(:id),
|
||||
array_mapping_scope: -> (project_id_expression) { Release.where(Release.arel_table[:project_id].eq(project_id_expression)) },
|
||||
finder_query: -> (order_by, id_expression) { Release.where(Release.arel_table[:id].eq(id_expression)) }
|
||||
)
|
||||
.execute
|
||||
end
|
||||
|
||||
def releases_scope(preload: true)
|
||||
scope = Release.all
|
||||
scope = order_releases(scope)
|
||||
scope = scope.preloaded if preload
|
||||
scope
|
||||
end
|
||||
|
||||
def order_releases(scope)
|
||||
scope.sort_by_attribute("released_at_#{params[:sort]}").order(id: params[:sort])
|
||||
def releases_scope
|
||||
Release.sort_by_attribute("released_at_#{params[:sort]}").order(id: params[:sort])
|
||||
end
|
||||
|
||||
def paginate_releases(releases)
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
%h5.gl-mt-0
|
||||
= _('Add an SSH key')
|
||||
%p.profile-settings-content
|
||||
- help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('ssh/index.md') }
|
||||
- help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/ssh.md') }
|
||||
= _('Add an SSH key for secure access to GitLab. %{help_link_start}Learn more.%{help_link_end}').html_safe % {help_link_start: help_link_start, help_link_end: '</a>'.html_safe }
|
||||
= render 'form'
|
||||
%hr
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: usage_data_i_code_review_user_gitlab_cli_api_request
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/83464
|
||||
rollout_issue_url:
|
||||
milestone: '14.10'
|
||||
type: development
|
||||
group: group::code review
|
||||
default_enabled: true
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
name: user_other_role_details
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45635
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/255170
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/growth/team-tasks/-/issues/282
|
||||
milestone: '13.7'
|
||||
type: development
|
||||
group: group::conversion
|
||||
|
|
|
@ -25,6 +25,7 @@ Rails.autoloaders.each do |autoloader|
|
|||
'cidr' => 'CIDR',
|
||||
'cli' => 'CLI',
|
||||
'dn' => 'DN',
|
||||
'gitlab_cli_activity_unique_counter' => 'GitLabCliActivityUniqueCounter',
|
||||
'global_id_type' => 'GlobalIDType',
|
||||
'global_id_compatibility' => 'GlobalIDCompatibility',
|
||||
'hll' => 'HLL',
|
||||
|
|
|
@ -74,6 +74,7 @@
|
|||
- 'i_code_review_post_merge_submit_revert_modal'
|
||||
- 'i_code_review_post_merge_submit_cherry_pick_modal'
|
||||
- 'i_code_review_user_jetbrains_api_request'
|
||||
- 'i_code_review_user_gitlab_cli_api_request'
|
||||
- name: code_review_category_monthly_active_users
|
||||
operator: OR
|
||||
source: redis
|
||||
|
@ -146,3 +147,4 @@
|
|||
events:
|
||||
- 'i_code_review_user_vs_code_api_request'
|
||||
- 'i_code_review_user_jetbrains_api_request'
|
||||
- 'i_code_review_user_gitlab_cli_api_request'
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_user_gitlab_cli_api_request_monthly
|
||||
description: Count of unique users per month who use the GitLab CLI
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: group::code review
|
||||
product_category: editor_extension
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "14.10"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/83464
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_user_gitlab_cli_api_request
|
||||
performance_indicator_type: []
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_user_gitlab_cli_api_request_weekly
|
||||
description: Count of unique users per week who use the GitLab CLI
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: group::code review
|
||||
product_category: editor_extension
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "14.10"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/83464
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_code_review_user_gitlab_cli_api_request
|
||||
performance_indicator_type: []
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -1458,7 +1458,7 @@ Input type: `CreateIssueInput`
|
|||
|
||||
WARNING:
|
||||
**Deprecated** in 14.0.
|
||||
Use iterationCreate.
|
||||
Manual iteration management is deprecated. Only automatic iteration cadences will be supported in the future.
|
||||
|
||||
Input type: `CreateIterationInput`
|
||||
|
||||
|
@ -1470,7 +1470,7 @@ Input type: `CreateIterationInput`
|
|||
| <a id="mutationcreateiterationdescription"></a>`description` | [`String`](#string) | Description of the iteration. |
|
||||
| <a id="mutationcreateiterationduedate"></a>`dueDate` | [`String`](#string) | End date of the iteration. |
|
||||
| <a id="mutationcreateiterationgrouppath"></a>`groupPath` | [`ID`](#id) | Full path of the group with which the resource is associated. |
|
||||
| <a id="mutationcreateiterationiterationscadenceid"></a>`iterationsCadenceId` | [`IterationsCadenceID`](#iterationscadenceid) | Global ID of the iterations cadence to be assigned to newly created iteration. |
|
||||
| <a id="mutationcreateiterationiterationscadenceid"></a>`iterationsCadenceId` **{warning-solid}** | [`IterationsCadenceID`](#iterationscadenceid) | **Deprecated:** `iterationCadenceId` is deprecated and will be removed in the future. This argument is ignored, because you can't create an iteration in a specific cadence. In the future only automatic iteration cadences will be allowed. Deprecated in 14.10. |
|
||||
| <a id="mutationcreateiterationprojectpath"></a>`projectPath` | [`ID`](#id) | Full path of the project with which the resource is associated. |
|
||||
| <a id="mutationcreateiterationstartdate"></a>`startDate` | [`String`](#string) | Start date of the iteration. |
|
||||
| <a id="mutationcreateiterationtitle"></a>`title` | [`String`](#string) | Title of the iteration. |
|
||||
|
@ -3213,6 +3213,10 @@ Input type: `IterationCadenceUpdateInput`
|
|||
|
||||
### `Mutation.iterationCreate`
|
||||
|
||||
WARNING:
|
||||
**Deprecated** in 14.10.
|
||||
Manual iteration management is deprecated. Only automatic iteration cadences will be supported in the future.
|
||||
|
||||
Input type: `iterationCreateInput`
|
||||
|
||||
#### Arguments
|
||||
|
@ -3223,7 +3227,7 @@ Input type: `iterationCreateInput`
|
|||
| <a id="mutationiterationcreatedescription"></a>`description` | [`String`](#string) | Description of the iteration. |
|
||||
| <a id="mutationiterationcreateduedate"></a>`dueDate` | [`String`](#string) | End date of the iteration. |
|
||||
| <a id="mutationiterationcreategrouppath"></a>`groupPath` | [`ID`](#id) | Full path of the group with which the resource is associated. |
|
||||
| <a id="mutationiterationcreateiterationscadenceid"></a>`iterationsCadenceId` | [`IterationsCadenceID`](#iterationscadenceid) | Global ID of the iterations cadence to be assigned to newly created iteration. |
|
||||
| <a id="mutationiterationcreateiterationscadenceid"></a>`iterationsCadenceId` **{warning-solid}** | [`IterationsCadenceID`](#iterationscadenceid) | **Deprecated:** `iterationCadenceId` is deprecated and will be removed in the future. This argument is ignored, because you can't create an iteration in a specific cadence. In the future only automatic iteration cadences will be allowed. Deprecated in 14.10. |
|
||||
| <a id="mutationiterationcreateprojectpath"></a>`projectPath` | [`ID`](#id) | Full path of the project with which the resource is associated. |
|
||||
| <a id="mutationiterationcreatestartdate"></a>`startDate` | [`String`](#string) | Start date of the iteration. |
|
||||
| <a id="mutationiterationcreatetitle"></a>`title` | [`String`](#string) | Title of the iteration. |
|
||||
|
|
|
@ -12,6 +12,11 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
> - Moved to GitLab Premium in 13.9.
|
||||
> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/221047) in GitLab 14.6. [Feature flag `group_iterations`](https://gitlab.com/gitlab-org/gitlab/-/issues/221047) removed.
|
||||
|
||||
WARNING:
|
||||
After [Iteration Cadences](#iteration-cadences) becomes generally available,
|
||||
manual iteration scheduling will be [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/356069) in GitLab 15.6.
|
||||
To enhance the role of iterations as time boundaries, we will also deprecate the title field.
|
||||
|
||||
Iterations are a way to track issues over a period of time. This allows teams
|
||||
to track velocity and volatility metrics. Iterations can be used with [milestones](../../project/milestones/index.md)
|
||||
for tracking over different time periods.
|
||||
|
@ -28,54 +33,6 @@ In GitLab, iterations are similar to milestones, with a few differences:
|
|||
- Iterations require both a start and an end date.
|
||||
- Iteration date ranges cannot overlap.
|
||||
|
||||
## Iteration cadences
|
||||
|
||||
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5077) in GitLab 14.1.
|
||||
> - Deployed behind a [feature flag](../../feature_flags.md), disabled by default.
|
||||
> - Disabled on GitLab.com.
|
||||
> - Not recommended for production use.
|
||||
> - To use in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-iteration-cadences).
|
||||
|
||||
This in-development feature might not be available for your use. There can be
|
||||
[risks when enabling features still in development](../../../administration/feature_flags.md#risks-when-enabling-features-still-in-development).
|
||||
Refer to this feature's version history for more details.
|
||||
|
||||
Iteration cadences automate some common iteration tasks. They can be used to
|
||||
automatically create iterations every 1, 2, 3, 4, or 6 weeks. They can also
|
||||
be configured to automatically roll over incomplete issues to the next iteration.
|
||||
|
||||
With iteration cadences enabled, you must first
|
||||
[create an iteration cadence](#create-an-iteration-cadence) before you can
|
||||
[create an iteration](#create-an-iteration).
|
||||
|
||||
### Create an iteration cadence
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must have at least the Developer role for a group.
|
||||
|
||||
To create an iteration cadence:
|
||||
|
||||
1. On the top bar, select **Menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Issues > Iterations**.
|
||||
1. Select **New iteration cadence**.
|
||||
1. Fill out required fields, and select **Create iteration cadence**. The cadence list page opens.
|
||||
|
||||
### Delete an iteration cadence
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must have at least the Developer role for a group.
|
||||
|
||||
Deleting an iteration cadence also deletes all iterations within that cadence.
|
||||
|
||||
To delete an iteration cadence:
|
||||
|
||||
1. On the top bar, select **Menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Issues > Iterations**.
|
||||
1. Select the three-dot menu (**{ellipsis_v}**) > **Delete cadence** for the cadence you want to delete.
|
||||
1. Select **Delete cadence** in the confirmation modal.
|
||||
|
||||
## View the iterations list
|
||||
|
||||
To view the iterations list, go to **{issues}** **Issues > Iterations**.
|
||||
|
@ -94,8 +51,6 @@ Prerequisites:
|
|||
|
||||
- You must have at least the Developer role for a group.
|
||||
|
||||
For manually scheduled iteration cadences, you create and add iterations yourself.
|
||||
|
||||
To create an iteration:
|
||||
|
||||
1. On the top bar, select **Menu > Groups** and find your group.
|
||||
|
@ -153,7 +108,7 @@ The report also shows a breakdown of total issues in an iteration.
|
|||
Open iteration reports show a summary of completed, unstarted, and in-progress issues.
|
||||
Closed iteration reports show the total number of issues completed by the due date.
|
||||
|
||||
To view an iteration report, go to the iterations list page and select an iteration's title.
|
||||
To view an iteration report, go to the iterations list page and select an iteration's period.
|
||||
|
||||
### Iteration burndown and burnup charts
|
||||
|
||||
|
@ -212,33 +167,61 @@ To group issues by label:
|
|||
You can also search for labels by typing in the search input.
|
||||
1. Select any area outside the label dropdown list. The page is now grouped by the selected labels.
|
||||
|
||||
### Enable or disable iteration cadences **(PREMIUM SELF)**
|
||||
## Iteration cadences
|
||||
|
||||
Iteration Cadences feature is under development and not ready for production use. It is
|
||||
deployed behind a feature flag that is **disabled by default**.
|
||||
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
|
||||
can enable it.
|
||||
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5077) in GitLab 14.1.
|
||||
> - Deployed behind a [feature flag](../../feature_flags.md), named `iteration_cadences`, disabled by default.
|
||||
|
||||
To enable it:
|
||||
FLAG:
|
||||
On self-managed GitLab, by default this feature is not available. To make it available, ask an
|
||||
administrator to [enable the feature flag](../../../administration/feature_flags.md) named
|
||||
`iteration_cadences` for a root group.
|
||||
On GitLab.com, this feature is not available. This feature is not ready for production use.
|
||||
|
||||
```ruby
|
||||
Feature.enable(:iteration_cadences)
|
||||
```
|
||||
Iteration cadences automate iteration scheduling. You can use them to
|
||||
automate creating iterations every 1, 2, 3, 4, or 6 weeks. You can also
|
||||
configure iteration cadences to automatically roll over incomplete issues to the next iteration.
|
||||
|
||||
To disable it:
|
||||
### Create an iteration cadence
|
||||
|
||||
```ruby
|
||||
Feature.disable(:iteration_cadences)
|
||||
```
|
||||
Prerequisites:
|
||||
|
||||
<!-- ## Troubleshooting
|
||||
- You must have at least the Developer role for a group.
|
||||
|
||||
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
|
||||
one might have when setting this up, or when something is changed, or on upgrading, it's
|
||||
important to describe those, too. Think of things that may go wrong and include them here.
|
||||
This is important to minimize requests for support, and to avoid doc comments with
|
||||
questions that you know someone might ask.
|
||||
To create an iteration cadence:
|
||||
|
||||
Each scenario can be a third-level heading, e.g. `### Getting error message X`.
|
||||
If you have none to add when creating a doc, leave this section in place
|
||||
but commented out to help encourage others to add to it in the future. -->
|
||||
1. On the top bar, select **Menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Issues > Iterations**.
|
||||
1. Select **New iteration cadence**.
|
||||
1. Fill out required fields, and select **Create iteration cadence**. The cadence list page opens.
|
||||
|
||||
### Delete an iteration cadence
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must have at least the Developer role for a group.
|
||||
|
||||
Deleting an iteration cadence also deletes all iterations within that cadence.
|
||||
|
||||
To delete an iteration cadence:
|
||||
|
||||
1. On the top bar, select **Menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Issues > Iterations**.
|
||||
1. Select the three-dot menu (**{ellipsis_v}**) > **Delete cadence** for the cadence you want to delete.
|
||||
1. Select **Delete cadence** in the confirmation modal.
|
||||
|
||||
### Convert manual cadence to use automatic scheduling
|
||||
|
||||
WARNING:
|
||||
The upgrade is irreversible. After it's done, manual iteration cadences cannot be created.
|
||||
|
||||
When you **enable** the iteration cadences feature, all iterations are added
|
||||
to a default iteration cadence.
|
||||
In this default iteration cadence, you can continue to add, edit, and remove iterations.
|
||||
|
||||
To upgrade the iteration cadence to use the automation features:
|
||||
|
||||
1. On the top bar, select **Menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Issues > Iterations**.
|
||||
1. Select the three-dot menu (**{ellipsis_v}**) > **Edit cadence** for the cadence you want to upgrade.
|
||||
1. Fill out required fields, and select **Save changes**.
|
||||
|
|
|
@ -80,6 +80,10 @@ module API
|
|||
Gitlab::UsageDataCounters::JetBrainsPluginActivityUniqueCounter.track_api_request_when_trackable(user_agent: request&.user_agent, user: @current_user)
|
||||
end
|
||||
|
||||
after do
|
||||
Gitlab::UsageDataCounters::GitLabCliActivityUniqueCounter.track_api_request_when_trackable(user_agent: request&.user_agent, user: @current_user)
|
||||
end
|
||||
|
||||
# The locale is set to the current user's locale when `current_user` is loaded
|
||||
after { Gitlab::I18n.use_default_locale }
|
||||
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module UsageDataCounters
|
||||
module GitLabCliActivityUniqueCounter
|
||||
GITLAB_CLI_API_REQUEST_ACTION = 'i_code_review_user_gitlab_cli_api_request'
|
||||
GITLAB_CLI_USER_AGENT_REGEX = /GitLab\sCLI$/.freeze
|
||||
|
||||
class << self
|
||||
def track_api_request_when_trackable(user_agent:, user:)
|
||||
user_agent&.match?(GITLAB_CLI_USER_AGENT_REGEX) && track_unique_action_by_user(GITLAB_CLI_API_REQUEST_ACTION, user)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def track_unique_action_by_user(action, user)
|
||||
return unless user
|
||||
|
||||
track_unique_action(action, user.id)
|
||||
end
|
||||
|
||||
def track_unique_action(action, value)
|
||||
Gitlab::UsageDataCounters::HLLRedisCounter.track_usage_event(action, value)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -132,6 +132,11 @@
|
|||
category: code_review
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_i_code_review_user_jetbrains_api_request
|
||||
- name: i_code_review_user_gitlab_cli_api_request
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_i_code_review_user_gitlab_cli_api_request
|
||||
- name: i_code_review_user_create_mr_from_issue
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
|
|
|
@ -21107,10 +21107,13 @@ msgstr ""
|
|||
msgid "Iterations"
|
||||
msgstr ""
|
||||
|
||||
msgid "Iterations|Add a duration, and number of future iterations in order to convert this cadence to automatic scheduling."
|
||||
msgstr ""
|
||||
|
||||
msgid "Iterations|Add iteration"
|
||||
msgstr ""
|
||||
|
||||
msgid "Iterations|Automated scheduling"
|
||||
msgid "Iterations|All"
|
||||
msgstr ""
|
||||
|
||||
msgid "Iterations|Cadence configuration is invalid."
|
||||
|
@ -21125,12 +21128,6 @@ msgstr ""
|
|||
msgid "Iterations|Create cadence"
|
||||
msgstr ""
|
||||
|
||||
msgid "Iterations|Create cadence and start iteration"
|
||||
msgstr ""
|
||||
|
||||
msgid "Iterations|Create iteration"
|
||||
msgstr ""
|
||||
|
||||
msgid "Iterations|Delete cadence"
|
||||
msgstr ""
|
||||
|
||||
|
@ -21140,6 +21137,9 @@ msgstr ""
|
|||
msgid "Iterations|Delete iteration?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Iterations|Done"
|
||||
msgstr ""
|
||||
|
||||
msgid "Iterations|Duration"
|
||||
msgstr ""
|
||||
|
||||
|
@ -21161,10 +21161,13 @@ msgstr ""
|
|||
msgid "Iterations|Iteration cadences"
|
||||
msgstr ""
|
||||
|
||||
msgid "Iterations|Iteration scheduling will be handled automatically"
|
||||
msgid "Iterations|Iterations are a way to track issues over a period of time, allowing teams to also track velocity and volatility metrics."
|
||||
msgstr ""
|
||||
|
||||
msgid "Iterations|Iterations are a way to track issues over a period of time, allowing teams to also track velocity and volatility metrics."
|
||||
msgid "Iterations|Iterations can no longer be scheduled manually. Convert all cadences to automatic scheduling to keep your iterations working as expected."
|
||||
msgstr ""
|
||||
|
||||
msgid "Iterations|Learn more about automatic scheduling"
|
||||
msgstr ""
|
||||
|
||||
msgid "Iterations|Move incomplete issues to the next iteration"
|
||||
|
@ -21194,10 +21197,16 @@ msgstr ""
|
|||
msgid "Iterations|Number of future iterations you would like to have scheduled"
|
||||
msgstr ""
|
||||
|
||||
msgid "Iterations|Open"
|
||||
msgstr ""
|
||||
|
||||
msgid "Iterations|Requires update"
|
||||
msgstr ""
|
||||
|
||||
msgid "Iterations|Roll over issues"
|
||||
msgstr ""
|
||||
|
||||
msgid "Iterations|Save cadence"
|
||||
msgid "Iterations|Save changes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Iterations|Select duration"
|
||||
|
@ -21209,6 +21218,9 @@ msgstr ""
|
|||
msgid "Iterations|Select start date"
|
||||
msgstr ""
|
||||
|
||||
msgid "Iterations|Some of your cadences need to be updated"
|
||||
msgstr ""
|
||||
|
||||
msgid "Iterations|Start date"
|
||||
msgstr ""
|
||||
|
||||
|
@ -21221,6 +21233,9 @@ msgstr ""
|
|||
msgid "Iterations|The start date of your first iteration"
|
||||
msgstr ""
|
||||
|
||||
msgid "Iterations|This cadence requires an update"
|
||||
msgstr ""
|
||||
|
||||
msgid "Iterations|This will delete the cadence as well as all of the iterations within it."
|
||||
msgstr ""
|
||||
|
||||
|
@ -25646,6 +25661,9 @@ msgstr ""
|
|||
msgid "Notes|This comment has changed since you started editing, please review the %{open_link}updated comment%{close_link} to ensure information is not lost"
|
||||
msgstr ""
|
||||
|
||||
msgid "Notes|This comment is confidential and only visible to group members"
|
||||
msgstr ""
|
||||
|
||||
msgid "Notes|This comment is confidential and only visible to project members"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ module QA
|
|||
Logger.debug('Getting repository storage moves')
|
||||
|
||||
Support::Waiter.wait_until do
|
||||
with_paginated_response_body(Request.new(api_client, "/#{resource_name(resource)}_repository_storage_moves", per_page: '100').url) do |page|
|
||||
get(Request.new(api_client, "/#{resource_name(resource)}_repository_storage_moves", per_page: '100').url) do |page|
|
||||
break true if page.any? { |item| yield item }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
module QA
|
||||
RSpec.describe 'Create' do
|
||||
describe 'Changing Gitaly repository storage', :requires_admin do
|
||||
describe 'Changing Gitaly repository storage', :requires_admin, except: { job: 'review-qa-*' } do
|
||||
praefect_manager = Service::PraefectManager.new
|
||||
|
||||
shared_examples 'repository storage move' do
|
||||
|
@ -11,12 +11,16 @@ module QA
|
|||
expect { project.change_repository_storage(destination_storage[:name]) }.not_to raise_error
|
||||
expect { praefect_manager.verify_storage_move(source_storage, destination_storage, repo_type: :project) }.not_to raise_error
|
||||
|
||||
Resource::Repository::ProjectPush.fabricate! do |push|
|
||||
push.project = project
|
||||
push.file_name = 'new_file'
|
||||
push.file_content = '# This is a new file'
|
||||
push.commit_message = 'Add new file'
|
||||
push.new_branch = false
|
||||
Support::Retrier.retry_on_exception(sleep_interval: 5) do
|
||||
# For a short period of time after migrating, the repository can be 'read only' which may lead to errors
|
||||
# 'The repository is temporarily read-only. Please try again later.'
|
||||
Resource::Repository::Commit.fabricate_via_api! do |commit|
|
||||
commit.project = project
|
||||
commit.commit_message = 'Add new file'
|
||||
commit.add_files([
|
||||
{ file_path: 'new_file', content: '# This is a new file' }
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
expect(project).to have_file('README.md')
|
||||
|
@ -45,7 +49,7 @@ module QA
|
|||
# Note: This test doesn't have the :orchestrated tag because it runs in the Test::Integration::Praefect
|
||||
# scenario with other tests that aren't considered orchestrated.
|
||||
# It also runs on staging using nfs-file07 as non-cluster storage and nfs-file22 as cluster/praefect storage
|
||||
context 'when moving from Gitaly to Gitaly Cluster', :requires_praefect, testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347828', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/284645', type: :investigating } do
|
||||
context 'when moving from Gitaly to Gitaly Cluster', :requires_praefect, testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347828' do
|
||||
let(:source_storage) { { type: :gitaly, name: QA::Runtime::Env.non_cluster_repository_storage } }
|
||||
let(:destination_storage) { { type: :praefect, name: QA::Runtime::Env.praefect_repository_storage } }
|
||||
let(:project) do
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
module QA
|
||||
RSpec.describe 'Manage' do
|
||||
describe 'Create project badge' do
|
||||
describe 'Create project badge', :reliable do
|
||||
let(:badge_name) { "project-badge-#{SecureRandom.hex(8)}" }
|
||||
let(:expected_badge_link_url) { "#{Runtime::Scenario.gitlab_address}/#{project.path_with_namespace}" }
|
||||
let(:expected_badge_image_url) { "#{Runtime::Scenario.gitlab_address}/#{project.path_with_namespace}/badges/main/pipeline.svg" }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
module QA
|
||||
RSpec.describe 'Manage' do
|
||||
describe 'Personal project permissions' do
|
||||
describe 'Personal project permissions', :reliable do
|
||||
let!(:owner) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) }
|
||||
|
||||
let!(:owner_api_client) { Runtime::API::Client.new(:gitlab, user: owner) }
|
||||
|
|
|
@ -135,6 +135,16 @@ RSpec.describe GraphqlController do
|
|||
post :execute
|
||||
end
|
||||
|
||||
it 'calls the track gitlab cli when trackable method' do
|
||||
agent = 'GLab - GitLab CLI'
|
||||
request.env['HTTP_USER_AGENT'] = agent
|
||||
|
||||
expect(Gitlab::UsageDataCounters::GitLabCliActivityUniqueCounter)
|
||||
.to receive(:track_api_request_when_trackable).with(user_agent: agent, user: user)
|
||||
|
||||
post :execute
|
||||
end
|
||||
|
||||
it "assigns username in ApplicationContext" do
|
||||
post :execute
|
||||
|
||||
|
@ -220,6 +230,16 @@ RSpec.describe GraphqlController do
|
|||
|
||||
subject
|
||||
end
|
||||
|
||||
it 'calls the track gitlab cli when trackable method' do
|
||||
agent = 'GLab - GitLab CLI'
|
||||
request.env['HTTP_USER_AGENT'] = agent
|
||||
|
||||
expect(Gitlab::UsageDataCounters::GitLabCliActivityUniqueCounter)
|
||||
.to receive(:track_api_request_when_trackable).with(user_agent: agent, user: user)
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is not logged in' do
|
||||
|
|
|
@ -142,11 +142,11 @@ RSpec.describe HelpController do
|
|||
context 'for Markdown formats' do
|
||||
subject { get :show, params: { path: path }, format: :md }
|
||||
|
||||
let(:path) { 'ssh/index' }
|
||||
let(:path) { 'user/ssh' }
|
||||
|
||||
context 'when requested file exists' do
|
||||
before do
|
||||
expect_file_read(File.join(Rails.root, 'doc/ssh/index.md'), content: fixture_file('blockquote_fence_after.md'))
|
||||
expect_file_read(File.join(Rails.root, 'doc/user/ssh.md'), content: fixture_file('blockquote_fence_after.md'))
|
||||
|
||||
subject
|
||||
end
|
||||
|
@ -257,7 +257,7 @@ RSpec.describe HelpController do
|
|||
it 'always renders not found' do
|
||||
get :show,
|
||||
params: {
|
||||
path: 'ssh/index'
|
||||
path: 'user/ssh'
|
||||
},
|
||||
format: :foo
|
||||
expect(response).to be_not_found
|
||||
|
|
|
@ -148,6 +148,13 @@ RSpec.describe Projects::IssuesController do
|
|||
allow(Kaminari.config).to receive(:default_per_page).and_return(1)
|
||||
end
|
||||
|
||||
it 'redirects to last page when out of bounds on non-html requests' do
|
||||
get :index, params: params.merge(page: last_page + 1), format: 'atom'
|
||||
|
||||
expect(response).to have_gitlab_http_status(:redirect)
|
||||
expect(response).to redirect_to(action: 'index', format: 'atom', page: last_page, state: 'opened')
|
||||
end
|
||||
|
||||
it 'does not use pagination if disabled' do
|
||||
allow(controller).to receive(:pagination_disabled?).and_return(true)
|
||||
|
||||
|
|
|
@ -95,8 +95,6 @@ RSpec.describe Releases::GroupReleasesFinder do
|
|||
end
|
||||
|
||||
describe 'with subgroups' do
|
||||
let(:params) { { include_subgroups: true } }
|
||||
|
||||
subject(:releases) { described_class.new(group, user, params).execute(**args) }
|
||||
|
||||
context 'with a single-level subgroup' do
|
||||
|
@ -164,22 +162,12 @@ RSpec.describe Releases::GroupReleasesFinder do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when the user a guest on the group' do
|
||||
before do
|
||||
group.add_guest(user)
|
||||
end
|
||||
|
||||
it 'returns all releases' do
|
||||
expect(releases).to match_array([v1_1_1, v1_1_0, v6, v1_0_0, p3])
|
||||
end
|
||||
end
|
||||
|
||||
context 'performance testing' do
|
||||
shared_examples 'avoids N+1 queries' do |query_params = {}|
|
||||
context 'with subgroups' do
|
||||
let(:params) { query_params }
|
||||
|
||||
it 'include_subgroups avoids N+1 queries' do
|
||||
it 'subgroups avoids N+1 queries' do
|
||||
control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do
|
||||
releases
|
||||
end.count
|
||||
|
@ -196,7 +184,6 @@ RSpec.describe Releases::GroupReleasesFinder do
|
|||
end
|
||||
|
||||
it_behaves_like 'avoids N+1 queries'
|
||||
it_behaves_like 'avoids N+1 queries', { simple: true }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -296,5 +296,13 @@ describe('NoteHeader component', () => {
|
|||
createComponent({ isConfidential: status });
|
||||
expect(findConfidentialIndicator().exists()).toBe(status);
|
||||
});
|
||||
|
||||
it('shows confidential indicator tooltip for project context', () => {
|
||||
createComponent({ isConfidential: true, noteableType: 'issue' });
|
||||
|
||||
expect(findConfidentialIndicator().attributes('title')).toBe(
|
||||
'This comment is confidential and only visible to project members',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11,6 +11,7 @@ import NoteBody from '~/notes/components/note_body.vue';
|
|||
import NoteHeader from '~/notes/components/note_header.vue';
|
||||
import issueNote from '~/notes/components/noteable_note.vue';
|
||||
import NotesModule from '~/notes/stores/modules';
|
||||
import { NOTEABLE_TYPE_MAPPING } from '~/notes/constants';
|
||||
|
||||
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
|
||||
|
||||
|
@ -226,6 +227,7 @@ describe('issue_note', () => {
|
|||
expect(noteHeaderProps.author).toBe(note.author);
|
||||
expect(noteHeaderProps.createdAt).toBe(note.created_at);
|
||||
expect(noteHeaderProps.noteId).toBe(note.id);
|
||||
expect(noteHeaderProps.noteableType).toBe(NOTEABLE_TYPE_MAPPING[note.noteable_type]);
|
||||
});
|
||||
|
||||
it('should render note actions', () => {
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::UsageDataCounters::GitLabCliActivityUniqueCounter, :clean_gitlab_redis_shared_state do # rubocop:disable RSpec/FilePath
|
||||
let(:user1) { build(:user, id: 1) }
|
||||
let(:user2) { build(:user, id: 2) }
|
||||
let(:time) { Time.current }
|
||||
let(:action) { described_class::GITLAB_CLI_API_REQUEST_ACTION }
|
||||
let(:user_agent) { { user_agent: 'GLab - GitLab CLI' } }
|
||||
|
||||
context 'when tracking a gitlab cli request' do
|
||||
it_behaves_like 'a request from an extension'
|
||||
end
|
||||
end
|
|
@ -28,7 +28,7 @@ require (
|
|||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/smartystreets/goconvey v1.6.4
|
||||
github.com/stretchr/testify v1.7.0
|
||||
gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc1
|
||||
gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059
|
||||
gitlab.com/gitlab-org/golang-archive-zip v0.1.1
|
||||
gitlab.com/gitlab-org/labkit v1.6.0
|
||||
gocloud.dev v0.23.0
|
||||
|
|
|
@ -606,7 +606,7 @@ github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
|||
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/libgit2/git2go v0.0.0-20190104134018-ecaeb7a21d47/go.mod h1:4bKN42efkbNYMZlvDfxGDxzl066GhpvIircZDsm8Y+Y=
|
||||
github.com/libgit2/git2go/v31 v31.4.12/go.mod h1:c/rkJcBcUFx6wHaT++UwNpKvIsmPNqCeQ/vzO4DrEec=
|
||||
github.com/libgit2/git2go/v33 v33.0.6/go.mod h1:KdpqkU+6+++4oHna/MIOgx4GCQ92IPCdpVRMRI80J+4=
|
||||
github.com/libgit2/git2go/v33 v33.0.9/go.mod h1:KdpqkU+6+++4oHna/MIOgx4GCQ92IPCdpVRMRI80J+4=
|
||||
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
|
||||
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20200305213919-a88bf8de3718/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
|
||||
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7 h1:YjW+hUb8Fh2S58z4av4t/0cBMK/Q0aP48RocCFsC8yI=
|
||||
|
@ -886,8 +886,8 @@ github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wK
|
|||
gitlab.com/gitlab-org/gitaly v1.68.0 h1:VlcJs1+PrhW7lqJUU7Fh1q8FMJujmbbivdfde/cwB98=
|
||||
gitlab.com/gitlab-org/gitaly v1.68.0/go.mod h1:/pCsB918Zu5wFchZ9hLYin9WkJ2yQqdVNz0zlv5HbXg=
|
||||
gitlab.com/gitlab-org/gitaly/v14 v14.0.0-rc1/go.mod h1:4Cz8tOAyueSZX5o6gYum1F/unupaOclxqETPcg4ODvQ=
|
||||
gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc1 h1:9vStRdXxcBQ8dHlVnpV28fwLOgyDkSFIpGnPqwzdTvw=
|
||||
gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc1/go.mod h1:Xk5pn6IWsejg3z2X6BRczC5QaI97PRF3GU5OrJ5Amkg=
|
||||
gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059 h1:X7+3GQIxUpScXpIMCU5+sfpYvZyBIQ3GMlEosP7Jssw=
|
||||
gitlab.com/gitlab-org/gitaly/v14 v14.9.0-rc5.0.20220329111719-51da8bc17059/go.mod h1:uX1qhFKBDuPqATlpMcFL2dKDiX8D/tbUg7CYWx7OXt4=
|
||||
gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20201117050822-3f9890ef73dc/go.mod h1:5QSTbpAHY2v0iIH5uHh2KA9w7sPUqPmnLjDApI/sv1U=
|
||||
gitlab.com/gitlab-org/gitlab-shell v1.9.8-0.20210720163109-50da611814d2/go.mod h1:QWDYBwuy24qGMandtCngLRPzFgnGPg6LSNoJWPKmJMc=
|
||||
gitlab.com/gitlab-org/golang-archive-zip v0.1.1 h1:35k9giivbxwF03+8A05Cm8YoxoakU8FBCj5gysjCTCE=
|
||||
|
|
Loading…
Reference in a new issue