Commit 5a6f26cf authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 2ff257ec 5affad35
...@@ -223,6 +223,10 @@ export default { ...@@ -223,6 +223,10 @@ export default {
testAlertModal() { testAlertModal() {
return this.isFormDirty ? testAlertModalId : null; return this.isFormDirty ? testAlertModalId : null;
}, },
prometheusUrlInvalidFeedback() {
const { blankUrlError, invalidUrlError } = i18n.integrationFormSteps.prometheusFormUrl;
return this.integrationForm.apiUrl?.length ? invalidUrlError : blankUrlError;
},
}, },
watch: { watch: {
tabIndex(val) { tabIndex(val) {
...@@ -288,6 +292,9 @@ export default { ...@@ -288,6 +292,9 @@ export default {
if (this.isHttp) { if (this.isHttp) {
this.validationState.apiUrl = true; this.validationState.apiUrl = true;
this.validateName(); this.validateName();
if (!this.validationState.name) {
this.$refs.integrationName.$el.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
} else if (this.isPrometheus) { } else if (this.isPrometheus) {
this.validationState.name = true; this.validationState.name = true;
this.validateApiUrl(); this.validateApiUrl();
...@@ -300,6 +307,11 @@ export default { ...@@ -300,6 +307,11 @@ export default {
this.$emit('save-and-test-alert-payload', this.dataForSave, this.testAlertPayload); this.$emit('save-and-test-alert-payload', this.dataForSave, this.testAlertPayload);
}, },
submit(testAfterSubmit = false) { submit(testAfterSubmit = false) {
this.triggerValidation();
if (!this.isFormValid) {
return;
}
const event = this.currentIntegration ? 'update-integration' : 'create-new-integration'; const event = this.currentIntegration ? 'update-integration' : 'create-new-integration';
this.$emit(event, this.dataForSave, testAfterSubmit); this.$emit(event, this.dataForSave, testAfterSubmit);
}, },
...@@ -412,7 +424,6 @@ export default { ...@@ -412,7 +424,6 @@ export default {
:disabled="isSelectDisabled" :disabled="isSelectDisabled"
class="gl-max-w-full" class="gl-max-w-full"
:options="integrationTypesOptions" :options="integrationTypesOptions"
@change="triggerValidation"
/> />
<alert-settings-form-help-block <alert-settings-form-help-block
...@@ -439,6 +450,7 @@ export default { ...@@ -439,6 +450,7 @@ export default {
> >
<gl-form-input <gl-form-input
id="name-integration" id="name-integration"
ref="integrationName"
v-model="integrationForm.name" v-model="integrationForm.name"
type="text" type="text"
:placeholder="$options.i18n.integrationFormSteps.nameIntegration.placeholder" :placeholder="$options.i18n.integrationFormSteps.nameIntegration.placeholder"
...@@ -473,7 +485,7 @@ export default { ...@@ -473,7 +485,7 @@ export default {
class="gl-my-4" class="gl-my-4"
:label="$options.i18n.integrationFormSteps.prometheusFormUrl.label" :label="$options.i18n.integrationFormSteps.prometheusFormUrl.label"
label-for="api-url" label-for="api-url"
:invalid-feedback="$options.i18n.integrationFormSteps.prometheusFormUrl.error" :invalid-feedback="prometheusUrlInvalidFeedback"
:state="validationState.apiUrl" :state="validationState.apiUrl"
> >
<gl-form-input <gl-form-input
......
...@@ -70,7 +70,8 @@ export const i18n = { ...@@ -70,7 +70,8 @@ export const i18n = {
prometheusFormUrl: { prometheusFormUrl: {
label: s__('AlertSettings|Prometheus API base URL'), label: s__('AlertSettings|Prometheus API base URL'),
help: s__('AlertSettings|URL cannot be blank and must start with http or https'), help: s__('AlertSettings|URL cannot be blank and must start with http or https'),
error: s__('AlertSettings|URL is invalid.'), blankUrlError: __('URL cannot be blank'),
invalidUrlError: __('URL is invalid'),
}, },
restKeyInfo: { restKeyInfo: {
label: s__( label: s__(
......
---
title: Reduce number of SQL queries in Profiles::SlacksController#edit
merge_request: 57674
author:
type: performance
---
title: Update validation trigger flow on the alerts integration form
merge_request: 57697
author:
type: changed
...@@ -43,6 +43,18 @@ dedicated containers for each analysis. ...@@ -43,6 +43,18 @@ dedicated containers for each analysis.
SAST is pre-configured with a set of **default images** that are maintained by SAST is pre-configured with a set of **default images** that are maintained by
GitLab, but users can also integrate their own **custom images**. GitLab, but users can also integrate their own **custom images**.
## SAST analyzer features
For an analyzer to be considered Generally Available, it is expected to minimally
support the following features:
- [Customizable configuration](index.md#available-variables)
- [Customizable rulesets](index.md#customize-rulesets)
- [Scan projects](index.md#supported-languages-and-frameworks)
- [Multi-project support](index.md#multi-project-support)
- [Offline support](index.md#running-sast-in-an-offline-environment)
- [Emits JSON report format](index.md#reports-json-format)
## Official default analyzers ## Official default analyzers
Any custom change to the official analyzers can be achieved by using a Any custom change to the official analyzers can be achieved by using a
......
...@@ -6,7 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w ...@@ -6,7 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Related issues **(FREE)** # Related issues **(FREE)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/1797) in [GitLab Starter](https://about.gitlab.com/pricing/) 9.4. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/1797) in GitLab 9.4.
> - The simple "relates to" relationship [moved](https://gitlab.com/gitlab-org/gitlab/-/issues/212329) to [GitLab Free](https://about.gitlab.com/pricing/) in 13.4. > - The simple "relates to" relationship [moved](https://gitlab.com/gitlab-org/gitlab/-/issues/212329) to [GitLab Free](https://about.gitlab.com/pricing/) in 13.4.
Related issues are a bi-directional relationship between any two issues Related issues are a bi-directional relationship between any two issues
...@@ -16,8 +16,8 @@ and projects. ...@@ -16,8 +16,8 @@ and projects.
You can set any issue as: You can set any issue as:
- Related to another issue - Related to another issue
- Blocking another issue **(STARTER)** - Blocking another issue **(PREMIUM)**
- Blocked by another issue **(STARTER)** - Blocked by another issue **(PREMIUM)**
The relationship only shows up in the UI if the user can see both issues. The relationship only shows up in the UI if the user can see both issues.
...@@ -28,28 +28,30 @@ To manage related issues through our API, visit the [issue links API documentati ...@@ -28,28 +28,30 @@ To manage related issues through our API, visit the [issue links API documentati
## Adding a related issue ## Adding a related issue
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/2035) in [GitLab Starter](https://about.gitlab.com/pricing/) 12.8. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/2035) in GitLab 12.8.
> - [Improved](https://gitlab.com/gitlab-org/gitlab/-/issues/34239) to warn when attempting to close an issue that is blocked by others in [GitLab Starter](https://about.gitlab.com/pricing/) 13.0. > - [Improved](https://gitlab.com/gitlab-org/gitlab/-/issues/34239) to warn when attempting to close an issue that is blocked by others in GitLab 13.0.
> When you try to close an issue with open blockers, you see a warning that you can dismiss. > When you try to close an issue with open blockers, you see a warning that you can dismiss.
1. Relate one issue to another by clicking the related issues "+" button 1. Relate one issue to another by clicking the related issues "+" button
in the header of the related issue block. in the header of the related issue block.
1. Select the relationship the between the two issues. Either:
- **relates to**.
- **blocks**. **(PREMIUM)**
- **is blocked by**. **(PREMIUM)**
1. Input the issue reference number or paste in the full URL of the issue. 1. Input the issue reference number or paste in the full URL of the issue.
1. **(STARTER)** Select whether the current issue relates to, blocks, or is blocked by the issues being entered. ![Adding a related issue](img/related_issues_add_v12_8.png)
![Adding a related issue](img/related_issues_add_v12_8.png) Issues of the same project can be specified just by the reference number.
Issues from a different project require additional information like the
group and the project name. For example:
Issues of the same project can be specified just by the reference number. - The same project: `#44`
Issues from a different project require additional information like the - The same group: `project#44`
group and the project name. For example: - Different group: `group/project#44`
- same project: `#44` Valid references are added to a temporary list that you can review.
- same group: `project#44`
- different group: `group/project#44`
Valid references are added to a temporary list that you can review.
1. When you have added all the related issues, click **Add** to submit. 1. When you have added all the related issues, click **Add** to submit.
...@@ -60,7 +62,7 @@ them categorized so their relationships can be better understood visually. ...@@ -60,7 +62,7 @@ them categorized so their relationships can be better understood visually.
## Removing a related issue ## Removing a related issue
In the related issues block, click the "x" icon on the right-side of each issue In the related issues block, click the remove button (**{close}**) on the right-side of each issue
token that you wish to remove. token that you wish to remove.
Due to the bi-directional relationship, it no longer appears in either issue. Due to the bi-directional relationship, it no longer appears in either issue.
......
...@@ -10,7 +10,7 @@ class Profiles::SlacksController < Profiles::ApplicationController ...@@ -10,7 +10,7 @@ class Profiles::SlacksController < Profiles::ApplicationController
feature_category :users feature_category :users
def edit def edit
@projects = disabled_projects if current_user @projects = disabled_projects.select([:id, :name]) if current_user
end end
def slack_link def slack_link
......
...@@ -36,7 +36,7 @@ module EE ...@@ -36,7 +36,7 @@ module EE
def add_to_slack_data(projects) def add_to_slack_data(projects)
{ {
projects: projects, projects: projects.as_json(only: [:id, :name]),
sign_in_path: new_session_path(:user, redirect_to_referer: 'yes'), sign_in_path: new_session_path(:user, redirect_to_referer: 'yes'),
is_signed_in: current_user.present?, is_signed_in: current_user.present?,
slack_link_profile_slack_path: slack_link_profile_slack_path, slack_link_profile_slack_path: slack_link_profile_slack_path,
......
---
title: Allow SCIM deprovisioning for minimal access users
merge_request: 57178
author:
type: fixed
...@@ -69,7 +69,7 @@ module API ...@@ -69,7 +69,7 @@ module API
parsed_hash = parser.update_params parsed_hash = parser.update_params
if parser.deprovision_user? if parser.deprovision_user?
deprovision(identity) patch_deprovision(identity)
elsif reprovisionable?(identity) && parser.reprovision_user? elsif reprovisionable?(identity) && parser.reprovision_user?
reprovision(identity) reprovision(identity)
elsif parsed_hash[:extern_uid] elsif parsed_hash[:extern_uid]
...@@ -103,14 +103,28 @@ module API ...@@ -103,14 +103,28 @@ module API
group.scim_identities.with_extern_uid(extern_uid).first group.scim_identities.with_extern_uid(extern_uid).first
end end
def deprovision(identity) # delete_deprovision handles the response and returns either no_content! or a detailed error message.
::EE::Gitlab::Scim::DeprovisionService.new(identity).execute def delete_deprovision(identity)
service = ::EE::Gitlab::Scim::DeprovisionService.new(identity).execute
true if service.success?
rescue => e no_content!
logger.error(identity: identity, error: e.class.name, message: e.message, source: "#{__FILE__}:#{__LINE__}") else
logger.error(identity: identity, error: service.class.name, message: service.message, source: "#{__FILE__}:#{__LINE__}")
scim_error!(message: service.message)
end
end
false # The method that calls patch_deprovision, update_scim_user, expects a truthy/falsey value, and then continues to handle the request.
def patch_deprovision(identity)
service = ::EE::Gitlab::Scim::DeprovisionService.new(identity).execute
if service.success?
true
else
logger.error(identity: identity, error: service.class.name, message: service.message, source: "#{__FILE__}:#{__LINE__}")
false
end
end end
def reprovision(identity) def reprovision(identity)
...@@ -212,11 +226,7 @@ module API ...@@ -212,11 +226,7 @@ module API
scim_not_found!(message: "Resource #{params[:id]} not found") unless identity scim_not_found!(message: "Resource #{params[:id]} not found") unless identity
destroyed = deprovision(identity) delete_deprovision(identity)
scim_not_found!(message: "Resource #{params[:id]} not found") unless destroyed
no_content!
end end
end end
end end
......
...@@ -13,23 +13,29 @@ module EE ...@@ -13,23 +13,29 @@ module EE
end end
def execute def execute
return error(_("Could not remove %{user} from %{group}. User is not a group member.") % { user: user.name, group: group.name }) unless group_membership
return error(_("Could not remove %{user} from %{group}. Cannot remove last group owner.") % { user: user.name, group: group.name }) if group.last_owner?(user)
ScimIdentity.transaction do ScimIdentity.transaction do
identity.update!(active: false) identity.update!(active: false)
remove_group_access remove_group_access
end end
ServiceResponse.success(message: _("User %{user} was removed from %{group}.") % { user: user.name, group: group.name })
end end
private private
def remove_group_access def remove_group_access
return unless group_membership
return if group.last_owner?(user)
::Members::DestroyService.new(user).execute(group_membership) ::Members::DestroyService.new(user).execute(group_membership)
end end
def group_membership def group_membership
@group_membership ||= group.group_member(user) @group_membership ||= group.all_group_members.with_user(user).first
end
def error(message)
ServiceResponse.error(message: message)
end end
end end
end end
......
...@@ -3,9 +3,12 @@ ...@@ -3,9 +3,12 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe EE::ServicesHelper do RSpec.describe EE::ServicesHelper do
include Devise::Test::ControllerHelpers
let(:controller_class) do let(:controller_class) do
Class.new(ActionController::Base) do Class.new(ActionController::Base) do
include EE::ServicesHelper include EE::ServicesHelper
include ActionView::Helpers::AssetUrlHelper
def slack_auth_project_settings_slack_url(project) def slack_auth_project_settings_slack_url(project)
"http://some-path/project/1" "http://some-path/project/1"
...@@ -111,4 +114,54 @@ RSpec.describe EE::ServicesHelper do ...@@ -111,4 +114,54 @@ RSpec.describe EE::ServicesHelper do
is_expected.to include(:issues_show_path) is_expected.to include(:issues_show_path)
end end
end end
describe '#add_to_slack_data' do
let_it_be(:projects) { create_list(:project, 3) }
def relation
Project.id_in(projects.pluck(:id))
end
let(:request) do
double(:Request,
optional_port: nil,
path_parameters: {},
protocol: 'https',
routes: nil,
env: { 'warden' => warden },
engine_script_name: nil,
original_script_name: nil,
host: 'example.com')
end
before do
allow(subject).to receive(:request).and_return(request)
end
it 'includes the required keys' do
additions = Gitlab::Json.parse(subject.add_to_slack_data(relation))
expect(additions.keys).to match_array %w[
projects
sign_in_path
is_signed_in
slack_link_profile_slack_path
gitlab_for_slack_gif_path
gitlab_logo_path
slack_logo_path
docs_path
]
end
it 'does not suffer from N+1 performance issues' do
baseline = ActiveRecord::QueryRecorder.new { subject.add_to_slack_data(relation.limit(1)) }
expect do
subject.add_to_slack_data(relation)
end.not_to exceed_query_limit(baseline)
end
it 'serializes nil projects without error' do
expect(subject.add_to_slack_data(nil)).to include('"projects":null')
end
end
end end
...@@ -4,36 +4,89 @@ require 'spec_helper' ...@@ -4,36 +4,89 @@ require 'spec_helper'
RSpec.describe ::EE::Gitlab::Scim::DeprovisionService do RSpec.describe ::EE::Gitlab::Scim::DeprovisionService do
describe '#execute' do describe '#execute' do
let_it_be(:identity) { create(:scim_identity, active: true) } let(:identity) { create(:scim_identity, active: true) }
let_it_be(:group) { identity.group } let(:group) { identity.group }
let_it_be(:user) { identity.user } let(:user) { identity.user }
let(:service) { described_class.new(identity) } let(:service) { described_class.new(identity) }
it 'deactivates scim identity' do context 'when user is successfully removed' do
service.execute before do
create(:group_member, group: group, user: user, access_level: GroupMember::REPORTER)
end
expect(identity.active).to be false it 'deactivates scim identity' do
end expect { service.execute }.to change { identity.active }.from(true).to(false)
end
it 'removes group access' do
service.execute
it 'removes group access' do expect(group.all_group_members.pluck(:user_id)).not_to include(user.id)
create(:group_member, group: group, user: user, access_level: GroupMember::REPORTER) end
service.execute it 'returns the successful deprovision message' do
response = service.execute
expect(group.members.pluck(:user_id)).not_to include(user.id) expect(response.message).to include("User #{user.name} was removed from #{group.name}.")
end
end end
it 'does not remove the last owner' do context 'with minimal access role' do
create(:group_member, group: group, user: user, access_level: GroupMember::OWNER) before do
stub_licensed_features(minimal_access_role: true)
create(:group_member, group: group, user: user, access_level: ::Gitlab::Access::MINIMAL_ACCESS)
end
it 'deactivates scim identity' do
expect { service.execute }.to change { identity.active }.from(true).to(false)
end
service.execute it 'removes group access' do
service.execute
expect(identity.group.members.pluck(:user_id)).to include(user.id) expect(group.all_group_members.pluck(:user_id)).not_to include(user.id)
end
it 'returns the successful deprovision message' do
response = service.execute
expect(response.message).to include("User #{user.name} was removed from #{group.name}.")
end
end end
it 'does not change group membership when the user is not a member' do context 'when user is not successfully removed' do
expect { service.execute }.not_to change { group.members.count } context 'when user is the last owner' do
before do
create(:group_member, group: group, user: user, access_level: GroupMember::OWNER)
end
it 'does not remove the last owner' do
service.execute
expect(identity.group.members.pluck(:user_id)).to include(user.id)
end
it 'returns the last group owner error' do
response = service.execute
expect(response.error?).to be true
expect(response.errors).to include("Could not remove #{user.name} from #{group.name}. Cannot remove last group owner.")
end
end
context 'when user is not a group member' do
it 'does not change group membership when the user is not a member' do
expect { service.execute }.not_to change { group.members.count }
end
it 'returns the group membership error' do
response = service.execute
expect(response.error?).to be true
expect(response.errors).to include("Could not remove #{user.name} from #{group.name}. User is not a group member.")
end
end
end end
end end
end end
...@@ -38,5 +38,16 @@ RSpec.describe ::EE::Gitlab::Scim::ReprovisionService do ...@@ -38,5 +38,16 @@ RSpec.describe ::EE::Gitlab::Scim::ReprovisionService do
expect { service.execute }.not_to change { group.members.count } expect { service.execute }.not_to change { group.members.count }
end end
context 'with minimal access user' do
before do
stub_licensed_features(minimal_access_role: true)
create(:group_member, group: group, user: user, access_level: ::Gitlab::Access::MINIMAL_ACCESS)
end
it 'does not change group membership when the user is already a member' do
expect { service.execute }.not_to change { group.all_group_members.count }
end
end
end end
end end
...@@ -12,7 +12,7 @@ RSpec.describe API::Scim do ...@@ -12,7 +12,7 @@ RSpec.describe API::Scim do
before do before do
stub_licensed_features(group_allowed_email_domains: true, group_saml: true) stub_licensed_features(group_allowed_email_domains: true, group_saml: true)
group.add_owner(user) group.add_developer(user)
create(:saml_provider, group: group, default_membership_role: Gitlab::Access::DEVELOPER) create(:saml_provider, group: group, default_membership_role: Gitlab::Access::DEVELOPER)
end end
...@@ -337,6 +337,27 @@ RSpec.describe API::Scim do ...@@ -337,6 +337,27 @@ RSpec.describe API::Scim do
expect(identity.reload.active).to be false expect(identity.reload.active).to be false
end end
context 'with owner' do
params = { Operations: [{ 'op': 'Replace', 'path': 'active', 'value': 'False' }] }.to_query
before do
group.add_owner(user)
call_patch_api(params)
end
it 'responds with 412' do
expect(response).to have_gitlab_http_status(:precondition_failed)
end
it 'returns the last group owner error' do
expect(response.body).to include("Error updating #{user.name}")
end
it 'does not deactivate the identity' do
expect(identity.reload.active).to be true
end
end
context 'Reprovision user' do context 'Reprovision user' do
let_it_be(:params) { { Operations: [{ 'op': 'Replace', 'path': 'active', 'value': 'true' }] }.to_query } let_it_be(:params) { { Operations: [{ 'op': 'Replace', 'path': 'active', 'value': 'true' }] }.to_query }
...@@ -450,6 +471,25 @@ RSpec.describe API::Scim do ...@@ -450,6 +471,25 @@ RSpec.describe API::Scim do
end end
end end
context 'with owner' do
before do
group.add_owner(user)
delete scim_api("scim/v2/groups/#{group.full_path}/Users/#{identity.extern_uid}")
end
it 'responds with 412' do
expect(response).to have_gitlab_http_status(:precondition_failed)
end
it 'returns the last group owner error' do
expect(response.body).to include("Could not remove #{user.name} from #{group.name}. Cannot remove last group owner.")
end
it 'does not deactivate the identity' do
expect(identity.reload.active).to be true
end
end
it 'responds with 404 if there is no user' do it 'responds with 404 if there is no user' do
delete scim_api("scim/v2/groups/#{group.full_path}/Users/123") delete scim_api("scim/v2/groups/#{group.full_path}/Users/123")
......
...@@ -3005,9 +3005,6 @@ msgstr "" ...@@ -3005,9 +3005,6 @@ msgstr ""
msgid "AlertSettings|URL cannot be blank and must start with http or https" msgid "AlertSettings|URL cannot be blank and must start with http or https"
msgstr "" msgstr ""
msgid "AlertSettings|URL is invalid."
msgstr ""
msgid "AlertSettings|Utilize the URL and authorization key below to authorize Prometheus to send alerts to GitLab. Review the Prometheus documentation to learn where to add these details, and the %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint." msgid "AlertSettings|Utilize the URL and authorization key below to authorize Prometheus to send alerts to GitLab. Review the Prometheus documentation to learn where to add these details, and the %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint."
msgstr "" msgstr ""
...@@ -8726,6 +8723,12 @@ msgstr "" ...@@ -8726,6 +8723,12 @@ msgstr ""
msgid "Could not load usage counts. Please refresh the page to try again." msgid "Could not load usage counts. Please refresh the page to try again."
msgstr "" msgstr ""
msgid "Could not remove %{user} from %{group}. Cannot remove last group owner."
msgstr ""
msgid "Could not remove %{user} from %{group}. User is not a group member."
msgstr ""
msgid "Could not remove the trigger." msgid "Could not remove the trigger."
msgstr "" msgstr ""
...@@ -32403,6 +32406,12 @@ msgstr "" ...@@ -32403,6 +32406,12 @@ msgstr ""
msgid "URL" msgid "URL"
msgstr "" msgstr ""
msgid "URL cannot be blank"
msgstr ""
msgid "URL is invalid"
msgstr ""
msgid "URL is required" msgid "URL is required"
msgstr "" msgstr ""
...@@ -33165,6 +33174,9 @@ msgstr "" ...@@ -33165,6 +33174,9 @@ msgstr ""
msgid "User %{username} was successfully removed." msgid "User %{username} was successfully removed."
msgstr "" msgstr ""
msgid "User %{user} was removed from %{group}."
msgstr ""
msgid "User ID" msgid "User ID"
msgstr "" msgstr ""
......
...@@ -10,6 +10,9 @@ import alertFields from '../mocks/alert_fields.json'; ...@@ -10,6 +10,9 @@ import alertFields from '../mocks/alert_fields.json';
import parsedMapping from '../mocks/parsed_mapping.json'; import parsedMapping from '../mocks/parsed_mapping.json';
import { defaultAlertSettingsConfig } from './util'; import { defaultAlertSettingsConfig } from './util';
const scrollIntoViewMock = jest.fn();
HTMLElement.prototype.scrollIntoView = scrollIntoViewMock;
describe('AlertsSettingsForm', () => { describe('AlertsSettingsForm', () => {
let wrapper; let wrapper;
const mockToastShow = jest.fn(); const mockToastShow = jest.fn();
...@@ -410,33 +413,35 @@ describe('AlertsSettingsForm', () => { ...@@ -410,33 +413,35 @@ describe('AlertsSettingsForm', () => {
createComponent(); createComponent();
}); });
it('should not be able to submit when no integration type is selected', () => { it('should not be able to submit when no integration type is selected', async () => {
selectOptionAtIndex(0); await selectOptionAtIndex(0);
expect(findSubmitButton().attributes('disabled')).toBe('disabled'); expect(findSubmitButton().attributes('disabled')).toBe('disabled');
}); });
it('should not be able to submit when HTTP integration form is invalid', () => { it('should not be able to submit when HTTP integration form is invalid', async () => {
selectOptionAtIndex(1); await selectOptionAtIndex(1);
await findFormFields().at(0).vm.$emit('input', '');
expect(findSubmitButton().attributes('disabled')).toBe('disabled'); expect(findSubmitButton().attributes('disabled')).toBe('disabled');
}); });
it('should be able to submit when HTTP integration form is valid', async () => { it('should be able to submit when HTTP integration form is valid', async () => {
await selectOptionAtIndex(1); await selectOptionAtIndex(1);
await findFormFields().at(0).setValue('Name'); await findFormFields().at(0).vm.$emit('input', 'Name');
expect(findSubmitButton().attributes('disabled')).toBe(undefined); expect(findSubmitButton().attributes('disabled')).toBe(undefined);
}); });
it('should not be able to submit when Prometheus integration form is invalid', () => { it('should not be able to submit when Prometheus integration form is invalid', async () => {
selectOptionAtIndex(2); await selectOptionAtIndex(2);
await findFormFields().at(0).vm.$emit('input', '');
expect(findSubmitButton().attributes('disabled')).toBe('disabled'); expect(findSubmitButton().attributes('disabled')).toBe('disabled');
}); });
it('should be able to submit when Prometheus integration form is valid', async () => { it('should be able to submit when Prometheus integration form is valid', async () => {
await selectOptionAtIndex(2); await selectOptionAtIndex(2);
await findFormFields().at(0).setValue('http://valid.url'); await findFormFields().at(0).vm.$emit('input', 'http://valid.url');
expect(findSubmitButton().attributes('disabled')).toBe(undefined); expect(findSubmitButton().attributes('disabled')).toBe(undefined);
}); });
...@@ -445,7 +450,7 @@ describe('AlertsSettingsForm', () => { ...@@ -445,7 +450,7 @@ describe('AlertsSettingsForm', () => {
currentIntegration: { type: typeSet.http, name: 'Existing integration' }, currentIntegration: { type: typeSet.http, name: 'Existing integration' },
}); });
await nextTick(); await nextTick();
await findFormFields().at(0).setValue('Updated name'); await findFormFields().at(0).vm.$emit('input', 'Updated name');
expect(findSubmitButton().attributes('disabled')).toBe(undefined); expect(findSubmitButton().attributes('disabled')).toBe(undefined);
}); });
...@@ -458,5 +463,20 @@ describe('AlertsSettingsForm', () => { ...@@ -458,5 +463,20 @@ describe('AlertsSettingsForm', () => {
expect(findSubmitButton().attributes('disabled')).toBe('disabled'); expect(findSubmitButton().attributes('disabled')).toBe('disabled');
}); });
it('should disable submit button after click on validation failure', async () => {
await selectOptionAtIndex(1);
findSubmitButton().trigger('click');
await nextTick();
expect(findSubmitButton().attributes('disabled')).toBe('disabled');
});
it('should scroll to invalid field on validation failure', async () => {
await selectOptionAtIndex(1);
findSubmitButton().trigger('click');
expect(scrollIntoViewMock).toHaveBeenCalledWith({ behavior: 'smooth', block: 'center' });
});
}); });
}); });
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