Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
b962adf4c3
commit
29f6c0bff8
|
@ -3573,7 +3573,6 @@ Layout/LineLength:
|
|||
- 'lib/gitlab/sidekiq_daemon/monitor.rb'
|
||||
- 'lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job.rb'
|
||||
- 'lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies/deduplicates_when_scheduling.rb'
|
||||
- 'lib/gitlab/sidekiq_middleware/memory_killer.rb'
|
||||
- 'lib/gitlab/sidekiq_middleware/server_metrics.rb'
|
||||
- 'lib/gitlab/sidekiq_middleware/size_limiter/compressor.rb'
|
||||
- 'lib/gitlab/sidekiq_versioning.rb'
|
||||
|
|
|
@ -592,7 +592,6 @@ Style/PercentLiteralDelimiters:
|
|||
- 'lib/gitlab/search/abuse_detection.rb'
|
||||
- 'lib/gitlab/search_context.rb'
|
||||
- 'lib/gitlab/sidekiq_daemon/memory_killer.rb'
|
||||
- 'lib/gitlab/sidekiq_middleware/memory_killer.rb'
|
||||
- 'lib/gitlab/slash_commands/presenters/base.rb'
|
||||
- 'lib/gitlab/ssh_public_key.rb'
|
||||
- 'lib/gitlab/task_helpers.rb'
|
||||
|
|
1
Gemfile
1
Gemfile
|
@ -38,7 +38,6 @@ gem 'doorkeeper', '~> 5.5.0.rc2'
|
|||
gem 'doorkeeper-openid_connect', '~> 1.7.5'
|
||||
gem 'rexml', '~> 3.2.5'
|
||||
gem 'ruby-saml', '~> 1.13.0'
|
||||
gem 'omniauth-rails_csrf_protection'
|
||||
gem 'omniauth', '~> 2.1.0'
|
||||
gem 'omniauth-auth0', '~> 2.0.0'
|
||||
gem 'omniauth-azure-activedirectory-v2', '~> 1.0'
|
||||
|
|
|
@ -958,9 +958,6 @@ GEM
|
|||
omniauth (>= 1.9, < 3)
|
||||
omniauth-oauth2-generic (0.2.2)
|
||||
omniauth-oauth2 (~> 1.0)
|
||||
omniauth-rails_csrf_protection (1.0.1)
|
||||
actionpack (>= 4.2)
|
||||
omniauth (~> 2.0)
|
||||
omniauth-saml (2.0.0)
|
||||
omniauth (~> 2.0)
|
||||
ruby-saml (~> 1.9)
|
||||
|
@ -1702,7 +1699,6 @@ DEPENDENCIES
|
|||
omniauth-gitlab (~> 4.0.0)!
|
||||
omniauth-google-oauth2 (~> 1.0.1)!
|
||||
omniauth-oauth2-generic (~> 0.2.2)
|
||||
omniauth-rails_csrf_protection
|
||||
omniauth-salesforce (~> 1.0.5)!
|
||||
omniauth-saml (~> 2.0.0)
|
||||
omniauth-shibboleth (~> 1.3.0)
|
||||
|
|
|
@ -79,7 +79,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="js-search-settings-section">
|
||||
<token
|
||||
v-for="(tokenData, tokenType) in enabledTokenTypes"
|
||||
:key="tokenType"
|
||||
|
|
|
@ -196,7 +196,7 @@ export default {
|
|||
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="loading" class="contributors-loader text-center">
|
||||
<div v-if="loading" class="gl-text-center gl-pt-13">
|
||||
<gl-loading-icon :inline="true" size="xl" />
|
||||
</div>
|
||||
|
||||
|
|
|
@ -86,7 +86,7 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
errorAlertDismissed() {
|
||||
this.error = true;
|
||||
this.error = false;
|
||||
},
|
||||
extractContacts(data) {
|
||||
const contacts = data?.group?.contacts?.nodes || [];
|
||||
|
@ -146,7 +146,7 @@ export default {
|
|||
editButtonLabel: __('Edit'),
|
||||
title: s__('Crm|Customer relations contacts'),
|
||||
newContact: s__('Crm|New contact'),
|
||||
errorText: __('Something went wrong. Please try again.'),
|
||||
errorMsg: __('Something went wrong. Please try again.'),
|
||||
},
|
||||
EDIT_ROUTE_NAME,
|
||||
NEW_ROUTE_NAME,
|
||||
|
@ -176,7 +176,7 @@ export default {
|
|||
<div>
|
||||
<paginated-table-with-search-and-tabs
|
||||
:show-items="true"
|
||||
:show-error-msg="false"
|
||||
:show-error-msg="error"
|
||||
:i18n="$options.i18n"
|
||||
:items="contacts.list"
|
||||
:page-info="contacts.pageInfo"
|
||||
|
@ -243,10 +243,7 @@ export default {
|
|||
</template>
|
||||
|
||||
<template #empty>
|
||||
<span v-if="error">
|
||||
{{ $options.i18n.errorText }}
|
||||
</span>
|
||||
<span v-else>
|
||||
<span>
|
||||
{{ $options.i18n.emptyText }}
|
||||
</span>
|
||||
</template>
|
||||
|
|
|
@ -137,7 +137,7 @@ export default {
|
|||
editButtonLabel: __('Edit'),
|
||||
title: s__('Crm|Customer relations organizations'),
|
||||
newOrganization: s__('Crm|New organization'),
|
||||
errorText: __('Something went wrong. Please try again.'),
|
||||
errorMsg: __('Something went wrong. Please try again.'),
|
||||
},
|
||||
EDIT_ROUTE_NAME,
|
||||
NEW_ROUTE_NAME,
|
||||
|
@ -167,7 +167,7 @@ export default {
|
|||
<div>
|
||||
<paginated-table-with-search-and-tabs
|
||||
:show-items="true"
|
||||
:show-error-msg="false"
|
||||
:show-error-msg="error"
|
||||
:i18n="$options.i18n"
|
||||
:items="organizations.list"
|
||||
:page-info="organizations.pageInfo"
|
||||
|
@ -238,10 +238,7 @@ export default {
|
|||
</template>
|
||||
|
||||
<template #empty>
|
||||
<span v-if="error">
|
||||
{{ $options.i18n.errorText }}
|
||||
</span>
|
||||
<span v-else>
|
||||
<span>
|
||||
{{ $options.i18n.emptyText }}
|
||||
</span>
|
||||
</template>
|
||||
|
|
|
@ -74,6 +74,9 @@ export default {
|
|||
|
||||
return utcDate.toISOString();
|
||||
},
|
||||
hasTimelineText() {
|
||||
return this.timelineText.length > 0;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.focusDate();
|
||||
|
@ -167,6 +170,8 @@ export default {
|
|||
variant="confirm"
|
||||
category="primary"
|
||||
class="gl-mr-3"
|
||||
data-testid="save-button"
|
||||
:disabled="!hasTimelineText"
|
||||
:loading="isEventProcessed"
|
||||
@click="handleSave(false)"
|
||||
>
|
||||
|
@ -177,6 +182,8 @@ export default {
|
|||
variant="confirm"
|
||||
category="secondary"
|
||||
class="gl-mr-3 gl-ml-n2"
|
||||
data-testid="save-and-add-button"
|
||||
:disabled="!hasTimelineText"
|
||||
:loading="isEventProcessed"
|
||||
@click="handleSave(true)"
|
||||
>
|
||||
|
|
|
@ -131,9 +131,9 @@ export default {
|
|||
:message-url="view.message_url"
|
||||
:config="$options.integrationViewConfigs[view.name]"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<hr />
|
||||
</div>
|
||||
<div class="col-sm-12 js-hide-when-nothing-matches-search">
|
||||
<gl-button
|
||||
category="primary"
|
||||
variant="confirm"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { GlSearchBoxByType } from '@gitlab/ui';
|
||||
import { GlEmptyState, GlSearchBoxByType } from '@gitlab/ui';
|
||||
import { escapeRegExp } from 'lodash';
|
||||
import {
|
||||
EXCLUDED_NODES,
|
||||
|
@ -96,6 +96,8 @@ const displayResults = ({ sectionSelector, expandSection, searchTerm }, matching
|
|||
hideSectionsExcept(sectionSelector, sections);
|
||||
sections.forEach(expandSection);
|
||||
highlightText(matchingTextNodes, searchTerm);
|
||||
|
||||
return sections.length > 0;
|
||||
};
|
||||
|
||||
const clearResults = (params) => {
|
||||
|
@ -126,6 +128,7 @@ const search = (root, searchTerm) => {
|
|||
|
||||
export default {
|
||||
components: {
|
||||
GlEmptyState,
|
||||
GlSearchBoxByType,
|
||||
},
|
||||
props: {
|
||||
|
@ -137,6 +140,11 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
hideWhenEmptySelector: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: null,
|
||||
},
|
||||
isExpandedFn: {
|
||||
type: Function,
|
||||
required: false,
|
||||
|
@ -147,8 +155,16 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
searchTerm: '',
|
||||
hasMatches: true,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
hasMatches(newHasMatches) {
|
||||
document.querySelectorAll(this.hideWhenEmptySelector).forEach((section) => {
|
||||
section.classList.toggle(HIDE_CLASS, !newHasMatches);
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
search(value) {
|
||||
this.searchTerm = value;
|
||||
|
@ -161,11 +177,12 @@ export default {
|
|||
};
|
||||
|
||||
clearResults(displayOptions);
|
||||
this.hasMatches = true;
|
||||
|
||||
if (value.length) {
|
||||
saveExpansionState(document.querySelectorAll(this.sectionSelector), displayOptions);
|
||||
|
||||
displayResults(displayOptions, search(this.searchRoot, this.searchTerm));
|
||||
this.hasMatches = displayResults(displayOptions, search(this.searchRoot, this.searchTerm));
|
||||
} else {
|
||||
restoreExpansionState(displayOptions);
|
||||
}
|
||||
|
@ -181,10 +198,18 @@ export default {
|
|||
};
|
||||
</script>
|
||||
<template>
|
||||
<gl-search-box-by-type
|
||||
:value="searchTerm"
|
||||
:debounce="$options.TYPING_DELAY"
|
||||
:placeholder="__('Search page')"
|
||||
@input="search"
|
||||
/>
|
||||
<div>
|
||||
<gl-search-box-by-type
|
||||
:value="searchTerm"
|
||||
:debounce="$options.TYPING_DELAY"
|
||||
:placeholder="__('Search page')"
|
||||
@input="search"
|
||||
/>
|
||||
|
||||
<gl-empty-state
|
||||
v-if="!hasMatches"
|
||||
:title="__('No results found')"
|
||||
:description="__('Edit your search and try again')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -11,6 +11,7 @@ const mountSearch = ({ el }) =>
|
|||
props: {
|
||||
searchRoot: document.querySelector('#content-body'),
|
||||
sectionSelector: '.js-search-settings-section, section.settings',
|
||||
hideWhenEmptySelector: '.js-hide-when-nothing-matches-search',
|
||||
isExpandedFn: isExpanded,
|
||||
},
|
||||
on: {
|
||||
|
|
|
@ -275,7 +275,7 @@ export default {
|
|||
<template>
|
||||
<div class="incident-management-list">
|
||||
<gl-alert v-if="showErrorMsg" variant="danger" @dismiss="$emit('error-alert-dismissed')">
|
||||
<p v-safe-html="serverErrorMessage || i18n.errorMsg"></p>
|
||||
<span v-safe-html="serverErrorMessage || i18n.errorMsg"></span>
|
||||
</gl-alert>
|
||||
|
||||
<div
|
||||
|
|
|
@ -27,12 +27,14 @@ module SendsBlob
|
|||
private
|
||||
|
||||
def cached_blob?(blob, allow_caching: false)
|
||||
stale = stale?(etag: blob.id) # The #stale? method sets cache headers.
|
||||
stale =
|
||||
if Feature.enabled?(:improve_blobs_cache_headers)
|
||||
stale?(strong_etag: blob.id)
|
||||
else
|
||||
stale?(etag: blob.id)
|
||||
end
|
||||
|
||||
# Because we are opinionated we set the cache headers ourselves.
|
||||
response.cache_control[:public] = allow_caching
|
||||
|
||||
response.cache_control[:max_age] =
|
||||
max_age =
|
||||
if @ref && @commit && @ref == @commit.id # rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||
# This is a link to a commit by its commit SHA. That means that the blob
|
||||
# is immutable. The only reason to invalidate the cache is if the commit
|
||||
|
@ -44,6 +46,16 @@ module SendsBlob
|
|||
Blob::CACHE_TIME
|
||||
end
|
||||
|
||||
# Because we are opinionated we set the cache headers ourselves.
|
||||
if Feature.enabled?(:improve_blobs_cache_headers)
|
||||
expires_in(max_age,
|
||||
public: allow_caching, must_revalidate: true, stale_if_error: 5.minutes,
|
||||
stale_while_revalidate: 1.minute, 's-maxage': 1.minute)
|
||||
else
|
||||
response.cache_control[:public] = allow_caching
|
||||
response.cache_control[:max_age] = max_age
|
||||
end
|
||||
|
||||
!stale
|
||||
end
|
||||
|
||||
|
|
|
@ -5,25 +5,26 @@
|
|||
- @content_class = "limit-container-width" unless fluid_layout
|
||||
- payload_class = 'js-service-ping-payload'
|
||||
|
||||
%h3= name
|
||||
%section.js-search-settings-section
|
||||
%h3= name
|
||||
|
||||
- if @service_ping_data_present
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-payload-preview-trigger gl-mr-2', data: { payload_selector: ".#{payload_class}" } } ) do
|
||||
= gl_loading_icon(css_class: 'js-spinner gl-display-none', inline: true)
|
||||
%span.js-text.gl-display-inline= _('Preview payload')
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-payload-download-trigger gl-mr-2', data: { endpoint: usage_data_admin_application_settings_path(format: :json) } } ) do
|
||||
= gl_loading_icon(css_class: 'js-spinner gl-display-none', inline: true)
|
||||
%span.js-text.gl-display-inline= _('Download payload')
|
||||
%pre.js-syntax-highlight.code.highlight.gl-mt-2.gl-display-none{ class: payload_class, data: { endpoint: usage_data_admin_application_settings_path(format: :html) } }
|
||||
- else
|
||||
= render Pajamas::AlertComponent.new(variant: :warning,
|
||||
dismissible: false,
|
||||
title: _('Service Ping payload not found in the application cache')) do |c|
|
||||
- if @service_ping_data_present
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-payload-preview-trigger gl-mr-2', data: { payload_selector: ".#{payload_class}" } } ) do
|
||||
= gl_loading_icon(css_class: 'js-spinner gl-display-none', inline: true)
|
||||
%span.js-text.gl-display-inline= _('Preview payload')
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-payload-download-trigger gl-mr-2', data: { endpoint: usage_data_admin_application_settings_path(format: :json) } } ) do
|
||||
= gl_loading_icon(css_class: 'js-spinner gl-display-none', inline: true)
|
||||
%span.js-text.gl-display-inline= _('Download payload')
|
||||
%pre.js-syntax-highlight.code.highlight.gl-mt-2.gl-display-none{ class: payload_class, data: { endpoint: usage_data_admin_application_settings_path(format: :html) } }
|
||||
- else
|
||||
= render Pajamas::AlertComponent.new(variant: :warning,
|
||||
dismissible: false,
|
||||
title: _('Service Ping payload not found in the application cache')) do |c|
|
||||
|
||||
= c.body do
|
||||
- enable_service_ping_link_url = help_page_path('user/admin_area/settings/usage_statistics', anchor: 'enable-or-disable-usage-statistics')
|
||||
- enable_service_ping_link = '<a href="%{url}">'.html_safe % { url: enable_service_ping_link_url }
|
||||
- generate_manually_link_url = help_page_path('administration/troubleshooting/gitlab_rails_cheat_sheet', anchor: 'generate-service-ping')
|
||||
- generate_manually_link = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: generate_manually_link_url }
|
||||
= c.body do
|
||||
- enable_service_ping_link_url = help_page_path('user/admin_area/settings/usage_statistics', anchor: 'enable-or-disable-usage-statistics')
|
||||
- enable_service_ping_link = '<a href="%{url}">'.html_safe % { url: enable_service_ping_link_url }
|
||||
- generate_manually_link_url = help_page_path('administration/troubleshooting/gitlab_rails_cheat_sheet', anchor: 'generate-service-ping')
|
||||
- generate_manually_link = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: generate_manually_link_url }
|
||||
|
||||
= html_escape(s_('%{enable_service_ping_link_start}Enable%{link_end} or %{generate_manually_link_start}generate%{link_end} Service Ping to preview and download service usage data payload.')) % { enable_service_ping_link_start: enable_service_ping_link, generate_manually_link_start: generate_manually_link, link_end: '</a>'.html_safe }
|
||||
= html_escape(s_('%{enable_service_ping_link_start}Enable%{link_end} or %{generate_manually_link_start}generate%{link_end} Service Ping to preview and download service usage data payload.')) % { enable_service_ping_link_start: enable_service_ping_link, generate_manually_link_start: generate_manually_link, link_end: '</a>'.html_safe }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
- page_title _("Projects")
|
||||
- @content_class = "limit-container-width" unless fluid_layout
|
||||
|
||||
.card.gl-mt-3
|
||||
.card.gl-mt-3.js-search-settings-section
|
||||
.card-header
|
||||
%strong= @group.name
|
||||
projects:
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
- type_plural = _('group access tokens')
|
||||
- @content_class = 'limit-container-width' unless fluid_layout
|
||||
|
||||
.row.gl-mt-3
|
||||
.row.gl-mt-3.js-search-settings-section
|
||||
.col-lg-4
|
||||
%h4.gl-mt-0
|
||||
= page_title
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
- page_title s_('Integrations|Group-level integration management')
|
||||
- @content_class = 'limit-container-width' unless fluid_layout
|
||||
|
||||
%h3= s_('Integrations|Group-level integration management')
|
||||
%section.js-search-settings-section
|
||||
%h3= s_('Integrations|Group-level integration management')
|
||||
|
||||
- integrations_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: integrations_help_page_path }
|
||||
%p= s_("Integrations|GitLab administrators can set up integrations that all projects in a group inherit and use by default. These integrations apply to all projects that don't already use custom settings. You can override custom settings for a project if the settings are necessary at that level. Learn more about %{integrations_link_start}group-level integration management%{link_end}.").html_safe % { integrations_link_start: integrations_link_start, link_end: "</a>".html_safe }
|
||||
= render 'shared/integrations/index', integrations: @integrations
|
||||
- integrations_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: integrations_help_page_path }
|
||||
%p= s_("Integrations|GitLab administrators can set up integrations that all projects in a group inherit and use by default. These integrations apply to all projects that don't already use custom settings. You can override custom settings for a project if the settings are necessary at that level. Learn more about %{integrations_link_start}group-level integration management%{link_end}.").html_safe % { integrations_link_start: integrations_link_start, link_end: "</a>".html_safe }
|
||||
= render 'shared/integrations/index', integrations: @integrations
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
- page_title _('Active Sessions')
|
||||
- @content_class = "limit-container-width" unless fluid_layout
|
||||
|
||||
.row.gl-mt-3
|
||||
.row.gl-mt-3.js-search-settings-section
|
||||
.col-lg-4.profile-settings-sidebar
|
||||
%h4.gl-mt-0
|
||||
= page_title
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
- page_title _('Authentication log')
|
||||
- @content_class = "limit-container-width" unless fluid_layout
|
||||
|
||||
.row.gl-mt-3
|
||||
.row.gl-mt-3.js-search-settings-section
|
||||
.col-lg-4.profile-settings-sidebar
|
||||
%h4.gl-mt-0
|
||||
= page_title
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
- page_title _('Chat')
|
||||
- @content_class = "limit-container-width" unless fluid_layout
|
||||
|
||||
.row.gl-mt-3
|
||||
.row.gl-mt-3.js-search-settings-section
|
||||
.col-lg-4.profile-settings-sidebar
|
||||
%h4.gl-mt-0
|
||||
= page_title
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
- page_title _('Emails')
|
||||
- @content_class = "limit-container-width" unless fluid_layout
|
||||
|
||||
.row.gl-mt-3
|
||||
.row.gl-mt-3.js-search-settings-section
|
||||
.col-lg-4.profile-settings-sidebar
|
||||
%h4.gl-mt-0
|
||||
= page_title
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
- page_title _('GPG Keys')
|
||||
- @content_class = "limit-container-width" unless fluid_layout
|
||||
|
||||
.row.gl-mt-3
|
||||
.row.gl-mt-3.js-search-settings-section
|
||||
.col-lg-4.profile-settings-sidebar
|
||||
%h4.gl-mt-0
|
||||
= page_title
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
- page_title _('SSH Keys')
|
||||
- @content_class = "limit-container-width" unless fluid_layout
|
||||
|
||||
.row.gl-mt-3
|
||||
.row.gl-mt-3.js-search-settings-section
|
||||
.col-lg-4.profile-settings-sidebar
|
||||
%h4.gl-mt-0
|
||||
= page_title
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
%li= msg
|
||||
|
||||
= hidden_field_tag :notification_type, 'global'
|
||||
.row.gl-mt-3
|
||||
.row.gl-mt-3.js-search-settings-section
|
||||
.col-lg-4.profile-settings-sidebar
|
||||
%h4.gl-mt-0
|
||||
= page_title
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
- page_title _('Password')
|
||||
- @content_class = "limit-container-width" unless fluid_layout
|
||||
|
||||
.row.gl-mt-3
|
||||
.row.gl-mt-3.js-search-settings-section
|
||||
.col-lg-4.profile-settings-sidebar
|
||||
%h4.gl-mt-0
|
||||
= page_title
|
||||
|
|
|
@ -133,9 +133,11 @@
|
|||
= f.gitlab_ui_checkbox_component :include_private_contributions,
|
||||
s_('Profiles|Include private contributions on my profile'),
|
||||
help_text: s_("Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information.")
|
||||
%hr
|
||||
= f.submit s_("Profiles|Update profile settings"), class: 'gl-button btn btn-confirm gl-mr-3 js-password-prompt-btn'
|
||||
= link_to _("Cancel"), user_path(current_user), class: 'gl-button btn btn-default btn-cancel'
|
||||
.row.js-hide-when-nothing-matches-search
|
||||
.col-lg-12
|
||||
%hr
|
||||
= f.submit s_("Profiles|Update profile settings"), class: 'gl-button btn btn-confirm gl-mr-3 js-password-prompt-btn'
|
||||
= link_to _("Cancel"), user_path(current_user), class: 'gl-button btn btn-default btn-cancel'
|
||||
|
||||
#password-prompt-modal
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
- breadcrumb_title _('Webhook Settings')
|
||||
- page_title _('Webhooks')
|
||||
|
||||
.row.gl-mt-3
|
||||
.row.gl-mt-3.js-search-settings-section
|
||||
.col-lg-4
|
||||
= render 'shared/web_hooks/title_and_docs', hook: @hook
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
- if Feature.enabled?(:use_pipeline_wizard_for_pages, @project.group)
|
||||
#js-pages{ data: @pipeline_wizard_data }
|
||||
%section.js-search-settings-section
|
||||
- if Feature.enabled?(:use_pipeline_wizard_for_pages, @project.group)
|
||||
#js-pages{ data: @pipeline_wizard_data }
|
||||
|
||||
- else
|
||||
= render 'header'
|
||||
- else
|
||||
= render 'header'
|
||||
|
||||
= render 'use'
|
||||
= render 'use'
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
- type_plural = _('project access tokens')
|
||||
- @content_class = 'limit-container-width' unless fluid_layout
|
||||
|
||||
.row.gl-mt-3
|
||||
.row.gl-mt-3.js-search-settings-section
|
||||
.col-lg-4
|
||||
%h4.gl-mt-0
|
||||
= page_title
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
- breadcrumb_title _('Integration Settings')
|
||||
- page_title _('Integrations')
|
||||
|
||||
%h3= _('Integrations')
|
||||
- integrations_link_start = '<a href="%{url}">'.html_safe % { url: help_page_url('user/project/integrations/overview') }
|
||||
- webhooks_link_start = '<a href="%{url}">'.html_safe % { url: project_hooks_path(@project) }
|
||||
%p= _("%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}.").html_safe % { integrations_link_start: integrations_link_start, webhooks_link_start: webhooks_link_start, link_end: '</a>'.html_safe }
|
||||
= render 'shared/integrations/index', integrations: @integrations
|
||||
%section.js-search-settings-section
|
||||
%h3= _('Integrations')
|
||||
- integrations_link_start = '<a href="%{url}">'.html_safe % { url: help_page_url('user/project/integrations/overview') }
|
||||
- webhooks_link_start = '<a href="%{url}">'.html_safe % { url: project_hooks_path(@project) }
|
||||
%p= _("%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}.").html_safe % { integrations_link_start: integrations_link_start, webhooks_link_start: webhooks_link_start, link_end: '</a>'.html_safe }
|
||||
= render 'shared/integrations/index', integrations: @integrations
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
- @content_class = "limit-container-width" unless fluid_layout
|
||||
|
||||
.row.gl-mt-3
|
||||
.row.gl-mt-3.js-search-settings-section
|
||||
.col-lg-4.profile-settings-sidebar
|
||||
%h4.gl-mt-0
|
||||
= page_title
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: improve_blobs_cache_headers
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/98110
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/374126
|
||||
milestone: '15.5'
|
||||
type: development
|
||||
group: group::source code
|
||||
default_enabled: false
|
|
@ -27,7 +27,7 @@ OmniAuth.config.full_host = Gitlab::OmniauthInitializer.full_host
|
|||
OmniAuth.config.allowed_request_methods = [:post]
|
||||
# In case of auto sign-in, the GET method is used (users don't get to click on a button)
|
||||
OmniAuth.config.allowed_request_methods << :get if Gitlab.config.omniauth.auto_sign_in_with_provider.present?
|
||||
OmniAuth.config.before_request_phase do |env|
|
||||
OmniAuth.config.request_validation_phase do |env|
|
||||
Gitlab::RequestForgeryProtection.call(env)
|
||||
end
|
||||
|
||||
|
|
|
@ -23,8 +23,6 @@ queues_config_hash[:namespace] = Gitlab::Redis::Queues::SIDEKIQ_NAMESPACE
|
|||
|
||||
enable_json_logs = Gitlab.config.sidekiq.log_format == 'json'
|
||||
enable_sidekiq_memory_killer = ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS'].to_i.nonzero?
|
||||
use_sidekiq_daemon_memory_killer = ENV.fetch("SIDEKIQ_DAEMON_MEMORY_KILLER", 1).to_i.nonzero?
|
||||
use_sidekiq_legacy_memory_killer = !use_sidekiq_daemon_memory_killer
|
||||
|
||||
Sidekiq.configure_server do |config|
|
||||
config.options[:strict] = false
|
||||
|
@ -45,8 +43,7 @@ Sidekiq.configure_server do |config|
|
|||
|
||||
config.server_middleware(&Gitlab::SidekiqMiddleware.server_configurator(
|
||||
metrics: Settings.monitoring.sidekiq_exporter,
|
||||
arguments_logger: SidekiqLogArguments.enabled? && !enable_json_logs,
|
||||
memory_killer: enable_sidekiq_memory_killer && use_sidekiq_legacy_memory_killer
|
||||
arguments_logger: SidekiqLogArguments.enabled? && !enable_json_logs
|
||||
))
|
||||
|
||||
config.client_middleware(&Gitlab::SidekiqMiddleware.client_configurator)
|
||||
|
@ -62,7 +59,7 @@ Sidekiq.configure_server do |config|
|
|||
# To cancel job, it requires `SIDEKIQ_MONITOR_WORKER=1` to enable notification channel
|
||||
Gitlab::SidekiqDaemon::Monitor.instance.start
|
||||
|
||||
Gitlab::SidekiqDaemon::MemoryKiller.instance.start if enable_sidekiq_memory_killer && use_sidekiq_daemon_memory_killer
|
||||
Gitlab::SidekiqDaemon::MemoryKiller.instance.start if enable_sidekiq_memory_killer
|
||||
|
||||
first_sidekiq_worker = !ENV['SIDEKIQ_WORKER_ID'] || ENV['SIDEKIQ_WORKER_ID'] == '0'
|
||||
health_checks = Settings.monitoring.sidekiq_health_checks
|
||||
|
|
|
@ -40,7 +40,9 @@ if you want to implement this.
|
|||
|
||||
If you have installed GitLab from source:
|
||||
|
||||
1. You must [install Registry](https://docs.docker.com/registry/deploying/) by yourself.
|
||||
1. You must [deploy a registry](https://docs.docker.com/registry/deploying/) using the image corresponding to the
|
||||
version of GitLab you are installing
|
||||
(for example: `registry.gitlab.com/gitlab-org/build/cng/gitlab-container-registry:v3.15.0-gitlab`)
|
||||
1. After the installation is complete, to enable it, you must configure the Registry's
|
||||
settings in `gitlab.yml`.
|
||||
1. Use the sample NGINX configuration file from under
|
||||
|
|
|
@ -32,26 +32,12 @@ run as a process group leader (for example, using `chpst -P`). If using Omnibus
|
|||
|
||||
The MemoryKiller is controlled using environment variables.
|
||||
|
||||
- `SIDEKIQ_DAEMON_MEMORY_KILLER`: defaults to 1. When set to 0, the MemoryKiller
|
||||
works in _legacy_ mode. Otherwise, the MemoryKiller works in _daemon_ mode.
|
||||
|
||||
In _legacy_ mode, the MemoryKiller checks the Sidekiq process RSS
|
||||
([Resident Set Size](https://github.com/mperham/sidekiq/wiki/Memory#rss))
|
||||
after each job.
|
||||
|
||||
In _daemon_ mode, the MemoryKiller checks the Sidekiq process RSS every 3 seconds
|
||||
(defined by `SIDEKIQ_MEMORY_KILLER_CHECK_INTERVAL`).
|
||||
|
||||
- `SIDEKIQ_MEMORY_KILLER_MAX_RSS` (KB): if this variable is set, and its value is greater
|
||||
than 0, the MemoryKiller is enabled. Otherwise the MemoryKiller is disabled.
|
||||
|
||||
`SIDEKIQ_MEMORY_KILLER_MAX_RSS` defines the Sidekiq process allowed RSS.
|
||||
|
||||
In _legacy_ mode, if the Sidekiq process exceeds the allowed RSS then an irreversible
|
||||
delayed graceful restart is triggered. The restart of Sidekiq happens
|
||||
after `SIDEKIQ_MEMORY_KILLER_GRACE_TIME` seconds.
|
||||
|
||||
In _daemon_ mode, if the Sidekiq process exceeds the allowed RSS for longer than
|
||||
If the Sidekiq process exceeds the allowed RSS for longer than
|
||||
`SIDEKIQ_MEMORY_KILLER_GRACE_TIME` the graceful restart is triggered. If the
|
||||
Sidekiq process go below the allowed RSS within `SIDEKIQ_MEMORY_KILLER_GRACE_TIME`,
|
||||
the restart is aborted.
|
||||
|
@ -59,11 +45,11 @@ The MemoryKiller is controlled using environment variables.
|
|||
The default value for Omnibus packages is set
|
||||
[in the Omnibus GitLab repository](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/attributes/default.rb).
|
||||
|
||||
- `SIDEKIQ_MEMORY_KILLER_HARD_LIMIT_RSS` (KB): is used by _daemon_ mode. If the Sidekiq
|
||||
- `SIDEKIQ_MEMORY_KILLER_HARD_LIMIT_RSS` (KB): If the Sidekiq
|
||||
process RSS (expressed in kilobytes) exceeds `SIDEKIQ_MEMORY_KILLER_HARD_LIMIT_RSS`,
|
||||
an immediate graceful restart of Sidekiq is triggered.
|
||||
|
||||
- `SIDEKIQ_MEMORY_KILLER_CHECK_INTERVAL`: used in _daemon_ mode to define how
|
||||
- `SIDEKIQ_MEMORY_KILLER_CHECK_INTERVAL`: Define how
|
||||
often to check process RSS, default to 3 seconds.
|
||||
|
||||
- `SIDEKIQ_MEMORY_KILLER_GRACE_TIME`: defaults to 900 seconds (15 minutes).
|
||||
|
|
|
@ -15,6 +15,19 @@ We do not allow gems that are fetched from Git repositories. All gems have
|
|||
to be available in the RubyGems index. We want to minimize external build
|
||||
dependencies and build times.
|
||||
|
||||
## Review the new dependency for quality
|
||||
|
||||
We should not add 3rd-party dependencies to GitLab that would not pass our own quality standards.
|
||||
This means that new dependencies should, at a minimum, meet the following criteria:
|
||||
|
||||
- They have an active developer community. At the minimum a maintainer should still be active
|
||||
to merge change requests in case of emergencies.
|
||||
- There are no issues open that we know may impact the availablity or performance of GitLab.
|
||||
- The project is tested using some form of test automation. The test suite must be passing
|
||||
using the Ruby version currently used by GitLab.
|
||||
- If the project uses a C extension, consider requesting an additional review from a C or MRI
|
||||
domain expert. C extensions can greatly impact GitLab stability and performance.
|
||||
|
||||
## Request an Appsec review
|
||||
|
||||
When adding a new gem to our `Gemfile` or even changing versions in
|
||||
|
|
|
@ -352,8 +352,6 @@ To create an [untarred](#skipping-tar-creation) incremental backup from a tarred
|
|||
sudo gitlab-backup create INCREMENTAL=yes SKIP=tar
|
||||
```
|
||||
|
||||
You can't create an incremental backup from an [untarred](#skipping-tar-creation) backup.
|
||||
|
||||
### Back up specific repository storages
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/86896) in GitLab 15.0.
|
||||
|
|
|
@ -895,12 +895,11 @@ include:
|
|||
|
||||
merge cyclonedx sboms:
|
||||
stage: merge-cyclonedx-sboms
|
||||
image: alpine:latest
|
||||
image:
|
||||
name: cyclonedx/cyclonedx-cli:0.22.0
|
||||
entrypoint: [""]
|
||||
script:
|
||||
- wget https://github.com/CycloneDX/cyclonedx-cli/releases/download/v0.22.0/cyclonedx-linux-musl-x64 -O /usr/local/bin/cyclonedx-cli
|
||||
- chmod 755 /usr/local/bin/cyclonedx-cli
|
||||
- apk --update add --no-cache icu-dev libstdc++
|
||||
- find * -name "gl-sbom-*.cdx.json" -exec cyclonedx-cli merge --input-files {} --output-file gl-sbom-all.cdx.json +
|
||||
- find . -name "gl-sbom-*.cdx.json" -exec /cyclonedx merge --output-file gl-sbom-all.cdx.json --input-files "{}" +
|
||||
artifacts:
|
||||
paths:
|
||||
- gl-sbom-all.cdx.json
|
||||
|
|
|
@ -7,13 +7,13 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
# Migrating groups **(FREE)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/249160) in GitLab 13.7 for group resources [with a flag](../../feature_flags.md) named `bulk_import`. Disabled by default.
|
||||
> - Group resources [enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/338985) in GitLab 14.3.
|
||||
> - Group items [enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/338985) in GitLab 14.3.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/267945) in GitLab 14.4 for project resources [with a flag](../../feature_flags.md) named `bulk_import_projects`. Disabled by default.
|
||||
|
||||
FLAG:
|
||||
On self-managed GitLab, by default [migrating group resources](#migrated-group-resources) is available. To hide the
|
||||
On self-managed GitLab, by default [migrating group items](#migrated-group-items) is available. To hide the
|
||||
feature, ask an administrator to [disable the feature flag](../../../administration/feature_flags.md) named `bulk_import`.
|
||||
On self-managed GitLab, by default [migrating project resources](#migrated-project-resources) is not available. To show
|
||||
On self-managed GitLab, by default [migrating project items](#migrated-project-items) is not available. To show
|
||||
this feature, ask an administrator to [enable the feature flag](../../../administration/feature_flags.md) named
|
||||
`bulk_import_projects`. On GitLab.com, migrating group resources is available but migrating project resources is not
|
||||
available.
|
||||
|
@ -33,8 +33,8 @@ another GitLab instance. Migrating groups using the method documented here autom
|
|||
When you migrate a group, you connect to your GitLab instance and then choose
|
||||
groups to import. Not all the data is migrated. See:
|
||||
|
||||
- [Migrated group resources](#migrated-group-resources).
|
||||
- [Migrated project resources](#migrated-project-resources).
|
||||
- [Migrated group items](#migrated-group-items).
|
||||
- [Migrated project items](#migrated-project-items).
|
||||
|
||||
Leave feedback about group migration in [the relevant issue](https://gitlab.com/gitlab-org/gitlab/-/issues/284495).
|
||||
|
||||
|
@ -79,10 +79,17 @@ Migration importer page. The remote groups you have the Owner role for are liste
|
|||
For information on automating user, group, and project import API calls, see
|
||||
[Automate group and project import](../../project/import/index.md#automate-group-and-project-import).
|
||||
|
||||
## Migrated group resources
|
||||
## Migrated group items
|
||||
|
||||
Only the following resources are migrated to the target instance. Any other items are **not**
|
||||
migrated:
|
||||
The [`import_export.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/import_export/group/import_export.yml)
|
||||
file for groups lists many of the items migrated when migrating groups using group migration. View this file in the branch
|
||||
for your version of GitLab to see the list of items relevant to you. For example,
|
||||
[`import_export.yml` on the `14-10-stable-ee` branch](https://gitlab.com/gitlab-org/gitlab/-/blob/14-10-stable-ee/lib/gitlab/import_export/group/import_export.yml).
|
||||
|
||||
Migrating projects with file exports uses the same export and import mechanisms as creating projects from templates at the [group](../custom_project_templates.md) and
|
||||
[instance](../../admin_area/custom_project_templates.md) levels. Therefore, the list of exported items is the same.
|
||||
|
||||
Items that are migrated to the target instance include:
|
||||
|
||||
- Badges ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/292431) in 13.11)
|
||||
- Board Lists
|
||||
|
@ -105,13 +112,23 @@ migrated:
|
|||
- Sub-Groups
|
||||
- Uploads
|
||||
|
||||
## Migrated project resources
|
||||
Any other items are **not** migrated.
|
||||
|
||||
## Migrated project items
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/267945) in GitLab 14.4 [with a flag](../../feature_flags.md) named `bulk_import_projects`. Disabled by default.
|
||||
|
||||
FLAG:
|
||||
On self-managed GitLab, migrating project resources are not available by default. To make them available, ask an administrator to [enable the feature flag](../../../administration/feature_flags.md) named `bulk_import_projects`. On GitLab.com, migrating project resources are not available.
|
||||
|
||||
The [`import_export.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/import_export/project/import_export.yml)
|
||||
file for projects lists many of the items migrated when migrating projects using group migration. View this file in the branch
|
||||
for your version of GitLab to see the list of items relevant to you. For example,
|
||||
[`import_export.yml` on the `14-10-stable-ee` branch](https://gitlab.com/gitlab-org/gitlab/-/blob/14-10-stable-ee/lib/gitlab/import_export/project/import_export.yml).
|
||||
|
||||
Migrating projects with file exports uses the same export and import mechanisms as creating projects from templates at the [group](../../group/custom_project_templates.md) and
|
||||
[instance](../../admin_area/custom_project_templates.md) levels. Therefore, the list of exported items is the same.
|
||||
|
||||
- Projects ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/267945) in GitLab 14.4)
|
||||
- Auto DevOps ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/339410) in GitLab 14.6)
|
||||
- Branches (including protected branches) ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/339414) in GitLab 14.7)
|
||||
|
|
|
@ -48,7 +48,15 @@ sure these users exist before importing the desired groups.
|
|||
|
||||
### Exported contents
|
||||
|
||||
The following items are exported:
|
||||
The [`import_export.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/import_export/group/import_export.yml)
|
||||
file for groups lists many of the items exported and imported when migrating groups using file exports. View this file in the branch
|
||||
for your version of GitLab to see the list of items relevant to you. For example,
|
||||
[`import_export.yml` on the `14-10-stable-ee` branch](https://gitlab.com/gitlab-org/gitlab/-/blob/14-10-stable-ee/lib/gitlab/import_export/group/import_export.yml).
|
||||
|
||||
Migrating projects with file exports uses the same export and import mechanisms as creating projects from templates at the [group](../custom_project_templates.md) and
|
||||
[instance](../../admin_area/custom_project_templates.md) levels. Therefore, the list of exported items is the same.
|
||||
|
||||
Items that are exported include:
|
||||
|
||||
- Milestones
|
||||
- Labels
|
||||
|
@ -62,7 +70,7 @@ The following items are exported:
|
|||
([Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/53247) in GitLab 13.9)
|
||||
- Iterations cadences ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/95372) in 15.4)
|
||||
|
||||
The following items are **not** exported:
|
||||
Items that are **not** exported include:
|
||||
|
||||
- Projects
|
||||
- Runner tokens
|
||||
|
|
|
@ -58,7 +58,7 @@ moved to your configured `uploads_directory`. Every 24 hours, a worker deletes t
|
|||
### Items that are exported
|
||||
|
||||
The [`import_export.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/import_export/project/import_export.yml)
|
||||
file lists the items exported and imported when migrating projects using file exports. View this file in the branch
|
||||
file for projects lists many of the items exported and imported when migrating projects using file exports. View this file in the branch
|
||||
for your version of GitLab to see the list of items relevant to you. For example,
|
||||
[`import_export.yml` on the `14-10-stable-ee` branch](https://gitlab.com/gitlab-org/gitlab/-/blob/14-10-stable-ee/lib/gitlab/import_export/project/import_export.yml).
|
||||
|
||||
|
@ -137,16 +137,15 @@ To import a project:
|
|||
To get the status of an import, you can query it through the [Project import/export API](../../../api/project_import_export.md#import-status).
|
||||
As described in the API documentation, the query may return an import error or exceptions.
|
||||
|
||||
### Items that are imported
|
||||
### Changes to imported items
|
||||
|
||||
The following items are imported but changed slightly:
|
||||
Exported items are imported with the following changes:
|
||||
|
||||
- Project members with the Owner role are imported as Maintainers.
|
||||
- If an imported project contains merge requests originating from forks, then new branches
|
||||
associated with such merge requests are created in a project during the import/export. Thus, the
|
||||
number of branches in the exported project might be bigger than in the original project.
|
||||
- If use of the `Internal` visibility level
|
||||
[is restricted](../../public_access.md#restrict-use-of-public-or-internal-projects),
|
||||
- Project members with the Owner role are imported with the Maintainer role.
|
||||
- If an imported project contains merge requests originating from forks, new branches associated with these merge
|
||||
requests are created in the project. Therefore, the number of branches in the new project can be more than in the
|
||||
source project.
|
||||
- If the `Internal` visibility level [is restricted](../../public_access.md#restrict-use-of-public-or-internal-projects),
|
||||
all imported projects are given `Private` visibility.
|
||||
|
||||
Deploy keys aren't imported. To use deploy keys, you must enable them in your imported project and update protected branches.
|
||||
|
|
|
@ -7,7 +7,7 @@ module Gitlab
|
|||
# The result of this method should be passed to
|
||||
# Sidekiq's `config.server_middleware` method
|
||||
# eg: `config.server_middleware(&Gitlab::SidekiqMiddleware.server_configurator)`
|
||||
def self.server_configurator(metrics: true, arguments_logger: true, memory_killer: true)
|
||||
def self.server_configurator(metrics: true, arguments_logger: true)
|
||||
lambda do |chain|
|
||||
# Size limiter should be placed at the top
|
||||
chain.add ::Gitlab::SidekiqMiddleware::SizeLimiter::Server
|
||||
|
@ -27,7 +27,6 @@ module Gitlab
|
|||
end
|
||||
|
||||
chain.add ::Gitlab::SidekiqMiddleware::ArgumentsLogger if arguments_logger
|
||||
chain.add ::Gitlab::SidekiqMiddleware::MemoryKiller if memory_killer
|
||||
chain.add ::Gitlab::SidekiqMiddleware::RequestStoreMiddleware
|
||||
chain.add ::Gitlab::SidekiqMiddleware::ExtraDoneLogMetadata
|
||||
chain.add ::Gitlab::SidekiqMiddleware::BatchLoader
|
||||
|
|
|
@ -1,91 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module SidekiqMiddleware
|
||||
class MemoryKiller
|
||||
# Default the RSS limit to 0, meaning the MemoryKiller is disabled
|
||||
MAX_RSS = (ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS'] || 0).to_s.to_i
|
||||
# Give Sidekiq 15 minutes of grace time after exceeding the RSS limit
|
||||
GRACE_TIME = (ENV['SIDEKIQ_MEMORY_KILLER_GRACE_TIME'] || 15 * 60).to_s.to_i
|
||||
# Wait 30 seconds for running jobs to finish during graceful shutdown
|
||||
SHUTDOWN_WAIT = (ENV['SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT'] || 30).to_s.to_i
|
||||
|
||||
# Create a mutex used to ensure there will be only one thread waiting to
|
||||
# shut Sidekiq down
|
||||
MUTEX = Mutex.new
|
||||
|
||||
attr_reader :worker
|
||||
|
||||
def call(worker, job, queue)
|
||||
yield
|
||||
|
||||
@worker = worker
|
||||
current_rss = get_rss
|
||||
|
||||
return unless MAX_RSS > 0 && current_rss > MAX_RSS
|
||||
|
||||
Thread.new do
|
||||
# Return if another thread is already waiting to shut Sidekiq down
|
||||
next unless MUTEX.try_lock
|
||||
|
||||
warn("Sidekiq worker PID-#{pid} current RSS #{current_rss}"\
|
||||
" exceeds maximum RSS #{MAX_RSS} after finishing job #{worker.class} JID-#{job['jid']}")
|
||||
|
||||
warn("Sidekiq worker PID-#{pid} will stop fetching new jobs"\
|
||||
" in #{GRACE_TIME} seconds, and will be shut down #{SHUTDOWN_WAIT} seconds later")
|
||||
|
||||
# Wait `GRACE_TIME` to give the memory intensive job time to finish.
|
||||
# Then, tell Sidekiq to stop fetching new jobs.
|
||||
wait_and_signal(GRACE_TIME, 'SIGTSTP', 'stop fetching new jobs')
|
||||
|
||||
# Wait `SHUTDOWN_WAIT` to give already fetched jobs time to finish.
|
||||
# Then, tell Sidekiq to gracefully shut down by giving jobs a few more
|
||||
# moments to finish, killing and requeuing them if they didn't, and
|
||||
# then terminating itself. Sidekiq will replicate the TERM to all its
|
||||
# children if it can.
|
||||
wait_and_signal(SHUTDOWN_WAIT, 'SIGTERM', 'gracefully shut down')
|
||||
|
||||
# Wait for Sidekiq to shutdown gracefully, and kill it if it didn't.
|
||||
# Kill the whole pgroup, so we can be sure no children are left behind
|
||||
wait_and_signal_pgroup(Sidekiq.options[:timeout] + 2, 'SIGKILL', 'die')
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_rss
|
||||
output, status = Gitlab::Popen.popen(%W(ps -o rss= -p #{pid}), Rails.root.to_s)
|
||||
return 0 unless status == 0
|
||||
|
||||
output.to_i
|
||||
end
|
||||
|
||||
# If this sidekiq process is pgroup leader, signal to the whole pgroup
|
||||
def wait_and_signal_pgroup(time, signal, explanation)
|
||||
return wait_and_signal(time, signal, explanation) unless Process.getpgrp == pid
|
||||
|
||||
warn("waiting #{time} seconds before sending Sidekiq worker PGRP-#{pid} #{signal} (#{explanation})", signal: signal)
|
||||
sleep(time)
|
||||
|
||||
warn("sending Sidekiq worker PGRP-#{pid} #{signal} (#{explanation})", signal: signal)
|
||||
Process.kill(signal, 0)
|
||||
end
|
||||
|
||||
def wait_and_signal(time, signal, explanation)
|
||||
warn("waiting #{time} seconds before sending Sidekiq worker PID-#{pid} #{signal} (#{explanation})", signal: signal)
|
||||
sleep(time)
|
||||
|
||||
warn("sending Sidekiq worker PID-#{pid} #{signal} (#{explanation})", signal: signal)
|
||||
Process.kill(signal, pid)
|
||||
end
|
||||
|
||||
def pid
|
||||
Process.pid
|
||||
end
|
||||
|
||||
def warn(message, signal: nil)
|
||||
Sidekiq.logger.warn(class: worker.class.name, pid: pid, signal: signal, message: message)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -14400,6 +14400,9 @@ msgstr ""
|
|||
msgid "Edit your most recent comment in a thread (from an empty textarea)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Edit your search and try again"
|
||||
msgstr ""
|
||||
|
||||
msgid "Edit your search filter and try again."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -132,7 +132,7 @@ RSpec.describe Projects::DesignManagement::Designs::RawImagesController do
|
|||
subject
|
||||
|
||||
expect(response.header['ETag']).to be_present
|
||||
expect(response.header['Cache-Control']).to eq("max-age=60, private")
|
||||
expect(response.header['Cache-Control']).to eq("max-age=60, private, must-revalidate, stale-while-revalidate=60, stale-if-error=300, s-maxage=60")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -247,9 +247,11 @@ RSpec.describe Projects::RawController do
|
|||
sign_in create(:user)
|
||||
request_file
|
||||
|
||||
expect(response.cache_control[:public]).to eq(true)
|
||||
expect(response.cache_control[:max_age]).to eq(60)
|
||||
expect(response.headers['ETag']).to eq("\"bdd5aa537c1e1f6d1b66de4bac8a6132\"")
|
||||
expect(response.cache_control[:no_store]).to be_nil
|
||||
expect(response.header['Cache-Control']).to eq(
|
||||
'max-age=60, public, must-revalidate, stale-while-revalidate=60, stale-if-error=300, s-maxage=60'
|
||||
)
|
||||
end
|
||||
|
||||
context 'when a public project has private repo' do
|
||||
|
@ -260,7 +262,9 @@ RSpec.describe Projects::RawController do
|
|||
sign_in user
|
||||
request_file
|
||||
|
||||
expect(response.header['Cache-Control']).to include('max-age=60, private')
|
||||
expect(response.header['Cache-Control']).to eq(
|
||||
'max-age=60, private, must-revalidate, stale-while-revalidate=60, stale-if-error=300, s-maxage=60'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -274,6 +278,21 @@ RSpec.describe Projects::RawController do
|
|||
expect(response).to have_gitlab_http_status(:not_modified)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when improve_blobs_cache_headers disabled' do
|
||||
before do
|
||||
stub_feature_flags(improve_blobs_cache_headers: false)
|
||||
end
|
||||
|
||||
it 'uses weak etags with a restricted set of headers' do
|
||||
sign_in create(:user)
|
||||
request_file
|
||||
|
||||
expect(response.headers['ETag']).to eq("W/\"bdd5aa537c1e1f6d1b66de4bac8a6132\"")
|
||||
expect(response.cache_control[:no_store]).to be_nil
|
||||
expect(response.header['Cache-Control']).to eq('max-age=60, public')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import Vue, { nextTick } from 'vue';
|
||||
import ContributorsCharts from '~/contributors/components/contributors.vue';
|
||||
|
@ -52,14 +53,14 @@ describe('Contributors charts', () => {
|
|||
it('should display loader whiled loading data', async () => {
|
||||
wrapper.vm.$store.state.loading = true;
|
||||
await nextTick();
|
||||
expect(wrapper.find('.contributors-loader').exists()).toBe(true);
|
||||
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should render charts when loading completed and there is chart data', async () => {
|
||||
wrapper.vm.$store.state.loading = false;
|
||||
wrapper.vm.$store.state.chartData = chartData;
|
||||
await nextTick();
|
||||
expect(wrapper.find('.contributors-loader').exists()).toBe(false);
|
||||
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false);
|
||||
expect(wrapper.find('.contributors-charts').exists()).toBe(true);
|
||||
expect(wrapper.element).toMatchSnapshot();
|
||||
});
|
||||
|
|
|
@ -87,7 +87,7 @@ describe('Customer relations contacts root app', () => {
|
|||
editButtonLabel: 'Edit',
|
||||
title: 'Customer relations contacts',
|
||||
newContact: 'New contact',
|
||||
errorText: 'Something went wrong. Please try again.',
|
||||
errorMsg: 'Something went wrong. Please try again.',
|
||||
},
|
||||
serverErrorMessage: '',
|
||||
filterSearchKey: 'contacts',
|
||||
|
@ -117,6 +117,18 @@ describe('Customer relations contacts root app', () => {
|
|||
|
||||
expect(wrapper.text()).toContain('Something went wrong. Please try again.');
|
||||
});
|
||||
|
||||
it('should be removed on error-alert-dismissed event', async () => {
|
||||
mountComponent({ queryHandler: jest.fn().mockRejectedValue('ERROR') });
|
||||
await waitForPromises();
|
||||
|
||||
expect(wrapper.text()).toContain('Something went wrong. Please try again.');
|
||||
|
||||
findTable().vm.$emit('error-alert-dismissed');
|
||||
await waitForPromises();
|
||||
|
||||
expect(wrapper.text()).not.toContain('Something went wrong. Please try again.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('on successful load', () => {
|
||||
|
|
|
@ -91,7 +91,7 @@ describe('Customer relations organizations root app', () => {
|
|||
editButtonLabel: 'Edit',
|
||||
title: 'Customer relations organizations',
|
||||
newOrganization: 'New organization',
|
||||
errorText: 'Something went wrong. Please try again.',
|
||||
errorMsg: 'Something went wrong. Please try again.',
|
||||
},
|
||||
serverErrorMessage: '',
|
||||
filterSearchKey: 'organizations',
|
||||
|
|
|
@ -22,12 +22,15 @@ describe('Timeline events form', () => {
|
|||
useFakeDate(fakeDate);
|
||||
let wrapper;
|
||||
|
||||
const mountComponent = ({ mountMethod = shallowMountExtended }) => {
|
||||
const mountComponent = ({ mountMethod = shallowMountExtended } = {}) => {
|
||||
wrapper = mountMethod(TimelineEventsForm, {
|
||||
propsData: {
|
||||
showSaveAndAdd: true,
|
||||
isEventProcessed: false,
|
||||
},
|
||||
stubs: {
|
||||
GlButton: true,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -48,17 +51,18 @@ describe('Timeline events form', () => {
|
|||
findHourInput().setValue(5);
|
||||
findMinuteInput().setValue(45);
|
||||
};
|
||||
const findTextarea = () => wrapper.findByTestId('input-note');
|
||||
|
||||
const submitForm = async () => {
|
||||
findSubmitButton().trigger('click');
|
||||
findSubmitButton().vm.$emit('click');
|
||||
await waitForPromises();
|
||||
};
|
||||
const submitFormAndAddAnother = async () => {
|
||||
findSubmitAndAddButton().trigger('click');
|
||||
findSubmitAndAddButton().vm.$emit('click');
|
||||
await waitForPromises();
|
||||
};
|
||||
const cancelForm = async () => {
|
||||
findCancelButton().trigger('click');
|
||||
findCancelButton().vm.$emit('click');
|
||||
await waitForPromises();
|
||||
};
|
||||
|
||||
|
@ -118,5 +122,17 @@ describe('Timeline events form', () => {
|
|||
expect(findHourInput().element.value).toBe('0');
|
||||
expect(findMinuteInput().element.value).toBe('0');
|
||||
});
|
||||
|
||||
it('should disable the save buttons when event content does not exist', async () => {
|
||||
expect(findSubmitButton().props('disabled')).toBe(true);
|
||||
expect(findSubmitAndAddButton().props('disabled')).toBe(true);
|
||||
});
|
||||
|
||||
it('should enable the save buttons when event content exists', async () => {
|
||||
await findTextarea().setValue('hello');
|
||||
|
||||
expect(findSubmitButton().props('disabled')).toBe(false);
|
||||
expect(findSubmitAndAddButton().props('disabled')).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { GlSearchBoxByType } from '@gitlab/ui';
|
||||
import { GlEmptyState, GlSearchBoxByType } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { setHTMLFixture } from 'helpers/fixtures';
|
||||
import SearchSettings from '~/search_settings/components/search_settings.vue';
|
||||
|
@ -14,7 +14,7 @@ describe('search_settings/components/search_settings.vue', () => {
|
|||
const EXTRA_SETTINGS_ID = 'js-extra-settings';
|
||||
const TEXT_CONTAIN_SEARCH_TERM = `This text contain ${SEARCH_TERM}.`;
|
||||
const TEXT_WITH_SIBLING_ELEMENTS = `${SEARCH_TERM} <a data-testid="sibling" href="#">Learn more</a>.`;
|
||||
|
||||
const HIDE_WHEN_EMPTY_CLASS = 'js-hide-when-nothing-matches-search';
|
||||
let wrapper;
|
||||
|
||||
const buildWrapper = () => {
|
||||
|
@ -22,6 +22,7 @@ describe('search_settings/components/search_settings.vue', () => {
|
|||
propsData: {
|
||||
searchRoot: document.querySelector(`#${ROOT_ID}`),
|
||||
sectionSelector: SECTION_SELECTOR,
|
||||
hideWhenEmptySelector: `.${HIDE_WHEN_EMPTY_CLASS}`,
|
||||
isExpandedFn: isExpanded,
|
||||
},
|
||||
// Add real listeners so we can simplify and strengthen some tests.
|
||||
|
@ -46,6 +47,8 @@ describe('search_settings/components/search_settings.vue', () => {
|
|||
|
||||
const findMatchSiblingElement = () => document.querySelector(`[data-testid="sibling"]`);
|
||||
const findSearchBox = () => wrapper.find(GlSearchBoxByType);
|
||||
const findEmptyState = () => wrapper.find(GlEmptyState);
|
||||
const findHideWhenEmpty = () => document.querySelector(`.${HIDE_WHEN_EMPTY_CLASS}`);
|
||||
const search = (term) => {
|
||||
findSearchBox().vm.$emit('input', term);
|
||||
};
|
||||
|
@ -67,6 +70,9 @@ describe('search_settings/components/search_settings.vue', () => {
|
|||
<span>${TEXT_CONTAIN_SEARCH_TERM}</span>
|
||||
<span>${TEXT_WITH_SIBLING_ELEMENTS}</span>
|
||||
</section>
|
||||
<div class="row ${HIDE_WHEN_EMPTY_CLASS}">
|
||||
<button type="submit">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
@ -93,13 +99,41 @@ describe('search_settings/components/search_settings.vue', () => {
|
|||
expect(wrapper.emitted('expand')).toEqual([[section]]);
|
||||
});
|
||||
|
||||
describe('when nothing matches the search term', () => {
|
||||
beforeEach(() => {
|
||||
search('xxxxxxxxxxx');
|
||||
});
|
||||
|
||||
it('shows an empty state', () => {
|
||||
expect(findEmptyState().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('hides the form buttons', () => {
|
||||
expect(findHideWhenEmpty()).toHaveClass(HIDE_CLASS);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when something matches the search term', () => {
|
||||
beforeEach(() => {
|
||||
search(SEARCH_TERM);
|
||||
});
|
||||
|
||||
it('shows no empty state', () => {
|
||||
expect(findEmptyState().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('shows the form buttons', () => {
|
||||
expect(findHideWhenEmpty()).not.toHaveClass(HIDE_CLASS);
|
||||
});
|
||||
});
|
||||
|
||||
it('highlight elements that match the search term', () => {
|
||||
search(SEARCH_TERM);
|
||||
|
||||
expect(highlightedElementsCount()).toBe(3);
|
||||
});
|
||||
|
||||
it('highlight only search term and not the whole line', () => {
|
||||
it('highlights only search term and not the whole line', () => {
|
||||
search(SEARCH_TERM);
|
||||
|
||||
expect(highlightedTextNodes()).toBe(true);
|
||||
|
@ -142,6 +176,10 @@ describe('search_settings/components/search_settings.vue', () => {
|
|||
expect(visibleSectionsCount()).toBe(sectionsCount());
|
||||
});
|
||||
|
||||
it('hides the empty state', () => {
|
||||
expect(findEmptyState().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('removes the highlight from all elements', () => {
|
||||
expect(highlightedElementsCount()).toBe(0);
|
||||
});
|
||||
|
|
|
@ -1,83 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::SidekiqMiddleware::MemoryKiller do
|
||||
subject { described_class.new }
|
||||
|
||||
let(:pid) { 999 }
|
||||
|
||||
let(:worker) { double(:worker, class: ProjectCacheWorker) }
|
||||
let(:job) { { 'jid' => 123 } }
|
||||
let(:queue) { 'test_queue' }
|
||||
|
||||
def run
|
||||
thread = subject.call(worker, job, queue) { nil }
|
||||
thread&.join
|
||||
end
|
||||
|
||||
before do
|
||||
allow(subject).to receive(:get_rss).and_return(10.kilobytes)
|
||||
allow(subject).to receive(:pid).and_return(pid)
|
||||
end
|
||||
|
||||
context 'when MAX_RSS is set to 0' do
|
||||
before do
|
||||
stub_const("#{described_class}::MAX_RSS", 0)
|
||||
end
|
||||
|
||||
it 'does nothing' do
|
||||
expect(subject).not_to receive(:sleep)
|
||||
|
||||
run
|
||||
end
|
||||
end
|
||||
|
||||
context 'when MAX_RSS is exceeded' do
|
||||
before do
|
||||
stub_const("#{described_class}::MAX_RSS", 5.kilobytes)
|
||||
end
|
||||
|
||||
it 'sends the TSTP, TERM and KILL signals at expected times' do
|
||||
expect(subject).to receive(:sleep).with(15 * 60).ordered
|
||||
expect(Process).to receive(:kill).with('SIGTSTP', pid).ordered
|
||||
|
||||
expect(subject).to receive(:sleep).with(30).ordered
|
||||
expect(Process).to receive(:kill).with('SIGTERM', pid).ordered
|
||||
|
||||
expect(subject).to receive(:sleep).with(Sidekiq.options[:timeout] + 2).ordered
|
||||
expect(Process).to receive(:kill).with('SIGKILL', pid).ordered
|
||||
|
||||
expect(Sidekiq.logger)
|
||||
.to receive(:warn).with(class: 'ProjectCacheWorker',
|
||||
message: anything,
|
||||
pid: pid,
|
||||
signal: anything).at_least(:once)
|
||||
|
||||
run
|
||||
end
|
||||
|
||||
it 'sends TSTP and TERM to the pid, but KILL to the pgroup, when running as process leader' do
|
||||
allow(Process).to receive(:getpgrp) { pid }
|
||||
allow(subject).to receive(:sleep)
|
||||
|
||||
expect(Process).to receive(:kill).with('SIGTSTP', pid).ordered
|
||||
expect(Process).to receive(:kill).with('SIGTERM', pid).ordered
|
||||
expect(Process).to receive(:kill).with('SIGKILL', 0).ordered
|
||||
|
||||
run
|
||||
end
|
||||
end
|
||||
|
||||
context 'when MAX_RSS is not exceeded' do
|
||||
before do
|
||||
stub_const("#{described_class}::MAX_RSS", 15.kilobytes)
|
||||
end
|
||||
|
||||
it 'does nothing' do
|
||||
expect(subject).not_to receive(:sleep)
|
||||
|
||||
run
|
||||
end
|
||||
end
|
||||
end
|
|
@ -322,8 +322,7 @@ RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics do
|
|||
with_sidekiq_server_middleware do |chain|
|
||||
Gitlab::SidekiqMiddleware.server_configurator(
|
||||
metrics: true,
|
||||
arguments_logger: false,
|
||||
memory_killer: false
|
||||
arguments_logger: false
|
||||
).call(chain)
|
||||
|
||||
Sidekiq::Testing.inline! { example.run }
|
||||
|
|
|
@ -60,7 +60,6 @@ RSpec.describe Gitlab::SidekiqMiddleware do
|
|||
::Labkit::Middleware::Sidekiq::Server,
|
||||
::Gitlab::SidekiqMiddleware::ServerMetrics,
|
||||
::Gitlab::SidekiqMiddleware::ArgumentsLogger,
|
||||
::Gitlab::SidekiqMiddleware::MemoryKiller,
|
||||
::Gitlab::SidekiqMiddleware::RequestStoreMiddleware,
|
||||
::Gitlab::SidekiqMiddleware::ExtraDoneLogMetadata,
|
||||
::Gitlab::SidekiqMiddleware::BatchLoader,
|
||||
|
@ -79,8 +78,7 @@ RSpec.describe Gitlab::SidekiqMiddleware do
|
|||
with_sidekiq_server_middleware do |chain|
|
||||
described_class.server_configurator(
|
||||
metrics: true,
|
||||
arguments_logger: true,
|
||||
memory_killer: true
|
||||
arguments_logger: true
|
||||
).call(chain)
|
||||
|
||||
Sidekiq::Testing.inline! { example.run }
|
||||
|
@ -112,16 +110,14 @@ RSpec.describe Gitlab::SidekiqMiddleware do
|
|||
let(:configurator) do
|
||||
described_class.server_configurator(
|
||||
metrics: false,
|
||||
arguments_logger: false,
|
||||
memory_killer: false
|
||||
arguments_logger: false
|
||||
)
|
||||
end
|
||||
|
||||
let(:disabled_sidekiq_middlewares) do
|
||||
[
|
||||
Gitlab::SidekiqMiddleware::ServerMetrics,
|
||||
Gitlab::SidekiqMiddleware::ArgumentsLogger,
|
||||
Gitlab::SidekiqMiddleware::MemoryKiller
|
||||
Gitlab::SidekiqMiddleware::ArgumentsLogger
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
@ -407,8 +407,7 @@ RSpec.configure do |config|
|
|||
with_sidekiq_server_middleware do |chain|
|
||||
Gitlab::SidekiqMiddleware.server_configurator(
|
||||
metrics: false, # The metrics don't go anywhere in tests
|
||||
arguments_logger: false, # We're not logging the regular messages for inline jobs
|
||||
memory_killer: false # This is not a thing we want to do inline in tests
|
||||
arguments_logger: false # We're not logging the regular messages for inline jobs
|
||||
).call(chain)
|
||||
chain.add DisableQueryLimit
|
||||
chain.insert_after ::Gitlab::SidekiqMiddleware::RequestStoreMiddleware, IsolatedRequestStore
|
||||
|
|
|
@ -300,7 +300,7 @@ RSpec.shared_examples 'wiki controller actions' do
|
|||
expect(response.headers['Content-Disposition']).to match(/^inline/)
|
||||
expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq('true')
|
||||
expect(response.cache_control[:public]).to be(false)
|
||||
expect(response.headers['Cache-Control']).to eq('max-age=60, private')
|
||||
expect(response.headers['Cache-Control']).to eq('max-age=60, private, must-revalidate, stale-while-revalidate=60, stale-if-error=300, s-maxage=60')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue