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" 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 .default-cache: &default-cache
key: "ruby-235-with-yarn" key: "ruby-235-with-yarn"
paths: paths:
...@@ -18,7 +23,6 @@ image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.5-golang-1.8-git ...@@ -18,7 +23,6 @@ image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.5-golang-1.8-git
variables: variables:
MYSQL_ALLOW_EMPTY_PASSWORD: "1" MYSQL_ALLOW_EMPTY_PASSWORD: "1"
ELASTIC_URL: "http://elastic:changeme@docker.elastic.co-elasticsearch-elasticsearch:9200"
RAILS_ENV: "test" RAILS_ENV: "test"
NODE_ENV: "test" NODE_ENV: "test"
SIMPLECOV: "true" SIMPLECOV: "true"
...@@ -28,8 +32,10 @@ variables: ...@@ -28,8 +32,10 @@ variables:
KNAPSACK_RSPEC_SUITE_REPORT_PATH: knapsack/${CI_PROJECT_NAME}/rspec_report-master.json 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 KNAPSACK_SPINACH_SUITE_REPORT_PATH: knapsack/${CI_PROJECT_NAME}/spinach_report-master.json
FLAKY_RSPEC_SUITE_REPORT_PATH: rspec_flaky/report-suite.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 # This hack is needed to make ES not that memory hungry
ES_JAVA_OPTS: "-Xms256m -Xmx256m" ES_JAVA_OPTS: "-Xms256m -Xmx256m"
ELASTIC_URL: "http://elastic:changeme@docker.elastic.co-elasticsearch-elasticsearch:9200"
before_script: before_script:
- bundle --version - bundle --version
...@@ -45,11 +51,6 @@ stages: ...@@ -45,11 +51,6 @@ stages:
- post-cleanup - post-cleanup
# Predefined scopes # Predefined scopes
.dedicated-runner: &dedicated-runner
retry: 1
tags:
- gitlab-org
.tests-metadata-state: &tests-metadata-state .tests-metadata-state: &tests-metadata-state
<<: *dedicated-runner <<: *dedicated-runner
variables: variables:
...@@ -89,8 +90,8 @@ stages: ...@@ -89,8 +90,8 @@ stages:
.rspec-metadata: &rspec-metadata .rspec-metadata: &rspec-metadata
<<: *dedicated-runner <<: *dedicated-runner
<<: *pull-cache
<<: *except-docs <<: *except-docs
<<: *pull-cache
stage: test stage: test
script: script:
- JOB_NAME=( $CI_JOB_NAME ) - JOB_NAME=( $CI_JOB_NAME )
...@@ -138,8 +139,8 @@ stages: ...@@ -138,8 +139,8 @@ stages:
.spinach-metadata: &spinach-metadata .spinach-metadata: &spinach-metadata
<<: *dedicated-runner <<: *dedicated-runner
<<: *pull-cache
<<: *except-docs <<: *except-docs
<<: *pull-cache
stage: test stage: test
script: script:
- JOB_NAME=( $CI_JOB_NAME ) - JOB_NAME=( $CI_JOB_NAME )
...@@ -178,6 +179,7 @@ stages: ...@@ -178,6 +179,7 @@ stages:
# Trigger a package build in omnibus-gitlab repository # Trigger a package build in omnibus-gitlab repository
# #
package-qa: package-qa:
<<: *dedicated-runner
image: ruby:2.4-alpine image: ruby:2.4-alpine
before_script: [] before_script: []
stage: build stage: build
...@@ -191,6 +193,7 @@ package-qa: ...@@ -191,6 +193,7 @@ package-qa:
# Review docs base # Review docs base
.review-docs: &review-docs .review-docs: &review-docs
<<: *dedicated-runner
image: ruby:2.4-alpine image: ruby:2.4-alpine
before_script: before_script:
- gem install gitlab --no-doc - gem install gitlab --no-doc
...@@ -297,9 +300,9 @@ flaky-examples-check: ...@@ -297,9 +300,9 @@ flaky-examples-check:
- scripts/detect-new-flaky-examples $NEW_FLAKY_SPECS_REPORT - scripts/detect-new-flaky-examples $NEW_FLAKY_SPECS_REPORT
setup-test-env: setup-test-env:
<<: *use-pg
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs <<: *except-docs
<<: *use-pg
stage: prepare stage: prepare
cache: cache:
<<: *default-cache <<: *default-cache
...@@ -390,18 +393,18 @@ spinach-mysql 3 4: *spinach-metadata-mysql ...@@ -390,18 +393,18 @@ spinach-mysql 3 4: *spinach-metadata-mysql
SETUP_DB: "false" SETUP_DB: "false"
.rake-exec: &rake-exec .rake-exec: &rake-exec
<<: *ruby-static-analysis
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs <<: *except-docs
<<: *pull-cache <<: *pull-cache
<<: *ruby-static-analysis
stage: test stage: test
script: script:
- bundle exec rake $CI_JOB_NAME - bundle exec rake $CI_JOB_NAME
static-analysis: static-analysis:
<<: *ruby-static-analysis
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs <<: *except-docs
<<: *ruby-static-analysis
stage: test stage: test
script: script:
- scripts/static-analysis - scripts/static-analysis
...@@ -467,10 +470,16 @@ db:migrate:reset-mysql: ...@@ -467,10 +470,16 @@ db:migrate:reset-mysql:
<<: *db-migrate-reset <<: *db-migrate-reset
<<: *use-mysql <<: *use-mysql
db:check-schema-pg:
<<: *db-migrate-reset
<<: *use-pg
script:
- source scripts/schema_changed.sh
.migration-paths: &migration-paths .migration-paths: &migration-paths
<<: *dedicated-runner <<: *dedicated-runner
<<: *pull-cache
<<: *except-docs <<: *except-docs
<<: *pull-cache
stage: test stage: test
variables: variables:
SETUP_DB: "false" SETUP_DB: "false"
...@@ -538,12 +547,6 @@ db:seed_fu-mysql: ...@@ -538,12 +547,6 @@ db:seed_fu-mysql:
<<: *db-seed_fu <<: *db-seed_fu
<<: *use-mysql <<: *use-mysql
db:check-schema-pg:
<<: *db-migrate-reset
<<: *use-pg
script:
- sh scripts/schema_changed.sh
# Frontend-related jobs # Frontend-related jobs
gitlab:assets:compile: gitlab:assets:compile:
<<: *dedicated-runner <<: *dedicated-runner
...@@ -568,10 +571,10 @@ gitlab:assets:compile: ...@@ -568,10 +571,10 @@ gitlab:assets:compile:
- webpack-report/ - webpack-report/
karma: karma:
<<: *use-pg
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs <<: *except-docs
<<: *pull-cache <<: *pull-cache
<<: *use-pg
stage: test stage: test
variables: variables:
BABEL_ENV: "coverage" BABEL_ENV: "coverage"
...@@ -610,6 +613,7 @@ codequality: ...@@ -610,6 +613,7 @@ codequality:
paths: [codeclimate.json] paths: [codeclimate.json]
qa:internal: qa:internal:
<<: *dedicated-runner
<<: *except-docs <<: *except-docs
stage: test stage: test
variables: variables:
...@@ -699,8 +703,9 @@ cache gems: ...@@ -699,8 +703,9 @@ cache gems:
- master@gitlab-org/gitlab-ee - master@gitlab-org/gitlab-ee
gitlab_git_test: gitlab_git_test:
<<: *pull-cache <<: *dedicated-runner
<<: *except-docs <<: *except-docs
<<: *pull-cache
variables: variables:
SETUP_DB: "false" SETUP_DB: "false"
script: script:
......
/* eslint-disable comma-dangle, space-before-function-paren, no-new */ /* eslint-disable comma-dangle, space-before-function-paren, no-new */
/* global MilestoneSelect */ /* global MilestoneSelect */
/* global Sidebar */
import Vue from 'vue'; import Vue from 'vue';
import weight from 'ee/sidebar/components/weight/weight.vue'; import weight from 'ee/sidebar/components/weight/weight.vue';
import Flash from '../../flash'; import Flash from '../../flash';
import Sidebar from '../../right_sidebar';
import eventHub from '../../sidebar/event_hub'; import eventHub from '../../sidebar/event_hub';
import assigneeTitle from '../../sidebar/components/assignees/assignee_title'; import assigneeTitle from '../../sidebar/components/assignees/assignee_title';
import assignees from '../../sidebar/components/assignees/assignees'; import assignees from '../../sidebar/components/assignees/assignees';
......
...@@ -25,7 +25,6 @@ class ListIssue { ...@@ -25,7 +25,6 @@ class ListIssue {
this.isLoading = { this.isLoading = {
weight: false, weight: false,
}; };
this.isLoading = {};
this.sidebarInfoEndpoint = obj.issue_sidebar_endpoint; this.sidebarInfoEndpoint = obj.issue_sidebar_endpoint;
this.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint; this.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint;
this.milestone_id = obj.milestone_id; this.milestone_id = obj.milestone_id;
......
/* global CommentsStore */ /* global CommentsStore */
/* global notes */
import Vue from 'vue'; import Vue from 'vue';
import collapseIcon from '../icons/collapse_icon.svg'; import collapseIcon from '../icons/collapse_icon.svg';
import Notes from '../../notes';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue'; import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
const DiffNoteAvatars = Vue.extend({ const DiffNoteAvatars = Vue.extend({
...@@ -129,7 +129,7 @@ const DiffNoteAvatars = Vue.extend({ ...@@ -129,7 +129,7 @@ const DiffNoteAvatars = Vue.extend({
}, },
methods: { methods: {
clickedAvatar(e) { clickedAvatar(e) {
notes.onAddDiffNote(e); Notes.instance.onAddDiffNote(e);
// Toggle the active state of the toggle all button // Toggle the active state of the toggle all button
this.toggleDiscussionsToggleState(); this.toggleDiscussionsToggleState();
......
...@@ -11,7 +11,7 @@ import NewBranchForm from './new_branch_form'; ...@@ -11,7 +11,7 @@ import NewBranchForm from './new_branch_form';
/* global NotificationsDropdown */ /* global NotificationsDropdown */
import groupAvatar from './group_avatar'; import groupAvatar from './group_avatar';
import GroupLabelSubscription from './group_label_subscription'; import GroupLabelSubscription from './group_label_subscription';
/* global LineHighlighter */ import LineHighlighter from './line_highlighter';
import BuildArtifacts from './build_artifacts'; import BuildArtifacts from './build_artifacts';
import CILintEditor from './ci_lint_editor'; import CILintEditor from './ci_lint_editor';
import groupsSelect from './groups_select'; import groupsSelect from './groups_select';
...@@ -21,7 +21,7 @@ import NamespaceSelect from './namespace_select'; ...@@ -21,7 +21,7 @@ import NamespaceSelect from './namespace_select';
import NewCommitForm from './new_commit_form'; import NewCommitForm from './new_commit_form';
import Project from './project'; import Project from './project';
import projectAvatar from './project_avatar'; import projectAvatar from './project_avatar';
/* global MergeRequest */ import MergeRequest from './merge_request';
import Compare from './compare'; import Compare from './compare';
import initCompareAutocomplete from './compare_autocomplete'; import initCompareAutocomplete from './compare_autocomplete';
/* global PathLocks */ /* global PathLocks */
...@@ -30,7 +30,7 @@ import ProjectNew from './project_new'; ...@@ -30,7 +30,7 @@ import ProjectNew from './project_new';
import projectImport from './project_import'; import projectImport from './project_import';
import Labels from './labels'; import Labels from './labels';
import LabelManager from './label_manager'; import LabelManager from './label_manager';
/* global Sidebar */ import Sidebar from './right_sidebar';
/* global WeightSelect */ /* global WeightSelect */
/* global AdminEmailSelect */ /* global AdminEmailSelect */
......
...@@ -104,8 +104,8 @@ class GeoNodeStatus { ...@@ -104,8 +104,8 @@ class GeoNodeStatus {
graphItems.forEach((item) => { graphItems.forEach((item) => {
$itemEl.find(item.itemSel) $itemEl.find(item.itemSel)
.toggleClass('has-value has-tooltip', !!item.itemCount) .toggleClass('has-value has-tooltip', !!item.itemCount)
.attr('data-original-title', `${item.itemTooltip}: ${item.itemPercent}%`) .attr('data-original-title', `${item.itemTooltip}: ${item.itemCount}`)
.text(item.itemCount || '') .text(`${item.itemPercent}%` || '')
.css('width', `${item.itemPercent}%`); .css('width', `${item.itemPercent}%`);
}); });
} }
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
/* global WeightSelect */ /* global WeightSelect */
import LabelsSelect from './labels_select'; import LabelsSelect from './labels_select';
import IssuableContext from './issuable_context'; import IssuableContext from './issuable_context';
/* global Sidebar */ import Sidebar from './right_sidebar';
import DueDateSelectors from './due_date_select'; import DueDateSelectors from './due_date_select';
...@@ -17,5 +17,5 @@ export default () => { ...@@ -17,5 +17,5 @@ export default () => {
new WeightSelect(); new WeightSelect();
new IssuableContext(sidebarOptions.currentUser); new IssuableContext(sidebarOptions.currentUser);
new DueDateSelectors(); new DueDateSelectors();
window.sidebar = new Sidebar(); Sidebar.initialize();
}; };
/* global Notes */ import Notes from './notes';
export default () => { export default () => {
const dataEl = document.querySelector('.js-notes-data'); const dataEl = document.querySelector('.js-notes-data');
...@@ -10,5 +10,7 @@ export default () => { ...@@ -10,5 +10,7 @@ export default () => {
autocomplete, autocomplete,
} = JSON.parse(dataEl.innerHTML); } = 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) { ...@@ -175,4 +175,4 @@ LineHighlighter.prototype.__setLocationHash__ = function(value) {
}, document.title, value); }, document.title, value);
}; };
window.LineHighlighter = LineHighlighter; export default LineHighlighter;
...@@ -50,10 +50,7 @@ import './layout_nav'; ...@@ -50,10 +50,7 @@ import './layout_nav';
import LazyLoader from './lazy_loader'; import LazyLoader from './lazy_loader';
import './line_highlighter'; import './line_highlighter';
import initLogoAnimation from './logo'; import initLogoAnimation from './logo';
import './merge_request';
import './merge_request_tabs';
import './milestone_select'; import './milestone_select';
import './notes';
import './notifications_dropdown'; import './notifications_dropdown';
import './notifications_form'; import './notifications_form';
import './pager'; import './pager';
...@@ -61,7 +58,6 @@ import './preview_markdown'; ...@@ -61,7 +58,6 @@ import './preview_markdown';
import './project_import'; import './project_import';
import './projects_dropdown'; import './projects_dropdown';
import './render_gfm'; import './render_gfm';
import './right_sidebar';
import initBreadcrumbs from './breadcrumb'; import initBreadcrumbs from './breadcrumb';
// EE-only scripts // EE-only scripts
......
...@@ -7,9 +7,7 @@ import './merge_request_tabs'; ...@@ -7,9 +7,7 @@ import './merge_request_tabs';
import IssuablesHelper from './helpers/issuables_helper'; import IssuablesHelper from './helpers/issuables_helper';
import { addDelimiter } from './lib/utils/text_utility'; import { addDelimiter } from './lib/utils/text_utility';
(function() { function MergeRequest(opts) {
this.MergeRequest = (function() {
function MergeRequest(opts) {
// Initialize MergeRequest behavior // Initialize MergeRequest behavior
// //
// Options: // Options:
...@@ -40,26 +38,26 @@ import { addDelimiter } from './lib/utils/text_utility'; ...@@ -40,26 +38,26 @@ import { addDelimiter } from './lib/utils/text_utility';
} }
}); });
} }
} }
// Local jQuery finder // Local jQuery finder
MergeRequest.prototype.$ = function(selector) { MergeRequest.prototype.$ = function(selector) {
return this.$el.find(selector); return this.$el.find(selector);
}; };
MergeRequest.prototype.initTabs = function() { MergeRequest.prototype.initTabs = function() {
if (window.mrTabs) { if (window.mrTabs) {
window.mrTabs.unbindEvents(); window.mrTabs.unbindEvents();
} }
window.mrTabs = new gl.MergeRequestTabs(this.opts); window.mrTabs = new gl.MergeRequestTabs(this.opts);
}; };
MergeRequest.prototype.showAllCommits = function() { MergeRequest.prototype.showAllCommits = function() {
this.$('.first-commits').remove(); this.$('.first-commits').remove();
return this.$('.all-commits').removeClass('hide'); return this.$('.all-commits').removeClass('hide');
}; };
MergeRequest.prototype.initMRBtnListeners = function() { MergeRequest.prototype.initMRBtnListeners = function() {
var _this; var _this;
_this = this; _this = this;
return $('a.btn-close, a.btn-reopen').on('click', function(e) { return $('a.btn-close, a.btn-reopen').on('click', function(e) {
...@@ -81,9 +79,9 @@ import { addDelimiter } from './lib/utils/text_utility'; ...@@ -81,9 +79,9 @@ import { addDelimiter } from './lib/utils/text_utility';
} }
} }
}); });
}; };
MergeRequest.prototype.submitNoteForm = function(form, $button) { MergeRequest.prototype.submitNoteForm = function(form, $button) {
var noteText; var noteText;
noteText = form.find("textarea.js-note-text").val(); noteText = form.find("textarea.js-note-text").val();
if (noteText.trim().length > 0) { if (noteText.trim().length > 0) {
...@@ -91,9 +89,9 @@ import { addDelimiter } from './lib/utils/text_utility'; ...@@ -91,9 +89,9 @@ import { addDelimiter } from './lib/utils/text_utility';
$button.data('submitted', true); $button.data('submitted', true);
return $button.trigger('click'); return $button.trigger('click');
} }
}; };
MergeRequest.prototype.initCommitMessageListeners = function() { MergeRequest.prototype.initCommitMessageListeners = function() {
$(document).on('click', 'a.js-with-description-link', function(e) { $(document).on('click', 'a.js-with-description-link', function(e) {
var textarea = $('textarea.js-commit-message'); var textarea = $('textarea.js-commit-message');
e.preventDefault(); e.preventDefault();
...@@ -111,24 +109,24 @@ import { addDelimiter } from './lib/utils/text_utility'; ...@@ -111,24 +109,24 @@ import { addDelimiter } from './lib/utils/text_utility';
$('.js-with-description-hint').show(); $('.js-with-description-hint').show();
$('.js-without-description-hint').hide(); $('.js-without-description-hint').hide();
}); });
}; };
MergeRequest.prototype.updateStatusText = function(classToRemove, classToAdd, newStatusText) { MergeRequest.prototype.updateStatusText = function(classToRemove, classToAdd, newStatusText) {
$('.detail-page-header .status-box') $('.detail-page-header .status-box')
.removeClass(classToRemove) .removeClass(classToRemove)
.addClass(classToAdd) .addClass(classToAdd)
.find('span') .find('span')
.text(newStatusText); .text(newStatusText);
}; };
MergeRequest.prototype.decreaseCounter = function(by = 1) { MergeRequest.prototype.decreaseCounter = function(by = 1) {
const $el = $('.nav-links .js-merge-counter'); const $el = $('.nav-links .js-merge-counter');
const count = Math.max((parseInt($el.text().replace(/[^\d]/, ''), 10) - by), 0); const count = Math.max((parseInt($el.text().replace(/[^\d]/, ''), 10) - by), 0);
$el.text(addDelimiter(count)); $el.text(addDelimiter(count));
}; };
MergeRequest.prototype.hideCloseButton = function() { MergeRequest.prototype.hideCloseButton = function() {
const el = document.querySelector('.merge-request .js-issuable-actions'); const el = document.querySelector('.merge-request .js-issuable-actions');
const closeDropdownItem = el.querySelector('li.close-item'); const closeDropdownItem = el.querySelector('li.close-item');
if (closeDropdownItem) { if (closeDropdownItem) {
...@@ -141,8 +139,6 @@ import { addDelimiter } from './lib/utils/text_utility'; ...@@ -141,8 +139,6 @@ import { addDelimiter } from './lib/utils/text_utility';
} }
// Dropdown for mobile screen // Dropdown for mobile screen
el.querySelector('li.js-close-item').classList.add('hidden'); el.querySelector('li.js-close-item').classList.add('hidden');
}; };
return MergeRequest; export default MergeRequest;
})();
}).call(window);
/* eslint-disable no-new, class-methods-use-this */ /* eslint-disable no-new, class-methods-use-this */
/* global notes */
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import Flash from './flash'; import Flash from './flash';
...@@ -16,6 +15,7 @@ import initDiscussionTab from './image_diff/init_discussion_tab'; ...@@ -16,6 +15,7 @@ import initDiscussionTab from './image_diff/init_discussion_tab';
import Diff from './diff'; import Diff from './diff';
import { localTimeAgo } from './lib/utils/datetime_utility'; import { localTimeAgo } from './lib/utils/datetime_utility';
import syntaxHighlight from './syntax_highlight'; import syntaxHighlight from './syntax_highlight';
import Notes from './notes';
/* eslint-disable max-len */ /* eslint-disable max-len */
// MergeRequestTabs // MergeRequestTabs
...@@ -325,7 +325,7 @@ import syntaxHighlight from './syntax_highlight'; ...@@ -325,7 +325,7 @@ import syntaxHighlight from './syntax_highlight';
if (anchor && anchor.length > 0) { if (anchor && anchor.length > 0) {
const notesContent = anchor.closest('.notes_content'); const notesContent = anchor.closest('.notes_content');
const lineType = notesContent.hasClass('new') ? 'new' : 'old'; const lineType = notesContent.hasClass('new') ? 'new' : 'old';
notes.toggleDiffNote({ Notes.instance.toggleDiffNote({
target: anchor, target: anchor,
lineType, lineType,
forceShow: true, forceShow: true,
......
...@@ -37,6 +37,12 @@ const MAX_VISIBLE_COMMIT_LIST_COUNT = 3; ...@@ -37,6 +37,12 @@ const MAX_VISIBLE_COMMIT_LIST_COUNT = 3;
const REGEX_QUICK_ACTIONS = /^\/\w+.*$/gm; const REGEX_QUICK_ACTIONS = /^\/\w+.*$/gm;
export default class Notes { 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) { constructor(notes_url, note_ids, last_fetched_at, view, enableGFM = true) {
this.updateTargetButtons = this.updateTargetButtons.bind(this); this.updateTargetButtons = this.updateTargetButtons.bind(this);
this.updateComment = this.updateComment.bind(this); this.updateComment = this.updateComment.bind(this);
......
<script> <script>
/* global LineHighlighter */
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import LineHighlighter from '../../line_highlighter';
import syntaxHighlight from '../../syntax_highlight'; import syntaxHighlight from '../../syntax_highlight';
export default { export default {
......
...@@ -3,25 +3,29 @@ ...@@ -3,25 +3,29 @@
import _ from 'underscore'; import _ from 'underscore';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
(function() { function Sidebar(currentUser) {
this.Sidebar = (function() {
function Sidebar(currentUser) {
this.toggleTodo = this.toggleTodo.bind(this); this.toggleTodo = this.toggleTodo.bind(this);
this.sidebar = $('aside'); this.sidebar = $('aside');
this.removeListeners(); this.removeListeners();
this.addEventListeners(); this.addEventListeners();
}
Sidebar.initialize = function(currentUser) {
if (!this.instance) {
this.instance = new Sidebar(currentUser);
} }
};
Sidebar.prototype.removeListeners = function () { Sidebar.prototype.removeListeners = function () {
this.sidebar.off('click', '.sidebar-collapsed-icon'); this.sidebar.off('click', '.sidebar-collapsed-icon');
this.sidebar.off('hidden.gl.dropdown'); this.sidebar.off('hidden.gl.dropdown');
$('.dropdown').off('loading.gl.dropdown'); $('.dropdown').off('loading.gl.dropdown');
$('.dropdown').off('loaded.gl.dropdown'); $('.dropdown').off('loaded.gl.dropdown');
$(document).off('click', '.js-sidebar-toggle'); $(document).off('click', '.js-sidebar-toggle');
}; };
Sidebar.prototype.addEventListeners = function() { Sidebar.prototype.addEventListeners = function() {
const $document = $(document); const $document = $(document);
this.sidebar.on('click', '.sidebar-collapsed-icon', this, this.sidebarCollapseClicked); this.sidebar.on('click', '.sidebar-collapsed-icon', this, this.sidebarCollapseClicked);
...@@ -31,9 +35,9 @@ import Cookies from 'js-cookie'; ...@@ -31,9 +35,9 @@ import Cookies from 'js-cookie';
$document.on('click', '.js-sidebar-toggle', this.sidebarToggleClicked); $document.on('click', '.js-sidebar-toggle', this.sidebarToggleClicked);
return $(document).off('click', '.js-issuable-todo').on('click', '.js-issuable-todo', this.toggleTodo); return $(document).off('click', '.js-issuable-todo').on('click', '.js-issuable-todo', this.toggleTodo);
}; };
Sidebar.prototype.sidebarToggleClicked = function (e, triggered) { Sidebar.prototype.sidebarToggleClicked = function (e, triggered) {
var $allGutterToggleIcons, $this, $thisIcon; var $allGutterToggleIcons, $this, $thisIcon;
e.preventDefault(); e.preventDefault();
$this = $(this); $this = $(this);
...@@ -53,9 +57,9 @@ import Cookies from 'js-cookie'; ...@@ -53,9 +57,9 @@ import Cookies from 'js-cookie';
if (!triggered) { if (!triggered) {
Cookies.set("collapsed_gutter", $('.right-sidebar').hasClass('right-sidebar-collapsed')); Cookies.set("collapsed_gutter", $('.right-sidebar').hasClass('right-sidebar-collapsed'));
} }
}; };
Sidebar.prototype.toggleTodo = function(e) { Sidebar.prototype.toggleTodo = function(e) {
var $btnText, $this, $todoLoading, ajaxType, url; var $btnText, $this, $todoLoading, ajaxType, url;
$this = $(e.currentTarget); $this = $(e.currentTarget);
ajaxType = $this.attr('data-delete-path') ? 'DELETE' : 'POST'; ajaxType = $this.attr('data-delete-path') ? 'DELETE' : 'POST';
...@@ -86,9 +90,9 @@ import Cookies from 'js-cookie'; ...@@ -86,9 +90,9 @@ import Cookies from 'js-cookie';
return _this.todoUpdateDone(data); return _this.todoUpdateDone(data);
}; };
})(this)); })(this));
}; };
Sidebar.prototype.todoUpdateDone = function(data) { Sidebar.prototype.todoUpdateDone = function(data) {
const deletePath = data.delete_path ? data.delete_path : null; const deletePath = data.delete_path ? data.delete_path : null;
const attrPrefix = deletePath ? 'mark' : 'todo'; const attrPrefix = deletePath ? 'mark' : 'todo';
const $todoBtns = $('.js-issuable-todo'); const $todoBtns = $('.js-issuable-todo');
...@@ -115,9 +119,9 @@ import Cookies from 'js-cookie'; ...@@ -115,9 +119,9 @@ import Cookies from 'js-cookie';
$elText.text($el.data(`${attrPrefix}-text`)); $elText.text($el.data(`${attrPrefix}-text`));
} }
}); });
}; };
Sidebar.prototype.sidebarDropdownLoading = function(e) { Sidebar.prototype.sidebarDropdownLoading = function(e) {
var $loading, $sidebarCollapsedIcon, i, img; var $loading, $sidebarCollapsedIcon, i, img;
$sidebarCollapsedIcon = $(this).closest('.block').find('.sidebar-collapsed-icon'); $sidebarCollapsedIcon = $(this).closest('.block').find('.sidebar-collapsed-icon');
img = $sidebarCollapsedIcon.find('img'); img = $sidebarCollapsedIcon.find('img');
...@@ -130,9 +134,9 @@ import Cookies from 'js-cookie'; ...@@ -130,9 +134,9 @@ import Cookies from 'js-cookie';
i.before($loading); i.before($loading);
return i.hide(); return i.hide();
} }
}; };
Sidebar.prototype.sidebarDropdownLoaded = function(e) { Sidebar.prototype.sidebarDropdownLoaded = function(e) {
var $sidebarCollapsedIcon, i, img; var $sidebarCollapsedIcon, i, img;
$sidebarCollapsedIcon = $(this).closest('.block').find('.sidebar-collapsed-icon'); $sidebarCollapsedIcon = $(this).closest('.block').find('.sidebar-collapsed-icon');
img = $sidebarCollapsedIcon.find('img'); img = $sidebarCollapsedIcon.find('img');
...@@ -143,9 +147,9 @@ import Cookies from 'js-cookie'; ...@@ -143,9 +147,9 @@ import Cookies from 'js-cookie';
} else { } else {
return i.show(); return i.show();
} }
}; };
Sidebar.prototype.sidebarCollapseClicked = function(e) { Sidebar.prototype.sidebarCollapseClicked = function(e) {
var $block, sidebar; var $block, sidebar;
if ($(e.currentTarget).hasClass('dont-change-state')) { if ($(e.currentTarget).hasClass('dont-change-state')) {
return; return;
...@@ -154,9 +158,9 @@ import Cookies from 'js-cookie'; ...@@ -154,9 +158,9 @@ import Cookies from 'js-cookie';
e.preventDefault(); e.preventDefault();
$block = $(this).closest('.block'); $block = $(this).closest('.block');
return sidebar.openDropdown($block); return sidebar.openDropdown($block);
}; };
Sidebar.prototype.openDropdown = function(blockOrName) { Sidebar.prototype.openDropdown = function(blockOrName) {
var $block; var $block;
$block = _.isString(blockOrName) ? this.getBlock(blockOrName) : blockOrName; $block = _.isString(blockOrName) ? this.getBlock(blockOrName) : blockOrName;
if (!this.isOpen()) { if (!this.isOpen()) {
...@@ -169,34 +173,34 @@ import Cookies from 'js-cookie'; ...@@ -169,34 +173,34 @@ import Cookies from 'js-cookie';
setTimeout(() => { setTimeout(() => {
$block.find('.js-sidebar-dropdown-toggle').trigger('click'); $block.find('.js-sidebar-dropdown-toggle').trigger('click');
}); });
}; };
Sidebar.prototype.setCollapseAfterUpdate = function($block) { Sidebar.prototype.setCollapseAfterUpdate = function($block) {
$block.addClass('collapse-after-update'); $block.addClass('collapse-after-update');
return $('.layout-page').addClass('with-overlay'); return $('.layout-page').addClass('with-overlay');
}; };
Sidebar.prototype.onSidebarDropdownHidden = function(e) { Sidebar.prototype.onSidebarDropdownHidden = function(e) {
var $block, sidebar; var $block, sidebar;
sidebar = e.data; sidebar = e.data;
e.preventDefault(); e.preventDefault();
$block = $(e.target).closest('.block'); $block = $(e.target).closest('.block');
return sidebar.sidebarDropdownHidden($block); return sidebar.sidebarDropdownHidden($block);
}; };
Sidebar.prototype.sidebarDropdownHidden = function($block) { Sidebar.prototype.sidebarDropdownHidden = function($block) {
if ($block.hasClass('collapse-after-update')) { if ($block.hasClass('collapse-after-update')) {
$block.removeClass('collapse-after-update'); $block.removeClass('collapse-after-update');
$('.layout-page').removeClass('with-overlay'); $('.layout-page').removeClass('with-overlay');
return this.toggleSidebar('hide'); return this.toggleSidebar('hide');
} }
}; };
Sidebar.prototype.triggerOpenSidebar = function() { Sidebar.prototype.triggerOpenSidebar = function() {
return this.sidebar.find('.js-sidebar-toggle').trigger('click'); return this.sidebar.find('.js-sidebar-toggle').trigger('click');
}; };
Sidebar.prototype.toggleSidebar = function(action) { Sidebar.prototype.toggleSidebar = function(action) {
if (action == null) { if (action == null) {
action = 'toggle'; action = 'toggle';
} }
...@@ -213,16 +217,14 @@ import Cookies from 'js-cookie'; ...@@ -213,16 +217,14 @@ import Cookies from 'js-cookie';
return this.triggerOpenSidebar(); return this.triggerOpenSidebar();
} }
} }
}; };
Sidebar.prototype.isOpen = function() { Sidebar.prototype.isOpen = function() {
return this.sidebar.is('.right-sidebar-expanded'); return this.sidebar.is('.right-sidebar-expanded');
}; };
Sidebar.prototype.getBlock = function(name) { Sidebar.prototype.getBlock = function(name) {
return this.sidebar.find(".block." + name); return this.sidebar.find(".block." + name);
}; };
return Sidebar; export default Sidebar;
})();
}).call(window);
/* global Mousetrap */ /* global Mousetrap */
/* global sidebar */
import _ from 'underscore'; import _ from 'underscore';
import 'mousetrap'; import 'mousetrap';
import Sidebar from './right_sidebar';
import ShortcutsNavigation from './shortcuts_navigation'; import ShortcutsNavigation from './shortcuts_navigation';
import { CopyAsGFM } from './behaviors/copy_as_gfm'; import { CopyAsGFM } from './behaviors/copy_as_gfm';
...@@ -69,7 +69,7 @@ export default class ShortcutsIssuable extends ShortcutsNavigation { ...@@ -69,7 +69,7 @@ export default class ShortcutsIssuable extends ShortcutsNavigation {
} }
static openSidebarDropdown(name) { static openSidebarDropdown(name) {
sidebar.openDropdown(name); Sidebar.instance.openDropdown(name);
return false; return false;
} }
} }
...@@ -6,7 +6,7 @@ Vue.use(VueResource); ...@@ -6,7 +6,7 @@ Vue.use(VueResource);
export default class MRWidgetService { export default class MRWidgetService {
constructor(endpoints) { constructor(endpoints) {
this.mergeResource = Vue.resource(endpoints.mergePath); 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.cancelAutoMergeResource = Vue.resource(endpoints.cancelAutoMergePath);
this.removeWIPResource = Vue.resource(endpoints.removeWIPPath); this.removeWIPResource = Vue.resource(endpoints.removeWIPPath);
this.removeSourceBranchResource = Vue.resource(endpoints.sourceBranchPath); this.removeSourceBranchResource = Vue.resource(endpoints.sourceBranchPath);
......
...@@ -11,12 +11,14 @@ ...@@ -11,12 +11,14 @@
.status-neutral, .status-neutral,
.status-red, { .status-red, {
height: 100%; height: 100%;
font-size: $tooltip-font-size;
font-weight: normal; font-weight: normal;
color: $white-light; color: $white-light;
line-height: 20px; line-height: 20px;
&.has-value { &.has-value {
padding: 0 10px; min-width: 25px;
padding: 0 5px;
} }
&:hover { &:hover {
......
...@@ -133,7 +133,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -133,7 +133,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
.new(project, current_user, wip_event: 'unwip') .new(project, current_user, wip_event: 'unwip')
.execute(@merge_request) .execute(@merge_request)
render json: serializer.represent(@merge_request) render json: serialize_widget(@merge_request)
end end
def commit_change_content def commit_change_content
...@@ -149,7 +149,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -149,7 +149,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
.new(@project, current_user) .new(@project, current_user)
.cancel(@merge_request) .cancel(@merge_request)
render json: serializer.represent(@merge_request) render json: serialize_widget(@merge_request)
end end
def merge def merge
...@@ -313,6 +313,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -313,6 +313,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end end
end end
def serialize_widget(merge_request)
serializer.represent(merge_request, serializer: 'widget')
end
def serializer def serializer
MergeRequestSerializer.new(current_user: current_user, project: merge_request.project) MergeRequestSerializer.new(current_user: current_user, project: merge_request.project)
end end
......
...@@ -32,7 +32,7 @@ module IssuablesHelper ...@@ -32,7 +32,7 @@ module IssuablesHelper
end end
end end
def serialize_issuable(issuable) def serialize_issuable(issuable, serializer: nil)
serializer_klass = case issuable serializer_klass = case issuable
when Issue when Issue
IssueSerializer IssueSerializer
...@@ -42,7 +42,7 @@ module IssuablesHelper ...@@ -42,7 +42,7 @@ module IssuablesHelper
serializer_klass serializer_klass
.new(current_user: current_user, project: issuable.project) .new(current_user: current_user, project: issuable.project)
.represent(issuable) .represent(issuable, serializer: serializer)
.to_json .to_json
end end
......
...@@ -3,14 +3,6 @@ class IssuableEntity < Grape::Entity ...@@ -3,14 +3,6 @@ class IssuableEntity < Grape::Entity
expose :id expose :id
expose :iid expose :iid
expose :author_id
expose :description expose :description
expose :lock_version
expose :milestone_id
expose :title expose :title
expose :updated_by_id
expose :created_at
expose :updated_at
expose :milestone, using: API::Entities::Milestone
expose :labels, using: LabelEntity
end end
class IssuableSidebarEntity < Grape::Entity class IssuableSidebarEntity < Grape::Entity
include TimeTrackableEntity
include RequestAwareEntity include RequestAwareEntity
prepend ::EE::IssuableSidebarEntity prepend ::EE::IssuableSidebarEntity
...@@ -9,9 +10,4 @@ class IssuableSidebarEntity < Grape::Entity ...@@ -9,9 +10,4 @@ class IssuableSidebarEntity < Grape::Entity
expose :subscribed do |issuable| expose :subscribed do |issuable|
issuable.subscribed?(request.current_user, issuable.project) issuable.subscribed?(request.current_user, issuable.project)
end end
expose :time_estimate
expose :total_time_spent
expose :human_time_estimate
expose :human_total_time_spent
end end
...@@ -2,7 +2,15 @@ class IssueEntity < IssuableEntity ...@@ -2,7 +2,15 @@ class IssueEntity < IssuableEntity
include TimeTrackableEntity include TimeTrackableEntity
expose :state expose :state
expose :milestone_id
expose :updated_by_id
expose :created_at
expose :updated_at
expose :deleted_at expose :deleted_at
expose :milestone, using: API::Entities::Milestone
expose :labels, using: LabelEntity
expose :lock_version
expose :author_id
expose :confidential expose :confidential
expose :discussion_locked expose :discussion_locked
expose :assignees, using: API::Entities::UserBasic expose :assignees, using: API::Entities::UserBasic
......
class MergeRequestSerializer < BaseSerializer class MergeRequestSerializer < BaseSerializer
# This overrided method takes care of which entity should be used # 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. # Hence, `entity` doesn't need to be declared on the class scope.
def represent(merge_request, opts = {}) def represent(merge_request, opts = {})
entity = entity =
case opts[:serializer] case opts[:serializer]
when 'basic', 'sidebar' when 'basic', 'sidebar'
MergeRequestBasicEntity MergeRequestBasicEntity
else when 'widget'
MergeRequestEntity MergeRequestWidgetEntity
end end
super(merge_request, opts, entity) super(merge_request, opts, entity)
......
class MergeRequestEntity < IssuableEntity class MergeRequestWidgetEntity < IssuableEntity
include TimeTrackableEntity prepend ::EE::MergeRequestWidgetEntity
prepend ::EE::MergeRequestEntity
expose :state expose :state
expose :deleted_at
expose :in_progress_merge_commit_sha expose :in_progress_merge_commit_sha
expose :merge_commit_sha expose :merge_commit_sha
expose :merge_error expose :merge_error
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
-# haml-lint:disable InlineJavaScript -# haml-lint:disable InlineJavaScript
:javascript :javascript
window.gl = window.gl || {}; 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) // 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 // Object.assign would be useful here, but it blows up Phantom.js in tests
......
...@@ -8,11 +8,12 @@ ...@@ -8,11 +8,12 @@
= image_tag 'illustrations/issues.svg' = image_tag 'illustrations/issues.svg'
.col-xs-12 .col-xs-12
.text-content .text-content
- if has_button && current_user - if current_user
%h4 %h4
= _("The Issue Tracker is the place to add things that need to be improved or solved in a project") = _("The Issue Tracker is the place to add things that need to be improved or solved in a project")
%p %p
= _("Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable.") = _("Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable.")
- if has_button
.text-center .text-center
- if project_select_button - if project_select_button
= render 'shared/new_project_item_select', path: 'issues/new', label: 'New issue', type: :issues = render 'shared/new_project_item_select', path: 'issues/new', label: 'New issue', type: :issues
......
---
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: ...@@ -502,8 +502,8 @@ stages:
unit_test: unit_test:
stage: test stage: test
script: script:
- composer install
- cp .env.example .env - cp .env.example .env
- composer install
- php artisan key:generate - php artisan key:generate
- php artisan migrate - php artisan migrate
- vendor/bin/phpunit - vendor/bin/phpunit
......
...@@ -84,9 +84,19 @@ this branch to prevent any changes from being lost. ...@@ -84,9 +84,19 @@ this branch to prevent any changes from being lost.
![Diverged branch](repository_mirroring/repository_mirroring_diverged_branch.png) ![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 ### 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. 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` ...@@ -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 button to do so. You'll have to update the source repository with the new key
to keep the mirror running. 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. Once you activate the pull mirroring feature, the mirror will be inserted into
A scheduler will start every minute and schedule a fixed amount of mirrors for update, based a queue. A scheduler will start every minute and schedule a fixed amount of
on the configured maximum capacity. mirrors for update, based on the configured maximum capacity.
If the mirror successfully updates it will be enqueued once again with a small backoff If the mirror successfully updates it will be enqueued once again with a small
period. backoff period.
If the mirror fails (eg: branch diverged from upstream), the project's If the mirror fails (eg: branch diverged from upstream), the project's backoff
backoff period will be penalized each time it fails up to a maximum amount of time. period will be penalized each time it fails up to a maximum amount of time.
## Pushing to a remote repository ## 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" **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 section. Check the "Remote mirror repository" box and fill in the Git URL of
repository to push to. Hit **Save changes** for the changes to take effect. the repository to push to. Click **Save changes** for the changes to take
effect.
![Push settings](repository_mirroring/repository_mirroring_push_settings.png) ![Push settings](repository_mirroring/repository_mirroring_push_settings.png)
Similarly to the pull mirroring, since the upstream repository functions as a Similarly to pull mirroring, when push mirroring is enabled, you are advised
mirror to the repository in GitLab, you are advised not to push commits directly not to push commits directly to the mirrored repository to prevent the mirror
to the mirrored repository. Instead, all changes will end up in the mirrored repository diverging. All changes will end up in the mirrored repository whenever commits
whenever commits are pushed to GitLab, or when a [forced update](#forcing-an-update) is initiated. 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 Pushes into GitLab are automatically pushed to the remote mirror at least once
after they come in or 1 minute if **push only protected branches** is enabled. 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 In case of a diverged branch, you will see an error indicated at the **Mirror
**Mirror repository** settings. 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 ### 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. You can choose to only push your protected branches from GitLab to your remote repository.
...@@ -270,19 +285,38 @@ While mirrors are scheduled to update automatically, you can always force an upd ...@@ -270,19 +285,38 @@ While mirrors are scheduled to update automatically, you can always force an upd
- in the tags page - in the tags page
- in the **Mirror repository** settings 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 Bidirectional mirroring also creates a race condition where commits to the same
if you configure a repository to both pull and push to a second one, there is branch in close proximity will cause conflicts. The race condition can be
no guarantee that it will update correctly on both remotes. mitigated by reducing the mirroring delay by using a Push event webhook to
You can try [configuring custom Git hooks][hooks] on the GitLab server in order trigger an immediate pull to GitLab. Push mirroring from GitLab is rate limited
to resolve this issue. 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-51]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/51
[ee-2551]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2551 [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-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-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 [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 [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 EE
module MergeRequestEntity module MergeRequestWidgetEntity
extend ActiveSupport::Concern extend ActiveSupport::Concern
prepended do prepended do
......
...@@ -12,6 +12,27 @@ module QA ...@@ -12,6 +12,27 @@ module QA
autoload :Browser, 'qa/runtime/browser' autoload :Browser, 'qa/runtime/browser'
end 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 # GitLab QA Scenarios
# #
...@@ -34,31 +55,6 @@ module QA ...@@ -34,31 +55,6 @@ module QA
autoload :Mattermost, 'qa/scenario/test/integration/mattermost' autoload :Mattermost, 'qa/scenario/test/integration/mattermost'
end end
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 end
## ##
......
...@@ -16,18 +16,18 @@ module QA ...@@ -16,18 +16,18 @@ module QA
end end
end end
module Scenario module Factory
autoload :License, 'qa/ee/factory/license'
module Geo module Geo
autoload :Node, 'qa/ee/scenario/geo/node' autoload :Node, 'qa/ee/factory/geo/node'
end
end end
module Scenario
module Test module Test
autoload :Geo, 'qa/ee/scenario/test/geo' autoload :Geo, 'qa/ee/scenario/test/geo'
end end
module License
autoload :Add, 'qa/ee/scenario/license/add'
end
end end
end end
end end
module QA module QA
module EE module EE
module Scenario module Factory
module Geo module Geo
class Node < QA::Scenario::Template class Node < QA::Factory::Base
attr_accessor :address attr_accessor :address
def perform def fabricate!
QA::Page::Main::Login.act { sign_in_using_credentials } QA::Page::Main::Login.act { sign_in_using_credentials }
QA::Page::Main::Menu.act { go_to_admin_area } QA::Page::Main::Menu.act { go_to_admin_area }
QA::Page::Admin::Menu.act { go_to_geo_nodes } 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 ...@@ -39,32 +39,26 @@ module QA
end end
def add_license def add_license
# TODO EE license to Runtime.license, gitlab-org/gitlab-qa#86
#
puts 'Adding GitLab EE license ...' puts 'Adding GitLab EE license ...'
QA::Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do 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
end end
def enable_hashed_storage def enable_hashed_storage
# TODO, Factory::HashedStorage - gitlab-org/gitlab-qa#86
#
puts 'Enabling hashed repository storage setting ...' puts 'Enabling hashed repository storage setting ...'
QA::Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do 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
end end
def add_secondary_node def add_secondary_node
# TODO, Factory::Geo::Node - gitlab-org/gitlab-qa#86
#
puts 'Adding new Geo secondary node ...' puts 'Adding new Geo secondary node ...'
QA::Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do 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 node.address = QA::Runtime::Scenario.geo_secondary_address
end end
end end
......
...@@ -11,7 +11,7 @@ module QA ...@@ -11,7 +11,7 @@ module QA
return unless ENV['EE_LICENSE'] return unless ENV['EE_LICENSE']
QA::Runtime::Browser.visit(:gitlab, QA::Page::Main::Login) do 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 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 ...@@ -4,9 +4,9 @@ module QA
Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do
Page::Main::Login.act { sign_in_using_credentials } Page::Main::Login.act { sign_in_using_credentials }
Scenario::Gitlab::Project::Create.perform do |scenario| Factory::Resource::Project.fabricate! do |project|
scenario.name = 'geo-project' project.name = 'geo-project'
scenario.description = 'Geo test project' project.description = 'Geo test project'
end end
geo_project_name = Page::Project::Show.act { project_name } geo_project_name = Page::Project::Show.act { project_name }
......
...@@ -4,7 +4,7 @@ module QA ...@@ -4,7 +4,7 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login) Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials } 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.name = 'awesome-project'
project.description = 'create awesome project test' project.description = 'create awesome project test'
end end
......
...@@ -12,7 +12,7 @@ module QA ...@@ -12,7 +12,7 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login) Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials } 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.name = 'project-with-code'
scenario.description = 'project for git clone tests' scenario.description = 'project for git clone tests'
end end
......
...@@ -5,12 +5,12 @@ module QA ...@@ -5,12 +5,12 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login) Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials } 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.name = 'project_with_code'
scenario.description = 'project with repository' scenario.description = 'project with repository'
end end
Scenario::Gitlab::Repository::Push.perform do |scenario| Factory::Repository::Push.fabricate! do |scenario|
scenario.file_name = 'README.md' scenario.file_name = 'README.md'
scenario.file_content = '# This is test project' scenario.file_content = '# This is test project'
scenario.commit_message = 'Add README.md' scenario.commit_message = 'Add README.md'
......
...@@ -91,11 +91,11 @@ describe Projects::MergeRequestsController do ...@@ -91,11 +91,11 @@ describe Projects::MergeRequestsController do
end end
end end
context 'without basic serializer param' do context 'with widget serializer param' do
it 'renders the merge request in the json format' do it 'renders widget MR entity as json' do
go(format: :json) 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 end
end end
......
...@@ -10,8 +10,7 @@ describe EpicEntity do ...@@ -10,8 +10,7 @@ describe EpicEntity do
subject { described_class.new(resource, request: request).as_json } subject { described_class.new(resource, request: request).as_json }
it 'has Issuable attributes' do it 'has Issuable attributes' do
expect(subject).to include(:id, :iid, :author_id, :description, :lock_version, :milestone_id, expect(subject).to include(:id, :iid, :description, :title)
:title, :updated_by_id, :created_at, :updated_at, :milestone, :labels)
end end
it 'has epic specific attributes' do it 'has epic specific attributes' do
......
require 'spec_helper' require 'spec_helper'
describe MergeRequestEntity do describe MergeRequestWidgetEntity do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create :project, :repository } let(:project) { create :project, :repository }
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
......
...@@ -8,6 +8,19 @@ describe 'Issues' do ...@@ -8,6 +8,19 @@ describe 'Issues' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, :public) } let(:project) { create(:project, :public) }
describe 'while user is signed out' do
describe 'empty state' do
it 'user sees empty state' do
visit project_issues_path(project)
expect(page).to have_content('Register / Sign In')
expect(page).to have_content('The Issue Tracker is the place to add things that need to be improved or solved in a project.')
expect(page).to have_content('You can register or sign in to create issues for this project.')
end
end
end
describe 'while user is signed in' do
before do before do
sign_in(user) sign_in(user)
user2 = create(:user) user2 = create(:user)
...@@ -15,6 +28,16 @@ describe 'Issues' do ...@@ -15,6 +28,16 @@ describe 'Issues' do
project.team << [[user, user2], :developer] project.team << [[user, user2], :developer]
end end
describe 'empty state' do
it 'user sees empty state' do
visit project_issues_path(project)
expect(page).to have_content('The Issue Tracker is the place to add things that need to be improved or solved in a project')
expect(page).to have_content('Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable.')
expect(page).to have_content('New issue')
end
end
describe 'Edit issue' do describe 'Edit issue' do
let!(:issue) do let!(:issue) do
create(:issue, create(:issue,
...@@ -769,4 +792,5 @@ describe 'Issues' do ...@@ -769,4 +792,5 @@ describe 'Issues' do
expect(page).not_to have_css('.is-active') expect(page).not_to have_css('.is-active')
end end
end end
end
end end
...@@ -15,8 +15,8 @@ feature 'Mini Pipeline Graph', :js do ...@@ -15,8 +15,8 @@ feature 'Mini Pipeline Graph', :js do
visit_merge_request visit_merge_request
end end
def visit_merge_request(format = :html) def visit_merge_request(format: :html, serializer: nil)
visit project_merge_request_path(project, merge_request, format: format) visit project_merge_request_path(project, merge_request, format: format, serializer: serializer)
end end
it 'should display a mini pipeline graph' do it 'should display a mini pipeline graph' do
...@@ -33,12 +33,12 @@ feature 'Mini Pipeline Graph', :js do ...@@ -33,12 +33,12 @@ feature 'Mini Pipeline Graph', :js do
end end
it 'avoids repeated database queries' do 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, legacy_artifacts_file: artifacts_file2)
create(:ci_build, pipeline: pipeline, when: 'manual') 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.count).to eq(after.count)
expect(before.cached_count).to eq(after.cached_count) expect(before.cached_count).to eq(after.cached_count)
......
...@@ -81,15 +81,15 @@ ...@@ -81,15 +81,15 @@
"target_branch_tree_path": { "type": "string" }, "target_branch_tree_path": { "type": "string" },
"source_branch_path": { "type": "string" }, "source_branch_path": { "type": "string" },
"conflict_resolution_path": { "type": ["string", "null"] }, "conflict_resolution_path": { "type": ["string", "null"] },
"cancel_merge_when_pipeline_succeeds_path": { "type": "string" }, "cancel_merge_when_pipeline_succeeds_path": { "type": ["string", "null"] },
"create_issue_to_resolve_discussions_path": { "type": "string" }, "create_issue_to_resolve_discussions_path": { "type": ["string", "null"] },
"merge_path": { "type": "string" }, "merge_path": { "type": ["string", "null"] },
"cherry_pick_in_fork_path": { "type": ["string", "null"] }, "cherry_pick_in_fork_path": { "type": ["string", "null"] },
"revert_in_fork_path": { "type": ["string", "null"] }, "revert_in_fork_path": { "type": ["string", "null"] },
"email_patches_path": { "type": "string" }, "email_patches_path": { "type": "string" },
"plain_diff_path": { "type": "string" }, "plain_diff_path": { "type": "string" },
"status_path": { "type": "string" }, "status_path": { "type": "string" },
"new_blob_path": { "type": "string" }, "new_blob_path": { "type": ["string", "null"] },
"merge_check_path": { "type": "string" }, "merge_check_path": { "type": "string" },
"ci_environments_status_path": { "type": "string" }, "ci_environments_status_path": { "type": "string" },
"merge_commit_message_with_description": { "type": "string" }, "merge_commit_message_with_description": { "type": "string" },
......
/* global Sidebar */
/* eslint-disable no-new */ /* eslint-disable no-new */
import _ from 'underscore'; import _ from 'underscore';
import '~/right_sidebar'; import Sidebar from '~/right_sidebar';
describe('Issuable right sidebar collapsed todo toggle', () => { describe('Issuable right sidebar collapsed todo toggle', () => {
const fixtureName = 'issues/open-issue.html.raw'; 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 */ /* 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() { (function() {
describe('LineHighlighter', function() { describe('LineHighlighter', function() {
......
/* global Notes */
import 'autosize'; import 'autosize';
import '~/gl_form'; import '~/gl_form';
import '~/lib/utils/text_utility'; import '~/lib/utils/text_utility';
import '~/render_gfm'; import '~/render_gfm';
import '~/render_math'; import '~/render_math';
import '~/notes'; import Notes from '~/notes';
const upArrowKeyCode = 38; const upArrowKeyCode = 38;
......
/* eslint-disable space-before-function-paren, no-return-assign */ /* 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 CloseReopenReportToggle from '~/close_reopen_report_toggle';
import IssuablesHelper from '~/helpers/issuables_helper'; import IssuablesHelper from '~/helpers/issuables_helper';
......
/* eslint-disable no-var, comma-dangle, object-shorthand */ /* eslint-disable no-var, comma-dangle, object-shorthand */
/* global Notes */
import * as urlUtils from '~/lib/utils/url_utility'; import * as urlUtils from '~/lib/utils/url_utility';
import '~/merge_request_tabs'; import '~/merge_request_tabs';
...@@ -7,7 +6,7 @@ import '~/commit/pipelines/pipelines_bundle'; ...@@ -7,7 +6,7 @@ import '~/commit/pipelines/pipelines_bundle';
import '~/breakpoints'; import '~/breakpoints';
import '~/lib/utils/common_utils'; import '~/lib/utils/common_utils';
import Diff from '~/diff'; import Diff from '~/diff';
import '~/notes'; import Notes from '~/notes';
import 'vendor/jquery.scrollTo'; import 'vendor/jquery.scrollTo';
(function () { (function () {
...@@ -279,8 +278,8 @@ import 'vendor/jquery.scrollTo'; ...@@ -279,8 +278,8 @@ import 'vendor/jquery.scrollTo';
loadFixtures('merge_requests/diff_comment.html.raw'); loadFixtures('merge_requests/diff_comment.html.raw');
$('body').attr('data-page', 'projects:merge_requests:show'); $('body').attr('data-page', 'projects:merge_requests:show');
window.gl.ImageFile = () => {}; window.gl.ImageFile = () => {};
window.notes = new Notes('', []); Notes.initialize('', []);
spyOn(window.notes, 'toggleDiffNote').and.callThrough(); spyOn(Notes.instance, 'toggleDiffNote').and.callThrough();
}); });
afterEach(() => { afterEach(() => {
...@@ -338,7 +337,7 @@ import 'vendor/jquery.scrollTo'; ...@@ -338,7 +337,7 @@ import 'vendor/jquery.scrollTo';
this.class.loadDiff('/foo/bar/merge_requests/1/diffs'); this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
expect(noteId.length).toBeGreaterThan(0); expect(noteId.length).toBeGreaterThan(0);
expect(window.notes.toggleDiffNote).toHaveBeenCalledWith({ expect(Notes.instance.toggleDiffNote).toHaveBeenCalledWith({
target: jasmine.any(Object), target: jasmine.any(Object),
lineType: 'old', lineType: 'old',
forceShow: true, forceShow: true,
...@@ -349,7 +348,7 @@ import 'vendor/jquery.scrollTo'; ...@@ -349,7 +348,7 @@ import 'vendor/jquery.scrollTo';
spyOn(urlUtils, 'getLocationHash').and.returnValue('note_something-that-does-not-exist'); spyOn(urlUtils, 'getLocationHash').and.returnValue('note_something-that-does-not-exist');
this.class.loadDiff('/foo/bar/merge_requests/1/diffs'); 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'; ...@@ -359,7 +358,7 @@ import 'vendor/jquery.scrollTo';
this.class.loadDiff('/foo/bar/merge_requests/1/diffs'); this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
expect(noteLineNumId.length).toBeGreaterThan(0); 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'; ...@@ -393,7 +392,7 @@ import 'vendor/jquery.scrollTo';
this.class.loadDiff('/foo/bar/merge_requests/1/diffs'); this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
expect(noteId.length).toBeGreaterThan(0); expect(noteId.length).toBeGreaterThan(0);
expect(window.notes.toggleDiffNote).toHaveBeenCalledWith({ expect(Notes.instance.toggleDiffNote).toHaveBeenCalledWith({
target: jasmine.any(Object), target: jasmine.any(Object),
lineType: 'new', lineType: 'new',
forceShow: true, forceShow: true,
...@@ -404,7 +403,7 @@ import 'vendor/jquery.scrollTo'; ...@@ -404,7 +403,7 @@ import 'vendor/jquery.scrollTo';
spyOn(urlUtils, 'getLocationHash').and.returnValue('note_something-that-does-not-exist'); spyOn(urlUtils, 'getLocationHash').and.returnValue('note_something-that-does-not-exist');
this.class.loadDiff('/foo/bar/merge_requests/1/diffs'); 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'; ...@@ -414,7 +413,7 @@ import 'vendor/jquery.scrollTo';
this.class.loadDiff('/foo/bar/merge_requests/1/diffs'); this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
expect(noteLineNumId.length).toBeGreaterThan(0); 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 */ /* 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 * as urlUtils from '~/lib/utils/url_utility';
import 'autosize'; import 'autosize';
import '~/gl_form'; import '~/gl_form';
import '~/lib/utils/text_utility'; import '~/lib/utils/text_utility';
import '~/render_gfm'; import '~/render_gfm';
import '~/notes'; import Notes from '~/notes';
(function() { (function() {
window.gon || (window.gon = {}); 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 */ /* 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 '~/commons/bootstrap';
import '~/right_sidebar'; import Sidebar from '~/right_sidebar';
(function() { (function() {
var $aside, $icon, $labelsIcon, $page, $toggle, assertSidebarState; var $aside, $icon, $labelsIcon, $page, $toggle, assertSidebarState;
......
require 'spec_helper' require 'spec_helper'
describe MergeRequestSerializer do describe MergeRequestSerializer do
let(:user) { build_stubbed(:user) } let(:user) { create(:user) }
let(:merge_request) { build_stubbed(:merge_request) } let(:resource) { create(:merge_request) }
let(:json_entity) do
let(:serializer) do
described_class.new(current_user: user) described_class.new(current_user: user)
.represent(resource, serializer: serializer)
.with_indifferent_access
end end
describe '#represent' do context 'widget merge request serialization' do
let(:opts) { { serializer: serializer_entity } } let(:serializer) { 'widget' }
subject { serializer.represent(merge_request, serializer: serializer_entity) }
context 'when passing basic serializer param' do it 'matches issue json schema' do
let(:serializer_entity) { 'basic' } expect(json_entity).to match_schema('entities/merge_request_widget')
end
end
it 'calls super class #represent with correct params' do context 'sidebar merge request serialization' do
expect_any_instance_of(BaseSerializer).to receive(:represent) let(:serializer) { 'sidebar' }
.with(merge_request, opts, MergeRequestBasicEntity)
subject it 'matches basic merge request json schema' do
expect(json_entity).to match_schema('entities/merge_request_basic')
end end
end end
context 'when serializer param is falsy' do context 'basic merge request serialization' do
let(:serializer_entity) { nil } let(:serializer) { 'basic' }
it 'calls super class #represent with correct params' do
expect_any_instance_of(BaseSerializer).to receive(:represent)
.with(merge_request, opts, MergeRequestEntity)
subject it 'matches basic merge request json schema' do
expect(json_entity).to match_schema('entities/merge_request_basic')
end end
end end
context 'no serializer' do
let(:serializer) { nil }
it 'raises an error' do
expect { json_entity }.to raise_error(NoMethodError)
end
end end
end end
require 'spec_helper' require 'spec_helper'
describe MergeRequestEntity do describe MergeRequestWidgetEntity do
let(:project) { create :project, :repository } let(:project) { create :project, :repository }
let(:resource) { create(:merge_request, source_project: project, target_project: project) } let(:resource) { create(:merge_request, source_project: project, target_project: project) }
let(:user) { create(:user) } let(:user) { create(:user) }
...@@ -35,37 +35,6 @@ describe MergeRequestEntity do ...@@ -35,37 +35,6 @@ describe MergeRequestEntity do
end end
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 it 'has email_patches_path' do
expect(subject[:email_patches_path]) expect(subject[:email_patches_path])
.to eq("/#{resource.project.full_path}/merge_requests/#{resource.iid}.patch") .to eq("/#{resource.project.full_path}/merge_requests/#{resource.iid}.patch")
...@@ -120,18 +89,6 @@ describe MergeRequestEntity do ...@@ -120,18 +89,6 @@ describe MergeRequestEntity do
end end
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 describe 'diverged_commits_count' do
context 'when MR open and its diverging' do context 'when MR open and its diverging' do
it 'returns diverged commits count' 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