Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
7cd8614922
commit
f58a5001b9
|
|
@ -1,31 +1,39 @@
|
|||
---
|
||||
# Base Markdownlint configuration
|
||||
# Extended Markdownlint configuration in doc/.markdownlint/
|
||||
# See https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md for explanations of each rule
|
||||
# First, set the default
|
||||
default: true
|
||||
first-header-h1: true
|
||||
header-style:
|
||||
style: "atx"
|
||||
ul-style:
|
||||
style: "dash"
|
||||
no-trailing-spaces: false
|
||||
line-length: false
|
||||
no-duplicate-header:
|
||||
allow_different_nesting: true
|
||||
no-trailing-punctuation:
|
||||
punctuation: ".,;:!。,;:!?"
|
||||
ol-prefix:
|
||||
style: "one"
|
||||
no-inline-html: false
|
||||
hr-style:
|
||||
style: "---"
|
||||
no-emphasis-as-heading: false
|
||||
first-line-h1: false
|
||||
code-block-style:
|
||||
|
||||
# Per-rule settings in alphabetical order
|
||||
code-block-style: # MD046
|
||||
style: "fenced"
|
||||
emphasis-style: false
|
||||
link-fragments: false
|
||||
reference-links-images: false
|
||||
proper-names:
|
||||
emphasis-style: false # MD049
|
||||
first-header-h1: true # MD002
|
||||
first-line-h1: false # MD041
|
||||
header-style: # MD003
|
||||
style: "atx"
|
||||
hr-style: # MD035
|
||||
style: "---"
|
||||
line-length: false # MD013
|
||||
link-fragments: false # MD051
|
||||
no-duplicate-header: # MD024
|
||||
allow_different_nesting: true
|
||||
no-emphasis-as-heading: false # MD036
|
||||
no-inline-html: false # MD033
|
||||
no-trailing-punctuation: # MD026
|
||||
punctuation: ".,;:!。,;:!?"
|
||||
no-trailing-spaces: false # MD009
|
||||
ol-prefix: # MD029
|
||||
style: "one"
|
||||
reference-links-images: false # MD052
|
||||
ul-style: # MD004
|
||||
style: "dash"
|
||||
|
||||
# Keep this item last due to length
|
||||
proper-names: # MD044
|
||||
code_blocks: false
|
||||
html_elements: false
|
||||
names: [
|
||||
"Akismet",
|
||||
"Alertmanager",
|
||||
|
|
@ -150,5 +158,3 @@ proper-names:
|
|||
"YAML",
|
||||
"YouTrack"
|
||||
]
|
||||
code_blocks: false
|
||||
html_elements: false
|
||||
|
|
|
|||
|
|
@ -492,7 +492,7 @@
|
|||
{"name":"rails","version":"7.0.8","platform":"ruby","checksum":"8e43af921acf766fb429126f020ec90c3b25809631f8fbdff95c3553828d5867"},
|
||||
{"name":"rails-controller-testing","version":"1.0.5","platform":"ruby","checksum":"741448db59366073e86fc965ba403f881c636b79a2c39a48d0486f2607182e94"},
|
||||
{"name":"rails-dom-testing","version":"2.0.3","platform":"ruby","checksum":"b140c4f39f6e609c8113137b9a60dfc2ecb89864e496f87f23a68b3b8f12d8d1"},
|
||||
{"name":"rails-html-sanitizer","version":"1.5.0","platform":"ruby","checksum":"bf326075e8a968cd882c30b15a4c9100059be3af2356093dc68324ec3bd9ea79"},
|
||||
{"name":"rails-html-sanitizer","version":"1.6.0","platform":"ruby","checksum":"86e9f19d2e6748890dcc2633c8945ca45baa08a1df9d8c215ce17b3b0afaa4de"},
|
||||
{"name":"rails-i18n","version":"7.0.3","platform":"ruby","checksum":"e3158e98c5332d129fd5131f171ac575eb30dbb8919b21595382b08850cf2bd3"},
|
||||
{"name":"railties","version":"7.0.8","platform":"ruby","checksum":"12325c3933efd33f8ead640197dec3b8c27c8d45607dd68b7b925896bf09cc69"},
|
||||
{"name":"rainbow","version":"3.1.1","platform":"ruby","checksum":"039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a"},
|
||||
|
|
|
|||
|
|
@ -1324,8 +1324,9 @@ GEM
|
|||
rails-dom-testing (2.0.3)
|
||||
activesupport (>= 4.2.0)
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.5.0)
|
||||
loofah (~> 2.19, >= 2.19.1)
|
||||
rails-html-sanitizer (1.6.0)
|
||||
loofah (~> 2.21)
|
||||
nokogiri (~> 1.14)
|
||||
rails-i18n (7.0.3)
|
||||
i18n (>= 0.7, < 2)
|
||||
railties (>= 6.0.0, < 8)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { GlTabs, GlTab, GlSearchBoxByType, GlSorting, GlSortingItem } from '@gitlab/ui';
|
||||
import { GlTabs, GlTab, GlSearchBoxByType, GlSorting } from '@gitlab/ui';
|
||||
import { isString, debounce } from 'lodash';
|
||||
import { __ } from '~/locale';
|
||||
import { DEBOUNCE_DELAY } from '~/vue_shared/components/filtered_search_bar/constants';
|
||||
|
|
@ -30,7 +30,6 @@ export default {
|
|||
GroupsApp,
|
||||
GlSearchBoxByType,
|
||||
GlSorting,
|
||||
GlSortingItem,
|
||||
SubgroupsAndProjectsEmptyState,
|
||||
SharedProjectsEmptyState,
|
||||
ArchivedProjectsEmptyState,
|
||||
|
|
@ -84,6 +83,9 @@ export default {
|
|||
sortQueryStringValue() {
|
||||
return this.isAscending ? this.sort.asc : this.sort.desc;
|
||||
},
|
||||
activeTabSortOptions() {
|
||||
return this.activeTab.sortingItems.map(({ label }) => ({ value: label, text: label }));
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.search = this.$route.query?.filter || '';
|
||||
|
|
@ -178,12 +180,14 @@ export default {
|
|||
|
||||
this.handleSearchOrSortChange();
|
||||
},
|
||||
handleSortingItemClick(sortingItem) {
|
||||
if (sortingItem === this.sort) {
|
||||
handleSortingItemClick(value) {
|
||||
const selectedSortingItem = this.activeTab.sortingItems.find((item) => item.label === value);
|
||||
|
||||
if (selectedSortingItem === this.sort) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.sort = sortingItem;
|
||||
this.sort = selectedSortingItem;
|
||||
|
||||
this.handleSearchOrSortChange();
|
||||
},
|
||||
|
|
@ -239,16 +243,11 @@ export default {
|
|||
data-testid="group_sort_by_dropdown"
|
||||
:text="sort.label"
|
||||
:is-ascending="isAscending"
|
||||
:sort-options="activeTabSortOptions"
|
||||
:sort-by="sort.label"
|
||||
@sortByChange="handleSortingItemClick"
|
||||
@sortDirectionChange="handleSortDirectionChange"
|
||||
>
|
||||
<gl-sorting-item
|
||||
v-for="sortingItem in activeTab.sortingItems"
|
||||
:key="sortingItem.label"
|
||||
:active="sortingItem === sort"
|
||||
@click="handleSortingItemClick(sortingItem)"
|
||||
>{{ sortingItem.label }}</gl-sorting-item
|
||||
>
|
||||
</gl-sorting>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ new SigninTabsMemoizer(); // eslint-disable-line no-new
|
|||
new NoEmojiValidator(); // eslint-disable-line no-new
|
||||
|
||||
new OAuthRememberMe({
|
||||
container: $('.omniauth-container'),
|
||||
container: $('.js-oauth-login'),
|
||||
}).bindEvents();
|
||||
|
||||
// Save the URL fragment from the current window location. This will be present if the user was
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ export default class OAuthRememberMe {
|
|||
toggleRememberMe(event) {
|
||||
const rememberMe = $(event.target).is(':checked');
|
||||
|
||||
$('.js-oauth-login', this.container).each((i, element) => {
|
||||
const $form = $(element).parent('form');
|
||||
$('.js-oauth-login form', this.container).each((_, form) => {
|
||||
const $form = $(form);
|
||||
const href = $form.attr('action');
|
||||
|
||||
if (rememberMe) {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export default function preserveUrlFragment(fragment = '') {
|
|||
|
||||
// Append the fragment to all sign-in/sign-up form actions so it is preserved when the user is
|
||||
// eventually redirected back to the originally requested URL.
|
||||
const forms = document.querySelectorAll('#signin-container .tab-content form');
|
||||
const forms = document.querySelectorAll('.js-non-oauth-login form');
|
||||
Array.prototype.forEach.call(forms, (form) => {
|
||||
const actionWithFragment = setUrlFragment(form.getAttribute('action'), `#${normalFragment}`);
|
||||
form.setAttribute('action', actionWithFragment);
|
||||
|
|
@ -20,7 +20,7 @@ export default function preserveUrlFragment(fragment = '') {
|
|||
|
||||
// Append a redirect_fragment query param to all oauth provider links. The redirect_fragment
|
||||
// query param will be available in the omniauth callback upon successful authentication
|
||||
const oauthForms = document.querySelectorAll('#signin-container .omniauth-container form');
|
||||
const oauthForms = document.querySelectorAll('.js-oauth-login form');
|
||||
Array.prototype.forEach.call(oauthForms, (oauthForm) => {
|
||||
const newHref = mergeUrlParams(
|
||||
{ redirect_fragment: normalFragment },
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ export default {
|
|||
:no-results-text="$options.translations.noResultsText"
|
||||
:selected="tzValue"
|
||||
block
|
||||
fluid-width
|
||||
searchable
|
||||
@search="setSearchTerm"
|
||||
@select="selectTimezone"
|
||||
|
|
|
|||
|
|
@ -450,6 +450,7 @@ module ApplicationSettingsHelper
|
|||
:issues_create_limit,
|
||||
:notes_create_limit,
|
||||
:notes_create_limit_allowlist_raw,
|
||||
:members_delete_limit,
|
||||
:raw_blob_request_limit,
|
||||
:project_import_limit,
|
||||
:project_export_limit,
|
||||
|
|
|
|||
|
|
@ -33,6 +33,19 @@ module TimeZoneHelper
|
|||
end
|
||||
end
|
||||
|
||||
# The identifiers in `timezone_data` are not unique. Some cities (e.g. London and Edinburgh) have
|
||||
# the same `identifier` value (e.g. "Europe/London").
|
||||
# This method merges such entries into one, joining the city names.
|
||||
# This unique list is better suited for selectboxes etc.
|
||||
def timezone_data_with_unique_identifiers(format: :short)
|
||||
timezone_data(format: format)
|
||||
.group_by { |entry| entry[:identifier] }
|
||||
.map do |_identifier, entries|
|
||||
names = entries.map { |entry| entry[:name] }.sort.join(', ') # rubocop:disable Rails/Pluck -- Not a ActiveRecord object
|
||||
entries.first.merge({ name: names })
|
||||
end
|
||||
end
|
||||
|
||||
def local_timezone_instance(timezone)
|
||||
return Time.zone if timezone.blank?
|
||||
|
||||
|
|
|
|||
|
|
@ -579,6 +579,7 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord
|
|||
:max_import_size,
|
||||
:max_pages_custom_domains_per_project,
|
||||
:max_terraform_state_size_bytes,
|
||||
:members_delete_limit,
|
||||
:notes_create_limit,
|
||||
:package_registry_cleanup_policies_worker_capacity,
|
||||
:packages_cleanup_package_file_worker_capacity,
|
||||
|
|
@ -594,6 +595,11 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord
|
|||
:users_get_by_id_limit
|
||||
end
|
||||
|
||||
jsonb_accessor :rate_limits,
|
||||
members_delete_limit: [:integer, { default: 60 }]
|
||||
|
||||
validates :rate_limits, json_schema: { filename: "application_setting_rate_limits" }
|
||||
|
||||
validates :search_rate_limit_allowlist,
|
||||
length: { maximum: 100, message: N_('is too long (maximum is 100 entries)') },
|
||||
allow_nil: false
|
||||
|
|
|
|||
|
|
@ -137,6 +137,7 @@ module ApplicationSettingImplementation
|
|||
mirror_available: true,
|
||||
notes_create_limit: 300,
|
||||
notes_create_limit_allowlist: [],
|
||||
members_delete_limit: 60,
|
||||
notify_on_unknown_sign_in: true,
|
||||
outbound_local_requests_whitelist: [],
|
||||
password_authentication_enabled_for_git: true,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ module Integrations
|
|||
section: SECTION_TYPE_CONNECTION,
|
||||
type: :password,
|
||||
title: -> { s_('DiffblueCover|License key') },
|
||||
description: -> { s_('DiffblueCover|Diffblue Cover license key') },
|
||||
description: -> { s_('DiffblueCover|Diffblue Cover license key.') },
|
||||
non_empty_password_title: -> { s_('DiffblueCover|License key') },
|
||||
non_empty_password_help: -> {
|
||||
s_(
|
||||
|
|
@ -21,7 +21,7 @@ module Integrations
|
|||
format(
|
||||
s_(
|
||||
'DiffblueCover|Enter your Diffblue Cover license key or ' \
|
||||
'visit %{diffblue_link} to obtain a free trial license.'
|
||||
'go to %{diffblue_link} to obtain a free trial license.'
|
||||
),
|
||||
diffblue_link: diffblue_link
|
||||
)
|
||||
|
|
@ -30,7 +30,7 @@ module Integrations
|
|||
field :diffblue_access_token_name,
|
||||
section: SECTION_TYPE_CONFIGURATION,
|
||||
title: -> { s_('DiffblueCover|Name') },
|
||||
description: -> { s_('DiffblueCover|Access token name used by Diffblue Cover in pipelines') },
|
||||
description: -> { s_('DiffblueCover|Access token name used by Diffblue Cover in pipelines.') },
|
||||
required: true,
|
||||
placeholder: -> { s_('DiffblueCover|My token name') }
|
||||
|
||||
|
|
@ -38,7 +38,7 @@ module Integrations
|
|||
section: SECTION_TYPE_CONFIGURATION,
|
||||
type: :password,
|
||||
title: -> { s_('DiffblueCover|Secret') },
|
||||
description: -> { s_('DiffblueCover|Access token secret used by Diffblue Cover in pipelines') },
|
||||
description: -> { s_('DiffblueCover|Access token secret used by Diffblue Cover in pipelines.') },
|
||||
non_empty_password_title: -> { s_('DiffblueCover|Secret') },
|
||||
non_empty_password_help: -> { s_('DiffblueCover|Leave blank to use your current secret value.') },
|
||||
required: true,
|
||||
|
|
@ -82,8 +82,8 @@ module Integrations
|
|||
title: s_('DiffblueCover|Integration details'),
|
||||
description:
|
||||
s_(
|
||||
'DiffblueCover|Diffblue Cover is a reinforcement learning AI platform that automatically ' \
|
||||
'writes comprehensive, human-like Java unit tests. Integrate the power of Diffblue ' \
|
||||
'DiffblueCover|Diffblue Cover is a generative AI platform that automatically ' \
|
||||
'writes comprehensive, human-like Java unit tests. Integrate Diffblue ' \
|
||||
'Cover into your CI/CD workflow for fully autonomous operation.'
|
||||
)
|
||||
},
|
||||
|
|
@ -91,9 +91,9 @@ module Integrations
|
|||
type: SECTION_TYPE_CONFIGURATION,
|
||||
title: s_('DiffblueCover|Access token'),
|
||||
description:
|
||||
'A GitLab access token is required in allow Diffblue Cover to access your project. ' \
|
||||
'Use a GitLab access token with a <code>Developer</code> role, plus ' \
|
||||
'<code>api</code> and <code>write_repository</code> scopes.'
|
||||
'You must have a GitLab access token for Diffblue Cover to access your project. ' \
|
||||
'Use a GitLab access token with at least the Developer role and ' \
|
||||
'the <code>api</code> and <code>write_repository</code> permissions.'
|
||||
}
|
||||
]
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "Application rate limits",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"members_delete_limit": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "Number of project or group members a user can delete per minute."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
%section.settings.as-members-api-limits.no-animate#js-members-api-limits-settings{ class: ('expanded' if expanded_by_default?) }
|
||||
.settings-header
|
||||
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||
= _('Members API rate limit')
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||
= expanded_by_default? ? _('Collapse') : _('Expand')
|
||||
%p.gl-text-secondary
|
||||
= _('Limit the number of project or group members a user can delete per minute through API requests.')
|
||||
= link_to _('Learn more.'), help_page_path('administration/settings/rate_limit_on_members_api'), target: '_blank', rel: 'noopener noreferrer'
|
||||
.settings-content
|
||||
= gitlab_ui_form_for @application_setting, url: network_admin_application_settings_path(anchor: 'js-members-api-limits-settings'), html: { class: 'fieldset-form' } do |f|
|
||||
= form_errors(@application_setting)
|
||||
|
||||
%fieldset
|
||||
.form-group
|
||||
= f.label :members_delete_limit, _('Maximum requests per minute per group / project'), class: 'label-bold'
|
||||
= f.number_field :members_delete_limit, min: 0, class: 'form-control gl-form-input'
|
||||
.form-text.gl-text-gray-600
|
||||
= _("Set to 0 to disable the limit.")
|
||||
|
||||
= f.submit _('Save changes'), pajamas_button: true
|
||||
|
|
@ -151,6 +151,8 @@
|
|||
|
||||
= render 'projects_api_limits'
|
||||
|
||||
= render 'members_api_limits'
|
||||
|
||||
%section.settings.as-import-export-limits.no-animate#js-import-export-limits-settings{ class: ('expanded' if expanded_by_default?) }
|
||||
.settings-header
|
||||
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
.row.gl-mt-5.justify-content-center
|
||||
.col-md-5
|
||||
.login-page
|
||||
#signin-container.borderless
|
||||
.borderless
|
||||
- if any_form_based_providers_enabled?
|
||||
= render 'devise/shared/tabs_ldap', show_password_form: allow_admin_mode_password_authentication_for_web?, render_signup_link: false
|
||||
.tab-content
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
.row.justify-content-center
|
||||
.col-md-5
|
||||
.login-page
|
||||
#signin-container.borderless
|
||||
.borderless
|
||||
.login-box.gl-p-5
|
||||
.login-body
|
||||
- if current_user.two_factor_enabled?
|
||||
|
|
|
|||
|
|
@ -9,26 +9,23 @@
|
|||
|
||||
= render_if_exists "layouts/google_tag_manager_body"
|
||||
|
||||
#signin-container
|
||||
.js-non-oauth-login
|
||||
- if any_form_based_providers_enabled?
|
||||
= render 'devise/shared/tabs_ldap', render_signup_link: false
|
||||
.tab-content
|
||||
- if password_authentication_enabled_for_web? || ldap_sign_in_enabled? || crowd_enabled?
|
||||
= render 'devise/shared/signin_box'
|
||||
|
||||
-# Show a message if none of the mechanisms above are enabled
|
||||
- if !password_authentication_enabled_for_web? && !ldap_sign_in_enabled? && !(omniauth_enabled? && devise_mapping.omniauthable?)
|
||||
%div
|
||||
= _('No authentication methods configured.')
|
||||
|
||||
- if Gitlab::CurrentSettings.current_application_settings.terms
|
||||
%p.gl-px-5
|
||||
= html_escape(s_("SignUp|By signing in you accept the %{link_start}Terms of Use and acknowledge the Privacy Statement and Cookie Policy%{link_end}.")) % { link_start: "<a href='#{terms_path}' target='_blank' rel='noreferrer noopener'>".html_safe,
|
||||
link_end: '</a>'.html_safe }
|
||||
|
||||
- if allow_signup?
|
||||
%p.gl-mt-3.gl-text-center
|
||||
= _("Don't have an account yet?")
|
||||
= link_to _("Register now"), new_registration_path(:user, invite_email: @invite_email), data: { testid: 'register-link' }
|
||||
- if omniauth_enabled? && devise_mapping.omniauthable? && button_based_providers_enabled?
|
||||
= render 'devise/shared/omniauth_box'
|
||||
-# Show a message if none of the mechanisms above are enabled
|
||||
- if !password_authentication_enabled_for_web? && !ldap_sign_in_enabled? && !(omniauth_enabled? && devise_mapping.omniauthable?)
|
||||
%div
|
||||
= _('No authentication methods configured.')
|
||||
- if Gitlab::CurrentSettings.current_application_settings.terms
|
||||
%p.gl-px-5
|
||||
= html_escape(s_("SignUp|By signing in you accept the %{link_start}Terms of Use and acknowledge the Privacy Statement and Cookie Policy%{link_end}.")) % { link_start: "<a href='#{terms_path}' target='_blank' rel='noreferrer noopener'>".html_safe,
|
||||
link_end: '</a>'.html_safe }
|
||||
- if allow_signup?
|
||||
%p.gl-mt-3.gl-text-center
|
||||
= _("Don't have an account yet?")
|
||||
= link_to _("Register now"), new_registration_path(:user, invite_email: @invite_email), data: { testid: 'register-link' }
|
||||
- if omniauth_enabled? && devise_mapping.omniauthable? && button_based_providers_enabled?
|
||||
= render 'devise/shared/omniauth_box'
|
||||
|
|
|
|||
|
|
@ -4,12 +4,11 @@
|
|||
.omniauth-divider.gl-display-flex.gl-align-items-center
|
||||
= _("or sign in with")
|
||||
|
||||
.gl-mt-5.gl-px-5.omniauth-container.gl-text-center.gl-display-flex.gl-flex-direction-column.gl-gap-3
|
||||
.gl-mt-5.gl-px-5.gl-text-center.gl-display-flex.gl-flex-direction-column.gl-gap-3.js-oauth-login
|
||||
- enabled_button_based_providers.each do |provider|
|
||||
= render 'devise/shared/omniauth_provider_button',
|
||||
href: omniauth_authorize_path(:user, provider),
|
||||
provider: provider,
|
||||
classes: 'js-oauth-login',
|
||||
data: { testid: test_id_for_provider(provider) },
|
||||
id: "oauth-login-#{provider}"
|
||||
- if render_remember_me
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
- button_options = { class: classes, data: data, id: id }
|
||||
- button_options = { class: local_assigns.fetch(:classes, nil) || nil, data: data, id: id }
|
||||
|
||||
= render Pajamas::ButtonComponent.new(href: href, method: :post, form: true, block: true, button_options: button_options) do
|
||||
- if provider_has_icon?(provider)
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@
|
|||
%h4.gl-my-0= s_("Profiles|Time settings")
|
||||
%p.gl-text-secondary= s_("Profiles|Set your local time zone.")
|
||||
= f.label :user_timezone, _("Time zone")
|
||||
.js-timezone-dropdown{ data: { timezone_data: timezone_data.to_json, initial_value: @user.timezone, name: 'user[timezone]' } }
|
||||
.js-timezone-dropdown{ data: { timezone_data: timezone_data_with_unique_identifiers.to_json, initial_value: @user.timezone, name: 'user[timezone]' } }
|
||||
|
||||
.settings-section.js-search-settings-section.gl-border-t.gl-pt-6
|
||||
.settings-sticky-header
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@
|
|||
#
|
||||
- title: "Atlassian Crowd OmniAuth provider" # (required) The name of the feature to be deprecated
|
||||
announcement_milestone: "15.3" # (required) The milestone when this feature was first announced as deprecated.
|
||||
removal_milestone: "17.0" # (required) The milestone when this feature is planned to be removed
|
||||
removal_milestone: "18.0" # (required) The milestone when this feature is planned to be removed
|
||||
breaking_change: true # (required) If this deprecation is a breaking change, set this value to true
|
||||
reporter: hsutor # (required) GitLab username of the person reporting the deprecation
|
||||
stage: Manage # (required) String value of the stage that the feature was created in. e.g., Growth
|
||||
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/369117 # (required) Link to the deprecation issue in GitLab
|
||||
body: | # (required) Do not modify this line, instead modify the lines below.
|
||||
The `omniauth_crowd` gem that provides GitLab with the Atlassian Crowd OmniAuth provider will be removed in our
|
||||
next major release, GitLab 16.0. This gem sees very little use and its
|
||||
next major release, GitLab 18.0. This gem sees very little use and its
|
||||
[lack of compatibility](https://github.com/robdimarco/omniauth_crowd/issues/37) with OmniAuth 2.0 is
|
||||
[blocking our upgrade](https://gitlab.com/gitlab-org/gitlab/-/issues/30073).
|
||||
|
|
|
|||
|
|
@ -4,7 +4,16 @@ classes:
|
|||
- Clusters::Agents::Authorizations::CiAccess::ProjectAuthorization
|
||||
feature_categories:
|
||||
- deployment_management
|
||||
description: Configuration for a project that is authorized to use a particular cluster agent
|
||||
description: Configuration for a project that is authorized to use a particular cluster
|
||||
agent
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/67295
|
||||
milestone: '14.3'
|
||||
gitlab_schema: gitlab_main
|
||||
gitlab_schema: gitlab_main_cell
|
||||
allow_cross_joins:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_transactions:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_foreign_keys:
|
||||
- gitlab_main_clusterwide
|
||||
sharding_key:
|
||||
project_id: projects
|
||||
|
|
|
|||
|
|
@ -7,4 +7,12 @@ feature_categories:
|
|||
description: TODO
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44098
|
||||
milestone: '13.5'
|
||||
gitlab_schema: gitlab_main
|
||||
gitlab_schema: gitlab_main_cell
|
||||
allow_cross_joins:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_transactions:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_foreign_keys:
|
||||
- gitlab_main_clusterwide
|
||||
sharding_key:
|
||||
namespace_id: namespaces
|
||||
|
|
|
|||
|
|
@ -7,4 +7,12 @@ feature_categories:
|
|||
description: Stores project's external status checks
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/62186
|
||||
milestone: '14.0'
|
||||
gitlab_schema: gitlab_main
|
||||
gitlab_schema: gitlab_main_cell
|
||||
allow_cross_joins:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_transactions:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_foreign_keys:
|
||||
- gitlab_main_clusterwide
|
||||
sharding_key:
|
||||
project_id: projects
|
||||
|
|
|
|||
|
|
@ -7,4 +7,12 @@ feature_categories:
|
|||
description: TODO
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28182
|
||||
milestone: '13.0'
|
||||
gitlab_schema: gitlab_main
|
||||
gitlab_schema: gitlab_main_cell
|
||||
allow_cross_joins:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_transactions:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_foreign_keys:
|
||||
- gitlab_main_clusterwide
|
||||
sharding_key:
|
||||
project_id: projects
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
---
|
||||
table_name: project_compliance_standards_adherence
|
||||
classes:
|
||||
- Projects::ComplianceStandards::Adherence
|
||||
- Projects::ComplianceStandards::Adherence
|
||||
feature_categories:
|
||||
- compliance_management
|
||||
description: Stores the details about projects and their adherence to compliance standards
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122293
|
||||
milestone: '16.1'
|
||||
gitlab_schema: gitlab_main_cell
|
||||
sharding_key:
|
||||
project_id: projects
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddRateLimitsToApplicationSettings < Gitlab::Database::Migration[2.2]
|
||||
milestone '16.9'
|
||||
enable_lock_retries!
|
||||
|
||||
def change
|
||||
add_column :application_settings, :rate_limits, :jsonb, default: {}, null: false
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
9c9eb37365dae73fb68000d18675a280e063711eaed8f96f64724bff20326957
|
||||
|
|
@ -12633,6 +12633,7 @@ CREATE TABLE application_settings (
|
|||
lock_toggle_security_policy_custom_ci boolean DEFAULT false NOT NULL,
|
||||
toggle_security_policies_policy_scope boolean DEFAULT false NOT NULL,
|
||||
lock_toggle_security_policies_policy_scope boolean DEFAULT false NOT NULL,
|
||||
rate_limits jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
|
||||
CONSTRAINT app_settings_container_registry_pre_import_tags_rate_positive CHECK ((container_registry_pre_import_tags_rate >= (0)::numeric)),
|
||||
CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
stage: Data Stores
|
||||
group: Tenant Scale
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Rate limit on Members API **(FREE SELF)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/140633) in GitLab 16.9.
|
||||
|
||||
You can configure the rate limit per group (or project) per user to the
|
||||
[delete members API](../../api/members.md#remove-a-member-from-a-group-or-project).
|
||||
|
||||
To change the rate limit:
|
||||
|
||||
1. On the left sidebar, at the bottom, select **Admin Area**.
|
||||
1. Select **Settings > Network**.
|
||||
1. Expand **Members API rate limit**.
|
||||
1. In the **Maximum requests per minute per group / project** text box, enter the new value.
|
||||
1. Select **Save changes**.
|
||||
|
||||
The rate limit:
|
||||
|
||||
- Applies per group or project per user.
|
||||
- Can be set to 0 to disable rate limiting.
|
||||
|
||||
The default value of the rate limit is `60`.
|
||||
|
||||
Requests over the rate limit are logged into the `auth.log` file.
|
||||
|
||||
For example, if you set a limit of 60, requests sent to the
|
||||
[delete members API](../../api/members.md#remove-a-member-from-a-group-or-project) exceeding a rate of 300 per minute
|
||||
are blocked. Access to the endpoint is allowed after one minute.
|
||||
|
|
@ -462,6 +462,40 @@ Get the Datadog integration settings for a project.
|
|||
GET /projects/:id/integrations/datadog
|
||||
```
|
||||
|
||||
## Diffblue Cover
|
||||
|
||||
### Set up Diffblue Cover
|
||||
|
||||
Set up the Diffblue Cover integration for a project.
|
||||
|
||||
```plaintext
|
||||
PUT /projects/:id/integrations/diffblue-cover
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `diffblue_license_key` | string | true | Diffblue Cover license key. |
|
||||
| `diffblue_access_token_name` | string | true | Access token name used by Diffblue Cover in pipelines. |
|
||||
| `diffblue_access_token_secret` | string | true | Access token secret used by Diffblue Cover in pipelines. |
|
||||
|
||||
### Disable Diffblue Cover
|
||||
|
||||
Disable the Diffblue Cover integration for a project. Integration settings are reset.
|
||||
|
||||
```plaintext
|
||||
DELETE /projects/:id/integrations/diffblue-cover
|
||||
```
|
||||
|
||||
### Get Diffblue Cover settings
|
||||
|
||||
Get the Diffblue Cover integration settings for a project.
|
||||
|
||||
```plaintext
|
||||
GET /projects/:id/integrations/diffblue-cover
|
||||
```
|
||||
|
||||
## Discord Notifications
|
||||
|
||||
### Set up Discord Notifications
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ repositories that depend on the object pool.
|
|||
|
||||
The danger lies in `git prune`, and `git gc` calls `git prune`. The
|
||||
problem is that `git prune`, when running in a pool repository, cannot
|
||||
reliable decide if an object is no longer needed.
|
||||
reliably decide if an object is no longer needed.
|
||||
|
||||
### Git alternates in GitLab: pool repositories
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ which are hidden from the user. We then use Git
|
|||
alternates to let a collection of project repositories borrow from a
|
||||
single pool repository. We call such a collection of project
|
||||
repositories a pool. Pools form star-shaped networks of repositories
|
||||
that borrow from a single pool, which resemble (but not be
|
||||
that borrow from a single pool, which resemble (but are not
|
||||
identical to) the fork networks that get formed when users fork
|
||||
projects.
|
||||
|
||||
|
|
|
|||
|
|
@ -52,6 +52,23 @@ For deprecation reviewers (Technical Writers only):
|
|||
|
||||
<div class="deprecation breaking-change" data-milestone="18.0">
|
||||
|
||||
### Atlassian Crowd OmniAuth provider
|
||||
|
||||
<div class="deprecation-notes">
|
||||
- Announced in GitLab <span class="milestone">15.3</span>
|
||||
- Removal in GitLab <span class="milestone">18.0</span> ([breaking change](https://docs.gitlab.com/ee/update/terminology.html#breaking-change))
|
||||
- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/369117).
|
||||
</div>
|
||||
|
||||
The `omniauth_crowd` gem that provides GitLab with the Atlassian Crowd OmniAuth provider will be removed in our
|
||||
next major release, GitLab 18.0. This gem sees very little use and its
|
||||
[lack of compatibility](https://github.com/robdimarco/omniauth_crowd/issues/37) with OmniAuth 2.0 is
|
||||
[blocking our upgrade](https://gitlab.com/gitlab-org/gitlab/-/issues/30073).
|
||||
|
||||
</div>
|
||||
|
||||
<div class="deprecation breaking-change" data-milestone="18.0">
|
||||
|
||||
### GitLab Runner registration token in Runner Operator
|
||||
|
||||
<div class="deprecation-notes">
|
||||
|
|
@ -194,23 +211,6 @@ From GitLab 18.0 and later, the methods to register runners introduced by the ne
|
|||
|
||||
<div class="deprecation breaking-change" data-milestone="17.0">
|
||||
|
||||
### Atlassian Crowd OmniAuth provider
|
||||
|
||||
<div class="deprecation-notes">
|
||||
- Announced in GitLab <span class="milestone">15.3</span>
|
||||
- Removal in GitLab <span class="milestone">17.0</span> ([breaking change](https://docs.gitlab.com/ee/update/terminology.html#breaking-change))
|
||||
- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/369117).
|
||||
</div>
|
||||
|
||||
The `omniauth_crowd` gem that provides GitLab with the Atlassian Crowd OmniAuth provider will be removed in our
|
||||
next major release, GitLab 16.0. This gem sees very little use and its
|
||||
[lack of compatibility](https://github.com/robdimarco/omniauth_crowd/issues/37) with OmniAuth 2.0 is
|
||||
[blocking our upgrade](https://gitlab.com/gitlab-org/gitlab/-/issues/30073).
|
||||
|
||||
</div>
|
||||
|
||||
<div class="deprecation breaking-change" data-milestone="17.0">
|
||||
|
||||
### Auto DevOps support for Herokuish is deprecated
|
||||
|
||||
<div class="deprecation-notes">
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 74 KiB |
|
|
@ -1,94 +1,11 @@
|
|||
---
|
||||
stage: Plan
|
||||
group: Optimize
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
redirect_to: '../../project/insights/index.md'
|
||||
remove_date: '2024-04-20'
|
||||
---
|
||||
|
||||
# Insights for groups **(ULTIMATE ALL)**
|
||||
This document was moved to [another location](../../project/insights/index.md).
|
||||
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/725) in GitLab 12.0.
|
||||
|
||||
Configure insights to explore data about you group's activity, such as
|
||||
triage hygiene, issues created or closed in a given period, and average time for merge
|
||||
requests to be merged.
|
||||
You can also create custom insights reports that are relevant for your group.
|
||||
|
||||
## View group insights
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must have [permission](../../permissions.md#group-members-permissions) to view the group.
|
||||
- You must have access to a project to view information about its merge requests and issues,
|
||||
and permission to view them if they are confidential.
|
||||
|
||||
To access your group's insights:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your group.
|
||||
1. Select **Analyze > Insights**.
|
||||
|
||||
## Interact with insights charts
|
||||
|
||||
You can interact with the insights charts to view details about your group's activity.
|
||||
|
||||

|
||||
|
||||
### Display different reports
|
||||
|
||||
To display one of the available reports on the insights page, from the **Select report** dropdown list,
|
||||
select the report you want to display.
|
||||
|
||||
### View bar chart annotations
|
||||
|
||||
To view annotations, hover over each bar in the chart.
|
||||
|
||||
### Zoom in on chart
|
||||
|
||||
Insights display data from the last 90 days. You can zoom in to display data only from a subset of the 90-day range.
|
||||
|
||||
To do this, select the pause icons (**{status-paused}**) and slide them along the horizontal axis:
|
||||
|
||||
- To change the start date, slide the left pause icon to the left or right.
|
||||
- To change the end date, slide the right pause icon to the left or right.
|
||||
|
||||
### Exclude dimensions from charts
|
||||
|
||||
By default, insights display all available dimensions on the chart.
|
||||
|
||||
To exclude a dimension, from the legend below the chart, select the name of the dimension.
|
||||
|
||||
### Drill down on charts
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/372215/) in GitLab 16.7.
|
||||
|
||||
You can drill down into the data of the **Bugs created per month by priority** and **Bugs created per month by severity** charts from the [default configuration file](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/fixtures/insights/default.yml).
|
||||
|
||||
To view a drill-down report of the data for a specific priority or severity in a month:
|
||||
|
||||
- On the chart, select the bar stack you want to drill down on.
|
||||
|
||||
## Configure group insights
|
||||
|
||||
GitLab reads insights from the
|
||||
[default configuration file](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/fixtures/insights/default.yml).
|
||||
|
||||
To configure group insights:
|
||||
|
||||
1. Create a new file [`.gitlab/insights.yml`](../../project/insights/index.md#configure-project-insights)
|
||||
in a project that belongs to your group.
|
||||
1. On the left sidebar, select **Search or go to** and find your group.
|
||||
1. Select **Settings > General**.
|
||||
1. Expand **Analytics** and find the **Insights** section.
|
||||
1. Select the project that contains your `.gitlab/insights.yml` configuration file.
|
||||
1. Select **Save changes**.
|
||||
|
||||
<!-- ## Troubleshooting
|
||||
|
||||
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.
|
||||
|
||||
Each scenario can be a third-level heading, for example `### 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. -->
|
||||
<!-- This redirect file can be deleted after <YYYY-MM-DD>. -->
|
||||
<!-- Redirects that point to other docs in the same project expire in three months. -->
|
||||
<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
|
||||
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
|
||||
|
|
|
|||
|
|
@ -4,27 +4,28 @@ group: Optimize
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Insights for projects **(ULTIMATE ALL)**
|
||||
# Insights **(ULTIMATE ALL)**
|
||||
|
||||
Configure project insights to explore data such as:
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/725) in GitLab 12.0.
|
||||
|
||||
Configure insights for your projects and groups to explore data such as:
|
||||
|
||||
- Issues created and closed during a specified period.
|
||||
- Average time for merge requests to be merged.
|
||||
- Triage hygiene.
|
||||
|
||||
Insights are also available for [groups](../../group/insights/index.md).
|
||||
You can also create custom Insights reports that are relevant for your group.
|
||||
|
||||
## View project insights
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must have:
|
||||
- Access to a project to view information about its merge requests and issues.
|
||||
- Permission to view confidential merge requests and issues in the project.
|
||||
- For project insights, you must have access to the project and permission to view information about its merge requests and issues.
|
||||
- For group insights, you must have permission to view the group.
|
||||
|
||||
To view project insights:
|
||||
To view insights for a project or group:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. On the left sidebar, select **Search or go to** and find your project or group.
|
||||
1. Select **Analyze > Insights**.
|
||||
1. To view a report, select the **Select report** dropdown list.
|
||||
|
||||
|
|
@ -35,23 +36,61 @@ You can direct users to a specific report in Insights by using the deep-linked U
|
|||
To create a deep link, append the report key to the end of the Insights report URL.
|
||||
For example, a GitLab report with the key `bugsCharts` has the deep link URL `https://gitlab.com/gitlab-org/gitlab/insights/#/bugsCharts`.
|
||||
|
||||
## Interact with Insights charts
|
||||
|
||||
You can interact with the insights charts to view details about your group's activity.
|
||||
|
||||
### Display different reports
|
||||
|
||||
To display one of the available reports on the insights page, from the **Select report** dropdown list,
|
||||
select the report you want to display.
|
||||
|
||||
### View bar chart annotations
|
||||
|
||||
To view annotations, hover over each bar in the chart.
|
||||
|
||||
### Zoom in on chart
|
||||
|
||||
Insights display data from the last 90 days. You can zoom in to display data only from a subset of the 90-day range.
|
||||
|
||||
To do this, select the pause icons (**{status-paused}**) and slide them along the horizontal axis:
|
||||
|
||||
- To change the start date, slide the left pause icon to the left or right.
|
||||
- To change the end date, slide the right pause icon to the left or right.
|
||||
|
||||
### Exclude dimensions from charts
|
||||
|
||||
By default, insights display all available dimensions on the chart.
|
||||
|
||||
To exclude a dimension, from the legend below the chart, select the name of the dimension.
|
||||
|
||||
### Drill down on charts
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/372215/) in GitLab 16.7.
|
||||
|
||||
You can drill down into the data of the **Bugs created per month by priority** and **Bugs created per month by severity** charts from the [default configuration file](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/fixtures/insights/default.yml).
|
||||
|
||||
To view a drill-down report of the data for a specific priority or severity in a month:
|
||||
|
||||
- On the chart, select the bar stack you want to drill down on.
|
||||
|
||||
## Configure project insights
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Depending on your project configuration, you must have at least the Developer role.
|
||||
|
||||
Project insights are configured with the [`.gitlab/insights.yml`](#insights-configuration-file) file in the project. If a project doesn't have a configuration file, it uses the [group configuration](../../group/insights/index.md#configure-group-insights).
|
||||
Project insights are configured with the [`.gitlab/insights.yml`](#insights-configuration-file) file in the project. If a project doesn't have a configuration file, it uses the [group configuration](#configure-group-insights).
|
||||
|
||||
The `.gitlab/insights.yml` file is a YAML file where you define:
|
||||
|
||||
- The structure and order of charts in a report.
|
||||
- The style of charts displayed in the report of your project or group.
|
||||
|
||||
To configure project insights, either:
|
||||
To configure project insights, create a file `.gitlab/insights.yml` either:
|
||||
|
||||
- Create a `.gitlab/insights.yml` file locally in the root directory of your project, and push your changes.
|
||||
- Create a `.gitlab/insights.yml` file in the UI:
|
||||
- Locally, in the root directory of your project, and push your changes.
|
||||
- From the UI:
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. Above the file list, select the branch you want to commit to, select the plus icon, then select **New file**.
|
||||
1. In the **File name** text box, enter `.gitlab/insights.yml`.
|
||||
|
|
@ -59,7 +98,21 @@ To configure project insights, either:
|
|||
1. Select **Commit changes**.
|
||||
|
||||
After you create the configuration file, you can also
|
||||
[use it for the project's group](../../group/insights/index.md#configure-group-insights).
|
||||
use it for the project's group.
|
||||
|
||||
## Configure group insights
|
||||
|
||||
GitLab reads insights from the
|
||||
[default configuration file](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/fixtures/insights/default.yml).
|
||||
|
||||
To configure group insights:
|
||||
|
||||
1. In a project that belongs to your group, [create a `.gitlab/insights.yml` file](#configure-project-insights).
|
||||
1. On the left sidebar, select **Search or go to** and find your group.
|
||||
1. Select **Settings > General**.
|
||||
1. Expand **Analytics** and find the **Insights** section.
|
||||
1. Select the project that contains your `.gitlab/insights.yml` configuration file.
|
||||
1. Select **Save changes**.
|
||||
|
||||
## Insights configuration file
|
||||
|
||||
|
|
@ -403,7 +456,7 @@ Use `query.environment_tiers` to define an array of environments to include the
|
|||
|
||||
Use `projects` to limit where issuables are queried from:
|
||||
|
||||
- If `.gitlab/insights.yml` is used for a [group's insights](../../group/insights/index.md#configure-group-insights), use `projects` to define the projects from which to query issuables. By default, all projects under the group are used.
|
||||
- If `.gitlab/insights.yml` is used for a group's insights, use `projects` to define the projects from which to query issuables. By default, all projects under the group are used.
|
||||
- If `.gitlab/insights.yml` is used for a project's insights, specifying other projects does not yield results. By default, the project is used.
|
||||
|
||||
#### `projects.only`
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@ module API
|
|||
source = find_source(source_type, params[:id])
|
||||
member = source_members(source).find_by!(user_id: params[:user_id])
|
||||
|
||||
check_rate_limit!(:member_delete, scope: [source, current_user])
|
||||
check_rate_limit!(:members_delete, scope: [source, current_user])
|
||||
|
||||
destroy_conditionally!(member) do
|
||||
::Members::DestroyService.new(current_user).execute(member, skip_subresources: params[:skip_subresources], unassign_issuables: params[:unassign_issuables])
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ module Gitlab
|
|||
group_download_export: { threshold: -> { application_settings.group_download_export_limit }, interval: 1.minute },
|
||||
group_import: { threshold: -> { application_settings.group_import_limit }, interval: 1.minute },
|
||||
group_testing_hook: { threshold: 5, interval: 1.minute },
|
||||
member_delete: { threshold: 60, interval: 1.minute },
|
||||
members_delete: { threshold: -> { application_settings.members_delete_limit }, interval: 1.minute },
|
||||
profile_add_new_email: { threshold: 5, interval: 1.minute },
|
||||
web_hook_calls: { interval: 1.minute },
|
||||
web_hook_calls_mid: { interval: 1.minute },
|
||||
|
|
|
|||
|
|
@ -16658,6 +16658,9 @@ msgstr ""
|
|||
msgid "Dependencies|Export as JSON"
|
||||
msgstr ""
|
||||
|
||||
msgid "Dependencies|Filtering unavailable"
|
||||
msgstr ""
|
||||
|
||||
msgid "Dependencies|Job failed to generate the dependency list"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -16682,6 +16685,9 @@ msgstr ""
|
|||
msgid "Dependencies|Projects"
|
||||
msgstr ""
|
||||
|
||||
msgid "Dependencies|Search or filter dependencies..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Dependencies|Software Bill of Materials (SBOM) based on the %{linkStart}latest successful%{linkEnd} scan"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -16706,6 +16712,9 @@ msgstr ""
|
|||
msgid "Dependencies|There was an error fetching the projects for this group. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
msgid "Dependencies|This group exceeds the maximum number of 600 sub-groups. We cannot accurately filter or search the dependency list above this maximum. To view or filter a subset of this information, go to a subgroup's dependency list."
|
||||
msgstr ""
|
||||
|
||||
msgid "Dependencies|This group exceeds the maximum number of sub-groups of 600. We cannot accurately display a project list at this time. Please access a sub-group dependency list to view this information or see the %{linkStart}dependency list help %{linkEnd} page to learn more."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -17733,22 +17742,22 @@ msgstr ""
|
|||
msgid "DiffblueCover|Access token"
|
||||
msgstr ""
|
||||
|
||||
msgid "DiffblueCover|Access token name used by Diffblue Cover in pipelines"
|
||||
msgid "DiffblueCover|Access token name used by Diffblue Cover in pipelines."
|
||||
msgstr ""
|
||||
|
||||
msgid "DiffblueCover|Access token secret used by Diffblue Cover in pipelines"
|
||||
msgid "DiffblueCover|Access token secret used by Diffblue Cover in pipelines."
|
||||
msgstr ""
|
||||
|
||||
msgid "DiffblueCover|Automatically write comprehensive, human-like Java unit tests."
|
||||
msgstr ""
|
||||
|
||||
msgid "DiffblueCover|Diffblue Cover is a reinforcement learning AI platform that automatically writes comprehensive, human-like Java unit tests. Integrate the power of Diffblue Cover into your CI/CD workflow for fully autonomous operation."
|
||||
msgid "DiffblueCover|Diffblue Cover is a generative AI platform that automatically writes comprehensive, human-like Java unit tests. Integrate Diffblue Cover into your CI/CD workflow for fully autonomous operation."
|
||||
msgstr ""
|
||||
|
||||
msgid "DiffblueCover|Diffblue Cover license key"
|
||||
msgid "DiffblueCover|Diffblue Cover license key."
|
||||
msgstr ""
|
||||
|
||||
msgid "DiffblueCover|Enter your Diffblue Cover license key or visit %{diffblue_link} to obtain a free trial license."
|
||||
msgid "DiffblueCover|Enter your Diffblue Cover license key or go to %{diffblue_link} to obtain a free trial license."
|
||||
msgstr ""
|
||||
|
||||
msgid "DiffblueCover|Integration details"
|
||||
|
|
@ -29013,6 +29022,9 @@ msgstr ""
|
|||
msgid "Limit the number of pipeline creation requests per minute. This limit includes pipelines created through the UI, the API, and by background processing."
|
||||
msgstr ""
|
||||
|
||||
msgid "Limit the number of project or group members a user can delete per minute through API requests."
|
||||
msgstr ""
|
||||
|
||||
msgid "Limit the size of Sidekiq jobs stored in Redis."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -29882,6 +29894,9 @@ msgstr ""
|
|||
msgid "Maximum requests per minute"
|
||||
msgstr ""
|
||||
|
||||
msgid "Maximum requests per minute per group / project"
|
||||
msgstr ""
|
||||
|
||||
msgid "Maximum running slices"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -30071,6 +30086,9 @@ msgstr ""
|
|||
msgid "Members"
|
||||
msgstr ""
|
||||
|
||||
msgid "Members API rate limit"
|
||||
msgstr ""
|
||||
|
||||
msgid "Members can be added by project %{i_open}Maintainers%{i_close} or %{i_open}Owners%{i_close}"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -43389,9 +43407,6 @@ msgstr ""
|
|||
msgid "Search or filter commits"
|
||||
msgstr ""
|
||||
|
||||
msgid "Search or filter dependencies..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Search or filter results…"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ RSpec.describe 'Group show page', feature_category: :groups_and_projects do
|
|||
expect(find('.group-row:nth-child(1) .namespace-title > a')).to have_content(project2.title)
|
||||
expect(find('.group-row:nth-child(2) .namespace-title > a')).to have_content(project1.title)
|
||||
expect(find('.group-row:nth-child(3) .namespace-title > a')).to have_content(project3.title)
|
||||
expect(page).to have_selector('button[data-testid="base-dropdown-toggle"]', text: 'Stars')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@ RSpec.describe 'User sorts projects and order persists', feature_category: :grou
|
|||
let_it_be(:group_member) { create(:group_member, :maintainer, user: user, group: group) }
|
||||
let_it_be(:project) { create(:project, :public, group: group) }
|
||||
|
||||
def find_dropdown_toggle
|
||||
find('button[data-testid=base-dropdown-toggle]')
|
||||
end
|
||||
|
||||
shared_examples_for "sort order persists across all views" do |project_paths_label, group_paths_label|
|
||||
it "is set on the dashboard_projects_path" do
|
||||
visit(dashboard_projects_path)
|
||||
|
|
@ -27,7 +31,7 @@ RSpec.describe 'User sorts projects and order persists', feature_category: :grou
|
|||
visit(group_canonical_path(group))
|
||||
|
||||
within '[data-testid=group_sort_by_dropdown]' do
|
||||
expect(find('.gl-dropdown-toggle')).to have_content(group_paths_label)
|
||||
expect(find_dropdown_toggle).to have_content(group_paths_label)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -35,7 +39,7 @@ RSpec.describe 'User sorts projects and order persists', feature_category: :grou
|
|||
visit(details_group_path(group))
|
||||
|
||||
within '[data-testid=group_sort_by_dropdown]' do
|
||||
expect(find('.gl-dropdown-toggle')).to have_content(group_paths_label)
|
||||
expect(find_dropdown_toggle).to have_content(group_paths_label)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -67,8 +71,8 @@ RSpec.describe 'User sorts projects and order persists', feature_category: :grou
|
|||
sign_in(user)
|
||||
visit(group_canonical_path(group))
|
||||
within '[data-testid=group_sort_by_dropdown]' do
|
||||
find('button.gl-dropdown-toggle').click
|
||||
first(:button, 'Created').click
|
||||
find_dropdown_toggle.click
|
||||
find('li', text: 'Created').click
|
||||
wait_for_requests
|
||||
end
|
||||
end
|
||||
|
|
@ -81,8 +85,8 @@ RSpec.describe 'User sorts projects and order persists', feature_category: :grou
|
|||
sign_in(user)
|
||||
visit(details_group_path(group))
|
||||
within '[data-testid=group_sort_by_dropdown]' do
|
||||
find('button.gl-dropdown-toggle').click
|
||||
first(:button, 'Updated').click
|
||||
find_dropdown_toggle.click
|
||||
find('li', text: 'Updated').click
|
||||
wait_for_requests
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
<div id="oauth-container">
|
||||
<div class="js-oauth-login">
|
||||
<input id="remember_me_omniauth" type="checkbox" />
|
||||
|
||||
<form method="post" action="http://example.com/">
|
||||
<button class="js-oauth-login twitter" type="submit">
|
||||
<button class="twitter" type="submit">
|
||||
<span>Twitter</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<form method="post" action="http://example.com/">
|
||||
<button class="js-oauth-login github" type="submit">
|
||||
<button class="github" type="submit">
|
||||
<span>GitHub</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<form method="post" action="http://example.com/?redirect_fragment=L1">
|
||||
<button class="js-oauth-login facebook" type="submit">
|
||||
<button class="facebook" type="submit">
|
||||
<span>Facebook</span>
|
||||
</button>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { GlSorting, GlSortingItem, GlTab } from '@gitlab/ui';
|
||||
import { GlSorting, GlTab } from '@gitlab/ui';
|
||||
import Vue, { nextTick } from 'vue';
|
||||
import AxiosMockAdapter from 'axios-mock-adapter';
|
||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
|
|
@ -17,6 +17,7 @@ import {
|
|||
ACTIVE_TAB_SUBGROUPS_AND_PROJECTS,
|
||||
ACTIVE_TAB_SHARED,
|
||||
ACTIVE_TAB_ARCHIVED,
|
||||
OVERVIEW_TABS_SORTING_ITEMS,
|
||||
SORTING_ITEM_NAME,
|
||||
SORTING_ITEM_UPDATED,
|
||||
SORTING_ITEM_STARS,
|
||||
|
|
@ -74,6 +75,7 @@ describe('OverviewTabs', () => {
|
|||
const findTab = (name) => wrapper.findByRole('tab', { name });
|
||||
const findSelectedTab = () => wrapper.findByRole('tab', { selected: true });
|
||||
const findSearchInput = () => wrapper.findByPlaceholderText(OverviewTabs.i18n.searchPlaceholder);
|
||||
const findGlSorting = () => wrapper.findComponent(GlSorting);
|
||||
|
||||
beforeEach(() => {
|
||||
axiosMock = new AxiosMockAdapter(axios);
|
||||
|
|
@ -301,7 +303,7 @@ describe('OverviewTabs', () => {
|
|||
describe('when sort is changed', () => {
|
||||
beforeEach(async () => {
|
||||
await setup();
|
||||
wrapper.findAllComponents(GlSortingItem).at(2).vm.$emit('click');
|
||||
findGlSorting().vm.$emit('sortByChange', SORTING_ITEM_UPDATED.label);
|
||||
await nextTick();
|
||||
});
|
||||
|
||||
|
|
@ -403,12 +405,15 @@ describe('OverviewTabs', () => {
|
|||
});
|
||||
|
||||
it('sets sort dropdown', () => {
|
||||
expect(wrapper.findComponent(GlSorting).props()).toMatchObject({
|
||||
const expectedSortOptions = OVERVIEW_TABS_SORTING_ITEMS.map(({ label }) => {
|
||||
return { value: label, text: label };
|
||||
});
|
||||
expect(findGlSorting().props()).toMatchObject({
|
||||
text: SORTING_ITEM_UPDATED.label,
|
||||
isAscending: false,
|
||||
sortBy: SORTING_ITEM_UPDATED.label,
|
||||
sortOptions: expectedSortOptions,
|
||||
});
|
||||
|
||||
expect(wrapper.findAllComponents(GlSortingItem).at(2).vm.$attrs.active).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@ import OAuthRememberMe from '~/pages/sessions/new/oauth_remember_me';
|
|||
|
||||
describe('OAuthRememberMe', () => {
|
||||
const findFormAction = (selector) => {
|
||||
return $(`#oauth-container .js-oauth-login${selector}`).parent('form').attr('action');
|
||||
return $(`.js-oauth-login ${selector}`).parent('form').attr('action');
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
setHTMLFixture(htmlOauthRememberMe);
|
||||
|
||||
new OAuthRememberMe({ container: $('#oauth-container') }).bindEvents();
|
||||
new OAuthRememberMe({ container: $('.js-oauth-login') }).bindEvents();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
@ -19,7 +19,7 @@ describe('OAuthRememberMe', () => {
|
|||
});
|
||||
|
||||
it('adds and removes the "remember_me" query parameter from all OAuth login buttons', () => {
|
||||
$('#oauth-container #remember_me_omniauth').click();
|
||||
$('.js-oauth-login #remember_me_omniauth').click();
|
||||
|
||||
expect(findFormAction('.twitter')).toBe('http://example.com/?remember_me=1');
|
||||
expect(findFormAction('.github')).toBe('http://example.com/?remember_me=1');
|
||||
|
|
@ -27,7 +27,7 @@ describe('OAuthRememberMe', () => {
|
|||
'http://example.com/?redirect_fragment=L1&remember_me=1',
|
||||
);
|
||||
|
||||
$('#oauth-container #remember_me_omniauth').click();
|
||||
$('.js-oauth-login #remember_me_omniauth').click();
|
||||
|
||||
expect(findFormAction('.twitter')).toBe('http://example.com/');
|
||||
expect(findFormAction('.github')).toBe('http://example.com/');
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import preserveUrlFragment from '~/pages/sessions/new/preserve_url_fragment';
|
|||
|
||||
describe('preserve_url_fragment', () => {
|
||||
const findFormAction = (selector) => {
|
||||
return $(`.omniauth-container ${selector}`).parent('form').attr('action');
|
||||
return $(`.js-oauth-login ${selector}`).parent('form').attr('action');
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
@ -44,9 +44,7 @@ describe('preserve_url_fragment', () => {
|
|||
});
|
||||
|
||||
it('when "remember-me" is present', () => {
|
||||
$('.js-oauth-login')
|
||||
.parent('form')
|
||||
.attr('action', (i, href) => `${href}?remember_me=1`);
|
||||
$('.js-oauth-login form').attr('action', (i, href) => `${href}?remember_me=1`);
|
||||
|
||||
preserveUrlFragment('#L65');
|
||||
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ RSpec.describe ApplicationSettingsHelper do
|
|||
project_download_export_limit project_export_limit project_import_limit
|
||||
raw_blob_request_limit group_export_limit group_download_export_limit
|
||||
group_import_limit users_get_by_id_limit search_rate_limit search_rate_limit_unauthenticated
|
||||
members_delete_limit
|
||||
])
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -461,7 +461,7 @@ RSpec.describe MarkupHelper, feature_category: :team_planning do
|
|||
|
||||
it 'displays the first line of a code block' do
|
||||
object = create_object("```\nCode block\nwith two lines\n```")
|
||||
expected = %r{<pre.+><code><span class="line">Code block\.\.\.</span></code></pre>}
|
||||
expected = %r{<pre.+><code><span class="line" lang="plaintext">Code block\.\.\.</span></code></pre>}
|
||||
|
||||
expect(helper.first_line_in_markdown(object, attribute, 100, is_todo: true, project: project)).to match(expected)
|
||||
end
|
||||
|
|
@ -476,8 +476,8 @@ RSpec.describe MarkupHelper, feature_category: :team_planning do
|
|||
|
||||
it 'preserves code color scheme' do
|
||||
object = create_object("```ruby\ndef test\n 'hello world'\nend\n```")
|
||||
expected = "\n<pre class=\"code highlight js-syntax-highlight language-ruby\">" \
|
||||
"<code><span class=\"line\"><span class=\"k\">def</span> <span class=\"nf\">test</span>...</span>" \
|
||||
expected = "\n<pre class=\"code highlight js-syntax-highlight language-ruby\" lang=\"ruby\">" \
|
||||
"<code><span class=\"line\" lang=\"ruby\"><span class=\"k\">def</span> <span class=\"nf\">test</span>...</span>" \
|
||||
"</code></pre>\n"
|
||||
|
||||
expect(helper.first_line_in_markdown(object, attribute, 150, is_todo: true, project: project)).to eq(expected)
|
||||
|
|
|
|||
|
|
@ -93,6 +93,29 @@ RSpec.describe TimeZoneHelper, :aggregate_failures do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#timezone_data_with_unique_identifiers' do
|
||||
subject { helper.timezone_data_with_unique_identifiers }
|
||||
|
||||
before do
|
||||
allow(helper).to receive(:timezone_data).and_return([
|
||||
{ identifier: 'Europe/London', name: 'London' },
|
||||
{ identifier: 'Europe/London', name: 'Edinburgh' },
|
||||
{ identifier: 'Europe/Berlin', name: 'Berlin' },
|
||||
{ identifier: 'Europe/London', name: 'Hogwarts' }
|
||||
|
||||
])
|
||||
end
|
||||
|
||||
let(:expected) do
|
||||
[
|
||||
{ identifier: 'Europe/London', name: 'Edinburgh, Hogwarts, London' },
|
||||
{ identifier: 'Europe/Berlin', name: 'Berlin' }
|
||||
]
|
||||
end
|
||||
|
||||
it { is_expected.to eq(expected) }
|
||||
end
|
||||
|
||||
describe '#local_time' do
|
||||
let_it_be(:timezone) { 'America/Los_Angeles' }
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
|
|||
it { expect(setting.decompress_archive_file_timeout).to eq(210) }
|
||||
it { expect(setting.bulk_import_concurrent_pipeline_batch_limit).to eq(25) }
|
||||
it { expect(setting.allow_project_creation_for_guest_and_below).to eq(true) }
|
||||
it { expect(setting.members_delete_limit).to eq(60) }
|
||||
end
|
||||
|
||||
describe 'validations' do
|
||||
|
|
@ -58,6 +59,8 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
|
|||
}
|
||||
end
|
||||
|
||||
it { expect(described_class).to validate_jsonb_schema(['application_setting_rate_limits']) }
|
||||
|
||||
it { is_expected.to allow_value(nil).for(:home_page_url) }
|
||||
it { is_expected.to allow_value(http).for(:home_page_url) }
|
||||
it { is_expected.to allow_value(https).for(:home_page_url) }
|
||||
|
|
@ -225,6 +228,8 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
|
|||
max_import_size
|
||||
max_pages_custom_domains_per_project
|
||||
max_terraform_state_size_bytes
|
||||
members_delete_limit
|
||||
notes_create_limit
|
||||
package_registry_cleanup_policies_worker_capacity
|
||||
packages_cleanup_package_file_worker_capacity
|
||||
pipeline_limit_per_project_user_sha
|
||||
|
|
@ -237,7 +242,6 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
|
|||
sidekiq_job_limiter_limit_bytes
|
||||
terminal_max_session_time
|
||||
users_get_by_id_limit
|
||||
notes_create_limit
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -717,7 +717,7 @@ RSpec.describe API::Members, feature_category: :groups_and_projects do
|
|||
end.to change { source.members.count }.by(-1)
|
||||
end
|
||||
|
||||
it_behaves_like 'rate limited endpoint', rate_limit_key: :member_delete do
|
||||
it_behaves_like 'rate limited endpoint', rate_limit_key: :members_delete do
|
||||
let(:current_user) { maintainer }
|
||||
|
||||
let(:another_member) { create(:user) }
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ module LoginHelpers
|
|||
def login_via(provider, user, uid, remember_me: false, additional_info: {})
|
||||
mock_auth_hash(provider, uid, user.email, additional_info: additional_info)
|
||||
visit new_user_session_path
|
||||
expect(page).to have_css('.omniauth-container')
|
||||
expect(page).to have_css('.js-oauth-login')
|
||||
|
||||
check 'remember_me_omniauth' if remember_me
|
||||
|
||||
|
|
|
|||
|
|
@ -18,4 +18,12 @@ RSpec.describe 'admin/application_settings/network.html.haml', feature_category:
|
|||
expect(rendered).to have_field('application_setting_projects_api_rate_limit_unauthenticated')
|
||||
end
|
||||
end
|
||||
|
||||
context 'for Members API rate limit' do
|
||||
it 'renders the `members_delete_limit` field' do
|
||||
render
|
||||
|
||||
expect(rendered).to have_field('application_setting_members_delete_limit')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ RSpec.describe 'admin/sessions/new.html.haml' do
|
|||
expect(rendered).not_to have_content _('No authentication methods configured.')
|
||||
expect(rendered).to have_css('.omniauth-divider')
|
||||
expect(rendered).to have_content(_('or sign in with'))
|
||||
expect(rendered).to have_css('.omniauth-container')
|
||||
expect(rendered).to have_css('.js-oauth-login')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue