Commit efc8d99d authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 2738e865 d1efd8d3
...@@ -29,7 +29,7 @@ class CopyCodeButton extends HTMLElement { ...@@ -29,7 +29,7 @@ class CopyCodeButton extends HTMLElement {
} }
function addCodeButton() { function addCodeButton() {
[...document.querySelectorAll('pre.code.js-syntax-highlight')] [...document.querySelectorAll('pre.code.js-syntax-highlight:not(.content-editor-code-block)')]
.filter((el) => el.attr('lang') !== 'mermaid') .filter((el) => el.attr('lang') !== 'mermaid')
.filter((el) => !el.closest('.js-markdown-code')) .filter((el) => !el.closest('.js-markdown-code'))
.forEach((el) => { .forEach((el) => {
......
...@@ -20,7 +20,7 @@ export default { ...@@ -20,7 +20,7 @@ export default {
}; };
</script> </script>
<template> <template>
<node-view-wrapper class="gl-relative code highlight" as="pre"> <node-view-wrapper class="content-editor-code-block gl-relative code highlight" as="pre">
<span <span
data-testid="frontmatter-label" data-testid="frontmatter-label"
class="gl-absolute gl-top-0 gl-right-3" class="gl-absolute gl-top-0 gl-right-3"
......
...@@ -19,7 +19,14 @@ export default CodeBlockLowlight.extend({ ...@@ -19,7 +19,14 @@ export default CodeBlockLowlight.extend({
}; };
}, },
renderHTML({ HTMLAttributes }) { renderHTML({ HTMLAttributes }) {
return ['div', ['pre', HTMLAttributes, ['code', {}, 0]]]; return [
'pre',
{
...HTMLAttributes,
class: `content-editor-code-block ${HTMLAttributes.class}`,
},
['code', {}, 0],
];
}, },
}).configure({ }).configure({
lowlight, lowlight,
......
...@@ -357,7 +357,9 @@ ...@@ -357,7 +357,9 @@
} }
> li { > li {
.badge.badge-pill { // TODO: Remove this block once all sidebar badges use gl_badge_tag
// https://gitlab.com/gitlab-org/gitlab/-/issues/350061
.badge.badge-pill:not(.gl-badge) {
@include gl-rounded-lg; @include gl-rounded-lg;
@include gl-py-1; @include gl-py-1;
@include gl-px-3; @include gl-px-3;
...@@ -370,7 +372,9 @@ ...@@ -370,7 +372,9 @@
display: block; display: block;
} }
.badge.badge-pill { // TODO: Remove this block once all sidebar badges use gl_badge_tag
// https://gitlab.com/gitlab-org/gitlab/-/issues/350061
.badge.badge-pill:not(.gl-badge) {
@include gl-font-weight-normal; @include gl-font-weight-normal;
color: $blue-700; color: $blue-700;
} }
......
...@@ -314,6 +314,10 @@ h1 { ...@@ -314,6 +314,10 @@ h1 {
padding-left: 0.6em; padding-left: 0.6em;
border-radius: 10rem; border-radius: 10rem;
} }
.badge-info {
color: #fff;
background-color: #428fdc;
}
.bg-transparent { .bg-transparent {
background-color: transparent !important; background-color: transparent !important;
} }
...@@ -372,6 +376,24 @@ h1 { ...@@ -372,6 +376,24 @@ h1 {
padding-left: 0.5rem; padding-left: 0.5rem;
padding-right: 0.5rem; padding-right: 0.5rem;
} }
.gl-badge.sm {
padding-top: 0;
padding-bottom: 0;
}
.gl-badge.badge-info {
background-color: #064787;
color: #9dc7f1;
}
a.gl-badge.badge-info.active,
a.gl-badge.badge-info:active {
color: #e9f3fc;
background-color: #0b5cad;
}
a.gl-badge.badge-info:active {
box-shadow: inset 0 0 0 1px rgba(51, 51, 51, 0.8),
0 0 0 1px rgba(51, 51, 51, 0.4), 0 0 0 4px rgba(66, 143, 220, 0.48);
outline: none;
}
.gl-button .gl-badge { .gl-button .gl-badge {
top: 0; top: 0;
} }
...@@ -1367,7 +1389,7 @@ input { ...@@ -1367,7 +1389,7 @@ input {
border-radius: 4px; border-radius: 4px;
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.08); box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.08);
} }
.sidebar-top-level-items > li .badge.badge-pill { .sidebar-top-level-items > li .badge.badge-pill:not(.gl-badge) {
border-radius: 0.5rem; border-radius: 0.5rem;
padding-top: 0.125rem; padding-top: 0.125rem;
padding-bottom: 0.125rem; padding-bottom: 0.125rem;
...@@ -1381,7 +1403,7 @@ input { ...@@ -1381,7 +1403,7 @@ input {
.sidebar-sub-level-items:not(.is-fly-out-only) { .sidebar-sub-level-items:not(.is-fly-out-only) {
display: block; display: block;
} }
.sidebar-top-level-items > li.active .badge.badge-pill { .sidebar-top-level-items > li.active .badge.badge-pill:not(.gl-badge) {
font-weight: 400; font-weight: 400;
color: #9dc7f1; color: #9dc7f1;
} }
......
...@@ -295,6 +295,10 @@ h1 { ...@@ -295,6 +295,10 @@ h1 {
padding-left: 0.6em; padding-left: 0.6em;
border-radius: 10rem; border-radius: 10rem;
} }
.badge-info {
color: #fff;
background-color: #1f75cb;
}
.bg-transparent { .bg-transparent {
background-color: transparent !important; background-color: transparent !important;
} }
...@@ -353,6 +357,24 @@ h1 { ...@@ -353,6 +357,24 @@ h1 {
padding-left: 0.5rem; padding-left: 0.5rem;
padding-right: 0.5rem; padding-right: 0.5rem;
} }
.gl-badge.sm {
padding-top: 0;
padding-bottom: 0;
}
.gl-badge.badge-info {
background-color: #cbe2f9;
color: #0b5cad;
}
a.gl-badge.badge-info.active,
a.gl-badge.badge-info:active {
color: #033464;
background-color: #9dc7f1;
}
a.gl-badge.badge-info:active {
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.8),
0 0 0 1px rgba(255, 255, 255, 0.4), 0 0 0 4px rgba(31, 117, 203, 0.48);
outline: none;
}
.gl-button .gl-badge { .gl-button .gl-badge {
top: 0; top: 0;
} }
...@@ -1348,7 +1370,7 @@ input { ...@@ -1348,7 +1370,7 @@ input {
border-radius: 4px; border-radius: 4px;
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.08); box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.08);
} }
.sidebar-top-level-items > li .badge.badge-pill { .sidebar-top-level-items > li .badge.badge-pill:not(.gl-badge) {
border-radius: 0.5rem; border-radius: 0.5rem;
padding-top: 0.125rem; padding-top: 0.125rem;
padding-bottom: 0.125rem; padding-bottom: 0.125rem;
...@@ -1362,7 +1384,7 @@ input { ...@@ -1362,7 +1384,7 @@ input {
.sidebar-sub-level-items:not(.is-fly-out-only) { .sidebar-sub-level-items:not(.is-fly-out-only) {
display: block; display: block;
} }
.sidebar-top-level-items > li.active .badge.badge-pill { .sidebar-top-level-items > li.active .badge.badge-pill:not(.gl-badge) {
font-weight: 400; font-weight: 400;
color: #0b5cad; color: #0b5cad;
} }
......
...@@ -210,6 +210,8 @@ module Ci ...@@ -210,6 +210,8 @@ module Ci
validates :config, json_schema: { filename: 'ci_runner_config' } validates :config, json_schema: { filename: 'ci_runner_config' }
validates :maintainer_note, length: { maximum: 255 }
# Searches for runners matching the given query. # Searches for runners matching the given query.
# #
# This method uses ILIKE on PostgreSQL for the description field and performs a full match on tokens. # This method uses ILIKE on PostgreSQL for the description field and performs a full match on tokens.
......
...@@ -24,6 +24,7 @@ class CustomerRelations::Contact < ApplicationRecord ...@@ -24,6 +24,7 @@ class CustomerRelations::Contact < ApplicationRecord
validates :email, length: { maximum: 255 } validates :email, length: { maximum: 255 }
validates :description, length: { maximum: 1024 } validates :description, length: { maximum: 1024 }
validate :validate_email_format validate :validate_email_format
validate :unique_email_for_group_hierarchy
def self.find_ids_by_emails(group_id, emails) def self.find_ids_by_emails(group_id, emails)
raise ArgumentError, "Cannot lookup more than #{MAX_PLUCK} emails" if emails.length > MAX_PLUCK raise ArgumentError, "Cannot lookup more than #{MAX_PLUCK} emails" if emails.length > MAX_PLUCK
...@@ -39,4 +40,14 @@ class CustomerRelations::Contact < ApplicationRecord ...@@ -39,4 +40,14 @@ class CustomerRelations::Contact < ApplicationRecord
self.errors.add(:email, I18n.t(:invalid, scope: 'valid_email.validations.email')) unless ValidateEmail.valid?(self.email) self.errors.add(:email, I18n.t(:invalid, scope: 'valid_email.validations.email')) unless ValidateEmail.valid?(self.email)
end end
def unique_email_for_group_hierarchy
return unless group
return unless email
duplicate_email_exists = CustomerRelations::Contact
.where(group_id: group.self_and_hierarchy.pluck(:id), email: email)
.where.not(id: id).exists?
self.errors.add(:email, _('contact with same email already exists in group hierarchy')) if duplicate_email_exists
end
end end
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
%span.nav-item-name{ **sidebar_menu.title_html_options } %span.nav-item-name{ **sidebar_menu.title_html_options }
= sidebar_menu.title = sidebar_menu.title
- if sidebar_menu.has_pill? - if sidebar_menu.has_pill?
%span.badge.badge-pill.count{ **sidebar_menu.pill_html_options } = gl_badge_tag({ variant: :info, size: :sm }, { class: "count #{sidebar_menu.pill_html_options[:class]}" }) do
= number_with_delimiter(sidebar_menu.pill_count) = number_with_delimiter(sidebar_menu.pill_count)
= render partial: 'shared/nav/sidebar_submenu', locals: { sidebar_menu: sidebar_menu } = render partial: 'shared/nav/sidebar_submenu', locals: { sidebar_menu: sidebar_menu }
# frozen_string_literal: true
class AddMaintainerNoteToCiRunners < Gitlab::Database::Migration[1.0]
enable_lock_retries!
def change
# rubocop:disable Migration/AddLimitToTextColumns
# limit is added in 20220111095007_add_text_limit_to_ci_runners_maintainer_note.rb
add_column :ci_runners, :maintainer_note, :text
# rubocop:enable Migration/AddLimitToTextColumns
end
end
# frozen_string_literal: true
class AddTextLimitToCiRunnersMaintainerNote < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
def up
add_text_limit :ci_runners, :maintainer_note, 255
end
def down
remove_text_limit :ci_runners, :maintainer_note
end
end
0bc00cc8a5fa7cafa665ec113a4d0d1384c5acde37dfdf53ab1f5a2e1d6acb02
\ No newline at end of file
65259b0e71c1883b81c61354325cfeeade0013b55af8901bf707f2a94ee3a46a
\ No newline at end of file
...@@ -12177,7 +12177,9 @@ CREATE TABLE ci_runners ( ...@@ -12177,7 +12177,9 @@ CREATE TABLE ci_runners (
public_projects_minutes_cost_factor double precision DEFAULT 0.0 NOT NULL, public_projects_minutes_cost_factor double precision DEFAULT 0.0 NOT NULL,
private_projects_minutes_cost_factor double precision DEFAULT 1.0 NOT NULL, private_projects_minutes_cost_factor double precision DEFAULT 1.0 NOT NULL,
config jsonb DEFAULT '{}'::jsonb NOT NULL, config jsonb DEFAULT '{}'::jsonb NOT NULL,
executor_type smallint executor_type smallint,
maintainer_note text,
CONSTRAINT check_56f5ea8804 CHECK ((char_length(maintainer_note) <= 255))
); );
CREATE SEQUENCE ci_runners_id_seq CREATE SEQUENCE ci_runners_id_seq
...@@ -9,7 +9,13 @@ info: To determine the technical writer assigned to the Stage/Group associated w ...@@ -9,7 +9,13 @@ info: To determine the technical writer assigned to the Stage/Group associated w
GitLab provides a variety of tools to help operate and maintain GitLab provides a variety of tools to help operate and maintain
your applications. your applications.
## Measure reliability and stability with metrics ## Measure reliability and stability with metrics (DEPRECATED)
> [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346485) in GitLab 14.7.
WARNING:
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346485)
for use in GitLab 14.7, and is planned for removal in GitLab 15.0.
Metrics help you understand the health and performance of your infrastructure, Metrics help you understand the health and performance of your infrastructure,
applications, and systems by providing insights into your application's reliability, applications, and systems by providing insights into your application's reliability,
...@@ -30,13 +36,13 @@ performance degrades, and manage those alerts - all within GitLab. ...@@ -30,13 +36,13 @@ performance degrades, and manage those alerts - all within GitLab.
GitLab helps reduce alert fatigue for IT responders by providing tools to identify GitLab helps reduce alert fatigue for IT responders by providing tools to identify
issues across multiple systems and aggregate alerts in a centralized place. Your issues across multiple systems and aggregate alerts in a centralized place. Your
team needs a single, central interface where they can easily investigate alerts team needs a single, central interface where they can easily investigate alerts
using metrics and logs, and promote the critical alerts to incidents. and promote the critical alerts to incidents.
Are your alerts too noisy? Alerts configured on GitLab metrics can configured Are your alerts too noisy? Alerts configured on GitLab metrics can configured
and fine-tuned in GitLab immediately following a fire-fight. and fine-tuned in GitLab immediately following a fire-fight.
- [Manage alerts and incidents](incident_management/index.md) in GitLab. - [Manage alerts and incidents](incident_management/index.md) in GitLab.
- [Configure alerts for metrics](metrics/alerts.md) in GitLab. - [Configure alerts for metrics](metrics/alerts.md) in GitLab. (DEPRECATED)
- Create a [status page](incident_management/status_page.md) - Create a [status page](incident_management/status_page.md)
to communicate efficiently to your users during an incident. to communicate efficiently to your users during an incident.
...@@ -51,7 +57,13 @@ and the work required to fix them - all without leaving GitLab. ...@@ -51,7 +57,13 @@ and the work required to fix them - all without leaving GitLab.
- Discover and view errors generated by your applications with - Discover and view errors generated by your applications with
[Error Tracking](error_tracking.md). [Error Tracking](error_tracking.md).
## Trace application health and performance ## Trace application health and performance (DEPRECATED)
> [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346485) in GitLab 14.7.
WARNING:
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346485)
for use in GitLab 14.7, and is planned for removal in GitLab 15.0.
Application tracing in GitLab is a way to measure an application's performance and Application tracing in GitLab is a way to measure an application's performance and
health while it's running. After configuring your application to enable tracing, you health while it's running. After configuring your application to enable tracing, you
...@@ -65,7 +77,13 @@ microservices-based distributed systems - and displays results within GitLab. ...@@ -65,7 +77,13 @@ microservices-based distributed systems - and displays results within GitLab.
- [Trace the performance and health](tracing.md) of a deployed application. - [Trace the performance and health](tracing.md) of a deployed application.
## Aggregate and store logs ## Aggregate and store logs (DEPRECATED)
> [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346485) in GitLab 14.7.
WARNING:
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346485)
for use in GitLab 14.7, and is planned for removal in GitLab 15.0.
Developers need to troubleshoot application changes in development, and incident Developers need to troubleshoot application changes in development, and incident
responders need aggregated, real-time logs when troubleshooting problems with responders need aggregated, real-time logs when troubleshooting problems with
......
...@@ -314,6 +314,10 @@ h1 { ...@@ -314,6 +314,10 @@ h1 {
padding-left: 0.6em; padding-left: 0.6em;
border-radius: 10rem; border-radius: 10rem;
} }
.badge-info {
color: #fff;
background-color: #428fdc;
}
.bg-transparent { .bg-transparent {
background-color: transparent !important; background-color: transparent !important;
} }
...@@ -372,6 +376,24 @@ h1 { ...@@ -372,6 +376,24 @@ h1 {
padding-left: 0.5rem; padding-left: 0.5rem;
padding-right: 0.5rem; padding-right: 0.5rem;
} }
.gl-badge.sm {
padding-top: 0;
padding-bottom: 0;
}
.gl-badge.badge-info {
background-color: #064787;
color: #9dc7f1;
}
a.gl-badge.badge-info.active,
a.gl-badge.badge-info:active {
color: #e9f3fc;
background-color: #0b5cad;
}
a.gl-badge.badge-info:active {
box-shadow: inset 0 0 0 1px rgba(51, 51, 51, 0.8),
0 0 0 1px rgba(51, 51, 51, 0.4), 0 0 0 4px rgba(66, 143, 220, 0.48);
outline: none;
}
.gl-button .gl-badge { .gl-button .gl-badge {
top: 0; top: 0;
} }
...@@ -1367,7 +1389,7 @@ input { ...@@ -1367,7 +1389,7 @@ input {
border-radius: 4px; border-radius: 4px;
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.08); box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.08);
} }
.sidebar-top-level-items > li .badge.badge-pill { .sidebar-top-level-items > li .badge.badge-pill:not(.gl-badge) {
border-radius: 0.5rem; border-radius: 0.5rem;
padding-top: 0.125rem; padding-top: 0.125rem;
padding-bottom: 0.125rem; padding-bottom: 0.125rem;
...@@ -1381,7 +1403,7 @@ input { ...@@ -1381,7 +1403,7 @@ input {
.sidebar-sub-level-items:not(.is-fly-out-only) { .sidebar-sub-level-items:not(.is-fly-out-only) {
display: block; display: block;
} }
.sidebar-top-level-items > li.active .badge.badge-pill { .sidebar-top-level-items > li.active .badge.badge-pill:not(.gl-badge) {
font-weight: 400; font-weight: 400;
color: #9dc7f1; color: #9dc7f1;
} }
......
...@@ -295,6 +295,10 @@ h1 { ...@@ -295,6 +295,10 @@ h1 {
padding-left: 0.6em; padding-left: 0.6em;
border-radius: 10rem; border-radius: 10rem;
} }
.badge-info {
color: #fff;
background-color: #1f75cb;
}
.bg-transparent { .bg-transparent {
background-color: transparent !important; background-color: transparent !important;
} }
...@@ -353,6 +357,24 @@ h1 { ...@@ -353,6 +357,24 @@ h1 {
padding-left: 0.5rem; padding-left: 0.5rem;
padding-right: 0.5rem; padding-right: 0.5rem;
} }
.gl-badge.sm {
padding-top: 0;
padding-bottom: 0;
}
.gl-badge.badge-info {
background-color: #cbe2f9;
color: #0b5cad;
}
a.gl-badge.badge-info.active,
a.gl-badge.badge-info:active {
color: #033464;
background-color: #9dc7f1;
}
a.gl-badge.badge-info:active {
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.8),
0 0 0 1px rgba(255, 255, 255, 0.4), 0 0 0 4px rgba(31, 117, 203, 0.48);
outline: none;
}
.gl-button .gl-badge { .gl-button .gl-badge {
top: 0; top: 0;
} }
...@@ -1348,7 +1370,7 @@ input { ...@@ -1348,7 +1370,7 @@ input {
border-radius: 4px; border-radius: 4px;
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.08); box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.08);
} }
.sidebar-top-level-items > li .badge.badge-pill { .sidebar-top-level-items > li .badge.badge-pill:not(.gl-badge) {
border-radius: 0.5rem; border-radius: 0.5rem;
padding-top: 0.125rem; padding-top: 0.125rem;
padding-bottom: 0.125rem; padding-bottom: 0.125rem;
...@@ -1362,7 +1384,7 @@ input { ...@@ -1362,7 +1384,7 @@ input {
.sidebar-sub-level-items:not(.is-fly-out-only) { .sidebar-sub-level-items:not(.is-fly-out-only) {
display: block; display: block;
} }
.sidebar-top-level-items > li.active .badge.badge-pill { .sidebar-top-level-items > li.active .badge.badge-pill:not(.gl-badge) {
font-weight: 400; font-weight: 400;
color: #0b5cad; color: #0b5cad;
} }
......
...@@ -14,7 +14,7 @@ dast_scanner_profiles_builds: ...@@ -14,7 +14,7 @@ dast_scanner_profiles_builds:
- table: ci_builds - table: ci_builds
column: ci_build_id column: ci_build_id
on_delete: async_delete on_delete: async_delete
dast_scanner_profiles_builds: dast_site_profiles_builds:
- table: ci_builds - table: ci_builds
column: ci_build_id column: ci_build_id
on_delete: async_delete on_delete: async_delete
...@@ -115,3 +115,11 @@ ci_minutes_additional_packs: ...@@ -115,3 +115,11 @@ ci_minutes_additional_packs:
- table: namespaces - table: namespaces
column: namespace_id column: namespace_id
on_delete: async_delete on_delete: async_delete
requirements_management_test_reports:
- table: ci_builds
column: build_id
on_delete: async_nullify
security_scans:
- table: ci_builds
column: build_id
on_delete: async_delete
...@@ -41916,6 +41916,9 @@ msgstr "" ...@@ -41916,6 +41916,9 @@ msgstr ""
msgid "compliance violation has already been recorded" msgid "compliance violation has already been recorded"
msgstr "" msgstr ""
msgid "contact with same email already exists in group hierarchy"
msgstr ""
msgid "container_name can contain only lowercase letters, digits, '-', and '.' and must start and end with an alphanumeric character" msgid "container_name can contain only lowercase letters, digits, '-', and '.' and must start and end with an alphanumeric character"
msgstr "" msgstr ""
......
...@@ -26,6 +26,11 @@ describe('content/components/wrappers/frontmatter', () => { ...@@ -26,6 +26,11 @@ describe('content/components/wrappers/frontmatter', () => {
expect(wrapper.findComponent(NodeViewWrapper).classes()).toContain('gl-relative'); expect(wrapper.findComponent(NodeViewWrapper).classes()).toContain('gl-relative');
}); });
it('adds content-editor-code-block class to the pre element', () => {
createWrapper();
expect(wrapper.findComponent(NodeViewWrapper).classes()).toContain('content-editor-code-block');
});
it('renders a node-view-content as a code element', () => { it('renders a node-view-content as a code element', () => {
createWrapper(); createWrapper();
......
...@@ -36,4 +36,10 @@ describe('content_editor/extensions/code_block_highlight', () => { ...@@ -36,4 +36,10 @@ describe('content_editor/extensions/code_block_highlight', () => {
expect(editorHtmlOutput.classList.toString()).toContain('code highlight js-syntax-highlight'); expect(editorHtmlOutput.classList.toString()).toContain('code highlight js-syntax-highlight');
}); });
it('adds content-editor-code-block class to the pre element', () => {
const editorHtmlOutput = parseHTML(tiptapEditor.getHTML()).querySelector('pre');
expect(editorHtmlOutput.classList.toString()).toContain('content-editor-code-block');
});
}); });
...@@ -26,6 +26,38 @@ RSpec.describe CustomerRelations::Contact, type: :model do ...@@ -26,6 +26,38 @@ RSpec.describe CustomerRelations::Contact, type: :model do
it_behaves_like 'an object with RFC3696 compliant email-formatted attributes', :email it_behaves_like 'an object with RFC3696 compliant email-formatted attributes', :email
end end
describe '#unique_email_for_group_hierarchy' do
let_it_be(:parent) { create(:group) }
let_it_be(:group) { create(:group, parent: parent) }
let_it_be(:subgroup) { create(:group, parent: group) }
let_it_be(:existing_contact) { create(:contact, group: group) }
context 'with unique email for group hierarchy' do
subject { build(:contact, group: group) }
it { is_expected.to be_valid }
end
context 'with duplicate email in group' do
subject { build(:contact, email: existing_contact.email, group: group) }
it { is_expected.to be_invalid }
end
context 'with duplicate email in parent group' do
subject { build(:contact, email: existing_contact.email, group: subgroup) }
it { is_expected.to be_invalid }
end
context 'with duplicate email in subgroup' do
subject { build(:contact, email: existing_contact.email, group: parent) }
it { is_expected.to be_invalid }
end
end
describe '#before_validation' do describe '#before_validation' do
it 'strips leading and trailing whitespace' do it 'strips leading and trailing whitespace' do
contact = described_class.new(first_name: ' First ', last_name: ' Last ', phone: ' 123456 ') contact = described_class.new(first_name: ' First ', last_name: ' Last ', phone: ' 123456 ')
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment