Commit f8cda25d authored by Kamil Trzcinski's avatar Kamil Trzcinski

Merge remote-tracking branch 'origin-ee/master' into ee-fix-cluster-enviroment-missing

parents be0369a1 a45b739c
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.5-golang-1.8-git-2.13-chrome-62.0-node-8.x-yarn-1.2-postgresql-9.6"
.dedicated-runner: &dedicated-runner
retry: 1
tags:
- gitlab-org
.default-cache: &default-cache
key: "ruby-235-with-yarn"
paths:
......@@ -18,7 +23,6 @@ image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.5-golang-1.8-git
variables:
MYSQL_ALLOW_EMPTY_PASSWORD: "1"
ELASTIC_URL: "http://elastic:changeme@docker.elastic.co-elasticsearch-elasticsearch:9200"
RAILS_ENV: "test"
NODE_ENV: "test"
SIMPLECOV: "true"
......@@ -28,8 +32,10 @@ variables:
KNAPSACK_RSPEC_SUITE_REPORT_PATH: knapsack/${CI_PROJECT_NAME}/rspec_report-master.json
KNAPSACK_SPINACH_SUITE_REPORT_PATH: knapsack/${CI_PROJECT_NAME}/spinach_report-master.json
FLAKY_RSPEC_SUITE_REPORT_PATH: rspec_flaky/report-suite.json
## EE specific variables ##
# This hack is needed to make ES not that memory hungry
ES_JAVA_OPTS: "-Xms256m -Xmx256m"
ELASTIC_URL: "http://elastic:changeme@docker.elastic.co-elasticsearch-elasticsearch:9200"
before_script:
- bundle --version
......@@ -45,11 +51,6 @@ stages:
- post-cleanup
# Predefined scopes
.dedicated-runner: &dedicated-runner
retry: 1
tags:
- gitlab-org
.tests-metadata-state: &tests-metadata-state
<<: *dedicated-runner
variables:
......@@ -89,8 +90,8 @@ stages:
.rspec-metadata: &rspec-metadata
<<: *dedicated-runner
<<: *pull-cache
<<: *except-docs
<<: *pull-cache
stage: test
script:
- JOB_NAME=( $CI_JOB_NAME )
......@@ -138,8 +139,8 @@ stages:
.spinach-metadata: &spinach-metadata
<<: *dedicated-runner
<<: *pull-cache
<<: *except-docs
<<: *pull-cache
stage: test
script:
- JOB_NAME=( $CI_JOB_NAME )
......@@ -178,6 +179,7 @@ stages:
# Trigger a package build in omnibus-gitlab repository
#
package-qa:
<<: *dedicated-runner
image: ruby:2.4-alpine
before_script: []
stage: build
......@@ -191,6 +193,7 @@ package-qa:
# Review docs base
.review-docs: &review-docs
<<: *dedicated-runner
image: ruby:2.4-alpine
before_script:
- gem install gitlab --no-doc
......@@ -297,9 +300,9 @@ flaky-examples-check:
- scripts/detect-new-flaky-examples $NEW_FLAKY_SPECS_REPORT
setup-test-env:
<<: *use-pg
<<: *dedicated-runner
<<: *except-docs
<<: *use-pg
stage: prepare
cache:
<<: *default-cache
......@@ -390,18 +393,18 @@ spinach-mysql 3 4: *spinach-metadata-mysql
SETUP_DB: "false"
.rake-exec: &rake-exec
<<: *ruby-static-analysis
<<: *dedicated-runner
<<: *except-docs
<<: *pull-cache
<<: *ruby-static-analysis
stage: test
script:
- bundle exec rake $CI_JOB_NAME
static-analysis:
<<: *ruby-static-analysis
<<: *dedicated-runner
<<: *except-docs
<<: *ruby-static-analysis
stage: test
script:
- scripts/static-analysis
......@@ -467,10 +470,16 @@ db:migrate:reset-mysql:
<<: *db-migrate-reset
<<: *use-mysql
db:check-schema-pg:
<<: *db-migrate-reset
<<: *use-pg
script:
- source scripts/schema_changed.sh
.migration-paths: &migration-paths
<<: *dedicated-runner
<<: *pull-cache
<<: *except-docs
<<: *pull-cache
stage: test
variables:
SETUP_DB: "false"
......@@ -538,12 +547,6 @@ db:seed_fu-mysql:
<<: *db-seed_fu
<<: *use-mysql
db:check-schema-pg:
<<: *db-migrate-reset
<<: *use-pg
script:
- sh scripts/schema_changed.sh
# Frontend-related jobs
gitlab:assets:compile:
<<: *dedicated-runner
......@@ -568,10 +571,10 @@ gitlab:assets:compile:
- webpack-report/
karma:
<<: *use-pg
<<: *dedicated-runner
<<: *except-docs
<<: *pull-cache
<<: *use-pg
stage: test
variables:
BABEL_ENV: "coverage"
......@@ -610,6 +613,7 @@ codequality:
paths: [codeclimate.json]
qa:internal:
<<: *dedicated-runner
<<: *except-docs
stage: test
variables:
......@@ -699,8 +703,9 @@ cache gems:
- master@gitlab-org/gitlab-ee
gitlab_git_test:
<<: *pull-cache
<<: *dedicated-runner
<<: *except-docs
<<: *pull-cache
variables:
SETUP_DB: "false"
script:
......
/* eslint-disable comma-dangle, space-before-function-paren, no-new */
/* global MilestoneSelect */
/* global Sidebar */
import Vue from 'vue';
import weight from 'ee/sidebar/components/weight/weight.vue';
import Flash from '../../flash';
import Sidebar from '../../right_sidebar';
import eventHub from '../../sidebar/event_hub';
import assigneeTitle from '../../sidebar/components/assignees/assignee_title';
import assignees from '../../sidebar/components/assignees/assignees';
......
......@@ -25,7 +25,6 @@ class ListIssue {
this.isLoading = {
weight: false,
};
this.isLoading = {};
this.sidebarInfoEndpoint = obj.issue_sidebar_endpoint;
this.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint;
this.milestone_id = obj.milestone_id;
......
/* global CommentsStore */
/* global notes */
import Vue from 'vue';
import collapseIcon from '../icons/collapse_icon.svg';
import Notes from '../../notes';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
const DiffNoteAvatars = Vue.extend({
......@@ -129,7 +129,7 @@ const DiffNoteAvatars = Vue.extend({
},
methods: {
clickedAvatar(e) {
notes.onAddDiffNote(e);
Notes.instance.onAddDiffNote(e);
// Toggle the active state of the toggle all button
this.toggleDiscussionsToggleState();
......
......@@ -11,7 +11,7 @@ import NewBranchForm from './new_branch_form';
/* global NotificationsDropdown */
import groupAvatar from './group_avatar';
import GroupLabelSubscription from './group_label_subscription';
/* global LineHighlighter */
import LineHighlighter from './line_highlighter';
import BuildArtifacts from './build_artifacts';
import CILintEditor from './ci_lint_editor';
import groupsSelect from './groups_select';
......@@ -21,7 +21,7 @@ import NamespaceSelect from './namespace_select';
import NewCommitForm from './new_commit_form';
import Project from './project';
import projectAvatar from './project_avatar';
/* global MergeRequest */
import MergeRequest from './merge_request';
import Compare from './compare';
import initCompareAutocomplete from './compare_autocomplete';
/* global PathLocks */
......@@ -30,7 +30,7 @@ import ProjectNew from './project_new';
import projectImport from './project_import';
import Labels from './labels';
import LabelManager from './label_manager';
/* global Sidebar */
import Sidebar from './right_sidebar';
/* global WeightSelect */
/* global AdminEmailSelect */
......
......@@ -104,8 +104,8 @@ class GeoNodeStatus {
graphItems.forEach((item) => {
$itemEl.find(item.itemSel)
.toggleClass('has-value has-tooltip', !!item.itemCount)
.attr('data-original-title', `${item.itemTooltip}: ${item.itemPercent}%`)
.text(item.itemCount || '')
.attr('data-original-title', `${item.itemTooltip}: ${item.itemCount}`)
.text(`${item.itemPercent}%` || '')
.css('width', `${item.itemPercent}%`);
});
}
......
......@@ -3,7 +3,7 @@
/* global WeightSelect */
import LabelsSelect from './labels_select';
import IssuableContext from './issuable_context';
/* global Sidebar */
import Sidebar from './right_sidebar';
import DueDateSelectors from './due_date_select';
......@@ -17,5 +17,5 @@ export default () => {
new WeightSelect();
new IssuableContext(sidebarOptions.currentUser);
new DueDateSelectors();
window.sidebar = new Sidebar();
Sidebar.initialize();
};
/* global Notes */
import Notes from './notes';
export default () => {
const dataEl = document.querySelector('.js-notes-data');
......@@ -10,5 +10,7 @@ export default () => {
autocomplete,
} = JSON.parse(dataEl.innerHTML);
window.notes = new Notes(notesUrl, notesIds, now, diffView, autocomplete);
// Create a singleton so that we don't need to assign
// into the window object, we can just access the current isntance with Notes.instance
Notes.initialize(notesUrl, notesIds, now, diffView, autocomplete);
};
......@@ -175,4 +175,4 @@ LineHighlighter.prototype.__setLocationHash__ = function(value) {
}, document.title, value);
};
window.LineHighlighter = LineHighlighter;
export default LineHighlighter;
......@@ -50,10 +50,7 @@ import './layout_nav';
import LazyLoader from './lazy_loader';
import './line_highlighter';
import initLogoAnimation from './logo';
import './merge_request';
import './merge_request_tabs';
import './milestone_select';
import './notes';
import './notifications_dropdown';
import './notifications_form';
import './pager';
......@@ -61,7 +58,6 @@ import './preview_markdown';
import './project_import';
import './projects_dropdown';
import './render_gfm';
import './right_sidebar';
import initBreadcrumbs from './breadcrumb';
// EE-only scripts
......
......@@ -7,142 +7,138 @@ import './merge_request_tabs';
import IssuablesHelper from './helpers/issuables_helper';
import { addDelimiter } from './lib/utils/text_utility';
(function() {
this.MergeRequest = (function() {
function MergeRequest(opts) {
// Initialize MergeRequest behavior
//
// Options:
// action - String, current controller action
//
this.opts = opts != null ? opts : {};
this.submitNoteForm = this.submitNoteForm.bind(this);
this.$el = $('.merge-request');
this.$('.show-all-commits').on('click', (function(_this) {
return function() {
return _this.showAllCommits();
};
})(this));
this.initTabs();
this.initMRBtnListeners();
this.initCommitMessageListeners();
this.closeReopenReportToggle = IssuablesHelper.initCloseReopenReport();
if ($("a.btn-close").length) {
this.taskList = new TaskList({
dataType: 'merge_request',
fieldName: 'description',
selector: '.detail-page-description',
onSuccess: (result) => {
document.querySelector('#task_status').innerText = result.task_status;
document.querySelector('#task_status_short').innerText = result.task_status_short;
}
});
}
}
// Local jQuery finder
MergeRequest.prototype.$ = function(selector) {
return this.$el.find(selector);
function MergeRequest(opts) {
// Initialize MergeRequest behavior
//
// Options:
// action - String, current controller action
//
this.opts = opts != null ? opts : {};
this.submitNoteForm = this.submitNoteForm.bind(this);
this.$el = $('.merge-request');
this.$('.show-all-commits').on('click', (function(_this) {
return function() {
return _this.showAllCommits();
};
MergeRequest.prototype.initTabs = function() {
if (window.mrTabs) {
window.mrTabs.unbindEvents();
})(this));
this.initTabs();
this.initMRBtnListeners();
this.initCommitMessageListeners();
this.closeReopenReportToggle = IssuablesHelper.initCloseReopenReport();
if ($("a.btn-close").length) {
this.taskList = new TaskList({
dataType: 'merge_request',
fieldName: 'description',
selector: '.detail-page-description',
onSuccess: (result) => {
document.querySelector('#task_status').innerText = result.task_status;
document.querySelector('#task_status_short').innerText = result.task_status_short;
}
window.mrTabs = new gl.MergeRequestTabs(this.opts);
};
MergeRequest.prototype.showAllCommits = function() {
this.$('.first-commits').remove();
return this.$('.all-commits').removeClass('hide');
};
MergeRequest.prototype.initMRBtnListeners = function() {
var _this;
_this = this;
return $('a.btn-close, a.btn-reopen').on('click', function(e) {
var $this, shouldSubmit;
$this = $(this);
shouldSubmit = $this.hasClass('btn-comment');
if (shouldSubmit && $this.data('submitted')) {
return;
}
if (this.closeReopenReportToggle) this.closeReopenReportToggle.setDisable();
if (shouldSubmit) {
if ($this.hasClass('btn-comment-and-close') || $this.hasClass('btn-comment-and-reopen')) {
e.preventDefault();
e.stopImmediatePropagation();
_this.submitNoteForm($this.closest('form'), $this);
}
}
});
};
MergeRequest.prototype.submitNoteForm = function(form, $button) {
var noteText;
noteText = form.find("textarea.js-note-text").val();
if (noteText.trim().length > 0) {
form.submit();
$button.data('submitted', true);
return $button.trigger('click');
}
};
MergeRequest.prototype.initCommitMessageListeners = function() {
$(document).on('click', 'a.js-with-description-link', function(e) {
var textarea = $('textarea.js-commit-message');
e.preventDefault();
});
}
}
// Local jQuery finder
MergeRequest.prototype.$ = function(selector) {
return this.$el.find(selector);
};
MergeRequest.prototype.initTabs = function() {
if (window.mrTabs) {
window.mrTabs.unbindEvents();
}
window.mrTabs = new gl.MergeRequestTabs(this.opts);
};
MergeRequest.prototype.showAllCommits = function() {
this.$('.first-commits').remove();
return this.$('.all-commits').removeClass('hide');
};
MergeRequest.prototype.initMRBtnListeners = function() {
var _this;
_this = this;
return $('a.btn-close, a.btn-reopen').on('click', function(e) {
var $this, shouldSubmit;
$this = $(this);
shouldSubmit = $this.hasClass('btn-comment');
if (shouldSubmit && $this.data('submitted')) {
return;
}
textarea.val(textarea.data('messageWithDescription'));
$('.js-with-description-hint').hide();
$('.js-without-description-hint').show();
});
if (this.closeReopenReportToggle) this.closeReopenReportToggle.setDisable();
$(document).on('click', 'a.js-without-description-link', function(e) {
var textarea = $('textarea.js-commit-message');
if (shouldSubmit) {
if ($this.hasClass('btn-comment-and-close') || $this.hasClass('btn-comment-and-reopen')) {
e.preventDefault();
e.stopImmediatePropagation();
textarea.val(textarea.data('messageWithoutDescription'));
$('.js-with-description-hint').show();
$('.js-without-description-hint').hide();
});
};
MergeRequest.prototype.updateStatusText = function(classToRemove, classToAdd, newStatusText) {
$('.detail-page-header .status-box')
.removeClass(classToRemove)
.addClass(classToAdd)
.find('span')
.text(newStatusText);
};
MergeRequest.prototype.decreaseCounter = function(by = 1) {
const $el = $('.nav-links .js-merge-counter');
const count = Math.max((parseInt($el.text().replace(/[^\d]/, ''), 10) - by), 0);
$el.text(addDelimiter(count));
};
MergeRequest.prototype.hideCloseButton = function() {
const el = document.querySelector('.merge-request .js-issuable-actions');
const closeDropdownItem = el.querySelector('li.close-item');
if (closeDropdownItem) {
closeDropdownItem.classList.add('hidden');
// Selects the next dropdown item
el.querySelector('li.report-item').click();
} else {
// No dropdown just hide the Close button
el.querySelector('.btn-close').classList.add('hidden');
_this.submitNoteForm($this.closest('form'), $this);
}
// Dropdown for mobile screen
el.querySelector('li.js-close-item').classList.add('hidden');
};
return MergeRequest;
})();
}).call(window);
}
});
};
MergeRequest.prototype.submitNoteForm = function(form, $button) {
var noteText;
noteText = form.find("textarea.js-note-text").val();
if (noteText.trim().length > 0) {
form.submit();
$button.data('submitted', true);
return $button.trigger('click');
}
};
MergeRequest.prototype.initCommitMessageListeners = function() {
$(document).on('click', 'a.js-with-description-link', function(e) {
var textarea = $('textarea.js-commit-message');
e.preventDefault();
textarea.val(textarea.data('messageWithDescription'));
$('.js-with-description-hint').hide();
$('.js-without-description-hint').show();
});
$(document).on('click', 'a.js-without-description-link', function(e) {
var textarea = $('textarea.js-commit-message');
e.preventDefault();
textarea.val(textarea.data('messageWithoutDescription'));
$('.js-with-description-hint').show();
$('.js-without-description-hint').hide();
});
};
MergeRequest.prototype.updateStatusText = function(classToRemove, classToAdd, newStatusText) {
$('.detail-page-header .status-box')
.removeClass(classToRemove)
.addClass(classToAdd)
.find('span')
.text(newStatusText);
};
MergeRequest.prototype.decreaseCounter = function(by = 1) {
const $el = $('.nav-links .js-merge-counter');
const count = Math.max((parseInt($el.text().replace(/[^\d]/, ''), 10) - by), 0);
$el.text(addDelimiter(count));
};
MergeRequest.prototype.hideCloseButton = function() {
const el = document.querySelector('.merge-request .js-issuable-actions');
const closeDropdownItem = el.querySelector('li.close-item');
if (closeDropdownItem) {
closeDropdownItem.classList.add('hidden');
// Selects the next dropdown item
el.querySelector('li.report-item').click();
} else {
// No dropdown just hide the Close button
el.querySelector('.btn-close').classList.add('hidden');
}
// Dropdown for mobile screen
el.querySelector('li.js-close-item').classList.add('hidden');
};
export default MergeRequest;
/* eslint-disable no-new, class-methods-use-this */
/* global notes */
import Cookies from 'js-cookie';
import Flash from './flash';
......@@ -16,6 +15,7 @@ import initDiscussionTab from './image_diff/init_discussion_tab';
import Diff from './diff';
import { localTimeAgo } from './lib/utils/datetime_utility';
import syntaxHighlight from './syntax_highlight';
import Notes from './notes';
/* eslint-disable max-len */
// MergeRequestTabs
......@@ -325,7 +325,7 @@ import syntaxHighlight from './syntax_highlight';
if (anchor && anchor.length > 0) {
const notesContent = anchor.closest('.notes_content');
const lineType = notesContent.hasClass('new') ? 'new' : 'old';
notes.toggleDiffNote({
Notes.instance.toggleDiffNote({
target: anchor,
lineType,
forceShow: true,
......
......@@ -37,6 +37,12 @@ const MAX_VISIBLE_COMMIT_LIST_COUNT = 3;
const REGEX_QUICK_ACTIONS = /^\/\w+.*$/gm;
export default class Notes {
static initialize(notes_url, note_ids, last_fetched_at, view, enableGFM = true) {
if (!this.instance) {
this.instance = new Notes(notes_url, note_ids, last_fetched_at, view, enableGFM);
}
}
constructor(notes_url, note_ids, last_fetched_at, view, enableGFM = true) {
this.updateTargetButtons = this.updateTargetButtons.bind(this);
this.updateComment = this.updateComment.bind(this);
......
<script>
/* global LineHighlighter */
import { mapGetters } from 'vuex';
import LineHighlighter from '../../line_highlighter';
import syntaxHighlight from '../../syntax_highlight';
export default {
......
This diff is collapsed.
/* global Mousetrap */
/* global sidebar */
import _ from 'underscore';
import 'mousetrap';
import Sidebar from './right_sidebar';
import ShortcutsNavigation from './shortcuts_navigation';
import { CopyAsGFM } from './behaviors/copy_as_gfm';
......@@ -69,7 +69,7 @@ export default class ShortcutsIssuable extends ShortcutsNavigation {
}
static openSidebarDropdown(name) {
sidebar.openDropdown(name);
Sidebar.instance.openDropdown(name);
return false;
}
}
......@@ -6,7 +6,7 @@ Vue.use(VueResource);
export default class MRWidgetService {
constructor(endpoints) {
this.mergeResource = Vue.resource(endpoints.mergePath);
this.mergeCheckResource = Vue.resource(endpoints.statusPath);
this.mergeCheckResource = Vue.resource(`${endpoints.statusPath}?serializer=widget`);
this.cancelAutoMergeResource = Vue.resource(endpoints.cancelAutoMergePath);
this.removeWIPResource = Vue.resource(endpoints.removeWIPPath);
this.removeSourceBranchResource = Vue.resource(endpoints.sourceBranchPath);
......
......@@ -11,12 +11,14 @@
.status-neutral,
.status-red, {
height: 100%;
font-size: $tooltip-font-size;
font-weight: normal;
color: $white-light;
line-height: 20px;
&.has-value {
padding: 0 10px;
min-width: 25px;
padding: 0 5px;
}
&:hover {
......
......@@ -133,7 +133,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
.new(project, current_user, wip_event: 'unwip')
.execute(@merge_request)
render json: serializer.represent(@merge_request)
render json: serialize_widget(@merge_request)
end
def commit_change_content
......@@ -149,7 +149,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
.new(@project, current_user)
.cancel(@merge_request)
render json: serializer.represent(@merge_request)
render json: serialize_widget(@merge_request)
end
def merge
......@@ -313,6 +313,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end
end
def serialize_widget(merge_request)
serializer.represent(merge_request, serializer: 'widget')
end
def serializer
MergeRequestSerializer.new(current_user: current_user, project: merge_request.project)
end
......
......@@ -32,7 +32,7 @@ module IssuablesHelper
end
end
def serialize_issuable(issuable)
def serialize_issuable(issuable, serializer: nil)
serializer_klass = case issuable
when Issue
IssueSerializer
......@@ -42,7 +42,7 @@ module IssuablesHelper
serializer_klass
.new(current_user: current_user, project: issuable.project)
.represent(issuable)
.represent(issuable, serializer: serializer)
.to_json
end
......
......@@ -3,14 +3,6 @@ class IssuableEntity < Grape::Entity
expose :id
expose :iid
expose :author_id
expose :description
expose :lock_version
expose :milestone_id
expose :title
expose :updated_by_id
expose :created_at
expose :updated_at
expose :milestone, using: API::Entities::Milestone
expose :labels, using: LabelEntity
end
class IssuableSidebarEntity < Grape::Entity
include TimeTrackableEntity
include RequestAwareEntity
prepend ::EE::IssuableSidebarEntity
......@@ -9,9 +10,4 @@ class IssuableSidebarEntity < Grape::Entity
expose :subscribed do |issuable|
issuable.subscribed?(request.current_user, issuable.project)
end
expose :time_estimate
expose :total_time_spent
expose :human_time_estimate
expose :human_total_time_spent
end
......@@ -2,7 +2,15 @@ class IssueEntity < IssuableEntity
include TimeTrackableEntity
expose :state
expose :milestone_id
expose :updated_by_id
expose :created_at
expose :updated_at
expose :deleted_at
expose :milestone, using: API::Entities::Milestone
expose :labels, using: LabelEntity
expose :lock_version
expose :author_id
expose :confidential
expose :discussion_locked
expose :assignees, using: API::Entities::UserBasic
......
class MergeRequestSerializer < BaseSerializer
# This overrided method takes care of which entity should be used
# to serialize the `merge_request` based on `basic` key in `opts` param.
# to serialize the `merge_request` based on `serializer` key in `opts` param.
# Hence, `entity` doesn't need to be declared on the class scope.
def represent(merge_request, opts = {})
entity =
case opts[:serializer]
when 'basic', 'sidebar'
MergeRequestBasicEntity
else
MergeRequestEntity
when 'widget'
MergeRequestWidgetEntity
end
super(merge_request, opts, entity)
......
class MergeRequestEntity < IssuableEntity
include TimeTrackableEntity
prepend ::EE::MergeRequestEntity
class MergeRequestWidgetEntity < IssuableEntity
prepend ::EE::MergeRequestWidgetEntity
expose :state
expose :deleted_at
expose :in_progress_merge_commit_sha
expose :merge_commit_sha
expose :merge_error
......
......@@ -21,7 +21,7 @@
-# haml-lint:disable InlineJavaScript
:javascript
window.gl = window.gl || {};
window.gl.mrWidgetData = #{serialize_issuable(@merge_request)}
window.gl.mrWidgetData = #{serialize_issuable(@merge_request, serializer: 'widget')}
// Append static, server-generated data not included in merge request entity (EE-Only)
// Object.assign would be useful here, but it blows up Phantom.js in tests
......
......@@ -8,16 +8,17 @@
= image_tag 'illustrations/issues.svg'
.col-xs-12
.text-content
- if has_button && current_user
- if current_user
%h4
= _("The Issue Tracker is the place to add things that need to be improved or solved in a project")
%p
= _("Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable.")
.text-center
- if project_select_button
= render 'shared/new_project_item_select', path: 'issues/new', label: 'New issue', type: :issues
- else
= link_to 'New issue', button_path, class: 'btn btn-success', title: 'New issue', id: 'new_issue_link'
- if has_button
.text-center
- if project_select_button
= render 'shared/new_project_item_select', path: 'issues/new', label: 'New issue', type: :issues
- else
= link_to 'New issue', button_path, class: 'btn btn-success', title: 'New issue', id: 'new_issue_link'
- else
%h4.text-center= _("There are no issues to show")
%p
......
---
title: 'Geo: Show sync percent on bar graph and count within tooltips'
merge_request: 3794
author:
type: changed
---
title: Stop sending milestone and labels data over the wire for MR widget requests
merge_request:
author:
type: performance
......@@ -502,8 +502,8 @@ stages:
unit_test:
stage: test
script:
- composer install
- cp .env.example .env
- composer install
- php artisan key:generate
- php artisan migrate
- vendor/bin/phpunit
......
......@@ -84,9 +84,19 @@ this branch to prevent any changes from being lost.
![Diverged branch](repository_mirroring/repository_mirroring_diverged_branch.png)
### Trigger update using API
>[Introduced][ee-3453] in GitLab Enterprise Edition 10.3.
Pull mirroring uses polling to detect new branches and commits added upstream,
often many minutes afterwards. If you notify GitLab by [API][pull-api], updates
will be pulled immediately.
Read the [Pull Mirror Trigger API docs][pull-api].
### Pull only protected branches
>[Introduced][ee-3326] in Gitlab Enterprise Edition 10.3.
>[Introduced][ee-3326] in GitLab Enterprise Edition 10.3.
You can choose to only pull the protected branches from your remote repository to GitLab.
......@@ -199,45 +209,50 @@ If you need to change the key at any time, you can press the `Regenerate key`
button to do so. You'll have to update the source repository with the new key
to keep the mirror running.
## How it works
### How it works
Once you activate the pull mirroring feature, the mirror will be inserted into a queue.
A scheduler will start every minute and schedule a fixed amount of mirrors for update, based
on the configured maximum capacity.
Once you activate the pull mirroring feature, the mirror will be inserted into
a queue. A scheduler will start every minute and schedule a fixed amount of
mirrors for update, based on the configured maximum capacity.
If the mirror successfully updates it will be enqueued once again with a small backoff
period.
If the mirror successfully updates it will be enqueued once again with a small
backoff period.
If the mirror fails (eg: branch diverged from upstream), the project's
backoff period will be penalized each time it fails up to a maximum amount of time.
If the mirror fails (eg: branch diverged from upstream), the project's backoff
period will be penalized each time it fails up to a maximum amount of time.
## Pushing to a remote repository
>[Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/249) in GitLab Enterprise Edition 8.7.
>[Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/249) in
GitLab Enterprise Edition 8.7.
For an existing project, you can set up mirror pushing by visiting your project's
For an existing project, you can set up push mirror from your project's
**Settings ➔ Repository** and searching for the "Push to a remote repository"
section. Check the "Remote mirror repository" box and fill in the Git URL of the
repository to push to. Hit **Save changes** for the changes to take effect.
section. Check the "Remote mirror repository" box and fill in the Git URL of
the repository to push to. Click **Save changes** for the changes to take
effect.
![Push settings](repository_mirroring/repository_mirroring_push_settings.png)
Similarly to the pull mirroring, since the upstream repository functions as a
mirror to the repository in GitLab, you are advised not to push commits directly
to the mirrored repository. Instead, all changes will end up in the mirrored repository
whenever commits are pushed to GitLab, or when a [forced update](#forcing-an-update) is initiated.
Similarly to pull mirroring, when push mirroring is enabled, you are advised
not to push commits directly to the mirrored repository to prevent the mirror
diverging. All changes will end up in the mirrored repository whenever commits
are pushed to GitLab, or when a [forced update](#forcing-an-update) is
initiated.
Pushes into GitLab are automatically pushed to the remote mirror at least once every 5 minutes
after they come in or 1 minute if **push only protected branches** is enabled.
Pushes into GitLab are automatically pushed to the remote mirror at least once
every 5 minutes after they are received or once every minute if **push only
protected branches** is enabled.
In case of a diverged branch, you will see an error indicated at the
**Mirror repository** settings.
In case of a diverged branch, you will see an error indicated at the **Mirror
repository** settings.
![Diverged branch](repository_mirroring/repository_mirroring_diverged_branch_push.png)
![Diverged branch](
repository_mirroring/repository_mirroring_diverged_branch_push.png)
### Push only protected branches
>[Introduced][ee-3350] in Gitlab Enterprise Edition 10.3.
>[Introduced][ee-3350] in GitLab Enterprise Edition 10.3.
You can choose to only push your protected branches from GitLab to your remote repository.
......@@ -259,7 +274,7 @@ To set up a mirror from GitLab to GitHub, you need to follow these steps:
1. And either wait or trigger the "Update Now" button:
![update now](repository_mirroring/repository_mirroring_gitlab_push_to_a_remote_repository_update_now.png)
## Forcing an update
While mirrors are scheduled to update automatically, you can always force an update (either **push** or
......@@ -270,19 +285,38 @@ While mirrors are scheduled to update automatically, you can always force an upd
- in the tags page
- in the **Mirror repository** settings page
## Using both mirroring methods at the same time
## Bidirectional mirroring
> **Warning:** There is no bidirectional support without conflicts. If you
> configure a repository to pull and push to a second remote, there is no
> guarantee that it will update correctly on both remotes. If you configure
> a repository for bidirectional mirroring, you should consider when conflicts
> occur who and how they will be resolved.
Rewriting any mirrored commit on either remote will cause conflicts and
mirroring to fail. This can be prevented by [only pulling protected branches](
#pull-only-protected-branches) and [only pushing protected branches](
#push-only-protected-branches). You should protect the branches you wish to
mirror on both remotes to prevent conflicts caused by rewriting history.
Currently there is no bidirectional support without conflicts. That means that
if you configure a repository to both pull and push to a second one, there is
no guarantee that it will update correctly on both remotes.
You can try [configuring custom Git hooks][hooks] on the GitLab server in order
to resolve this issue.
Bidirectional mirroring also creates a race condition where commits to the same
branch in close proximity will cause conflicts. The race condition can be
mitigated by reducing the mirroring delay by using a Push event webhook to
trigger an immediate pull to GitLab. Push mirroring from GitLab is rate limited
to once per minute when only push mirroring protected branches.
It may be possible to implement a locking mechanism using the server-side
`pre-receive` hook to prevent the race condition. Read about [configuring
custom Git hooks][hooks] on the GitLab server.
[ee-51]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/51
[ee-2551]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2551
[ee-3117]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3117
[ee-3326]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3326
[ee-3350]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3350
[ee-3453]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3453
[perms]: ../user/permissions.md
[hooks]: https://docs.gitlab.com/ee/administration/custom_hooks.html
[hooks]: ../administration/custom_hooks.html
[deploy-key]: ../ssh/README.md#deploy-keys
[webhook]: ../user/project/integrations/webhooks.html#push-events
[pull-api]: ../api/projects.html#start-the-pull-mirroring-process-for-a-project
module EE
module MergeRequestEntity
module MergeRequestWidgetEntity
extend ActiveSupport::Concern
prepended do
......
......@@ -12,6 +12,27 @@ module QA
autoload :Browser, 'qa/runtime/browser'
end
##
# GitLab QA fabrication mechanisms
#
module Factory
autoload :Base, 'qa/factory/base'
module Resource
autoload :Sandbox, 'qa/factory/resource/sandbox'
autoload :Group, 'qa/factory/resource/group'
autoload :Project, 'qa/factory/resource/project'
end
module Repository
autoload :Push, 'qa/factory/repository/push'
end
module Settings
autoload :HashedStorage, 'qa/factory/settings/hashed_storage'
end
end
##
# GitLab QA Scenarios
#
......@@ -34,31 +55,6 @@ module QA
autoload :Mattermost, 'qa/scenario/test/integration/mattermost'
end
end
##
# GitLab instance scenarios.
#
module Gitlab
module Group
autoload :Create, 'qa/scenario/gitlab/group/create'
end
module Project
autoload :Create, 'qa/scenario/gitlab/project/create'
end
module Repository
autoload :Push, 'qa/scenario/gitlab/repository/push'
end
module Sandbox
autoload :Prepare, 'qa/scenario/gitlab/sandbox/prepare'
end
module Admin
autoload :HashedStorage, 'qa/scenario/gitlab/admin/hashed_storage'
end
end
end
##
......
......@@ -16,18 +16,18 @@ module QA
end
end
module Scenario
module Factory
autoload :License, 'qa/ee/factory/license'
module Geo
autoload :Node, 'qa/ee/scenario/geo/node'
autoload :Node, 'qa/ee/factory/geo/node'
end
end
module Scenario
module Test
autoload :Geo, 'qa/ee/scenario/test/geo'
end
module License
autoload :Add, 'qa/ee/scenario/license/add'
end
end
end
end
module QA
module EE
module Scenario
module Factory
module Geo
class Node < QA::Scenario::Template
class Node < QA::Factory::Base
attr_accessor :address
def perform
def fabricate!
QA::Page::Main::Login.act { sign_in_using_credentials }
QA::Page::Main::Menu.act { go_to_admin_area }
QA::Page::Admin::Menu.act { go_to_geo_nodes }
......
module QA
module EE
module Factory
class License < QA::Factory::Base
def fabricate!(license)
QA::Page::Main::Login.act { sign_in_using_credentials }
QA::Page::Main::Menu.act { go_to_admin_area }
QA::Page::Admin::Menu.act { go_to_license }
EE::Page::Admin::License.act(license) do |key|
add_new_license(key) if no_license?
end
QA::Page::Main::Menu.act { sign_out }
end
end
end
end
end
module QA
module EE
module Scenario
module License
class Add < QA::Scenario::Template
def perform(license)
QA::Page::Main::Login.act { sign_in_using_credentials }
QA::Page::Main::Menu.act { go_to_admin_area }
QA::Page::Admin::Menu.act { go_to_license }
EE::Page::Admin::License.act(license) do |key|
add_new_license(key) if no_license?
end
QA::Page::Main::Menu.act { sign_out }
end
end
end
end
end
end
......@@ -39,32 +39,26 @@ module QA
end
def add_license
# TODO EE license to Runtime.license, gitlab-org/gitlab-qa#86
#
puts 'Adding GitLab EE license ...'
QA::Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do
Scenario::License::Add.perform(ENV['EE_LICENSE'])
Factory::License.fabricate!(ENV['EE_LICENSE'])
end
end
def enable_hashed_storage
# TODO, Factory::HashedStorage - gitlab-org/gitlab-qa#86
#
puts 'Enabling hashed repository storage setting ...'
QA::Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do
QA::Scenario::Gitlab::Admin::HashedStorage.perform(:enabled)
QA::Factory::Settings::HashedStorage.fabricate!(:enabled)
end
end
def add_secondary_node
# TODO, Factory::Geo::Node - gitlab-org/gitlab-qa#86
#
puts 'Adding new Geo secondary node ...'
QA::Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do
Scenario::Geo::Node.perform do |node|
Factory::Geo::Node.fabricate! do |node|
node.address = QA::Runtime::Scenario.geo_secondary_address
end
end
......
......@@ -11,7 +11,7 @@ module QA
return unless ENV['EE_LICENSE']
QA::Runtime::Browser.visit(:gitlab, QA::Page::Main::Login) do
EE::Scenario::License::Add.perform(ENV['EE_LICENSE'])
EE::Factory::License.fabricate!(ENV['EE_LICENSE'])
end
end
end
......
module QA
module Factory
class Base
def self.fabricate!(*args)
new.tap do |factory|
yield factory if block_given?
return factory.fabricate!(*args)
end
end
def fabricate!(*_args)
raise NotImplementedError
end
end
end
end
require "pry-byebug"
module QA
module Factory
module Repository
class Push < Factory::Base
PAGE_REGEX_CHECK =
%r{\/#{Runtime::Namespace.sandbox_name}\/qa-test[^\/]+\/{1}[^\/]+\z}.freeze
attr_writer :file_name,
:file_content,
:commit_message,
:branch_name
def initialize
@file_name = 'file.txt'
@file_content = '# This is test project'
@commit_message = "Add #{@file_name}"
@branch_name = 'master'
end
def fabricate!
Git::Repository.perform do |repository|
repository.location = Page::Project::Show.act do
unless PAGE_REGEX_CHECK.match(current_path)
raise "To perform this scenario the current page should be project show."
end
choose_repository_clone_http
repository_location
end
repository.use_default_credentials
repository.clone
repository.configure_identity('GitLab QA', 'root@gitlab.com')
repository.add_file(@file_name, @file_content)
repository.commit(@commit_message)
repository.push_changes(@branch_name)
end
end
end
end
end
end
module QA
module Factory
module Resource
class Group < Factory::Base
attr_writer :path, :description
def initialize
@path = Runtime::Namespace.name
@description = "QA test run at #{Runtime::Namespace.time}"
end
def fabricate!
Page::Group::New.perform do |group|
group.set_path(@path)
group.set_description(@description)
group.set_visibility('Private')
group.create
end
end
end
end
end
end
require 'securerandom'
module QA
module Factory
module Resource
class Project < Factory::Base
attr_writer :description
def name=(name)
@name = "#{name}-#{SecureRandom.hex(8)}"
end
def fabricate!
Factory::Resource::Sandbox.fabricate!
Page::Group::Show.perform do |page|
if page.has_subgroup?(Runtime::Namespace.name)
page.go_to_subgroup(Runtime::Namespace.name)
else
page.go_to_new_subgroup
Factory::Resource::Group.fabricate! do |group|
group.path = Runtime::Namespace.name
end
end
page.go_to_new_project
end
Page::Project::New.perform do |page|
page.choose_test_namespace
page.choose_name(@name)
page.add_description(@description)
page.create_new_project
end
end
end
end
end
end
module QA
module Factory
module Resource
##
# Ensure we're in our sandbox namespace, either by navigating to it or by
# creating it if it doesn't yet exist.
#
class Sandbox < Factory::Base
def fabricate!
Page::Main::Menu.act { go_to_groups }
Page::Dashboard::Groups.perform do |page|
if page.has_group?(Runtime::Namespace.sandbox_name)
page.go_to_group(Runtime::Namespace.sandbox_name)
else
page.go_to_new_group
Resource::Group.fabricate! do |group|
group.path = Runtime::Namespace.sandbox_name
group.description = 'GitLab QA Sandbox'
end
end
end
end
end
end
end
end
module QA
module Factory
module Settings
class HashedStorage < Factory::Base
def fabricate!(*traits)
raise ArgumentError unless traits.include?(:enabled)
Page::Main::Login.act { sign_in_using_credentials }
Page::Main::Menu.act { go_to_admin_area }
Page::Admin::Menu.act { go_to_settings }
Page::Admin::Settings.act do
enable_hashed_storage
save_settings
end
QA::Page::Main::Menu.act { sign_out }
end
end
end
end
end
module QA
module Scenario
module Gitlab
module Admin
class HashedStorage < Scenario::Template
def perform(*traits)
raise ArgumentError unless traits.include?(:enabled)
Page::Main::Login.act { sign_in_using_credentials }
Page::Main::Menu.act { go_to_admin_area }
Page::Admin::Menu.act { go_to_settings }
Page::Admin::Settings.act do
enable_hashed_storage
save_settings
end
QA::Page::Main::Menu.act { sign_out }
end
end
end
end
end
end
require 'securerandom'
module QA
module Scenario
module Gitlab
module Group
class Create < Scenario::Template
attr_writer :path, :description
def initialize
@path = Runtime::Namespace.name
@description = "QA test run at #{Runtime::Namespace.time}"
end
def perform
Page::Group::New.perform do |group|
group.set_path(@path)
group.set_description(@description)
group.set_visibility('Private')
group.create
end
end
end
end
end
end
end
require 'securerandom'
module QA
module Scenario
module Gitlab
module Project
class Create < Scenario::Template
attr_writer :description
def name=(name)
@name = "#{name}-#{SecureRandom.hex(8)}"
end
def perform
Scenario::Gitlab::Sandbox::Prepare.perform
Page::Group::Show.perform do |page|
if page.has_subgroup?(Runtime::Namespace.name)
page.go_to_subgroup(Runtime::Namespace.name)
else
page.go_to_new_subgroup
Scenario::Gitlab::Group::Create.perform do |group|
group.path = Runtime::Namespace.name
end
end
page.go_to_new_project
end
Page::Project::New.perform do |page|
page.choose_test_namespace
page.choose_name(@name)
page.add_description(@description)
page.create_new_project
end
end
end
end
end
end
end
require "pry-byebug"
module QA
module Scenario
module Gitlab
module Repository
class Push < Scenario::Template
PAGE_REGEX_CHECK =
%r{\/#{Runtime::Namespace.sandbox_name}\/qa-test[^\/]+\/{1}[^\/]+\z}.freeze
attr_writer :file_name,
:file_content,
:commit_message,
:branch_name
def initialize
@file_name = 'file.txt'
@file_content = '# This is test project'
@commit_message = "Add #{@file_name}"
@branch_name = 'master'
end
def perform
Git::Repository.perform do |repository|
repository.location = Page::Project::Show.act do
unless PAGE_REGEX_CHECK.match(current_path)
raise "To perform this scenario the current page should be project show."
end
choose_repository_clone_http
repository_location
end
repository.use_default_credentials
repository.clone
repository.configure_identity('GitLab QA', 'root@gitlab.com')
repository.add_file(@file_name, @file_content)
repository.commit(@commit_message)
repository.push_changes(@branch_name)
end
end
end
end
end
end
end
module QA
module Scenario
module Gitlab
module Sandbox
# Ensure we're in our sandbox namespace, either by navigating to it or
# by creating it if it doesn't yet exist
class Prepare < Scenario::Template
def perform
Page::Main::Menu.act { go_to_groups }
Page::Dashboard::Groups.perform do |page|
if page.has_group?(Runtime::Namespace.sandbox_name)
page.go_to_group(Runtime::Namespace.sandbox_name)
else
page.go_to_new_group
Scenario::Gitlab::Group::Create.perform do |group|
group.path = Runtime::Namespace.sandbox_name
group.description = 'QA sandbox'
end
end
end
end
end
end
end
end
end
......@@ -4,9 +4,9 @@ module QA
Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do
Page::Main::Login.act { sign_in_using_credentials }
Scenario::Gitlab::Project::Create.perform do |scenario|
scenario.name = 'geo-project'
scenario.description = 'Geo test project'
Factory::Resource::Project.fabricate! do |project|
project.name = 'geo-project'
project.description = 'Geo test project'
end
geo_project_name = Page::Project::Show.act { project_name }
......
......@@ -4,7 +4,7 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
Scenario::Gitlab::Project::Create.perform do |project|
Factory::Resource::Project.fabricate! do |project|
project.name = 'awesome-project'
project.description = 'create awesome project test'
end
......
......@@ -12,7 +12,7 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
Scenario::Gitlab::Project::Create.perform do |scenario|
Factory::Resource::Project.fabricate! do |scenario|
scenario.name = 'project-with-code'
scenario.description = 'project for git clone tests'
end
......
......@@ -5,12 +5,12 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
Scenario::Gitlab::Project::Create.perform do |scenario|
Factory::Resource::Project.fabricate! do |scenario|
scenario.name = 'project_with_code'
scenario.description = 'project with repository'
end
Scenario::Gitlab::Repository::Push.perform do |scenario|
Factory::Repository::Push.fabricate! do |scenario|
scenario.file_name = 'README.md'
scenario.file_content = '# This is test project'
scenario.commit_message = 'Add README.md'
......
......@@ -91,11 +91,11 @@ describe Projects::MergeRequestsController do
end
end
context 'without basic serializer param' do
it 'renders the merge request in the json format' do
go(format: :json)
context 'with widget serializer param' do
it 'renders widget MR entity as json' do
go(serializer: 'widget', format: :json)
expect(response).to match_response_schema('entities/merge_request')
expect(response).to match_response_schema('entities/merge_request_widget')
end
end
end
......
......@@ -10,8 +10,7 @@ describe EpicEntity do
subject { described_class.new(resource, request: request).as_json }
it 'has Issuable attributes' do
expect(subject).to include(:id, :iid, :author_id, :description, :lock_version, :milestone_id,
:title, :updated_by_id, :created_at, :updated_at, :milestone, :labels)
expect(subject).to include(:id, :iid, :description, :title)
end
it 'has epic specific attributes' do
......
require 'spec_helper'
describe MergeRequestEntity do
describe MergeRequestWidgetEntity do
let(:user) { create(:user) }
let(:project) { create :project, :repository }
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
......
This diff is collapsed.
......@@ -15,8 +15,8 @@ feature 'Mini Pipeline Graph', :js do
visit_merge_request
end
def visit_merge_request(format = :html)
visit project_merge_request_path(project, merge_request, format: format)
def visit_merge_request(format: :html, serializer: nil)
visit project_merge_request_path(project, merge_request, format: format, serializer: serializer)
end
it 'should display a mini pipeline graph' do
......@@ -33,12 +33,12 @@ feature 'Mini Pipeline Graph', :js do
end
it 'avoids repeated database queries' do
before = ActiveRecord::QueryRecorder.new { visit_merge_request(:json) }
before = ActiveRecord::QueryRecorder.new { visit_merge_request(format: :json, serializer: 'widget') }
create(:ci_build, pipeline: pipeline, legacy_artifacts_file: artifacts_file2)
create(:ci_build, pipeline: pipeline, when: 'manual')
after = ActiveRecord::QueryRecorder.new { visit_merge_request(:json) }
after = ActiveRecord::QueryRecorder.new { visit_merge_request(format: :json, serializer: 'widget') }
expect(before.count).to eq(after.count)
expect(before.cached_count).to eq(after.cached_count)
......
......@@ -81,15 +81,15 @@
"target_branch_tree_path": { "type": "string" },
"source_branch_path": { "type": "string" },
"conflict_resolution_path": { "type": ["string", "null"] },
"cancel_merge_when_pipeline_succeeds_path": { "type": "string" },
"create_issue_to_resolve_discussions_path": { "type": "string" },
"merge_path": { "type": "string" },
"cancel_merge_when_pipeline_succeeds_path": { "type": ["string", "null"] },
"create_issue_to_resolve_discussions_path": { "type": ["string", "null"] },
"merge_path": { "type": ["string", "null"] },
"cherry_pick_in_fork_path": { "type": ["string", "null"] },
"revert_in_fork_path": { "type": ["string", "null"] },
"email_patches_path": { "type": "string" },
"plain_diff_path": { "type": "string" },
"status_path": { "type": "string" },
"new_blob_path": { "type": "string" },
"new_blob_path": { "type": ["string", "null"] },
"merge_check_path": { "type": "string" },
"ci_environments_status_path": { "type": "string" },
"merge_commit_message_with_description": { "type": "string" },
......
/* global Sidebar */
/* eslint-disable no-new */
import _ from 'underscore';
import '~/right_sidebar';
import Sidebar from '~/right_sidebar';
describe('Issuable right sidebar collapsed todo toggle', () => {
const fixtureName = 'issues/open-issue.html.raw';
......
/* eslint-disable space-before-function-paren, no-var, no-param-reassign, quotes, prefer-template, no-else-return, new-cap, dot-notation, no-return-assign, comma-dangle, no-new, one-var, one-var-declaration-per-line, jasmine/no-spec-dupes, no-underscore-dangle, max-len */
/* global LineHighlighter */
import '~/line_highlighter';
import LineHighlighter from '~/line_highlighter';
(function() {
describe('LineHighlighter', function() {
......
/* global Notes */
import 'autosize';
import '~/gl_form';
import '~/lib/utils/text_utility';
import '~/render_gfm';
import '~/render_math';
import '~/notes';
import Notes from '~/notes';
const upArrowKeyCode = 38;
......
/* eslint-disable space-before-function-paren, no-return-assign */
/* global MergeRequest */
import '~/merge_request';
import MergeRequest from '~/merge_request';
import CloseReopenReportToggle from '~/close_reopen_report_toggle';
import IssuablesHelper from '~/helpers/issuables_helper';
......
/* eslint-disable no-var, comma-dangle, object-shorthand */
/* global Notes */
import * as urlUtils from '~/lib/utils/url_utility';
import '~/merge_request_tabs';
......@@ -7,7 +6,7 @@ import '~/commit/pipelines/pipelines_bundle';
import '~/breakpoints';
import '~/lib/utils/common_utils';
import Diff from '~/diff';
import '~/notes';
import Notes from '~/notes';
import 'vendor/jquery.scrollTo';
(function () {
......@@ -279,8 +278,8 @@ import 'vendor/jquery.scrollTo';
loadFixtures('merge_requests/diff_comment.html.raw');
$('body').attr('data-page', 'projects:merge_requests:show');
window.gl.ImageFile = () => {};
window.notes = new Notes('', []);
spyOn(window.notes, 'toggleDiffNote').and.callThrough();
Notes.initialize('', []);
spyOn(Notes.instance, 'toggleDiffNote').and.callThrough();
});
afterEach(() => {
......@@ -338,7 +337,7 @@ import 'vendor/jquery.scrollTo';
this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
expect(noteId.length).toBeGreaterThan(0);
expect(window.notes.toggleDiffNote).toHaveBeenCalledWith({
expect(Notes.instance.toggleDiffNote).toHaveBeenCalledWith({
target: jasmine.any(Object),
lineType: 'old',
forceShow: true,
......@@ -349,7 +348,7 @@ import 'vendor/jquery.scrollTo';
spyOn(urlUtils, 'getLocationHash').and.returnValue('note_something-that-does-not-exist');
this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
expect(window.notes.toggleDiffNote).not.toHaveBeenCalled();
expect(Notes.instance.toggleDiffNote).not.toHaveBeenCalled();
});
});
......@@ -359,7 +358,7 @@ import 'vendor/jquery.scrollTo';
this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
expect(noteLineNumId.length).toBeGreaterThan(0);
expect(window.notes.toggleDiffNote).not.toHaveBeenCalled();
expect(Notes.instance.toggleDiffNote).not.toHaveBeenCalled();
});
});
});
......@@ -393,7 +392,7 @@ import 'vendor/jquery.scrollTo';
this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
expect(noteId.length).toBeGreaterThan(0);
expect(window.notes.toggleDiffNote).toHaveBeenCalledWith({
expect(Notes.instance.toggleDiffNote).toHaveBeenCalledWith({
target: jasmine.any(Object),
lineType: 'new',
forceShow: true,
......@@ -404,7 +403,7 @@ import 'vendor/jquery.scrollTo';
spyOn(urlUtils, 'getLocationHash').and.returnValue('note_something-that-does-not-exist');
this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
expect(window.notes.toggleDiffNote).not.toHaveBeenCalled();
expect(Notes.instance.toggleDiffNote).not.toHaveBeenCalled();
});
});
......@@ -414,7 +413,7 @@ import 'vendor/jquery.scrollTo';
this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
expect(noteLineNumId.length).toBeGreaterThan(0);
expect(window.notes.toggleDiffNote).not.toHaveBeenCalled();
expect(Notes.instance.toggleDiffNote).not.toHaveBeenCalled();
});
});
});
......
/* eslint-disable space-before-function-paren, no-unused-expressions, no-var, object-shorthand, comma-dangle, max-len */
/* global Notes */
import * as urlUtils from '~/lib/utils/url_utility';
import 'autosize';
import '~/gl_form';
import '~/lib/utils/text_utility';
import '~/render_gfm';
import '~/notes';
import Notes from '~/notes';
(function() {
window.gon || (window.gon = {});
......
/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, new-parens, no-return-assign, new-cap, vars-on-top, max-len */
/* global Sidebar */
import '~/commons/bootstrap';
import '~/right_sidebar';
import Sidebar from '~/right_sidebar';
(function() {
var $aside, $icon, $labelsIcon, $page, $toggle, assertSidebarState;
......
require 'spec_helper'
describe MergeRequestSerializer do
let(:user) { build_stubbed(:user) }
let(:merge_request) { build_stubbed(:merge_request) }
let(:serializer) do
let(:user) { create(:user) }
let(:resource) { create(:merge_request) }
let(:json_entity) do
described_class.new(current_user: user)
.represent(resource, serializer: serializer)
.with_indifferent_access
end
describe '#represent' do
let(:opts) { { serializer: serializer_entity } }
subject { serializer.represent(merge_request, serializer: serializer_entity) }
context 'widget merge request serialization' do
let(:serializer) { 'widget' }
context 'when passing basic serializer param' do
let(:serializer_entity) { 'basic' }
it 'matches issue json schema' do
expect(json_entity).to match_schema('entities/merge_request_widget')
end
end
it 'calls super class #represent with correct params' do
expect_any_instance_of(BaseSerializer).to receive(:represent)
.with(merge_request, opts, MergeRequestBasicEntity)
context 'sidebar merge request serialization' do
let(:serializer) { 'sidebar' }
subject
end
it 'matches basic merge request json schema' do
expect(json_entity).to match_schema('entities/merge_request_basic')
end
end
context 'when serializer param is falsy' do
let(:serializer_entity) { nil }
context 'basic merge request serialization' do
let(:serializer) { 'basic' }
it 'matches basic merge request json schema' do
expect(json_entity).to match_schema('entities/merge_request_basic')
end
end
it 'calls super class #represent with correct params' do
expect_any_instance_of(BaseSerializer).to receive(:represent)
.with(merge_request, opts, MergeRequestEntity)
context 'no serializer' do
let(:serializer) { nil }
subject
end
it 'raises an error' do
expect { json_entity }.to raise_error(NoMethodError)
end
end
end
require 'spec_helper'
describe MergeRequestEntity do
describe MergeRequestWidgetEntity do
let(:project) { create :project, :repository }
let(:resource) { create(:merge_request, source_project: project, target_project: project) }
let(:user) { create(:user) }
......@@ -35,37 +35,6 @@ describe MergeRequestEntity do
end
end
it 'includes issues_links' do
issues_links = subject[:issues_links]
expect(issues_links).to include(:closing, :mentioned_but_not_closing,
:assign_to_closing)
end
it 'has Issuable attributes' do
expect(subject).to include(:id, :iid, :author_id, :description, :lock_version, :milestone_id,
:title, :updated_by_id, :created_at, :updated_at, :milestone, :labels)
end
it 'has time estimation attributes' do
expect(subject).to include(:time_estimate, :total_time_spent, :human_time_estimate, :human_total_time_spent)
end
it 'has important MergeRequest attributes' do
expect(subject).to include(:state, :deleted_at, :diff_head_sha, :merge_commit_message,
:has_conflicts, :has_ci, :merge_path,
:conflict_resolution_path,
:cancel_merge_when_pipeline_succeeds_path,
:create_issue_to_resolve_discussions_path,
:source_branch_path, :target_branch_commits_path,
:target_branch_tree_path, :commits_count, :merge_ongoing,
:ff_only_enabled,
## EE
:can_push_to_source_branch, :approvals_before_merge,
:squash, :rebase_commit_sha, :rebase_in_progress,
:approvals_path)
end
it 'has email_patches_path' do
expect(subject[:email_patches_path])
.to eq("/#{resource.project.full_path}/merge_requests/#{resource.iid}.patch")
......@@ -120,18 +89,6 @@ describe MergeRequestEntity do
end
end
it 'includes merge_event' do
create(:event, :merged, author: user, project: resource.project, target: resource)
expect(subject[:merge_event]).to include(:author, :updated_at)
end
it 'includes closed_event' do
create(:event, :closed, author: user, project: resource.project, target: resource)
expect(subject[:closed_event]).to include(:author, :updated_at)
end
describe 'diverged_commits_count' do
context 'when MR open and its diverging' do
it 'returns diverged commits count' do
......
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