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

Automatic merge of gitlab-org/gitlab master

parents c43c891e f869a810
<script>
import { GlButton, GlModalDirective, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
import axios from 'axios';
import * as Sentry from '@sentry/browser';
import { mapState, mapActions, mapGetters } from 'vuex';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
......@@ -10,6 +11,7 @@ import {
I18N_SUCCESSFUL_CONNECTION_MESSAGE,
integrationLevels,
} from '~/integrations/constants';
import { refreshCurrentPage } from '~/lib/utils/url_utility';
import eventHub from '../event_hub';
import { testIntegrationSettings } from '../api';
import ActiveCheckbox from './active_checkbox.vue';
......@@ -55,11 +57,12 @@ export default {
integrationActive: false,
isTesting: false,
isSaving: false,
isResetting: false,
};
},
computed: {
...mapGetters(['currentKey', 'propsSource']),
...mapState(['defaultState', 'customState', 'override', 'isResetting']),
...mapState(['defaultState', 'customState', 'override']),
isEditable() {
return this.propsSource.editable;
},
......@@ -126,7 +129,20 @@ export default {
});
},
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() {
this.requestJiraIssueTypes(this.getFormData());
......@@ -208,6 +224,7 @@ export default {
variant="confirm"
:loading="isSaving"
:disabled="disableButtons"
data-testid="save-button-instance-group"
data-qa-selector="save_changes_button"
>
{{ __('Save changes') }}
......
......@@ -11,7 +11,7 @@ export default {
primaryProps() {
return {
text: __('Reset'),
attributes: [{ variant: 'warning' }, { category: 'primary' }],
attributes: [{ variant: 'danger' }, { category: 'primary' }],
};
},
cancelProps() {
......
import axios from 'axios';
import { refreshCurrentPage } from '~/lib/utils/url_utility';
import {
VALIDATE_INTEGRATION_FORM_EVENT,
I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE,
......@@ -10,27 +8,6 @@ import eventHub from '../event_hub';
import * as types from './mutation_types';
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) => {
commit(types.SET_JIRA_ISSUE_TYPES_ERROR_MESSAGE, '');
......
......@@ -4,15 +4,6 @@ export default {
[types.SET_OVERRIDE](state, 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) {
state.jiraIssueTypes = jiraIssueTypes;
},
......
......@@ -5,8 +5,6 @@ export default ({ defaultState = null, customState = {} } = {}) => {
override,
defaultState,
customState,
isSaving: false,
isResetting: false,
isLoadingJiraIssueTypes: false,
loadingJiraIssueTypesErrorMessage: '',
jiraIssueTypes: [],
......
......@@ -2,6 +2,7 @@
import { GlSafeHtmlDirective } from '@gitlab/ui';
import LineNumbers from '~/vue_shared/components/line_numbers.vue';
import { sanitize } from '~/lib/dompurify';
import '~/sourcegraph/load';
const LINE_SELECT_CLASS_NAME = 'hll';
......
......@@ -293,10 +293,8 @@
}
}
.modal-doorkeepr-auth {
.modal-body {
padding: $gl-padding;
}
.doorkeeper-authorize {
max-width: px-to-rem(500px);
}
.created-deploy-token-container {
......
%main{ :role => "main" }
.modal-dialog.modal-doorkeepr-auth
.modal-content.gl-shadow-none
.modal-header
%h3.page-title
.doorkeeper-authorize.gl-mx-auto.gl-mt-6
.gl-border-gray-200.gl-border-1.gl-border-solid.gl-rounded-base
.gl-p-5.gl-border-b-gray-200.gl-border-b-1.gl-border-b-solid
%h4.gl-m-0
- 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 }
.modal-body
.gl-p-5
- if current_user.admin?
.text-warning
.gl-text-orange-500
%p
= 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) }
......@@ -27,25 +27,25 @@
- @pre_auth.scopes.each do |scope|
%li
%strong= t scope, scope: [:doorkeeper, :scopes]
.text-secondary= t scope, scope: [:doorkeeper, :scope_desc]
.form-actions.text-right
= form_tag oauth_authorization_path, method: :delete, class: 'inline' do
= hidden_field_tag :client_id, @pre_auth.client.uid
= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
= hidden_field_tag :state, @pre_auth.state
= hidden_field_tag :response_type, @pre_auth.response_type
= hidden_field_tag :scope, @pre_auth.scope
= hidden_field_tag :nonce, @pre_auth.nonce
= hidden_field_tag :code_challenge, @pre_auth.code_challenge
= hidden_field_tag :code_challenge_method, @pre_auth.code_challenge_method
= submit_tag _("Deny"), class: "gl-button btn btn-danger"
= form_tag oauth_authorization_path, method: :post, class: 'inline' do
= hidden_field_tag :client_id, @pre_auth.client.uid
= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
= hidden_field_tag :state, @pre_auth.state
= hidden_field_tag :response_type, @pre_auth.response_type
= hidden_field_tag :scope, @pre_auth.scope
= hidden_field_tag :nonce, @pre_auth.nonce
= hidden_field_tag :code_challenge, @pre_auth.code_challenge
= 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' }
.gl-text-gray-500= t scope, scope: [:doorkeeper, :scope_desc]
.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
= hidden_field_tag :client_id, @pre_auth.client.uid
= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
= hidden_field_tag :state, @pre_auth.state
= hidden_field_tag :response_type, @pre_auth.response_type
= hidden_field_tag :scope, @pre_auth.scope
= hidden_field_tag :nonce, @pre_auth.nonce
= hidden_field_tag :code_challenge, @pre_auth.code_challenge
= hidden_field_tag :code_challenge_method, @pre_auth.code_challenge_method
= submit_tag _("Deny"), class: "btn btn-default gl-button"
= form_tag oauth_authorization_path, method: :post, class: 'inline' do
= hidden_field_tag :client_id, @pre_auth.client.uid
= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
= hidden_field_tag :state, @pre_auth.state
= hidden_field_tag :response_type, @pre_auth.response_type
= hidden_field_tag :scope, @pre_auth.scope
= hidden_field_tag :nonce, @pre_auth.nonce
= hidden_field_tag :code_challenge, @pre_auth.code_challenge
= hidden_field_tag :code_challenge_method, @pre_auth.code_challenge_method
= submit_tag _("Authorize"), class: "btn btn-danger gl-button gl-ml-3", data: { qa_selector: 'authorization_button' }
......@@ -94,8 +94,7 @@
= s_('Preferences|Surround text selection when typing quotes or brackets')
.form-text.text-muted
- 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}."
= s_(msg).html_safe
= 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
.form-group
= f.label :tab_width, s_('Preferences|Tab width'), class: 'label-bold'
......
......@@ -18,7 +18,7 @@ module Metrics
dashboard_paths = ::Gitlab::Metrics::Dashboard::RepoDashboardFinder.list_dashboards(project)
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
......
......@@ -166,7 +166,7 @@ deploy_prod:
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.
You can find the play button in the pipelines, environments, deployments, and jobs views.
......
......@@ -7,7 +7,9 @@ import {
GlFormGroup,
GlFormInput,
} from '@gitlab/ui';
import $ from 'jquery';
import createFlash from '~/flash';
import Autosave from '~/autosave';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { formatDate } from '~/lib/utils/datetime_utility';
import { visitUrl } from '~/lib/utils/url_utility';
......@@ -53,7 +55,31 @@ export default {
`),
epicDatesHint: s__('Epics|Leave empty to inherit from milestone dates'),
},
mounted() {
this.initAutosave();
},
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() {
this.loading = true;
......@@ -86,6 +112,7 @@ export default {
return;
}
this.resetAutosave();
visitUrl(epic.webUrl);
})
.catch(() => {
......@@ -117,6 +144,7 @@ export default {
<gl-form-group :label="__('Title')" label-for="epic-title">
<gl-form-input
id="epic-title"
ref="titleInput"
v-model="title"
data-testid="epic-title"
data-qa-selector="epic_title_field"
......@@ -141,6 +169,7 @@ export default {
<template #textarea>
<textarea
id="epic-description"
ref="descriptionInput"
v-model="description"
data-testid="epic-description"
class="note-textarea js-gfm-input js-autosize markdown-area"
......
......@@ -5,6 +5,7 @@ import { ApolloMutation } from 'vue-apollo';
import EpicForm from 'ee/epic/components/epic_form.vue';
import createEpic from 'ee/epic/queries/createEpic.mutation.graphql';
import { TEST_HOST } from 'helpers/test_constants';
import Autosave from '~/autosave';
import { visitUrl } from '~/lib/utils/url_utility';
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', () => {
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', () => {
expect(findCancelButton().attributes('href')).toBe(TEST_HOST);
});
......@@ -161,5 +171,25 @@ describe('ee/epic/components/epic_form.vue', () => {
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 ""
msgid "Preferences|Use relative times"
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"
msgstr ""
......
......@@ -3,7 +3,7 @@ import MockAdapter from 'axios-mock-adapter';
import * as Sentry from '@sentry/browser';
import { setHTMLFixture } from 'helpers/fixtures';
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 ConfirmationModal from '~/integrations/edit/components/confirmation_modal.vue';
import DynamicField from '~/integrations/edit/components/dynamic_field.vue';
......@@ -13,7 +13,6 @@ import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_field
import OverrideDropdown from '~/integrations/edit/components/override_dropdown.vue';
import ResetConfirmationModal from '~/integrations/edit/components/reset_confirmation_modal.vue';
import TriggerFields from '~/integrations/edit/components/trigger_fields.vue';
import waitForPromises from 'helpers/wait_for_promises';
import {
integrationLevels,
I18N_SUCCESSFUL_CONNECTION_MESSAGE,
......@@ -23,9 +22,12 @@ import {
import { createStore } from '~/integrations/edit/store';
import eventHub from '~/integrations/edit/event_hub';
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('@sentry/browser');
jest.mock('~/lib/utils/url_utility');
describe('IntegrationForm', () => {
const mockToastShow = jest.fn();
......@@ -80,7 +82,8 @@ describe('IntegrationForm', () => {
const findConfirmationModal = () => wrapper.findComponent(ConfirmationModal);
const findResetConfirmationModal = () => wrapper.findComponent(ResetConfirmationModal);
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 findJiraTriggerFields = () => wrapper.findComponent(JiraTriggerFields);
const findJiraIssuesFields = () => wrapper.findComponent(JiraIssuesFields);
......@@ -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`', () => {
expect(findSaveButton().props('loading')).toBe(true);
expect(findProjectSaveButton().props('loading')).toBe(true);
});
it('sets test button `disabled` prop to `true`', () => {
......@@ -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', () => {
......@@ -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', () => {
......@@ -453,7 +456,7 @@ describe('IntegrationForm', () => {
});
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`', () => {
......@@ -507,7 +510,7 @@ describe('IntegrationForm', () => {
});
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', () => {
});
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`, () => {
......@@ -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';
import { I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE } from '~/integrations/constants';
import {
setOverride,
setIsResetting,
requestResetIntegration,
receiveResetIntegrationSuccess,
receiveResetIntegrationError,
requestJiraIssueTypes,
receiveJiraIssueTypesSuccess,
receiveJiraIssueTypesError,
} from '~/integrations/edit/store/actions';
import * as types from '~/integrations/edit/store/mutation_types';
import createState from '~/integrations/edit/store/state';
import { refreshCurrentPage } from '~/lib/utils/url_utility';
import { mockJiraIssueTypes } from '../mock_data';
jest.mock('~/lib/utils/url_utility');
......@@ -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.each`
scenario | responseCode | response | action
......
......@@ -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}`, () => {
it('sets jiraIssueTypes', () => {
const jiraIssueTypes = ['issue', 'epic'];
......
......@@ -5,8 +5,6 @@ describe('Integration form state factory', () => {
expect(createState()).toEqual({
defaultState: null,
customState: {},
isSaving: false,
isResetting: false,
override: false,
isLoadingJiraIssueTypes: false,
jiraIssueTypes: [],
......
......@@ -10,16 +10,34 @@ RSpec.describe Metrics::Dashboard::SyncDashboardsWorker do
let(:dashboard_path) { '.gitlab/dashboards/test.yml' }
describe ".perform" do
it 'imports metrics' do
expect { worker.perform(project.id) }.to change(PrometheusMetric, :count).by(3)
context 'with valid dashboard hash' do
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
it 'is idempotent' do
2.times do
worker.perform(project.id)
context 'with invalid dashboard hash' do
before do
allow_next_instance_of(Gitlab::Metrics::Dashboard::Importer) do |instance|
allow(instance).to receive(:dashboard_hash).and_return({})
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
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