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>2022-10-12 09:08:58 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-10-12 09:08:58 +0300
commitde9b52d3bba0b5720c9b27df5058eabbe832fe49 (patch)
treeec02776008b2608d09931db1d56a50934c3816c6
parent488dae554074462d0f8bc0d63772b26c1c75aaa7 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/content_editor/components/suggestions_dropdown.vue15
-rw-r--r--app/assets/javascripts/content_editor/extensions/suggestions.js10
-rw-r--r--app/assets/stylesheets/_page_specific_files.scss1
-rw-r--r--app/assets/stylesheets/pages/service_desk.scss7
-rw-r--r--app/views/projects/issues/service_desk/_service_desk_info_content.html.haml2
-rw-r--r--config/feature_flags/development/child_epics_from_different_hierarchies.yml8
-rw-r--r--config/feature_flags/development/usage_data_ci_i_testing_test_report_uploaded.yml8
-rw-r--r--doc/administration/repository_storage_types.md4
-rw-r--r--doc/user/profile/user_passwords.md18
-rw-r--r--lib/api/todos.rb26
-rw-r--r--lib/gitlab/ci/templates/Security/Coverage-Fuzzing.gitlab-ci.yml10
-rw-r--r--lib/gitlab/ci/templates/Security/Coverage-Fuzzing.latest.gitlab-ci.yml10
-rw-r--r--lib/gitlab/usage_data_counters/known_events/common.yml1
-rw-r--r--spec/features/issues/service_desk_spec.rb1
-rw-r--r--spec/frontend/content_editor/components/suggestions_dropdown_spec.js24
-rw-r--r--workhorse/go.mod2
-rw-r--r--workhorse/go.sum4
17 files changed, 88 insertions, 63 deletions
diff --git a/app/assets/javascripts/content_editor/components/suggestions_dropdown.vue b/app/assets/javascripts/content_editor/components/suggestions_dropdown.vue
index fec32df406d..c4617983488 100644
--- a/app/assets/javascripts/content_editor/components/suggestions_dropdown.vue
+++ b/app/assets/javascripts/content_editor/components/suggestions_dropdown.vue
@@ -45,6 +45,10 @@ export default {
return this.nodeType === 'reference';
},
+ isCommand() {
+ return this.isReference && this.nodeProps.referenceType === 'command';
+ },
+
isUser() {
return this.isReference && this.nodeProps.referenceType === 'user';
},
@@ -84,6 +88,8 @@ export default {
return `${this.char}${item.iid}`;
case 'milestone':
return `${this.char}${item.title}`;
+ case 'command':
+ return `${this.char}${item.name} `;
default:
return '';
}
@@ -140,7 +146,6 @@ export default {
if (item) {
this.command({
text: this.getText(item),
- href: '#',
...this.getProps(item),
});
}
@@ -156,7 +161,7 @@ export default {
<template>
<ul
:class="{ show: items.length > 0 }"
- class="gl-new-dropdown dropdown-menu"
+ class="gl-new-dropdown dropdown-menu gl-relative"
data-testid="content-editor-suggestions-dropdown"
>
<div class="gl-new-dropdown-inner gl-overflow-y-auto">
@@ -182,6 +187,12 @@ export default {
<span v-if="isMilestone">
{{ item.title }}
</span>
+ <span v-if="isCommand">
+ /{{ item.name }} <small> {{ item.params[0] }} </small><br />
+ <em>
+ <small> {{ item.description }} </small>
+ </em>
+ </span>
<div v-if="isEmoji" class="gl-display-flex gl-flex gl-align-items-center">
<div class="gl-pr-4 gl-font-lg">{{ item.e }}</div>
<div class="gl-flex-grow-1">
diff --git a/app/assets/javascripts/content_editor/extensions/suggestions.js b/app/assets/javascripts/content_editor/extensions/suggestions.js
index 9985b6bd7f8..ef7f64e573e 100644
--- a/app/assets/javascripts/content_editor/extensions/suggestions.js
+++ b/app/assets/javascripts/content_editor/extensions/suggestions.js
@@ -161,6 +161,16 @@ export default Node.create({
}),
createSuggestionPlugin({
editor: this.editor,
+ char: '/',
+ dataSource: gl.GfmAutoComplete?.dataSources.commands,
+ nodeType: 'reference',
+ nodeProps: {
+ referenceType: 'command',
+ },
+ search: (query) => ({ name }) => find(name, query),
+ }),
+ createSuggestionPlugin({
+ editor: this.editor,
char: ':',
dataSource: () => Object.values(getAllEmoji()),
nodeType: 'emoji',
diff --git a/app/assets/stylesheets/_page_specific_files.scss b/app/assets/stylesheets/_page_specific_files.scss
index 68fe701c7e1..21d9db26382 100644
--- a/app/assets/stylesheets/_page_specific_files.scss
+++ b/app/assets/stylesheets/_page_specific_files.scss
@@ -19,7 +19,6 @@
@import './pages/projects';
@import './pages/registry';
@import './pages/search';
-@import './pages/service_desk';
@import './pages/settings';
@import './pages/storage_quota';
@import './pages/users';
diff --git a/app/assets/stylesheets/pages/service_desk.scss b/app/assets/stylesheets/pages/service_desk.scss
deleted file mode 100644
index 34ab5eb1b74..00000000000
--- a/app/assets/stylesheets/pages/service_desk.scss
+++ /dev/null
@@ -1,7 +0,0 @@
-.service-desk-issues {
- .non-empty-state {
- text-align: left;
- padding-bottom: $gl-padding-top;
- border-bottom: 1px solid $border-color;
- }
-}
diff --git a/app/views/projects/issues/service_desk/_service_desk_info_content.html.haml b/app/views/projects/issues/service_desk/_service_desk_info_content.html.haml
index bad75ac2cd9..2ed5675c0ad 100644
--- a/app/views/projects/issues/service_desk/_service_desk_info_content.html.haml
+++ b/app/views/projects/issues/service_desk/_service_desk_info_content.html.haml
@@ -4,7 +4,7 @@
- can_admin_issues = can?(current_user, :admin_issue, @project)
- title_text = s_("ServiceDesk|Use Service Desk to connect with your users and offer customer support through email right inside GitLab")
-.non-empty-state.media
+.media.gl-border-b.gl-pb-3.gl-text-left
.svg-content
= render partial: 'shared/empty_states/icons/service_desk_callout', formats: :svg
diff --git a/config/feature_flags/development/child_epics_from_different_hierarchies.yml b/config/feature_flags/development/child_epics_from_different_hierarchies.yml
deleted file mode 100644
index 9bafdea8465..00000000000
--- a/config/feature_flags/development/child_epics_from_different_hierarchies.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: child_epics_from_different_hierarchies
-introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/99147"
-rollout_issue_url: "https://gitlab.com/gitlab-org/gitlab/-/issues/375622"
-milestone: '15.5'
-type: development
-group: group::product planning
-default_enabled: false
diff --git a/config/feature_flags/development/usage_data_ci_i_testing_test_report_uploaded.yml b/config/feature_flags/development/usage_data_ci_i_testing_test_report_uploaded.yml
deleted file mode 100644
index 1635427485b..00000000000
--- a/config/feature_flags/development/usage_data_ci_i_testing_test_report_uploaded.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: usage_data_ci_i_testing_test_report_uploaded
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/95112
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/339720
-milestone: '15.4'
-type: development
-group: group::pipeline insights
-default_enabled: false
diff --git a/doc/administration/repository_storage_types.md b/doc/administration/repository_storage_types.md
index 6919dfa4022..77a5eae3747 100644
--- a/doc/administration/repository_storage_types.md
+++ b/doc/administration/repository_storage_types.md
@@ -167,6 +167,10 @@ For example:
"@groups/#{hash[0..1]}/#{hash[2..3]}/#{hash}.wiki.git"
```
+### Gitaly Cluster storage
+
+If Gitaly Cluster is used, Praefect manages storage locations. For more information, see [Praefect-generated replica paths](gitaly/index.md#praefect-generated-replica-paths-gitlab-150-and-later).
+
### Object storage support
This table shows which storable objects are storable in each storage type:
diff --git a/doc/user/profile/user_passwords.md b/doc/user/profile/user_passwords.md
index bb3d944d2ea..04d149c9709 100644
--- a/doc/user/profile/user_passwords.md
+++ b/doc/user/profile/user_passwords.md
@@ -6,12 +6,11 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# User passwords **(FREE)**
-If you use a password to sign in to GitLab, a strong password is very important. A weak or guessable password makes it easier
-for unauthorised people to log into your account.
+If you use a password to sign in to GitLab, a strong password is very important. A weak or guessable password makes it
+easier for unauthorized people to log into your account.
Some organizations require you to meet certain requirements when choosing a password.
-INFO:
Improve the security of your account with [two-factor authentication](account/two_factor_authentication.md)
## Choose your password
@@ -24,7 +23,8 @@ authorization provider, you do not need to choose a password. GitLab
## Change your password
-You can change your password. GitLab enforces [password requirements](#password-requirements) when you choose your new password.
+You can change your password. GitLab enforces [password requirements](#password-requirements) when you choose your new
+password.
1. On the top bar, in the top-right corner, select your avatar.
1. Select **Edit profile**.
@@ -33,7 +33,8 @@ You can change your password. GitLab enforces [password requirements](#password-
1. In the **New password** and **Password confirmation** text box, enter your new password.
1. Select **Save password**.
-If you don't know your current password, select the **I forgot my password** link. A password reset email is sent to the account's **primary** email address.
+If you don't know your current password, select the **I forgot my password** link. A password reset email is sent to the
+account's **primary** email address.
## Password requirements
@@ -48,7 +49,7 @@ Your passwords must meet a set of requirements when:
By default GitLab enforces the following password requirements:
-- [Minimum and maximum password lengths](../gitlab_com/index.md#password-requirements). For example,
+- Minimum and maximum password lengths. For example,
see [the settings for GitLab.com](../gitlab_com/index.md#password-requirements).
- Disallowing [weak passwords](#block-weak-passwords).
@@ -62,8 +63,9 @@ Self-managed installations can configure the following additional password requi
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/23610) in GitLab 15.4 [with a flag](../../administration/feature_flags.md) named `block_weak_passwords`, weak passwords aren't accepted. Disabled by default.
FLAG:
-On self-managed GitLab, by default blocking weak passwords is not available. To make it available, ask an administrator to [enable the feature flag](../../administration/feature_flags.md) named `block_weak_passwords`.
-On GitLab.com, this feature is available but can be configured by GitLab.com administrators only.
+On self-managed GitLab, by default blocking weak passwords is not available. To make it available, ask an administrator
+to [enable the feature flag](../../administration/feature_flags.md) named `block_weak_passwords`. On GitLab.com, this
+feature is available but can be configured by GitLab.com administrators only.
GitLab disallows weak passwords. Your password is considered weak when it:
diff --git a/lib/api/todos.rb b/lib/api/todos.rb
index f1779df7cc6..57745ee8802 100644
--- a/lib/api/todos.rb
+++ b/lib/api/todos.rb
@@ -15,17 +15,17 @@ module API
}.freeze
params do
- requires :id, type: String, desc: 'The ID of a project'
+ requires :id, type: String, desc: 'The ID or URL-encoded path of the project owned by the authenticated user'
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
ISSUABLE_TYPES.each do |type, finder|
type_id_str = "#{type.singularize}_iid".to_sym
- desc 'Create a todo on an issuable' do
+ desc 'Create a to-do item on an issuable' do
success Entities::Todo
end
params do
- requires type_id_str, type: Integer, desc: 'The IID of an issuable'
+ requires type_id_str, type: Integer, desc: 'The internal ID of an issuable'
end
post ":id/#{type}/:#{type_id_str}/todo" do
issuable = instance_exec(params[type_id_str], &finder)
@@ -44,12 +44,12 @@ module API
resource :todos do
helpers do
params :todo_filters do
- optional :action, String, values: Todo::ACTION_NAMES.values.map(&:to_s)
- optional :author_id, Integer
- optional :state, String, values: Todo.state_machine.states.map(&:name).map(&:to_s)
- optional :type, String, values: TodosFinder.todo_types
- optional :project_id, Integer
- optional :group_id, Integer
+ optional :action, type: String, values: Todo::ACTION_NAMES.values.map(&:to_s), desc: 'The action to be filtered'
+ optional :author_id, type: Integer, desc: 'The ID of an author'
+ optional :project_id, type: Integer, desc: 'The ID of a project'
+ optional :group_id, type: Integer, desc: 'The ID of a group'
+ optional :state, type: String, values: Todo.state_machine.states.map(&:name).map(&:to_s), desc: 'The state of the to-do item'
+ optional :type, type: String, values: TodosFinder.todo_types.map(&:to_s), desc: 'The type of to-do item'
end
def find_todos
@@ -81,7 +81,7 @@ module API
end
end
- desc 'Get a todo list' do
+ desc 'Get a list of to-do items' do
success Entities::Todo
end
params do
@@ -96,11 +96,11 @@ module API
present todos, options
end
- desc 'Mark a todo as done' do
+ desc 'Mark a to-do item as done' do
success Entities::Todo
end
params do
- requires :id, type: Integer, desc: 'The ID of the todo being marked as done'
+ requires :id, type: Integer, desc: 'The ID of to-do item'
end
post ':id/mark_as_done' do
todo = current_user.todos.find(params[:id])
@@ -110,7 +110,7 @@ module API
present todo, with: Entities::Todo, current_user: current_user
end
- desc 'Mark all todos as done'
+ desc 'Mark all to-do items as done'
post '/mark_as_done' do
todos = find_todos
diff --git a/lib/gitlab/ci/templates/Security/Coverage-Fuzzing.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Coverage-Fuzzing.gitlab-ci.yml
index d4b6a252b25..d933007ec61 100644
--- a/lib/gitlab/ci/templates/Security/Coverage-Fuzzing.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/Coverage-Fuzzing.gitlab-ci.yml
@@ -31,15 +31,15 @@ coverage_fuzzing_unlicensed:
stage: fuzz
allow_failure: true
before_script:
- - export COVFUZZ_JOB_TOKEN=$CI_JOB_TOKEN
- - export COVFUZZ_PRIVATE_TOKEN=$CI_PRIVATE_TOKEN
- - export COVFUZZ_PROJECT_PATH=$CI_PROJECT_PATH
- - export COVFUZZ_PROJECT_ID=$CI_PROJECT_ID
+ - export COVFUZZ_JOB_TOKEN="$CI_JOB_TOKEN"
+ - export COVFUZZ_PRIVATE_TOKEN="$CI_PRIVATE_TOKEN"
+ - export COVFUZZ_PROJECT_PATH="$CI_PROJECT_PATH"
+ - export COVFUZZ_PROJECT_ID="$CI_PROJECT_ID"
- if [ -x "$(command -v apt-get)" ] ; then apt-get update && apt-get install -y wget; fi
- wget -O gitlab-cov-fuzz "${COVFUZZ_URL_PREFIX}"/"${COVFUZZ_VERSION}"/binaries/gitlab-cov-fuzz_Linux_x86_64
- chmod a+x gitlab-cov-fuzz
- export REGRESSION=true
- - if [[ $CI_COMMIT_BRANCH = $COVFUZZ_BRANCH ]]; then REGRESSION=false; fi;
+ - if [[ "$CI_COMMIT_BRANCH" = "$COVFUZZ_BRANCH" ]]; then REGRESSION=false; fi;
artifacts:
paths:
- corpus
diff --git a/lib/gitlab/ci/templates/Security/Coverage-Fuzzing.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Coverage-Fuzzing.latest.gitlab-ci.yml
index 76a85d461f7..feed4c47157 100644
--- a/lib/gitlab/ci/templates/Security/Coverage-Fuzzing.latest.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/Coverage-Fuzzing.latest.gitlab-ci.yml
@@ -31,15 +31,15 @@ coverage_fuzzing_unlicensed:
stage: fuzz
allow_failure: true
before_script:
- - export COVFUZZ_JOB_TOKEN=$CI_JOB_TOKEN
- - export COVFUZZ_PRIVATE_TOKEN=$CI_PRIVATE_TOKEN
- - export COVFUZZ_PROJECT_PATH=$CI_PROJECT_PATH
- - export COVFUZZ_PROJECT_ID=$CI_PROJECT_ID
+ - export COVFUZZ_JOB_TOKEN="$CI_JOB_TOKEN"
+ - export COVFUZZ_PRIVATE_TOKEN="$CI_PRIVATE_TOKEN"
+ - export COVFUZZ_PROJECT_PATH="$CI_PROJECT_PATH"
+ - export COVFUZZ_PROJECT_ID="$CI_PROJECT_ID"
- if [ -x "$(command -v apt-get)" ] ; then apt-get update && apt-get install -y wget; fi
- wget -O gitlab-cov-fuzz "${COVFUZZ_URL_PREFIX}"/"${COVFUZZ_VERSION}"/binaries/gitlab-cov-fuzz_Linux_x86_64
- chmod a+x gitlab-cov-fuzz
- export REGRESSION=true
- - if [[ $CI_COMMIT_BRANCH = $COVFUZZ_BRANCH ]]; then REGRESSION=false; fi;
+ - if [[ "$CI_COMMIT_BRANCH" = "$COVFUZZ_BRANCH" ]]; then REGRESSION=false; fi;
artifacts:
paths:
- corpus
diff --git a/lib/gitlab/usage_data_counters/known_events/common.yml b/lib/gitlab/usage_data_counters/known_events/common.yml
index 4fb2e2266ee..c13c7657576 100644
--- a/lib/gitlab/usage_data_counters/known_events/common.yml
+++ b/lib/gitlab/usage_data_counters/known_events/common.yml
@@ -127,7 +127,6 @@
category: testing
redis_slot: testing
aggregation: weekly
- feature_flag: usage_data_ci_i_testing_test_report_uploaded
# Project Management group
- name: g_project_management_issue_title_changed
category: issues_edit
diff --git a/spec/features/issues/service_desk_spec.rb b/spec/features/issues/service_desk_spec.rb
index cc0d35afd60..87cd00fac6b 100644
--- a/spec/features/issues/service_desk_spec.rb
+++ b/spec/features/issues/service_desk_spec.rb
@@ -90,7 +90,6 @@ RSpec.describe 'Service Desk Issue Tracker', :js do
it 'displays the small info box, documentation, a button to configure service desk, and the address' do
aggregate_failures do
- expect(page).to have_css('.non-empty-state')
expect(page).to have_link('Learn more.', href: help_page_path('user/project/service_desk'))
expect(page).not_to have_link('Enable Service Desk')
expect(page).to have_content(project.service_desk_address)
diff --git a/spec/frontend/content_editor/components/suggestions_dropdown_spec.js b/spec/frontend/content_editor/components/suggestions_dropdown_spec.js
index 858160d6c59..f25f183a013 100644
--- a/spec/frontend/content_editor/components/suggestions_dropdown_spec.js
+++ b/spec/frontend/content_editor/components/suggestions_dropdown_spec.js
@@ -18,6 +18,11 @@ describe('~/content_editor/components/suggestions_dropdown', () => {
const exampleIssue = { iid: 123, title: 'Test Issue' };
const exampleMergeRequest = { iid: 224, title: 'Test MR' };
const exampleMilestone = { iid: 21, title: '1.3' };
+ const exampleCommand = {
+ name: 'due',
+ description: 'Set due date',
+ params: ['<in 2 days | this Friday | December 31st>'],
+ };
const exampleEmoji = {
c: 'people',
e: '😃',
@@ -40,6 +45,7 @@ describe('~/content_editor/components/suggestions_dropdown', () => {
${'reference'} | ${'issue'} | ${'#'} | ${exampleIssue} | ${`#123`} | ${{}}
${'reference'} | ${'merge_request'} | ${'!'} | ${exampleMergeRequest} | ${`!224`} | ${{}}
${'reference'} | ${'milestone'} | ${'%'} | ${exampleMilestone} | ${`%1.3`} | ${{}}
+ ${'reference'} | ${'command'} | ${'/'} | ${exampleCommand} | ${'/due '} | ${{}}
${'emoji'} | ${'emoji'} | ${':'} | ${exampleEmoji} | ${`😃`} | ${insertedEmojiProps}
`(
'runs a command to insert the selected $referenceType',
@@ -121,6 +127,24 @@ describe('~/content_editor/components/suggestions_dropdown', () => {
});
});
+ describe('rendering a command (quick action)', () => {
+ it('displays command name with a slash', () => {
+ buildWrapper({
+ propsData: {
+ char: '/',
+ command: jest.fn(),
+ nodeType: 'reference',
+ nodeProps: {
+ referenceType: 'command',
+ },
+ items: [exampleCommand],
+ },
+ });
+
+ expect(wrapper.text()).toContain(`${exampleCommand.name} `);
+ });
+ });
+
describe('rendering emoji references', () => {
it('displays emoji', () => {
const testEmojis = [
diff --git a/workhorse/go.mod b/workhorse/go.mod
index 664b63c3da4..fd6a6e984d3 100644
--- a/workhorse/go.mod
+++ b/workhorse/go.mod
@@ -7,7 +7,7 @@ require (
github.com/BurntSushi/toml v1.2.0
github.com/FZambia/sentinel v1.1.1
github.com/alecthomas/chroma/v2 v2.3.0
- github.com/aws/aws-sdk-go v1.44.109
+ github.com/aws/aws-sdk-go v1.44.114
github.com/disintegration/imaging v1.6.2
github.com/getsentry/raven-go v0.2.0
github.com/golang-jwt/jwt/v4 v4.4.2
diff --git a/workhorse/go.sum b/workhorse/go.sum
index e07588706b9..c472a1933f2 100644
--- a/workhorse/go.sum
+++ b/workhorse/go.sum
@@ -179,8 +179,8 @@ github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZo
github.com/aws/aws-sdk-go v1.17.4/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/aws/aws-sdk-go v1.43.31/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
-github.com/aws/aws-sdk-go v1.44.109 h1:+Na5JPeS0kiEHoBp5Umcuuf+IDqXqD0lXnM920E31YI=
-github.com/aws/aws-sdk-go v1.44.109/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
+github.com/aws/aws-sdk-go v1.44.114 h1:plIkWc/RsHr3DXBj4MEw9sEW4CcL/e2ryokc+CKyq1I=
+github.com/aws/aws-sdk-go v1.44.114/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go-v2 v1.16.2 h1:fqlCk6Iy3bnCumtrLz9r3mJ/2gUT0pJ0wLFVIdWh+JA=
github.com/aws/aws-sdk-go-v2 v1.16.2/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1 h1:SdK4Ppk5IzLs64ZMvr6MrSficMtjY2oS0WOORXTlxwU=