Skip to content
Snippets Groups Projects
Commit 194b499a authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 43b4b3e2
No related branches found
No related tags found
No related merge requests found
Showing
with 308 additions and 62 deletions
# frozen_string_literal: true
module Projects
module Prometheus
module Alerts
module AlertParams
def alert_params
return params if params[:operator].blank?
params.merge(
operator: PrometheusAlert.operator_to_enum(params[:operator])
)
end
end
end
end
end
# frozen_string_literal: true
module Projects
module Prometheus
module Alerts
class CreateService < BaseService
include AlertParams
def execute
project.prometheus_alerts.create(alert_params)
end
end
end
end
end
# frozen_string_literal: true
module Projects
module Prometheus
module Alerts
class DestroyService < BaseService
def execute(alert)
alert.destroy
end
end
end
end
end
# frozen_string_literal: true
module Projects
module Prometheus
module Alerts
class UpdateService < BaseService
include AlertParams
def execute(alert)
alert.update(alert_params)
end
end
end
end
end
---
title: Fix OpenAPI file detector
merge_request: 27321
author: Roger Meier
type: fixed
Loading
Loading
@@ -64,9 +64,12 @@ Parameters:
 
NOTE: **Note:**
[Starting in GitLab 12.8](https://gitlab.com/gitlab-org/gitlab/issues/29984),
the mergeability (`merge_status`) of each merge request will be checked
when `async_merge_request_check_mergeability` feature flag is enabled, the
mergeability (`merge_status`) of each merge request will be checked
asynchronously when a request is made to this endpoint. Poll this API endpoint
to get updated status.
to get updated status. This affects the `has_conflicts` property as it is
dependent on the `merge_status`. It'll return `false` unless `merge_status` is
`cannot_be_merged`.
 
```json
[
Loading
Loading
@@ -538,9 +541,12 @@ Parameters:
 
NOTE: **Note:**
[Starting in GitLab 12.8](https://gitlab.com/gitlab-org/gitlab/issues/29984),
the mergeability (`merge_status`) of a merge request will be checked
when `async_merge_request_check_mergeability` feature flag is enabled, the
mergeability (`merge_status`) of a merge request will be checked
asynchronously when a request is made to this endpoint. Poll this API endpoint
to get updated status.
to get updated status. This affects the `has_conflicts` property as it is
dependent on the `merge_status`. It'll return `false` unless `merge_status` is
`cannot_be_merged`.
 
```json
{
Loading
Loading
Loading
Loading
@@ -16,6 +16,8 @@ To request access to Chatops on GitLab.com:
1. Log into <https://ops.gitlab.net/users/sign_in> **using the same username** as for GitLab.com (you may have to rename it).
1. Ask in the [#production](https://gitlab.slack.com/messages/production) channel to add you by running `/chatops run member add <username> gitlab-com/chatops --ops`.
 
NOTE: **Note:** If you had to change your username for GitLab.com on the first step, make sure [to reflect this information](https://gitlab.com/gitlab-com/www-gitlab-com#adding-yourself-to-the-team-page) on [the team page](https://about.gitlab.com/team).
## See also
 
- [Chatops Usage](../ci/chatops/README.md)
Loading
Loading
Loading
Loading
@@ -40,7 +40,7 @@ module Gitlab
yarn_lock: 'yarn.lock',
 
# OpenAPI Specification files
openapi: %r{.*(openapi|swagger).*\.(yaml|yml|json)\z}i
openapi: %r{([^\/]+)*(openapi|swagger)([^\/]+)*\.(yaml|yml|json)\z}i
}.freeze
 
# Returns an Array of file types based on the given paths.
Loading
Loading
Loading
Loading
@@ -71,6 +71,7 @@ module Gitlab
 
import_failure_service.with_retry(action: 'relation_object.save!', relation_key: relation_key, relation_index: relation_index) do
relation_object.save!
log_relation_creation(@importable, relation_key, relation_object)
end
rescue => e
import_failure_service.log_import_failure(
Loading
Loading
@@ -218,6 +219,25 @@ module Gitlab
 
relation_reader.sort_ci_pipelines_by_id
end
# Enable logging of each top-level relation creation when Importing
# into a Group if feature flag is enabled
def log_relation_creation(importable, relation_key, relation_object)
root_ancestor_group = importable.try(:root_ancestor)
return unless root_ancestor_group
return unless root_ancestor_group.instance_of?(::Group)
return unless Feature.enabled?(:log_import_export_relation_creation, root_ancestor_group)
@shared.logger.info(
importable_type: importable.class.to_s,
importable_id: importable.id,
relation_key: relation_key,
relation_id: relation_object.id,
author_id: relation_object.try(:author_id),
message: '[Project/Group Import] Created new object relation'
)
end
end
end
end
import { shallowMount } from '@vue/test-utils';
import CommitWidget from '~/diffs/components/commit_widget.vue';
import CommitItem from '~/diffs/components/commit_item.vue';
describe('diffs/components/commit_widget', () => {
let wrapper;
beforeEach(() => {
wrapper = shallowMount(CommitWidget, {
propsData: { commit: {} },
});
});
it('renders commit item', () => {
const commitElement = wrapper.find(CommitItem);
expect(commitElement.exists()).toBe(true);
});
});
Loading
Loading
@@ -57,7 +57,7 @@ describe('DiffDiscussions', () => {
 
it('dispatches toggleDiscussion when clicking collapse button', () => {
createComponent({ shouldCollapseDiscussions: true });
spyOn(wrapper.vm.$store, 'dispatch').and.stub();
jest.spyOn(wrapper.vm.$store, 'dispatch').mockImplementation();
const diffNotesToggle = findDiffNotesToggle();
diffNotesToggle.trigger('click');
 
Loading
Loading
@@ -74,7 +74,7 @@ describe('DiffDiscussions', () => {
 
expect(diffNotesToggle.text().trim()).toBe('1');
expect(diffNotesToggle.classes()).toEqual(
jasmine.arrayContaining(['btn-transparent', 'badge', 'badge-pill']),
expect.arrayContaining(['btn-transparent', 'badge', 'badge-pill']),
);
});
 
Loading
Loading
import Vue from 'vue';
import { cloneDeep } from 'lodash';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import { createStore } from '~/mr_notes/stores';
import DiffExpansionCell from '~/diffs/components/diff_expansion_cell.vue';
import { getPreviousLineIndex } from '~/diffs/store/utils';
Loading
Loading
@@ -69,7 +69,7 @@ describe('DiffExpansionCell', () => {
mockLine = getLine(mockFile, INLINE_DIFF_VIEW_TYPE, LINE_TO_USE);
store = createStore();
store.state.diffs.diffFiles = [mockFile];
spyOn(store, 'dispatch').and.returnValue(Promise.resolve());
jest.spyOn(store, 'dispatch').mockReturnValue(Promise.resolve());
});
 
const createComponent = (options = {}) => {
Loading
Loading
import Vue from 'vue';
import { createStore } from 'ee_else_ce/mr_notes/stores';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { mockTracking, triggerEvent } from 'spec/helpers/tracking_helper';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import { mockTracking, triggerEvent } from 'helpers/tracking_helper';
import DiffFileComponent from '~/diffs/components/diff_file.vue';
import { diffViewerModes, diffViewerErrors } from '~/ide/constants';
import diffFileMockDataReadable from '../mock_data/diff_file';
Loading
Loading
@@ -16,7 +16,7 @@ describe('DiffFile', () => {
file: JSON.parse(JSON.stringify(diffFileMockDataReadable)),
canCurrentUserFork: false,
}).$mount();
trackingSpy = mockTracking('_category_', vm.$el, spyOn);
trackingSpy = mockTracking('_category_', vm.$el, jest.spyOn);
});
 
afterEach(() => {
Loading
Loading
@@ -164,7 +164,7 @@ describe('DiffFile', () => {
});
 
it('should update store state', done => {
spyOn(vm.$store, 'dispatch');
jest.spyOn(vm.$store, 'dispatch').mockImplementation(() => {});
 
vm.isCollapsed = true;
 
Loading
Loading
@@ -211,7 +211,7 @@ describe('DiffFile', () => {
 
describe('watch collapsed', () => {
it('calls handleLoadCollapsedDiff if collapsed changed & file has no lines', done => {
spyOn(vm, 'handleLoadCollapsedDiff');
jest.spyOn(vm, 'handleLoadCollapsedDiff').mockImplementation(() => {});
 
vm.file.highlighted_diff_lines = undefined;
vm.file.parallel_diff_lines = [];
Loading
Loading
@@ -237,7 +237,7 @@ describe('DiffFile', () => {
canCurrentUserFork: false,
}).$mount();
 
spyOn(vm, 'handleLoadCollapsedDiff');
jest.spyOn(vm, 'handleLoadCollapsedDiff').mockImplementation(() => {});
 
vm.file.highlighted_diff_lines = undefined;
vm.file.parallel_diff_lines = [];
Loading
Loading
import Vue from 'vue';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { shallowMount } from '@vue/test-utils';
import DiffLineNoteForm from '~/diffs/components/diff_line_note_form.vue';
import NoteForm from '~/notes/components/note_form.vue';
import { createStore } from '~/mr_notes/stores';
import diffFileMockData from '../mock_data/diff_file';
import { noteableDataMock } from '../../notes/mock_data';
 
describe('DiffLineNoteForm', () => {
let component;
let wrapper;
let diffFile;
let diffLines;
const getDiffFileMock = () => Object.assign({}, diffFileMockData);
Loading
Loading
@@ -14,58 +14,57 @@ describe('DiffLineNoteForm', () => {
beforeEach(() => {
diffFile = getDiffFileMock();
diffLines = diffFile.highlighted_diff_lines;
component = createComponentWithStore(Vue.extend(DiffLineNoteForm), createStore(), {
diffFileHash: diffFile.file_hash,
diffLines,
line: diffLines[0],
noteTargetLine: diffLines[0],
});
Object.defineProperties(component, {
noteableData: { value: noteableDataMock },
isLoggedIn: { value: true },
const store = createStore();
store.state.notes.userData.id = 1;
store.state.notes.noteableData = noteableDataMock;
wrapper = shallowMount(DiffLineNoteForm, {
store,
propsData: {
diffFileHash: diffFile.file_hash,
diffLines,
line: diffLines[0],
noteTargetLine: diffLines[0],
},
});
component.$mount();
});
 
describe('methods', () => {
describe('handleCancelCommentForm', () => {
it('should ask for confirmation when shouldConfirm and isDirty passed as truthy', () => {
spyOn(window, 'confirm').and.returnValue(false);
jest.spyOn(window, 'confirm').mockReturnValue(false);
 
component.handleCancelCommentForm(true, true);
wrapper.vm.handleCancelCommentForm(true, true);
 
expect(window.confirm).toHaveBeenCalled();
});
 
it('should ask for confirmation when one of the params false', () => {
spyOn(window, 'confirm').and.returnValue(false);
jest.spyOn(window, 'confirm').mockReturnValue(false);
 
component.handleCancelCommentForm(true, false);
wrapper.vm.handleCancelCommentForm(true, false);
 
expect(window.confirm).not.toHaveBeenCalled();
 
component.handleCancelCommentForm(false, true);
wrapper.vm.handleCancelCommentForm(false, true);
 
expect(window.confirm).not.toHaveBeenCalled();
});
 
it('should call cancelCommentForm with lineCode', done => {
spyOn(window, 'confirm');
spyOn(component, 'cancelCommentForm');
spyOn(component, 'resetAutoSave');
component.handleCancelCommentForm();
jest.spyOn(window, 'confirm').mockImplementation(() => {});
jest.spyOn(wrapper.vm, 'cancelCommentForm').mockImplementation(() => {});
jest.spyOn(wrapper.vm, 'resetAutoSave').mockImplementation(() => {});
wrapper.vm.handleCancelCommentForm();
 
expect(window.confirm).not.toHaveBeenCalled();
component.$nextTick(() => {
expect(component.cancelCommentForm).toHaveBeenCalledWith({
wrapper.vm.$nextTick(() => {
expect(wrapper.vm.cancelCommentForm).toHaveBeenCalledWith({
lineCode: diffLines[0].line_code,
fileHash: component.diffFileHash,
fileHash: wrapper.vm.diffFileHash,
});
 
expect(component.resetAutoSave).toHaveBeenCalled();
expect(wrapper.vm.resetAutoSave).toHaveBeenCalled();
 
done();
});
Loading
Loading
@@ -74,17 +73,16 @@ describe('DiffLineNoteForm', () => {
 
describe('saveNoteForm', () => {
it('should call saveNote action with proper params', done => {
const saveDiffDiscussionSpy = spyOn(component, 'saveDiffDiscussion').and.returnValue(
Promise.resolve(),
);
spyOnProperty(component, 'formData').and.returnValue('formData');
const saveDiffDiscussionSpy = jest
.spyOn(wrapper.vm, 'saveDiffDiscussion')
.mockReturnValue(Promise.resolve());
 
component
wrapper.vm
.handleSaveNote('note body')
.then(() => {
expect(saveDiffDiscussionSpy).toHaveBeenCalledWith({
note: 'note body',
formData: 'formData',
formData: wrapper.vm.formData,
});
})
.then(done)
Loading
Loading
@@ -97,18 +95,14 @@ describe('DiffLineNoteForm', () => {
it('should init autosave', () => {
const key = 'autosave/Note/Issue/98//DiffNote//1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_1';
 
expect(component.autosave).toBeDefined();
expect(component.autosave.key).toEqual(key);
expect(wrapper.vm.autosave).toBeDefined();
expect(wrapper.vm.autosave.key).toEqual(key);
});
});
 
describe('template', () => {
it('should have note form', () => {
const { $el } = component;
expect($el.querySelector('.js-vue-textarea')).toBeDefined();
expect($el.querySelector('.js-vue-issue-save')).toBeDefined();
expect($el.querySelector('.js-vue-markdown-field')).toBeDefined();
expect(wrapper.find(NoteForm).exists()).toBe(true);
});
});
});
import Vue from 'vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import mountComponent from 'helpers/vue_mount_component_helper';
import FileRowStats from '~/diffs/components/file_row_stats.vue';
 
describe('Diff file row stats', () => {
Loading
Loading
import Vue from 'vue';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { shallowMount } from '@vue/test-utils';
import ImageDiffOverlay from '~/diffs/components/image_diff_overlay.vue';
import { createStore } from '~/mr_notes/stores';
import { imageDiffDiscussions } from '../mock_data/diff_discussions';
import Icon from '~/vue_shared/components/icon.vue';
 
describe('Diffs image diff overlay component', () => {
const dimensions = {
width: 100,
height: 200,
};
let Component;
let vm;
let wrapper;
let dispatch;
const getAllImageBadges = () => wrapper.findAll('.js-image-badge');
 
function createComponent(props = {}, extendStore = () => {}) {
const store = createStore();
 
extendStore(store);
vm = createComponentWithStore(Component, store, {
discussions: [...imageDiffDiscussions],
fileHash: 'ABC',
...props,
dispatch = jest.spyOn(store, 'dispatch').mockImplementation();
wrapper = shallowMount(ImageDiffOverlay, {
store,
propsData: {
discussions: [...imageDiffDiscussions],
fileHash: 'ABC',
...props,
},
methods: {
getImageDimensions: jest.fn().mockReturnValue(dimensions),
},
});
}
 
beforeAll(() => {
Component = Vue.extend(ImageDiffOverlay);
});
afterEach(() => {
vm.$destroy();
wrapper.destroy();
wrapper = null;
});
 
it('renders comment badges', () => {
createComponent();
spyOn(vm, 'getImageDimensions').and.returnValue(dimensions);
vm.$mount();
 
expect(vm.$el.querySelectorAll('.js-image-badge').length).toBe(2);
expect(getAllImageBadges()).toHaveLength(2);
});
 
it('renders index of discussion in badge', () => {
createComponent();
spyOn(vm, 'getImageDimensions').and.returnValue(dimensions);
vm.$mount();
expect(vm.$el.querySelectorAll('.js-image-badge')[0].textContent.trim()).toBe('1');
expect(vm.$el.querySelectorAll('.js-image-badge')[1].textContent.trim()).toBe('2');
const imageBadges = getAllImageBadges();
expect(
imageBadges
.at(0)
.text()
.trim(),
).toBe('1');
expect(
imageBadges
.at(1)
.text()
.trim(),
).toBe('2');
});
 
it('renders icon when showCommentIcon is true', () => {
createComponent({ showCommentIcon: true });
spyOn(vm, 'getImageDimensions').and.returnValue(dimensions);
vm.$mount();
 
expect(vm.$el.querySelector('.js-image-badge svg')).not.toBe(null);
expect(wrapper.find(Icon).exists()).toBe(true);
});
 
it('sets badge comment positions', () => {
createComponent();
spyOn(vm, 'getImageDimensions').and.returnValue(dimensions);
vm.$mount();
const imageBadges = getAllImageBadges();
 
expect(vm.$el.querySelectorAll('.js-image-badge')[0].style.left).toBe('10px');
expect(vm.$el.querySelectorAll('.js-image-badge')[0].style.top).toBe('10px');
expect(vm.$el.querySelectorAll('.js-image-badge')[1].style.left).toBe('5px');
expect(vm.$el.querySelectorAll('.js-image-badge')[1].style.top).toBe('5px');
expect(imageBadges.at(0).attributes('style')).toBe('left: 10px; top: 10px;');
expect(imageBadges.at(1).attributes('style')).toBe('left: 5px; top: 5px;');
});
 
it('renders single badge for discussion object', () => {
Loading
Loading
@@ -75,22 +81,15 @@ describe('Diffs image diff overlay component', () => {
...imageDiffDiscussions[0],
},
});
spyOn(vm, 'getImageDimensions').and.returnValue(dimensions);
vm.$mount();
 
expect(vm.$el.querySelectorAll('.js-image-badge').length).toBe(1);
expect(getAllImageBadges()).toHaveLength(1);
});
 
it('dispatches openDiffFileCommentForm when clicking overlay', () => {
createComponent({ canComment: true });
spyOn(vm, 'getImageDimensions').and.returnValue(dimensions);
vm.$mount();
spyOn(vm.$store, 'dispatch').and.stub();
wrapper.find('.js-add-image-diff-note-button').trigger('click', { offsetX: 0, offsetY: 0 });
 
vm.$el.querySelector('.js-add-image-diff-note-button').click();
expect(vm.$store.dispatch).toHaveBeenCalledWith('diffs/openDiffFileCommentForm', {
expect(dispatch).toHaveBeenCalledWith('diffs/openDiffFileCommentForm', {
fileHash: 'ABC',
x: 0,
y: 0,
Loading
Loading
@@ -100,28 +99,26 @@ describe('Diffs image diff overlay component', () => {
});
 
describe('toggle discussion', () => {
const getImageBadge = () => wrapper.find('.js-image-badge');
it('disables buttons when shouldToggleDiscussion is false', () => {
createComponent({ shouldToggleDiscussion: false });
spyOn(vm, 'getImageDimensions').and.returnValue(dimensions);
vm.$mount();
 
expect(vm.$el.querySelector('.js-image-badge').hasAttribute('disabled')).toBe(true);
expect(getImageBadge().attributes('disabled')).toEqual('disabled');
});
 
it('dispatches toggleDiscussion when clicking image badge', () => {
createComponent();
spyOn(vm, 'getImageDimensions').and.returnValue(dimensions);
vm.$mount();
getImageBadge().trigger('click');
 
spyOn(vm.$store, 'dispatch').and.stub();
vm.$el.querySelector('.js-image-badge').click();
expect(vm.$store.dispatch).toHaveBeenCalledWith('toggleDiscussion', { discussionId: '1' });
expect(dispatch).toHaveBeenCalledWith('toggleDiscussion', {
discussionId: '1',
});
});
});
 
describe('comment form', () => {
const getCommentIndicator = () => wrapper.find('.comment-indicator');
beforeEach(() => {
createComponent({}, store => {
store.state.diffs.commentForms.push({
Loading
Loading
@@ -130,17 +127,14 @@ describe('Diffs image diff overlay component', () => {
y: 10,
});
});
spyOn(vm, 'getImageDimensions').and.returnValue(dimensions);
vm.$mount();
});
 
it('renders comment form badge', () => {
expect(vm.$el.querySelector('.comment-indicator')).not.toBe(null);
expect(getCommentIndicator().exists()).toBe(true);
});
 
it('sets comment form badge position', () => {
expect(vm.$el.querySelector('.comment-indicator').style.left).toBe('20px');
expect(vm.$el.querySelector('.comment-indicator').style.top).toBe('10px');
expect(getCommentIndicator().attributes('style')).toBe('left: 20px; top: 10px;');
});
});
});
import Vue from 'vue';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import { createStore } from '~/mr_notes/stores';
import InlineDiffExpansionRow from '~/diffs/components/inline_diff_expansion_row.vue';
import diffFileMockData from '../mock_data/diff_file';
Loading
Loading
import Vue from 'vue';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import { createStore } from '~/mr_notes/stores';
import InlineDiffTableRow from '~/diffs/components/inline_diff_table_row.vue';
import diffFileMockData from '../mock_data/diff_file';
Loading
Loading
import Vue from 'vue';
import '~/behaviors/markdown/render_gfm';
import { createStore } from 'ee_else_ce/mr_notes/stores';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import InlineDiffView from '~/diffs/components/inline_diff_view.vue';
import diffFileMockData from '../mock_data/diff_file';
import discussionsMockData from '../mock_data/diff_discussions';
Loading
Loading
import Vue from 'vue';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import { createStore } from '~/mr_notes/stores';
import ParallelDiffExpansionRow from '~/diffs/components/parallel_diff_expansion_row.vue';
import diffFileMockData from '../mock_data/diff_file';
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment