Commit 01f4230e authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents c43c891e f869a810
<script> <script>
import { GlButton, GlModalDirective, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui'; import { GlButton, GlModalDirective, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
import axios from 'axios';
import * as Sentry from '@sentry/browser'; import * as Sentry from '@sentry/browser';
import { mapState, mapActions, mapGetters } from 'vuex'; import { mapState, mapActions, mapGetters } from 'vuex';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
...@@ -10,6 +11,7 @@ import { ...@@ -10,6 +11,7 @@ import {
I18N_SUCCESSFUL_CONNECTION_MESSAGE, I18N_SUCCESSFUL_CONNECTION_MESSAGE,
integrationLevels, integrationLevels,
} from '~/integrations/constants'; } from '~/integrations/constants';
import { refreshCurrentPage } from '~/lib/utils/url_utility';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import { testIntegrationSettings } from '../api'; import { testIntegrationSettings } from '../api';
import ActiveCheckbox from './active_checkbox.vue'; import ActiveCheckbox from './active_checkbox.vue';
...@@ -55,11 +57,12 @@ export default { ...@@ -55,11 +57,12 @@ export default {
integrationActive: false, integrationActive: false,
isTesting: false, isTesting: false,
isSaving: false, isSaving: false,
isResetting: false,
}; };
}, },
computed: { computed: {
...mapGetters(['currentKey', 'propsSource']), ...mapGetters(['currentKey', 'propsSource']),
...mapState(['defaultState', 'customState', 'override', 'isResetting']), ...mapState(['defaultState', 'customState', 'override']),
isEditable() { isEditable() {
return this.propsSource.editable; return this.propsSource.editable;
}, },
...@@ -126,7 +129,20 @@ export default { ...@@ -126,7 +129,20 @@ export default {
}); });
}, },
onResetClick() { onResetClick() {
this.fetchResetIntegration(); this.isResetting = true;
return axios
.post(this.propsSource.resetPath)
.then(() => {
refreshCurrentPage();
})
.catch((error) => {
this.$toast.show(I18N_DEFAULT_ERROR_MESSAGE);
Sentry.captureException(error);
})
.finally(() => {
this.isResetting = false;
});
}, },
onRequestJiraIssueTypes() { onRequestJiraIssueTypes() {
this.requestJiraIssueTypes(this.getFormData()); this.requestJiraIssueTypes(this.getFormData());
...@@ -208,6 +224,7 @@ export default { ...@@ -208,6 +224,7 @@ export default {
variant="confirm" variant="confirm"
:loading="isSaving" :loading="isSaving"
:disabled="disableButtons" :disabled="disableButtons"
data-testid="save-button-instance-group"
data-qa-selector="save_changes_button" data-qa-selector="save_changes_button"
> >
{{ __('Save changes') }} {{ __('Save changes') }}
......
...@@ -11,7 +11,7 @@ export default { ...@@ -11,7 +11,7 @@ export default {
primaryProps() { primaryProps() {
return { return {
text: __('Reset'), text: __('Reset'),
attributes: [{ variant: 'warning' }, { category: 'primary' }], attributes: [{ variant: 'danger' }, { category: 'primary' }],
}; };
}, },
cancelProps() { cancelProps() {
......
import axios from 'axios';
import { refreshCurrentPage } from '~/lib/utils/url_utility';
import { import {
VALIDATE_INTEGRATION_FORM_EVENT, VALIDATE_INTEGRATION_FORM_EVENT,
I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE, I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE,
...@@ -10,27 +8,6 @@ import eventHub from '../event_hub'; ...@@ -10,27 +8,6 @@ import eventHub from '../event_hub';
import * as types from './mutation_types'; import * as types from './mutation_types';
export const setOverride = ({ commit }, override) => commit(types.SET_OVERRIDE, override); export const setOverride = ({ commit }, override) => commit(types.SET_OVERRIDE, override);
export const setIsResetting = ({ commit }, isResetting) =>
commit(types.SET_IS_RESETTING, isResetting);
export const requestResetIntegration = ({ commit }) => {
commit(types.REQUEST_RESET_INTEGRATION);
};
export const receiveResetIntegrationSuccess = () => {
refreshCurrentPage();
};
export const receiveResetIntegrationError = ({ commit }) => {
commit(types.RECEIVE_RESET_INTEGRATION_ERROR);
};
export const fetchResetIntegration = ({ dispatch, getters }) => {
dispatch('requestResetIntegration');
return axios
.post(getters.propsSource.resetPath, { params: { format: 'json' } })
.then(() => dispatch('receiveResetIntegrationSuccess'))
.catch(() => dispatch('receiveResetIntegrationError'));
};
export const requestJiraIssueTypes = ({ commit, dispatch, getters }, formData) => { export const requestJiraIssueTypes = ({ commit, dispatch, getters }, formData) => {
commit(types.SET_JIRA_ISSUE_TYPES_ERROR_MESSAGE, ''); commit(types.SET_JIRA_ISSUE_TYPES_ERROR_MESSAGE, '');
......
...@@ -4,15 +4,6 @@ export default { ...@@ -4,15 +4,6 @@ export default {
[types.SET_OVERRIDE](state, override) { [types.SET_OVERRIDE](state, override) {
state.override = override; state.override = override;
}, },
[types.SET_IS_RESETTING](state, isResetting) {
state.isResetting = isResetting;
},
[types.REQUEST_RESET_INTEGRATION](state) {
state.isResetting = true;
},
[types.RECEIVE_RESET_INTEGRATION_ERROR](state) {
state.isResetting = false;
},
[types.SET_JIRA_ISSUE_TYPES](state, jiraIssueTypes) { [types.SET_JIRA_ISSUE_TYPES](state, jiraIssueTypes) {
state.jiraIssueTypes = jiraIssueTypes; state.jiraIssueTypes = jiraIssueTypes;
}, },
......
...@@ -5,8 +5,6 @@ export default ({ defaultState = null, customState = {} } = {}) => { ...@@ -5,8 +5,6 @@ export default ({ defaultState = null, customState = {} } = {}) => {
override, override,
defaultState, defaultState,
customState, customState,
isSaving: false,
isResetting: false,
isLoadingJiraIssueTypes: false, isLoadingJiraIssueTypes: false,
loadingJiraIssueTypesErrorMessage: '', loadingJiraIssueTypesErrorMessage: '',
jiraIssueTypes: [], jiraIssueTypes: [],
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
import { GlSafeHtmlDirective } from '@gitlab/ui'; import { GlSafeHtmlDirective } from '@gitlab/ui';
import LineNumbers from '~/vue_shared/components/line_numbers.vue'; import LineNumbers from '~/vue_shared/components/line_numbers.vue';
import { sanitize } from '~/lib/dompurify'; import { sanitize } from '~/lib/dompurify';
import '~/sourcegraph/load';
const LINE_SELECT_CLASS_NAME = 'hll'; const LINE_SELECT_CLASS_NAME = 'hll';
......
...@@ -293,10 +293,8 @@ ...@@ -293,10 +293,8 @@
} }
} }
.modal-doorkeepr-auth { .doorkeeper-authorize {
.modal-body { max-width: px-to-rem(500px);
padding: $gl-padding;
}
} }
.created-deploy-token-container { .created-deploy-token-container {
......
%main{ :role => "main" } %main{ :role => "main" }
.modal-dialog.modal-doorkeepr-auth .doorkeeper-authorize.gl-mx-auto.gl-mt-6
.modal-content.gl-shadow-none .gl-border-gray-200.gl-border-1.gl-border-solid.gl-rounded-base
.modal-header .gl-p-5.gl-border-b-gray-200.gl-border-b-1.gl-border-b-solid
%h3.page-title %h4.gl-m-0
- link_to_client = link_to(@pre_auth.client.name, @pre_auth.redirect_uri, target: '_blank', rel: 'noopener noreferrer') - link_to_client = link_to(@pre_auth.client.name, @pre_auth.redirect_uri, target: '_blank', rel: 'noopener noreferrer')
= _("Authorize %{link_to_client} to use your account?").html_safe % { link_to_client: link_to_client } = _("Authorize %{link_to_client} to use your account?").html_safe % { link_to_client: link_to_client }
.modal-body .gl-p-5
- if current_user.admin? - if current_user.admin?
.text-warning .gl-text-orange-500
%p %p
= sprite_icon('warning-solid') = sprite_icon('warning-solid')
= html_escape(_('You are an admin, which means granting access to %{client_name} will allow them to interact with GitLab as an admin as well. Proceed with caution.')) % { client_name: tag.strong(@pre_auth.client.name) } = html_escape(_('You are an admin, which means granting access to %{client_name} will allow them to interact with GitLab as an admin as well. Proceed with caution.')) % { client_name: tag.strong(@pre_auth.client.name) }
...@@ -27,25 +27,25 @@ ...@@ -27,25 +27,25 @@
- @pre_auth.scopes.each do |scope| - @pre_auth.scopes.each do |scope|
%li %li
%strong= t scope, scope: [:doorkeeper, :scopes] %strong= t scope, scope: [:doorkeeper, :scopes]
.text-secondary= t scope, scope: [:doorkeeper, :scope_desc] .gl-text-gray-500= t scope, scope: [:doorkeeper, :scope_desc]
.form-actions.text-right .gl-p-5.gl-bg-gray-10.gl-border-t-gray-200.gl-border-t-1.gl-border-t-solid.gl-rounded-bottom-right-base.gl-rounded-bottom-left-base.gl-text-right
= form_tag oauth_authorization_path, method: :delete, class: 'inline' do = form_tag oauth_authorization_path, method: :delete, class: 'inline' do
= hidden_field_tag :client_id, @pre_auth.client.uid = hidden_field_tag :client_id, @pre_auth.client.uid
= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri = hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
= hidden_field_tag :state, @pre_auth.state = hidden_field_tag :state, @pre_auth.state
= hidden_field_tag :response_type, @pre_auth.response_type = hidden_field_tag :response_type, @pre_auth.response_type
= hidden_field_tag :scope, @pre_auth.scope = hidden_field_tag :scope, @pre_auth.scope
= hidden_field_tag :nonce, @pre_auth.nonce = hidden_field_tag :nonce, @pre_auth.nonce
= hidden_field_tag :code_challenge, @pre_auth.code_challenge = hidden_field_tag :code_challenge, @pre_auth.code_challenge
= hidden_field_tag :code_challenge_method, @pre_auth.code_challenge_method = hidden_field_tag :code_challenge_method, @pre_auth.code_challenge_method
= submit_tag _("Deny"), class: "gl-button btn btn-danger" = submit_tag _("Deny"), class: "btn btn-default gl-button"
= form_tag oauth_authorization_path, method: :post, class: 'inline' do = form_tag oauth_authorization_path, method: :post, class: 'inline' do
= hidden_field_tag :client_id, @pre_auth.client.uid = hidden_field_tag :client_id, @pre_auth.client.uid
= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri = hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
= hidden_field_tag :state, @pre_auth.state = hidden_field_tag :state, @pre_auth.state
= hidden_field_tag :response_type, @pre_auth.response_type = hidden_field_tag :response_type, @pre_auth.response_type
= hidden_field_tag :scope, @pre_auth.scope = hidden_field_tag :scope, @pre_auth.scope
= hidden_field_tag :nonce, @pre_auth.nonce = hidden_field_tag :nonce, @pre_auth.nonce
= hidden_field_tag :code_challenge, @pre_auth.code_challenge = hidden_field_tag :code_challenge, @pre_auth.code_challenge
= hidden_field_tag :code_challenge_method, @pre_auth.code_challenge_method = hidden_field_tag :code_challenge_method, @pre_auth.code_challenge_method
= submit_tag _("Authorize"), class: "gl-button btn btn-confirm gl-ml-3", data: { qa_selector: 'authorization_button' } = submit_tag _("Authorize"), class: "btn btn-danger gl-button gl-ml-3", data: { qa_selector: 'authorization_button' }
...@@ -94,8 +94,7 @@ ...@@ -94,8 +94,7 @@
= s_('Preferences|Surround text selection when typing quotes or brackets') = s_('Preferences|Surround text selection when typing quotes or brackets')
.form-text.text-muted .form-text.text-muted
- supported_characters = %w(" ' ` \( [ { < * _).map {|char| "<code>#{char}</code>" }.join(', ') - supported_characters = %w(" ' ` \( [ { < * _).map {|char| "<code>#{char}</code>" }.join(', ')
- msg = "Preferences|When you type in a description or comment box, selected text is surrounded by the corresponding character after typing one of the following characters: #{supported_characters}." = sprintf(s_( "Preferences|When you type in a description or comment box, selected text is surrounded by the corresponding character after typing one of the following characters: %{supported_characters}."), { supported_characters: supported_characters }).html_safe
= s_(msg).html_safe
.form-group .form-group
= f.label :tab_width, s_('Preferences|Tab width'), class: 'label-bold' = f.label :tab_width, s_('Preferences|Tab width'), class: 'label-bold'
......
...@@ -18,7 +18,7 @@ module Metrics ...@@ -18,7 +18,7 @@ module Metrics
dashboard_paths = ::Gitlab::Metrics::Dashboard::RepoDashboardFinder.list_dashboards(project) dashboard_paths = ::Gitlab::Metrics::Dashboard::RepoDashboardFinder.list_dashboards(project)
dashboard_paths.each do |dashboard_path| dashboard_paths.each do |dashboard_path|
::Gitlab::Metrics::Dashboard::Importer.new(dashboard_path, project).execute! ::Gitlab::Metrics::Dashboard::Importer.new(dashboard_path, project).execute
end end
end end
end end
......
...@@ -166,7 +166,7 @@ deploy_prod: ...@@ -166,7 +166,7 @@ deploy_prod:
The `when: manual` action: The `when: manual` action:
- Exposes a play button for the job in the GitLab UI. - Exposes a play button for the job in the GitLab UI, with the text **Can be manually deployed to &lt;environment&gt;**.
- Means the `deploy_prod` job is only triggered when the play button is clicked. - Means the `deploy_prod` job is only triggered when the play button is clicked.
You can find the play button in the pipelines, environments, deployments, and jobs views. You can find the play button in the pipelines, environments, deployments, and jobs views.
......
...@@ -7,7 +7,9 @@ import { ...@@ -7,7 +7,9 @@ import {
GlFormGroup, GlFormGroup,
GlFormInput, GlFormInput,
} from '@gitlab/ui'; } from '@gitlab/ui';
import $ from 'jquery';
import createFlash from '~/flash'; import createFlash from '~/flash';
import Autosave from '~/autosave';
import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { formatDate } from '~/lib/utils/datetime_utility'; import { formatDate } from '~/lib/utils/datetime_utility';
import { visitUrl } from '~/lib/utils/url_utility'; import { visitUrl } from '~/lib/utils/url_utility';
...@@ -53,7 +55,31 @@ export default { ...@@ -53,7 +55,31 @@ export default {
`), `),
epicDatesHint: s__('Epics|Leave empty to inherit from milestone dates'), epicDatesHint: s__('Epics|Leave empty to inherit from milestone dates'),
}, },
mounted() {
this.initAutosave();
},
methods: { methods: {
initAutosave() {
const { titleInput, descriptionInput } = this.$refs;
const { pathname, search } = document.location;
if (!titleInput || !descriptionInput) return;
/**
* We'd need to update Autosave to work with plain HTML elements instead of
* jQuery instance, but until then, we'd have to rely on jQuery.
*/
this.autosaveTitle = new Autosave($(titleInput.$el), [pathname, search, 'title']);
this.autosaveDescription = new Autosave($(descriptionInput), [
pathname,
search,
'description',
]);
},
resetAutosave() {
this.autosaveTitle.reset();
this.autosaveDescription.reset();
},
save() { save() {
this.loading = true; this.loading = true;
...@@ -86,6 +112,7 @@ export default { ...@@ -86,6 +112,7 @@ export default {
return; return;
} }
this.resetAutosave();
visitUrl(epic.webUrl); visitUrl(epic.webUrl);
}) })
.catch(() => { .catch(() => {
...@@ -117,6 +144,7 @@ export default { ...@@ -117,6 +144,7 @@ export default {
<gl-form-group :label="__('Title')" label-for="epic-title"> <gl-form-group :label="__('Title')" label-for="epic-title">
<gl-form-input <gl-form-input
id="epic-title" id="epic-title"
ref="titleInput"
v-model="title" v-model="title"
data-testid="epic-title" data-testid="epic-title"
data-qa-selector="epic_title_field" data-qa-selector="epic_title_field"
...@@ -141,6 +169,7 @@ export default { ...@@ -141,6 +169,7 @@ export default {
<template #textarea> <template #textarea>
<textarea <textarea
id="epic-description" id="epic-description"
ref="descriptionInput"
v-model="description" v-model="description"
data-testid="epic-description" data-testid="epic-description"
class="note-textarea js-gfm-input js-autosize markdown-area" class="note-textarea js-gfm-input js-autosize markdown-area"
......
...@@ -5,6 +5,7 @@ import { ApolloMutation } from 'vue-apollo'; ...@@ -5,6 +5,7 @@ import { ApolloMutation } from 'vue-apollo';
import EpicForm from 'ee/epic/components/epic_form.vue'; import EpicForm from 'ee/epic/components/epic_form.vue';
import createEpic from 'ee/epic/queries/createEpic.mutation.graphql'; import createEpic from 'ee/epic/queries/createEpic.mutation.graphql';
import { TEST_HOST } from 'helpers/test_constants'; import { TEST_HOST } from 'helpers/test_constants';
import Autosave from '~/autosave';
import { visitUrl } from '~/lib/utils/url_utility'; import { visitUrl } from '~/lib/utils/url_utility';
import LabelsSelectWidget from '~/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue'; import LabelsSelectWidget from '~/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue';
...@@ -67,6 +68,15 @@ describe('ee/epic/components/epic_form.vue', () => { ...@@ -67,6 +68,15 @@ describe('ee/epic/components/epic_form.vue', () => {
expect(findForm().exists()).toBe(true); expect(findForm().exists()).toBe(true);
}); });
it('initializes autosave support on title and description fields', () => {
// We discourage testing `wrapper.vm` properties but
// since `autosave` library instantiates on component
// there's no other way to test whether instantiation
// happened correctly or not.
expect(wrapper.vm.autosaveTitle).toBeInstanceOf(Autosave);
expect(wrapper.vm.autosaveDescription).toBeInstanceOf(Autosave);
});
it('can be canceled', () => { it('can be canceled', () => {
expect(findCancelButton().attributes('href')).toBe(TEST_HOST); expect(findCancelButton().attributes('href')).toBe(TEST_HOST);
}); });
...@@ -161,5 +171,25 @@ describe('ee/epic/components/epic_form.vue', () => { ...@@ -161,5 +171,25 @@ describe('ee/epic/components/epic_form.vue', () => {
expect(findSaveButton().props('loading')).toBe(loading); expect(findSaveButton().props('loading')).toBe(loading);
}); });
}); });
it('resets automatically saved title and description when request succeeds', async () => {
createWrapper();
// We discourage testing/spying on `wrapper.vm` properties but
// since `autosave` library instantiates on component there's no
// other way to test whether autosave class method was called
// correctly or not.
const autosaveTitleResetSpy = jest.spyOn(wrapper.vm.autosaveTitle, 'reset');
const autosaveDescriptionResetSpy = jest.spyOn(wrapper.vm.autosaveDescription, 'reset');
findTitle().vm.$emit('input', title);
findDescription().setValue(description);
findForm().vm.$emit('submit', { preventDefault: () => {} });
await nextTick();
expect(autosaveTitleResetSpy).toHaveBeenCalled();
expect(autosaveDescriptionResetSpy).toHaveBeenCalled();
});
}); });
}); });
...@@ -26863,6 +26863,9 @@ msgstr "" ...@@ -26863,6 +26863,9 @@ msgstr ""
msgid "Preferences|Use relative times" msgid "Preferences|Use relative times"
msgstr "" msgstr ""
msgid "Preferences|When you type in a description or comment box, selected text is surrounded by the corresponding character after typing one of the following characters: %{supported_characters}."
msgstr ""
msgid "Prev" msgid "Prev"
msgstr "" msgstr ""
......
...@@ -3,7 +3,7 @@ import MockAdapter from 'axios-mock-adapter'; ...@@ -3,7 +3,7 @@ import MockAdapter from 'axios-mock-adapter';
import * as Sentry from '@sentry/browser'; import * as Sentry from '@sentry/browser';
import { setHTMLFixture } from 'helpers/fixtures'; import { setHTMLFixture } from 'helpers/fixtures';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { mockIntegrationProps } from 'jest/integrations/edit/mock_data'; import waitForPromises from 'helpers/wait_for_promises';
import ActiveCheckbox from '~/integrations/edit/components/active_checkbox.vue'; import ActiveCheckbox from '~/integrations/edit/components/active_checkbox.vue';
import ConfirmationModal from '~/integrations/edit/components/confirmation_modal.vue'; import ConfirmationModal from '~/integrations/edit/components/confirmation_modal.vue';
import DynamicField from '~/integrations/edit/components/dynamic_field.vue'; import DynamicField from '~/integrations/edit/components/dynamic_field.vue';
...@@ -13,7 +13,6 @@ import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_field ...@@ -13,7 +13,6 @@ import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_field
import OverrideDropdown from '~/integrations/edit/components/override_dropdown.vue'; import OverrideDropdown from '~/integrations/edit/components/override_dropdown.vue';
import ResetConfirmationModal from '~/integrations/edit/components/reset_confirmation_modal.vue'; import ResetConfirmationModal from '~/integrations/edit/components/reset_confirmation_modal.vue';
import TriggerFields from '~/integrations/edit/components/trigger_fields.vue'; import TriggerFields from '~/integrations/edit/components/trigger_fields.vue';
import waitForPromises from 'helpers/wait_for_promises';
import { import {
integrationLevels, integrationLevels,
I18N_SUCCESSFUL_CONNECTION_MESSAGE, I18N_SUCCESSFUL_CONNECTION_MESSAGE,
...@@ -23,9 +22,12 @@ import { ...@@ -23,9 +22,12 @@ import {
import { createStore } from '~/integrations/edit/store'; import { createStore } from '~/integrations/edit/store';
import eventHub from '~/integrations/edit/event_hub'; import eventHub from '~/integrations/edit/event_hub';
import httpStatus from '~/lib/utils/http_status'; import httpStatus from '~/lib/utils/http_status';
import { refreshCurrentPage } from '~/lib/utils/url_utility';
import { mockIntegrationProps } from '../mock_data';
jest.mock('~/integrations/edit/event_hub'); jest.mock('~/integrations/edit/event_hub');
jest.mock('@sentry/browser'); jest.mock('@sentry/browser');
jest.mock('~/lib/utils/url_utility');
describe('IntegrationForm', () => { describe('IntegrationForm', () => {
const mockToastShow = jest.fn(); const mockToastShow = jest.fn();
...@@ -80,7 +82,8 @@ describe('IntegrationForm', () => { ...@@ -80,7 +82,8 @@ describe('IntegrationForm', () => {
const findConfirmationModal = () => wrapper.findComponent(ConfirmationModal); const findConfirmationModal = () => wrapper.findComponent(ConfirmationModal);
const findResetConfirmationModal = () => wrapper.findComponent(ResetConfirmationModal); const findResetConfirmationModal = () => wrapper.findComponent(ResetConfirmationModal);
const findResetButton = () => wrapper.findByTestId('reset-button'); const findResetButton = () => wrapper.findByTestId('reset-button');
const findSaveButton = () => wrapper.findByTestId('save-button'); const findProjectSaveButton = () => wrapper.findByTestId('save-button');
const findInstanceOrGroupSaveButton = () => wrapper.findByTestId('save-button-instance-group');
const findTestButton = () => wrapper.findByTestId('test-button'); const findTestButton = () => wrapper.findByTestId('test-button');
const findJiraTriggerFields = () => wrapper.findComponent(JiraTriggerFields); const findJiraTriggerFields = () => wrapper.findComponent(JiraTriggerFields);
const findJiraIssuesFields = () => wrapper.findComponent(JiraIssuesFields); const findJiraIssuesFields = () => wrapper.findComponent(JiraIssuesFields);
...@@ -395,11 +398,11 @@ describe('IntegrationForm', () => { ...@@ -395,11 +398,11 @@ describe('IntegrationForm', () => {
}, },
}); });
await findSaveButton().vm.$emit('click', new Event('click')); await findProjectSaveButton().vm.$emit('click', new Event('click'));
}); });
it('sets save button `loading` prop to `true`', () => { it('sets save button `loading` prop to `true`', () => {
expect(findSaveButton().props('loading')).toBe(true); expect(findProjectSaveButton().props('loading')).toBe(true);
}); });
it('sets test button `disabled` prop to `true`', () => { it('sets test button `disabled` prop to `true`', () => {
...@@ -425,7 +428,7 @@ describe('IntegrationForm', () => { ...@@ -425,7 +428,7 @@ describe('IntegrationForm', () => {
}, },
}); });
await findSaveButton().vm.$emit('click', new Event('click')); await findProjectSaveButton().vm.$emit('click', new Event('click'));
}); });
it('submit form', () => { it('submit form', () => {
...@@ -445,7 +448,7 @@ describe('IntegrationForm', () => { ...@@ -445,7 +448,7 @@ describe('IntegrationForm', () => {
}, },
}); });
await findSaveButton().vm.$emit('click', new Event('click')); await findProjectSaveButton().vm.$emit('click', new Event('click'));
}); });
it('does not submit form', () => { it('does not submit form', () => {
...@@ -453,7 +456,7 @@ describe('IntegrationForm', () => { ...@@ -453,7 +456,7 @@ describe('IntegrationForm', () => {
}); });
it('sets save button `loading` prop to `false`', () => { it('sets save button `loading` prop to `false`', () => {
expect(findSaveButton().props('loading')).toBe(false); expect(findProjectSaveButton().props('loading')).toBe(false);
}); });
it('sets test button `disabled` prop to `false`', () => { it('sets test button `disabled` prop to `false`', () => {
...@@ -507,7 +510,7 @@ describe('IntegrationForm', () => { ...@@ -507,7 +510,7 @@ describe('IntegrationForm', () => {
}); });
it('sets save button `disabled` prop to `true`', () => { it('sets save button `disabled` prop to `true`', () => {
expect(findSaveButton().props('disabled')).toBe(true); expect(findProjectSaveButton().props('disabled')).toBe(true);
}); });
}); });
...@@ -536,7 +539,7 @@ describe('IntegrationForm', () => { ...@@ -536,7 +539,7 @@ describe('IntegrationForm', () => {
}); });
it('sets save button `disabled` prop to `false`', () => { it('sets save button `disabled` prop to `false`', () => {
expect(findSaveButton().props('disabled')).toBe(false); expect(findProjectSaveButton().props('disabled')).toBe(false);
}); });
it(`${expectSentry ? 'does' : 'does not'} capture exception in Sentry`, () => { it(`${expectSentry ? 'does' : 'does not'} capture exception in Sentry`, () => {
...@@ -545,4 +548,83 @@ describe('IntegrationForm', () => { ...@@ -545,4 +548,83 @@ describe('IntegrationForm', () => {
}); });
}); });
}); });
describe('when `reset-confirmation-modal` emits `reset` event', () => {
const mockResetPath = '/reset';
describe('buttons', () => {
beforeEach(async () => {
createComponent({
customStateProps: {
integrationLevel: integrationLevels.GROUP,
canTest: true,
resetPath: mockResetPath,
},
});
await findResetConfirmationModal().vm.$emit('reset');
});
it('sets reset button `loading` prop to `true`', () => {
expect(findResetButton().props('loading')).toBe(true);
});
it('sets other button `disabled` props to `true`', () => {
expect(findInstanceOrGroupSaveButton().props('disabled')).toBe(true);
expect(findTestButton().props('disabled')).toBe(true);
});
});
describe('when "reset settings" request fails', () => {
beforeEach(async () => {
mockAxios.onPost(mockResetPath).replyOnce(httpStatus.INTERNAL_SERVER_ERROR);
createComponent({
customStateProps: {
integrationLevel: integrationLevels.GROUP,
canTest: true,
resetPath: mockResetPath,
},
});
await findResetConfirmationModal().vm.$emit('reset');
await waitForPromises();
});
it('displays a toast', () => {
expect(mockToastShow).toHaveBeenCalledWith(I18N_DEFAULT_ERROR_MESSAGE);
});
it('captures exception in Sentry', () => {
expect(Sentry.captureException).toHaveBeenCalledTimes(1);
});
it('sets reset button `loading` prop to `false`', () => {
expect(findResetButton().props('loading')).toBe(false);
});
it('sets button `disabled` props to `false`', () => {
expect(findInstanceOrGroupSaveButton().props('disabled')).toBe(false);
expect(findTestButton().props('disabled')).toBe(false);
});
});
describe('when "reset settings" succeeds', () => {
beforeEach(async () => {
mockAxios.onPost(mockResetPath).replyOnce(httpStatus.OK);
createComponent({
customStateProps: {
integrationLevel: integrationLevels.GROUP,
resetPath: mockResetPath,
},
});
await findResetConfirmationModal().vm.$emit('reset');
await waitForPromises();
});
it('calls `refreshCurrentPage`', () => {
expect(refreshCurrentPage).toHaveBeenCalledTimes(1);
});
});
});
}); });
...@@ -4,17 +4,12 @@ import testAction from 'helpers/vuex_action_helper'; ...@@ -4,17 +4,12 @@ import testAction from 'helpers/vuex_action_helper';
import { I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE } from '~/integrations/constants'; import { I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE } from '~/integrations/constants';
import { import {
setOverride, setOverride,
setIsResetting,
requestResetIntegration,
receiveResetIntegrationSuccess,
receiveResetIntegrationError,
requestJiraIssueTypes, requestJiraIssueTypes,
receiveJiraIssueTypesSuccess, receiveJiraIssueTypesSuccess,
receiveJiraIssueTypesError, receiveJiraIssueTypesError,
} from '~/integrations/edit/store/actions'; } from '~/integrations/edit/store/actions';
import * as types from '~/integrations/edit/store/mutation_types'; import * as types from '~/integrations/edit/store/mutation_types';
import createState from '~/integrations/edit/store/state'; import createState from '~/integrations/edit/store/state';
import { refreshCurrentPage } from '~/lib/utils/url_utility';
import { mockJiraIssueTypes } from '../mock_data'; import { mockJiraIssueTypes } from '../mock_data';
jest.mock('~/lib/utils/url_utility'); jest.mock('~/lib/utils/url_utility');
...@@ -38,38 +33,6 @@ describe('Integration form store actions', () => { ...@@ -38,38 +33,6 @@ describe('Integration form store actions', () => {
}); });
}); });
describe('setIsResetting', () => {
it('should commit isResetting mutation', () => {
return testAction(setIsResetting, true, state, [
{ type: types.SET_IS_RESETTING, payload: true },
]);
});
});
describe('requestResetIntegration', () => {
it('should commit REQUEST_RESET_INTEGRATION mutation', () => {
return testAction(requestResetIntegration, null, state, [
{ type: types.REQUEST_RESET_INTEGRATION },
]);
});
});
describe('receiveResetIntegrationSuccess', () => {
it('should call refreshCurrentPage()', () => {
return testAction(receiveResetIntegrationSuccess, null, state, [], [], () => {
expect(refreshCurrentPage).toHaveBeenCalled();
});
});
});
describe('receiveResetIntegrationError', () => {
it('should commit RECEIVE_RESET_INTEGRATION_ERROR mutation', () => {
return testAction(receiveResetIntegrationError, null, state, [
{ type: types.RECEIVE_RESET_INTEGRATION_ERROR },
]);
});
});
describe('requestJiraIssueTypes', () => { describe('requestJiraIssueTypes', () => {
describe.each` describe.each`
scenario | responseCode | response | action scenario | responseCode | response | action
......
...@@ -17,30 +17,6 @@ describe('Integration form store mutations', () => { ...@@ -17,30 +17,6 @@ describe('Integration form store mutations', () => {
}); });
}); });
describe(`${types.SET_IS_RESETTING}`, () => {
it('sets isResetting', () => {
mutations[types.SET_IS_RESETTING](state, true);
expect(state.isResetting).toBe(true);
});
});
describe(`${types.REQUEST_RESET_INTEGRATION}`, () => {
it('sets isResetting', () => {
mutations[types.REQUEST_RESET_INTEGRATION](state);
expect(state.isResetting).toBe(true);
});
});
describe(`${types.RECEIVE_RESET_INTEGRATION_ERROR}`, () => {
it('sets isResetting', () => {
mutations[types.RECEIVE_RESET_INTEGRATION_ERROR](state);
expect(state.isResetting).toBe(false);
});
});
describe(`${types.SET_JIRA_ISSUE_TYPES}`, () => { describe(`${types.SET_JIRA_ISSUE_TYPES}`, () => {
it('sets jiraIssueTypes', () => { it('sets jiraIssueTypes', () => {
const jiraIssueTypes = ['issue', 'epic']; const jiraIssueTypes = ['issue', 'epic'];
......
...@@ -5,8 +5,6 @@ describe('Integration form state factory', () => { ...@@ -5,8 +5,6 @@ describe('Integration form state factory', () => {
expect(createState()).toEqual({ expect(createState()).toEqual({
defaultState: null, defaultState: null,
customState: {}, customState: {},
isSaving: false,
isResetting: false,
override: false, override: false,
isLoadingJiraIssueTypes: false, isLoadingJiraIssueTypes: false,
jiraIssueTypes: [], jiraIssueTypes: [],
......
...@@ -10,16 +10,34 @@ RSpec.describe Metrics::Dashboard::SyncDashboardsWorker do ...@@ -10,16 +10,34 @@ RSpec.describe Metrics::Dashboard::SyncDashboardsWorker do
let(:dashboard_path) { '.gitlab/dashboards/test.yml' } let(:dashboard_path) { '.gitlab/dashboards/test.yml' }
describe ".perform" do describe ".perform" do
it 'imports metrics' do context 'with valid dashboard hash' do
expect { worker.perform(project.id) }.to change(PrometheusMetric, :count).by(3) it 'imports metrics' do
expect { worker.perform(project.id) }.to change(PrometheusMetric, :count).by(3)
end
it 'is idempotent' do
2.times do
worker.perform(project.id)
end
expect(PrometheusMetric.count).to eq(3)
end
end end
it 'is idempotent' do context 'with invalid dashboard hash' do
2.times do before do
worker.perform(project.id) allow_next_instance_of(Gitlab::Metrics::Dashboard::Importer) do |instance|
allow(instance).to receive(:dashboard_hash).and_return({})
end
end end
expect(PrometheusMetric.count).to eq(3) it 'does not import metrics' do
expect { worker.perform(project.id) }.not_to change(PrometheusMetric, :count)
end
it 'does not raise an error' do
expect { worker.perform(project.id) }.not_to raise_error
end
end end
end end
end end
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