Commit c55bbe06 authored by Stan Hu's avatar Stan Hu

Merge branch 'master' into ce-to-ee-2018-02-26

parents ae1b6ea6 90488d20
<script>
import Sortable from 'vendor/Sortable';
import boardNewIssue from './board_new_issue';
import boardNewIssue from './board_new_issue.vue';
import boardCard from './board_card.vue';
import eventHub from '../eventhub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
......
/* global ListIssue */
<script>
import eventHub from '../eventhub';
import ProjectSelect from './project_select.vue';
import ProjectSelect from 'ee/boards/components/project_select.vue'; // eslint-disable-line import/first
import ListIssue from '../models/issue';
const Store = gl.issueBoards.BoardsStore;
export default {
name: 'BoardNewIssue',
components: {
ProjectSelect,
},
props: {
groupId: {
type: Number,
......@@ -24,9 +28,6 @@ export default {
selectedProject: {},
};
},
components: {
'project-select': ProjectSelect,
},
computed: {
disabled() {
if (this.groupId) {
......@@ -35,6 +36,10 @@ export default {
return this.title === '';
},
},
mounted() {
this.$refs.input.focus();
eventHub.$on('setSelectedProject', this.setSelectedProject);
},
methods: {
submit(e) {
e.preventDefault();
......@@ -81,49 +86,57 @@ export default {
this.selectedProject = selectedProject;
},
},
mounted() {
this.$refs.input.focus();
eventHub.$on('setSelectedProject', this.setSelectedProject);
},
template: `
<div class="board-new-issue-form">
<div class="card">
<form @submit="submit($event)">
<div class="flash-container"
v-if="error">
<div class="flash-alert">
An error occurred. Please try again.
</div>
</div>
<label class="label-light"
:for="list.id + '-title'">
Title
</label>
<input class="form-control"
type="text"
v-model="title"
ref="input"
autocomplete="off"
:id="list.id + '-title'" />
<project-select
v-if="groupId"
:groupId="groupId"
/>
<div class="clearfix prepend-top-10">
<button class="btn btn-success pull-left"
type="submit"
:disabled="disabled"
ref="submit-button">
Submit issue
</button>
<button class="btn btn-default pull-right"
type="button"
@click="cancel">
Cancel
</button>
};
</script>
<template>
<div class="board-new-issue-form">
<div class="card">
<form @submit="submit($event)">
<div
class="flash-container"
v-if="error"
>
<div class="flash-alert">
An error occurred. Please try again.
</div>
</form>
</div>
</div>
<label
class="label-light"
:for="list.id + '-title'"
>
Title
</label>
<input
class="form-control"
type="text"
v-model="title"
ref="input"
autocomplete="off"
:id="list.id + '-title'"
/>
<project-select
v-if="groupId"
:group-id="groupId"
/>
<div class="clearfix prepend-top-10">
<button
class="btn btn-success pull-left"
type="submit"
:disabled="disabled"
ref="submit-button"
>
Submit issue
</button>
<button
class="btn btn-default pull-right"
type="button"
@click="cancel"
>
Cancel
</button>
</div>
</form>
</div>
`,
};
</div>
</template>
......@@ -2,16 +2,17 @@
import _ from 'underscore';
import Vue from 'vue';
import Flash from '../flash';
import { __ } from '../locale';
import Flash from '~/flash';
import { __ } from '~/locale';
import FilteredSearchBoards from './filtered_search_boards';
import eventHub from './eventhub';
import sidebarEventHub from '../sidebar/event_hub';
import sidebarEventHub from '~/sidebar/event_hub'; // eslint-disable-line import/first
import './models/issue';
import './models/label';
import './models/list';
import './models/milestone';
import './models/project';
import './models/assignee';
import './stores/boards_store';
import './stores/modal_store';
......@@ -23,16 +24,15 @@ import './components/board';
import './components/board_sidebar';
import './components/new_list_dropdown';
import './components/modal/index';
import '../vue_shared/vue_resource_interceptor';
import '~/vue_shared/vue_resource_interceptor'; // eslint-disable-line import/first
import 'ee/boards/models/project'; // eslint-disable-line import/first
import 'ee/boards/components/boards_selector'; // eslint-disable-line import/first
import collapseIcon from 'ee/boards/icons/fullscreen_collapse.svg'; // eslint-disable-line import/first
import expandIcon from 'ee/boards/icons/fullscreen_expand.svg'; // eslint-disable-line import/first
import tooltip from '~/vue_shared/directives/tooltip'; // eslint-disable-line import/first
<<<<<<< HEAD:app/assets/javascripts/boards/index.js
import './components/boards_selector';
import collapseIcon from './icons/fullscreen_collapse.svg';
import expandIcon from './icons/fullscreen_expand.svg';
import tooltip from '../vue_shared/directives/tooltip';
=======
>>>>>>> upstream/master:app/assets/javascripts/boards/index.js
export default () => {
const $boardApp = document.getElementById('board-app');
const Store = gl.issueBoards.BoardsStore;
......
/* eslint-disable no-unused-vars, no-mixed-operators, comma-dangle */
/* global DocumentTouch */
import sortableConfig from '../../sortable/sortable_config';
import sortableConfig from 'ee/sortable/sortable_config';
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};
......
......@@ -4,7 +4,7 @@
/* global ListAssignee */
import Vue from 'vue';
import IssueProject from './project';
import IssueProject from 'ee/boards/models/project';
class ListIssue {
constructor (obj, defaultAvatar) {
......@@ -122,3 +122,5 @@ class ListIssue {
}
window.ListIssue = ListIssue;
export default ListIssue;
......@@ -3,7 +3,7 @@
import _ from 'underscore';
import Cookies from 'js-cookie';
import boardsStoreEE from 'ee/boards/stores/boards_store_ee';
import { getUrlParamsArray } from '../../lib/utils/common_utils';
import { getUrlParamsArray } from '~/lib/utils/common_utils';
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};
......
......@@ -6,177 +6,80 @@ import GlFieldErrors from './gl_field_errors';
import Shortcuts from './shortcuts';
import SearchAutocomplete from './search_autocomplete';
var Dispatcher;
(function() {
Dispatcher = (function() {
function Dispatcher() {
this.initSearch();
this.initFieldErrors();
this.initPageScripts();
}
Dispatcher.prototype.initPageScripts = function() {
var path, shortcut_handler;
const page = $('body').attr('data-page');
if (!page) {
return false;
}
const fail = () => Flash('Error loading dynamic module');
const callDefault = m => m.default();
path = page.split(':');
shortcut_handler = null;
$('.js-gfm-input:not(.js-vue-textarea)').each((i, el) => {
const gfm = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources);
const enableGFM = convertPermissionToBoolean(el.dataset.supportsAutocomplete);
gfm.setup($(el), {
emojis: true,
members: enableGFM,
issues: enableGFM,
milestones: enableGFM,
mergeRequests: enableGFM,
labels: enableGFM,
});
});
const shortcutHandlerPages = [
'projects:activity',
'projects:artifacts:browse',
'projects:artifacts:file',
'projects:blame:show',
'projects:blob:show',
'projects:commit:show',
'projects:commits:show',
'projects:find_file:show',
'projects:issues:edit',
'projects:issues:index',
'projects:issues:new',
'projects:issues:show',
'projects:merge_requests:creations:diffs',
'projects:merge_requests:creations:new',
'projects:merge_requests:edit',
'projects:merge_requests:index',
'projects:merge_requests:show',
'projects:network:show',
'projects:show',
'projects:tree:show',
'groups:show',
];
function initSearch() {
// Only when search form is present
if ($('.search').length) {
return new SearchAutocomplete();
}
}
if (shortcutHandlerPages.indexOf(page) !== -1) {
shortcut_handler = true;
}
function initFieldErrors() {
$('.gl-show-field-errors').each((i, form) => {
new GlFieldErrors(form);
});
}
switch (path[0]) {
case 'admin':
switch (path[1]) {
case 'broadcast_messages':
import('./pages/admin/broadcast_messages')
.then(callDefault)
.catch(fail);
break;
case 'cohorts':
import('./pages/admin/cohorts')
.then(callDefault)
.catch(fail);
break;
case 'groups':
switch (path[2]) {
case 'show':
import('./pages/admin/groups/show')
.then(callDefault)
.catch(fail);
break;
}
break;
case 'projects':
import('./pages/admin/projects')
.then(callDefault)
.catch(fail);
break;
case 'labels':
switch (path[2]) {
case 'new':
import('./pages/admin/labels/new')
.then(callDefault)
.catch(fail);
break;
case 'edit':
import('./pages/admin/labels/edit')
.then(callDefault)
.catch(fail);
break;
}
case 'abuse_reports':
import('./pages/admin/abuse_reports')
.then(callDefault)
.catch(fail);
break;
}
break;
case 'profiles':
import('./pages/profiles/index')
.then(callDefault)
.catch(fail);
break;
case 'projects':
import('./pages/projects')
.then(callDefault)
.catch(fail);
shortcut_handler = true;
switch (path[1]) {
case 'compare':
import('./pages/projects/compare')
.then(callDefault)
.catch(fail);
break;
case 'create':
case 'new':
import('./pages/projects/new')
.then(callDefault)
.catch(fail);
break;
case 'wikis':
import('./pages/projects/wikis')
.then(callDefault)
.catch(fail);
shortcut_handler = true;
break;
}
break;
}
// If we haven't installed a custom shortcut handler, install the default one
if (!shortcut_handler) {
new Shortcuts();
}
function initPageShortcuts(page) {
const pagesWithCustomShortcuts = [
'projects:activity',
'projects:artifacts:browse',
'projects:artifacts:file',
'projects:blame:show',
'projects:blob:show',
'projects:commit:show',
'projects:commits:show',
'projects:find_file:show',
'projects:issues:edit',
'projects:issues:index',
'projects:issues:new',
'projects:issues:show',
'projects:merge_requests:creations:diffs',
'projects:merge_requests:creations:new',
'projects:merge_requests:edit',
'projects:merge_requests:index',
'projects:merge_requests:show',
'projects:network:show',
'projects:show',
'projects:tree:show',
'groups:show',
];
if (document.querySelector('#peek')) {
import('./performance_bar')
.then(m => new m.default({ container: '#peek' })) // eslint-disable-line new-cap
.catch(fail);
}
};
if (pagesWithCustomShortcuts.indexOf(page) === -1) {
new Shortcuts();
}
}
Dispatcher.prototype.initSearch = function() {
// Only when search form is present
if ($('.search').length) {
return new SearchAutocomplete();
}
};
function initGFMInput() {
$('.js-gfm-input:not(.js-vue-textarea)').each((i, el) => {
const gfm = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources);
const enableGFM = convertPermissionToBoolean(el.dataset.supportsAutocomplete);
gfm.setup($(el), {
emojis: true,
members: enableGFM,
issues: enableGFM,
milestones: enableGFM,
mergeRequests: enableGFM,
labels: enableGFM,
});
});
}
Dispatcher.prototype.initFieldErrors = function() {
$('.gl-show-field-errors').each((i, form) => {
new GlFieldErrors(form);
});
};
function initPerformanceBar() {
if (document.querySelector('#peek')) {
import('./performance_bar')
.then(m => new m.default({ container: '#peek' })) // eslint-disable-line new-cap
.catch(() => Flash('Error loading performance bar module'));
}
}
return Dispatcher;
})();
})();
export default () => {
initSearch();
initFieldErrors();
export default function initDispatcher() {
return new Dispatcher();
}
const page = $('body').attr('data-page');
if (page) {
initPageShortcuts(page);
initGFMInput();
initPerformanceBar();
}
};
......@@ -2,9 +2,10 @@
/**
* Render environments table.
*/
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import environmentItem from './environment_item.vue';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import deployBoard from './deploy_board_component.vue';
import deployBoard from 'ee/environments/components/deploy_board_component.vue'; // eslint-disable-line import/first
export default {
components: {
......
<script>
import { mapState } from 'vuex';
import timeAgoMixin from '../../vue_shared/mixins/timeago';
import skeletonLoadingContainer from '../../vue_shared/components/skeleton_loading_container.vue';
import fileStatusIcon from './repo_file_status_icon.vue';
import timeAgoMixin from '~/vue_shared/mixins/timeago';
import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
import fileIcon from '~/vue_shared/components/file_icon.vue';
import newDropdown from './new_dropdown/index.vue';
import fileIcon from '../../vue_shared/components/file_icon.vue';
import changedFileIcon from './changed_file_icon.vue';
import fileStatusIcon from 'ee/ide/components/repo_file_status_icon.vue'; // eslint-disable-line import/first
import changedFileIcon from 'ee/ide/components/changed_file_icon.vue'; // eslint-disable-line import/first
export default {
components: {
......
<script>
import { mapActions } from 'vuex';
import fileStatusIcon from './repo_file_status_icon.vue';
import fileIcon from '../../vue_shared/components/file_icon.vue';
import icon from '../../vue_shared/components/icon.vue';
import changedFileIcon from './changed_file_icon.vue';
import fileIcon from '~/vue_shared/components/file_icon.vue';
import icon from '~/vue_shared/components/icon.vue';
import fileStatusIcon from 'ee/ide/components/repo_file_status_icon.vue';
import changedFileIcon from 'ee/ide/components/changed_file_icon.vue';
export default {
components: {
......
......@@ -5,7 +5,7 @@ import Disposable from './common/disposable';
import ModelManager from './common/model_manager';
import editorOptions from './editor_options';
import gitlabTheme from './themes/gl_theme';
import gitlabTheme from 'ee/ide/lib/themes/gl_theme'; // eslint-disable-line import/first
export default class Editor {
static create(monaco) {
......
import AbuseReports from './abuse_reports';
export default () => new AbuseReports();
document.addEventListener('DOMContentLoaded', () => new AbuseReports());
......@@ -3,7 +3,7 @@ import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
import { __ } from '~/locale';
export default function initBroadcastMessagesForm() {
export default () => {
$('input#broadcast_message_color').on('input', function onMessageColorInput() {
const previewColor = $(this).val();
$('div.broadcast-message-preview').css('background-color', previewColor);
......@@ -32,4 +32,4 @@ export default function initBroadcastMessagesForm() {
.catch(() => flash(__('An error occurred while rendering preview broadcast message')));
}
}, 250));
}
};
import initBroadcastMessagesForm from './broadcast_message';
export default () => initBroadcastMessagesForm();
document.addEventListener('DOMContentLoaded', initBroadcastMessagesForm);
import initUsagePing from './usage_ping';
export default () => initUsagePing();
document.addEventListener('DOMContentLoaded', initUsagePing);
import UsersSelect from '../../../../users_select';
export default () => new UsersSelect();
document.addEventListener('DOMContentLoaded', () => new UsersSelect());
import Labels from '../../../../labels';
export default () => new Labels();
document.addEventListener('DOMContentLoaded', () => new Labels());
import Labels from '../../../../labels';
export default () => new Labels();
document.addEventListener('DOMContentLoaded', () => new Labels());
import ProjectsList from '../../../projects_list';
import NamespaceSelect from '../../../namespace_select';
export default () => {
document.addEventListener('DOMContentLoaded', () => {
new ProjectsList(); // eslint-disable-line no-new
document.querySelectorAll('.js-namespace-select')
.forEach(dropdown => new NamespaceSelect({ dropdown }));
};
});
import NotificationsForm from '../../../notifications_form';
import notificationsDropdown from '../../../notifications_dropdown';
export default () => {
document.addEventListener('DOMContentLoaded', () => {
new NotificationsForm(); // eslint-disable-line no-new
notificationsDropdown();
};
});
import initCompareAutocomplete from '~/compare_autocomplete';
export default () => {
initCompareAutocomplete();
};
document.addEventListener('DOMContentLoaded', initCompareAutocomplete);
import Project from './project';
import ShortcutsNavigation from '../../shortcuts_navigation';
export default () => {
document.addEventListener('DOMContentLoaded', () => {
new Project(); // eslint-disable-line no-new
new ShortcutsNavigation(); // eslint-disable-line no-new
};
});
......@@ -2,8 +2,8 @@ import ProjectNew from '../shared/project_new';
import initProjectVisibilitySelector from '../../../project_visibility';
import initProjectNew from '../../../projects/project_new';
export default () => {
document.addEventListener('DOMContentLoaded', () => {
new ProjectNew(); // eslint-disable-line no-new
initProjectVisibilitySelector();
initProjectNew.bindEvents();
};
});
......@@ -3,9 +3,9 @@ import ShortcutsWiki from '../../../shortcuts_wiki';
import ZenMode from '../../../zen_mode';
import GLForm from '../../../gl_form';
export default () => {
document.addEventListener('DOMContentLoaded', () => {
new Wikis(); // eslint-disable-line no-new
new ShortcutsWiki(); // eslint-disable-line no-new
new ZenMode(); // eslint-disable-line no-new
new GLForm($('.wiki-form'), true); // eslint-disable-line no-new
};
});
<script>
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import linkedPipelinesColumn from './linked_pipelines_column.vue';
import stageColumnComponent from './stage_column_component.vue';
import linkedPipelinesColumn from 'ee/pipelines/components/graph/linked_pipelines_column.vue'; // eslint-disable-line import/first
export default {
components: {
linkedPipelinesColumn,
......
<script>
/* eslint-disable vue/require-default-prop */
import pipelineStage from '../../pipelines/components/stage.vue';
import ciIcon from '../../vue_shared/components/ci_icon.vue';
import icon from '../../vue_shared/components/icon.vue';
import linkedPipelinesMiniList from '../../vue_shared/components/linked_pipelines_mini_list.vue';
import pipelineStage from '~/pipelines/components/stage.vue';
import ciIcon from '~/vue_shared/components/ci_icon.vue';
import icon from '~/vue_shared/components/icon.vue';
import linkedPipelinesMiniList from 'ee/vue_shared/components/linked_pipelines_mini_list.vue';
export default {
name: 'MRWidgetPipeline',
......
......@@ -39,7 +39,7 @@ class LabelsFinder < UnionFinder
end
end
elsif only_group_labels?
label_ids << Label.where(group_id: group.id)
label_ids << Label.where(group_id: group_ids)
else
label_ids << Label.where(group_id: projects.group_ids)
label_ids << Label.where(project_id: projects.select(:id))
......@@ -59,10 +59,11 @@ class LabelsFinder < UnionFinder
items.where(title: title)
end
def group
strong_memoize(:group) do
def group_ids
strong_memoize(:group_ids) do
group = Group.find(params[:group_id])
authorized_to_read_labels?(group) && group
groups = params[:include_ancestor_groups].present? ? group.self_and_ancestors : [group]
groups_user_can_read_labels(groups).map(&:id)
end
end
......@@ -120,4 +121,10 @@ class LabelsFinder < UnionFinder
Ability.allowed?(current_user, :read_label, label_parent)
end
def groups_user_can_read_labels(groups)
DeclarativePolicy.user_scope do
groups.select { |group| authorized_to_read_labels?(group) }
end
end
end
......@@ -79,8 +79,12 @@ class IssuableBaseService < BaseService
return unless labels
params[:label_ids] = labels.split(",").map do |label_name|
service = Labels::FindOrCreateService.new(current_user, project, title: label_name.strip)
label = service.execute
label = Labels::FindOrCreateService.new(
current_user,
parent,
title: label_name.strip,
available_labels: available_labels
).execute
label.try(:id)
end.compact
......@@ -104,7 +108,7 @@ class IssuableBaseService < BaseService
end
def available_labels
LabelsFinder.new(current_user, project_id: @project.id).execute
@available_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute
end
def merge_quick_actions_into_params!(issuable)
......@@ -305,4 +309,8 @@ class IssuableBaseService < BaseService
def update_project_counter_caches?(issuable)
issuable.state_changed?
end
def parent
project
end
end
module Labels
class FindOrCreateService
def initialize(current_user, project, params = {})
def initialize(current_user, parent, params = {})
@current_user = current_user
@project = project
@parent = parent
@available_labels = params.delete(:available_labels)
@params = params.dup.with_indifferent_access
end
......@@ -13,12 +14,13 @@ module Labels
private
attr_reader :current_user, :project, :params, :skip_authorization
attr_reader :current_user, :parent, :params, :skip_authorization
def available_labels
@available_labels ||= LabelsFinder.new(
current_user,
project_id: project.id
"#{parent_type}_id".to_sym => parent.id,
only_group_labels: parent_is_group?
).execute(skip_authorization: skip_authorization)
end
......@@ -27,8 +29,8 @@ module Labels
def find_or_create_label
new_label = available_labels.find_by(title: title)
if new_label.nil? && (skip_authorization || Ability.allowed?(current_user, :admin_label, project))
new_label = Labels::CreateService.new(params).execute(project: project)
if new_label.nil? && (skip_authorization || Ability.allowed?(current_user, :admin_label, parent))
new_label = Labels::CreateService.new(params).execute(parent_type.to_sym => parent)
end
new_label
......@@ -37,5 +39,13 @@ module Labels
def title
params[:title] || params[:name]
end
def parent_type
parent.model_name.param_key
end
def parent_is_group?
parent_type == "group"
end
end
end
......@@ -27,6 +27,3 @@
- unless can?(current_user, :push_code, @project)
.inline.prepend-left-10
= commit_in_fork_help
- content_for :page_specific_javascripts do
= webpack_bundle_tag('blob')
......@@ -25,16 +25,10 @@ var NO_COMPRESSION = process.env.NO_COMPRESSION;
var autoEntries = {};
var pageEntries = glob.sync('pages/**/index.js', { cwd: path.join(ROOT_PATH, 'app/assets/javascripts') });
// filter out entries currently imported dynamically in dispatcher.js
var dispatcher = fs.readFileSync(path.join(ROOT_PATH, 'app/assets/javascripts/dispatcher.js')).toString();
var dispatcherChunks = dispatcher.match(/(?!import\(')\.\/pages\/[^']+/g);
function generateAutoEntries(path, prefix = '.') {
const chunkPath = path.replace(/\/index\.js$/, '');
if (!dispatcherChunks.includes(`${prefix}/${chunkPath}`)) {
const chunkName = chunkPath.replace(/\//g, '.');
autoEntries[chunkName] = `${prefix}/${path}`;
}
const chunkName = chunkPath.replace(/\//g, '.');
autoEntries[chunkName] = `${prefix}/${path}`;
}
pageEntries.forEach(( path ) => generateAutoEntries(path));
......@@ -98,19 +92,19 @@ var config = {
webpack_runtime: './webpack.js',
// EE-only
add_gitlab_slack_application: './add_gitlab_slack_application/index.js',
burndown_chart: './burndown_chart/index.js',
add_gitlab_slack_application: 'ee/add_gitlab_slack_application/index.js',
burndown_chart: 'ee/burndown_chart/index.js',
epic_show: 'ee/epics/epic_show/epic_show_bundle.js',
new_epic: 'ee/epics/new_epic/new_epic_bundle.js',
geo_nodes: 'ee/geo_nodes',
issuable: './issuable/issuable_bundle.js',
issues: './issues/issues_bundle.js',
ldap_group_links: './groups/ldap_group_links.js',
mirrors: './mirrors',
issuable: 'ee/issuable/issuable_bundle.js',
issues: 'ee/issues/issues_bundle.js',
ldap_group_links: 'ee/groups/ldap_group_links.js',
mirrors: 'ee/mirrors',
ee_protected_branches: 'ee/protected_branches',
ee_protected_tags: 'ee/protected_tags',
service_desk: './projects/settings_service_desk/service_desk_bundle.js',
service_desk_issues: './service_desk_issues/index.js',
service_desk: 'ee/projects/settings_service_desk/service_desk_bundle.js',
service_desk_issues: 'ee/service_desk_issues/index.js',
roadmap: 'ee/roadmap',
ee_sidebar: 'ee/sidebar/sidebar_bundle.js',
},
......@@ -331,6 +325,7 @@ var config = {
'images': path.join(ROOT_PATH, 'app/assets/images'),
'vendor': path.join(ROOT_PATH, 'vendor/assets/javascripts'),
'vue$': 'vue/dist/vue.esm.js',
'spec': path.join(ROOT_PATH, 'spec/javascripts'),
// EE-only
'ee': path.join(ROOT_PATH, 'ee/app/assets/javascripts'),
......
......@@ -17,12 +17,14 @@ Gets all epics of the requested group and its subgroups.
```
GET /groups/:id/-/epics
GET /groups/:id/-/epics?author_id=5
GET /groups/:id/-/epics?labels=bug,reproduced
```
| Attribute | Type | Required | Description |
| ------------------- | ---------------- | ---------- | ---------------------------------------------------------------------------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `author_id` | integer | no | Return epics created by the given user `id` |
| `labels` | string | no | Return epics matching a comma separated list of labels names. Label names from the epic group or a parent group can be used |
| `order_by` | string | no | Return epics ordered by `created_at` or `updated_at` fields. Default is `created_at` |
| `sort` | string | no | Return epics sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Search epics against their `title` and `description` |
......@@ -49,6 +51,7 @@ Example response:
"avatar_url": "http://www.gravatar.com/avatar/018729e129a6f31c80a6327a30196823?s=80&d=identicon",
"web_url": "http://localhost:3001/kam"
},
"labels": [],
"start_date": null,
"end_date": null,
"created_at": "2018-01-21T06:21:13.165Z",
......@@ -110,6 +113,7 @@ POST /groups/:id/-/epics
| ------------------- | ---------------- | ---------- | ---------------------------------------------------------------------------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `title` | string | yes | The title of the epic |
| `labels` | string | no | The comma separated list of labels |
| `description` | string | no | The description of the epic |
| `start_date` | string | no | The start date of the epic |
| `end_date` | string. | no | The end date of the epic |
......@@ -135,6 +139,7 @@ Example response:
"id" : 18,
"username" : "eileen.lowe"
},
"labels": [],
"start_date": null,
"end_date": null,
"created_at": "2018-01-21T06:21:13.165Z",
......@@ -156,6 +161,7 @@ PUT /groups/:id/-/epics/:epic_iid
| `epic_iid` | integer/string | yes | The internal ID of the epic |
| `title` | string | no | The title of an epic |
| `description` | string | no | The description of an epic |
| `labels` | string | no | The comma separated list of labels |
| `start_date` | string | no | The start date of an epic |
| `end_date` | string. | no | The end date of an epic |
......@@ -180,6 +186,7 @@ Example response:
"id" : 18,
"username" : "eileen.lowe"
},
"labels": [],
"start_date": null,
"end_date": null,
"created_at": "2018-01-21T06:21:13.165Z",
......
<script>
import Flash from '~/flash';
import { redirectTo } from '~/lib/utils/url_utility';
import GitlabSlackService from '../services/gitlab_slack_service';
import { redirectTo } from '../../lib/utils/url_utility';
export default {
props: {
......
import axios from '../../lib/utils/axios_utils';
import axios from '~/lib/utils/axios_utils';
export default {
addToSlack(url, projectId) {
......
......@@ -2,8 +2,8 @@
/* global BoardService */
import Flash from '~/flash';
import modal from '../../vue_shared/components/modal.vue';
import { visitUrl } from '../../lib/utils/url_utility';
import modal from '~/vue_shared/components/modal.vue';
import { visitUrl } from '~/lib/utils/url_utility';
import BoardMilestoneSelect from './milestone_select.vue';
import BoardWeightSelect from './weight_select.vue';
import BoardLabelsSelect from './labels_select.vue';
......
<script>
/* global ListIssue */
import _ from 'underscore';
import eventHub from '../eventhub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import Api from '../../api';
import eventHub from '~/boards/eventhub';
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import Api from '~/api';
export default {
name: 'BoardProjectSelect',
......
......@@ -9,10 +9,10 @@
* [Mockup](https://gitlab.com/gitlab-org/gitlab-ce/uploads/2f655655c0eadf655d0ae7467b53002a/environments__deploy-graphic.png)
*/
import _ from 'underscore';
import { n__ } from '~/locale';
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import deployBoardSvg from 'ee_empty_states/icons/_deploy_board.svg';
import { n__ } from '../../locale';
import instanceComponent from './deploy_board_instance_component.vue';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
export default {
components: {
......
......@@ -12,7 +12,7 @@
* this information in the tooltip and the colors.
* Mockup is https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1551#note_26595150
*/
import tooltip from '../../vue_shared/directives/tooltip';
import tooltip from '~/vue_shared/directives/tooltip';
export default {
directives: {
......
<script>
/* eslint-disable vue/require-default-prop */
import issuableApp from '~/issue_show/components/app.vue';
import relatedIssuesRoot from '~/issuable/related_issues/components/related_issues_root.vue';
import relatedIssuesRoot from 'ee/issuable/related_issues/components/related_issues_root.vue';
import issuableAppEventHub from '~/issue_show/event_hub';
import epicHeader from './epic_header.vue';
import epicSidebar from '../../sidebar/components/sidebar_app.vue';
......
<script>
import icon from '../../vue_shared/components/icon.vue';
import icon from '~/vue_shared/components/icon.vue';
export default {
components: {
......
<script>
import icon from '../../vue_shared/components/icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
import '../../lib/utils/datetime_utility';
import icon from '~/vue_shared/components/icon.vue';
import tooltip from '~/vue_shared/directives/tooltip';
import '~/lib/utils/datetime_utility';
export default {
components: {
......
import Vue from 'vue';
import { convertPermissionToBoolean } from '~/lib/utils/common_utils';
import RelatedIssuesRoot from './related_issues/components/related_issues_root.vue';
import { convertPermissionToBoolean } from '../lib/utils/common_utils';
document.addEventListener('DOMContentLoaded', () => {
const relatedIssuesRootElement = document.querySelector('.js-related-issues-root');
......
......@@ -2,7 +2,7 @@
import Sortable from 'vendor/Sortable';
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import tooltip from '~/vue_shared/directives/tooltip';
import sortableConfig from '~/sortable/sortable_config';
import sortableConfig from 'ee/sortable/sortable_config';
import eventHub from '../event_hub';
import issueItem from './issue_item.vue';
import addIssuableForm from './add_issuable_form.vue';
......
......@@ -24,7 +24,7 @@ Your caret can stop touching a `rawReference` can happen in a variety of ways:
*/
import _ from 'underscore';
import Flash from '../../../flash';
import Flash from '~/flash';
import eventHub from '../event_hub';
import RelatedIssuesBlock from './related_issues_block.vue';
import RelatedIssuesStore from '../stores/related_issues_store';
......
import tooltip from '../../../vue_shared/directives/tooltip';
import icon from '../../../vue_shared/components/icon.vue';
import tooltip from '~/vue_shared/directives/tooltip';
import icon from '~/vue_shared/components/icon.vue';
import eventHub from '../event_hub';
const mixins = {
......
import _ from 'underscore';
import { __ } from '~/locale';
import axios from '~/lib/utils/axios_utils';
import Flash from '../flash';
import Flash from '~/flash';
import { backOff } from '~/lib/utils/common_utils';
import AUTH_METHOD from './constants';
import { backOff } from '../lib/utils/common_utils';
export default class MirrorPull {
constructor(formSelector) {
......
<script>
import ciStatus from '../../../vue_shared/components/ci_icon.vue';
import tooltip from '../../../vue_shared/directives/tooltip';
import ciStatus from '~/vue_shared/components/ci_icon.vue';
import tooltip from '~/vue_shared/directives/tooltip';
export default {
directives: {
......
<script>
import Flash from '../../../flash';
import Flash from '~/flash';
import serviceDeskSetting from './service_desk_setting.vue';
import ServiceDeskStore from '../stores/service_desk_store';
import ServiceDeskService from '../services/service_desk_service';
......
<script>
import tooltip from '~/vue_shared/directives/tooltip';
import eventHub from '../event_hub';
import tooltip from '../../../vue_shared/directives/tooltip';
export default {
name: 'ServiceDeskSetting',
......
import Vue from 'vue';
import { convertPermissionToBoolean } from '~/lib/utils/common_utils';
import serviceDeskRoot from './components/service_desk_root.vue';
import { convertPermissionToBoolean } from '../../lib/utils/common_utils';
document.addEventListener('DOMContentLoaded', () => {
const serviceDeskRootElement = document.querySelector('.js-service-desk-setting-root');
......
import Flash from '~/flash';
import LinkToMemberAvatar from '~/vue_shared/components/link_to_member_avatar';
import LinkToMemberAvatar from 'ee/vue_shared/components/link_to_member_avatar';
import { s__ } from '~/locale';
import eventHub from '~/vue_merge_request_widget/event_hub';
......
<script>
import arrowSvg from 'ee_icons/_arrow_mini_pipeline_graph.svg';
import icon from './icon.vue';
import ciStatus from './ci_icon.vue';
import tooltip from '../directives/tooltip';
import icon from '~/vue_shared/components/icon.vue';
import ciStatus from '~/vue_shared/components/ci_icon.vue';
import tooltip from '~/vue_shared/directives/tooltip';
export default {
directives: {
......
......@@ -54,11 +54,12 @@ class Groups::EpicsController < Groups::ApplicationController
end
def epic_params_attributes
%i[
title
description
start_date
end_date
[
:title,
:description,
:start_date,
:end_date,
label_ids: []
]
end
......@@ -67,7 +68,7 @@ class Groups::EpicsController < Groups::ApplicationController
end
def update_service
Epics::UpdateService.new(nil, current_user, epic_params)
Epics::UpdateService.new(@group, current_user, epic_params)
end
def finder_type
......
......@@ -11,6 +11,7 @@ class EpicsFinder < IssuableFinder
items = by_search(items)
items = by_author(items)
items = by_timeframe(items)
items = by_label(items)
sort(items)
end
......
......@@ -141,11 +141,25 @@ module Geo
# @return [ActiveRecord::Relation<Project>] list of projects updated recently
def legacy_find_projects_updated_recently
legacy_inner_join_registry_ids(
current_node.projects,
Geo::ProjectRegistry.dirty.retry_due.pluck(:project_id),
Project
)
registries = Geo::ProjectRegistry.dirty.retry_due.pluck(:project_id, :last_repository_successful_sync_at)
return Project.none if registries.empty?
id_and_last_sync_values = registries.map do |id, last_repository_successful_sync_at|
"(#{id}, #{quote_value(last_repository_successful_sync_at)})"
end
joined_relation = current_node.projects.joins(<<~SQL)
INNER JOIN
(VALUES #{id_and_last_sync_values.join(',')})
project_registry(id, last_repository_successful_sync_at)
ON #{Project.table_name}.id = project_registry.id
SQL
joined_relation
end
def quote_value(value)
::Gitlab::SQL::Glob.q(value)
end
# @return [ActiveRecord::Relation<Geo::ProjectRegistry>] list of synced projects
......
......@@ -11,4 +11,5 @@ class EpicEntity < IssuableEntity
expose :web_url do |epic|
group_epic_path(epic.group, epic)
end
expose :labels, using: LabelEntity
end
module Epics
class BaseService < IssuableBaseService
attr_reader :group
def initialize(group, current_user, params)
@group, @current_user, @params = group, current_user, params
end
private
def available_labels
@available_labels ||= LabelsFinder.new(
current_user,
group_id: group.id,
only_group_labels: true,
include_ancestor_groups: true
).execute
end
def parent
group
end
end
end
module Epics
class CreateService < IssuableBaseService
attr_reader :group
def initialize(group, current_user, params)
@group, @current_user, @params = group, current_user, params
end
class CreateService < Epics::BaseService
def execute
@epic = group.epics.new(whitelisted_epic_params)
create(@epic)
......
module Epics
class UpdateService < ::IssuableBaseService
class UpdateService < Epics::BaseService
def execute(epic)
update(epic)
end
......
......@@ -65,7 +65,7 @@ module Geo
def find_project_ids_updated_recently(batch_size:)
shard_restriction(finder.find_projects_updated_recently(batch_size: batch_size))
.order(Gitlab::Database.nulls_first_order(:last_repository_updated_at, :desc))
.order('project_registry.last_repository_successful_sync_at ASC NULLS FIRST, projects.last_repository_updated_at ASC')
.pluck(:id)
end
......
---
title: Geo - Fix repository synchronization order for projects updated recently
merge_request:
author:
type: fixed
---
title: Allow adding or removing labels from epics and filter epics by labels
merge_request:
author:
type: added
---
title: Move BoardNewIssue vue component
merge_request: 16947
author: George Tsiolis
type: performance
......@@ -32,8 +32,9 @@ module API
def find_epics(args = {})
args = declared_params.merge(args)
args[:label_name] = args.delete(:labels)
epics = EpicsFinder.new(current_user, args).execute
epics = EpicsFinder.new(current_user, args).execute.preload(:labels)
epics.reorder(args[:order_by] => args[:sort])
end
......@@ -54,6 +55,7 @@ module API
desc: 'Return epics sorted in `asc` or `desc` order.'
optional :search, type: String, desc: 'Search epics for text present in the title or description'
optional :author_id, type: Integer, desc: 'Return epics which are authored by the user with the given ID'
optional :labels, type: String, desc: 'Comma-separated list of label names'
end
get ':id/-/epics' do
present find_epics(group_id: user_group.id), with: Entities::Epic
......@@ -79,6 +81,7 @@ module API
optional :description, type: String, desc: 'The description of an epic'
optional :start_date, type: String, desc: 'The start date of an epic'
optional :end_date, type: String, desc: 'The end date of an epic'
optional :labels, type: String, desc: 'Comma-separated list of label names'
end
post ':id/-/epics' do
authorize_can_create!
......@@ -100,14 +103,15 @@ module API
optional :description, type: String, desc: 'The description of an epic'
optional :start_date, type: String, desc: 'The start date of an epic'
optional :end_date, type: String, desc: 'The end date of an epic'
at_least_one_of :title, :description, :start_date, :end_date
optional :labels, type: String, desc: 'Comma-separated list of label names'
at_least_one_of :title, :description, :start_date, :end_date, :labels
end
put ':id/-/epics/:epic_iid' do
authorize_can_admin!
update_params = declared_params(include_missing: false)
update_params.delete(:epic_iid)
result = ::Epics::UpdateService.new(nil, current_user, update_params).execute(epic)
result = ::Epics::UpdateService.new(user_group, current_user, update_params).execute(epic)
if result.valid?
present result, with: Entities::Epic
......
......@@ -4,6 +4,7 @@ describe Groups::EpicsController do
let(:group) { create(:group, :private) }
let(:epic) { create(:epic, group: group) }
let(:user) { create(:user) }
let(:label) { create(:group_label, group: group, title: 'Bug') }
before do
sign_in(user)
......@@ -170,7 +171,7 @@ describe Groups::EpicsController do
describe 'PUT #update' do
before do
group.add_developer(user)
put :update, group_id: group, id: epic.to_param, epic: { title: 'New title' }, format: :json
put :update, group_id: group, id: epic.to_param, epic: { title: 'New title', label_ids: [label.id] }, format: :json
end
it 'returns status 200' do
......@@ -178,7 +179,10 @@ describe Groups::EpicsController do
end
it 'updates the epic correctly' do
expect(epic.reload.title).to eq('New title')
epic.reload
expect(epic.title).to eq('New title')
expect(epic.labels).to eq([label])
end
end
......@@ -210,7 +214,7 @@ describe Groups::EpicsController do
describe '#create' do
subject do
post :create, group_id: group, epic: { title: 'new epic', description: 'some descripition' }
post :create, group_id: group, epic: { title: 'new epic', description: 'some descripition', label_ids: [label.id] }
end
context 'when user has permissions to create an epic' do
......@@ -229,6 +233,10 @@ describe Groups::EpicsController do
expect { subject }.to change { Epic.count }.from(0).to(1)
end
it 'assigns labels to the new epic' do
expect { subject }.to change { LabelLink.count }.from(0).to(1)
end
it 'returns the correct json' do
subject
......
......@@ -3,5 +3,15 @@ FactoryBot.define do
title { generate(:title) }
group
author
factory :labeled_epic do
transient do
labels []
end
after(:create) do |epic, evaluator|
epic.update_attributes(labels: evaluator.labels)
end
end
end
end
......@@ -79,6 +79,15 @@ describe EpicsFinder do
end
end
context 'by label' do
let(:label) { create(:label) }
let!(:labeled_epic) { create(:labeled_epic, group: group, labels: [label]) }
it 'returns all epics with given label' do
expect(epics(label_name: label.title)).to contain_exactly(labeled_epic)
end
end
context 'when subgroups are supported', :nested_groups do
let(:subgroup) { create(:group, :private, parent: group) }
let(:subgroup2) { create(:group, :private, parent: subgroup) }
......
......@@ -18,6 +18,12 @@
},
"additionalProperties": false
},
"labels": {
"type": "array",
"items": {
"type": "string"
}
},
"start_date": { "type": ["string", "null"] },
"end_date": { "type": ["string", "null"] },
"created_at": { "type": ["string", "null"] },
......
......@@ -21,7 +21,13 @@
"start_date": { "type": ["string", "null"] },
"end_date": { "type": ["string", "null"] },
"created_at": { "type": ["string", "null"] },
"updated_at": { "type": ["string", "null"] }
"updated_at": { "type": ["string", "null"] },
"labels": {
"type": "array",
"items": {
"type": "string"
}
}
},
"additionalProperties": false
},
......
......@@ -78,6 +78,8 @@ describe API::Epics do
created_at: 2.days.ago,
updated_at: 3.days.ago)
end
let!(:label) { create(:group_label, title: 'a-test', group: group) }
let!(:label_link) { create(:label_link, label: label, target: epic2) }
before do
stub_licensed_features(epics: true)
......@@ -130,6 +132,12 @@ describe API::Epics do
expect_array_response([epic2.id, epic.id])
end
it 'returns an array of labeled epics' do
get api(url, user), labels: label.title
expect_array_response([epic2.id])
end
end
end
......@@ -157,7 +165,7 @@ describe API::Epics do
describe 'POST /groups/:id/-/epics' do
let(:url) { "/groups/#{group.path}/-/epics" }
let(:params) { { title: 'new epic', description: 'epic description' } }
let(:params) { { title: 'new epic', description: 'epic description', labels: 'label1' } }
it_behaves_like 'error requests'
......@@ -196,6 +204,7 @@ describe API::Epics do
expect(epic.title).to eq('new epic')
expect(epic.description).to eq('epic description')
expect(epic.labels.first.title).to eq('label1')
end
end
end
......@@ -203,7 +212,7 @@ describe API::Epics do
describe 'PUT /groups/:id/-/epics/:epic_iid' do
let(:url) { "/groups/#{group.path}/-/epics/#{epic.iid}" }
let(:params) { { title: 'new title', description: 'new description' } }
let(:params) { { title: 'new title', description: 'new description', labels: 'label2' } }
it_behaves_like 'error requests'
......@@ -250,6 +259,7 @@ describe API::Epics do
expect(result.title).to eq('new title')
expect(result.description).to eq('new description')
expect(result.labels.first.title).to eq('label2')
end
end
end
......
......@@ -10,7 +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, :description, :title)
expect(subject).to include(:id, :iid, :description, :title, :labels)
end
it 'has epic specific attributes' do
......
......@@ -7,7 +7,7 @@ describe Epics::UpdateService do
describe '#execute' do
def update_epic(opts)
described_class.new(nil, user, opts).execute(epic)
described_class.new(group, user, opts).execute(epic)
end
context 'multiple values update' do
......
......@@ -7,12 +7,13 @@ describe Geo::RepositoryShardSyncWorker, :geo, :delete, :clean_gitlab_redis_cach
let!(:primary) { create(:geo_node, :primary) }
let!(:secondary) { create(:geo_node) }
let!(:synced_group) { create(:group) }
let!(:project_in_synced_group) { create(:project, group: synced_group) }
let!(:restricted_group) { create(:group) }
let!(:unsynced_project_in_restricted_group) { create(:project, group: restricted_group) }
let!(:unsynced_project) { create(:project) }
let(:shard_name) { Gitlab.config.repositories.storages.keys.first }
subject { described_class.new }
let(:shard_name) { Gitlab.config.repositories.storages.keys.first }
before do
stub_current_geo_node(secondary)
......@@ -37,7 +38,7 @@ describe Geo::RepositoryShardSyncWorker, :geo, :delete, :clean_gitlab_redis_cach
end
it 'performs Geo::ProjectSyncWorker for projects where last attempt to sync failed' do
create(:geo_project_registry, :sync_failed, project: project_in_synced_group)
create(:geo_project_registry, :sync_failed, project: unsynced_project_in_restricted_group)
create(:geo_project_registry, :synced, project: unsynced_project)
expect(Geo::ProjectSyncWorker).to receive(:perform_async).once.and_return(spy)
......@@ -54,7 +55,7 @@ describe Geo::RepositoryShardSyncWorker, :geo, :delete, :clean_gitlab_redis_cach
end
it 'performs Geo::ProjectSyncWorker for synced projects updated recently' do
create(:geo_project_registry, :synced, :repository_dirty, project: project_in_synced_group)
create(:geo_project_registry, :synced, :repository_dirty, project: unsynced_project_in_restricted_group)
create(:geo_project_registry, :synced, project: unsynced_project)
create(:geo_project_registry, :synced, :wiki_dirty)
......@@ -104,12 +105,12 @@ describe Geo::RepositoryShardSyncWorker, :geo, :delete, :clean_gitlab_redis_cach
context 'when node has namespace restrictions' do
before do
secondary.update!(selective_sync_type: 'namespaces', namespaces: [synced_group])
secondary.update!(selective_sync_type: 'namespaces', namespaces: [restricted_group])
end
it 'does not perform Geo::ProjectSyncWorker for projects that do not belong to selected namespaces to replicate' do
expect(Geo::ProjectSyncWorker).to receive(:perform_async)
.with(project_in_synced_group.id, within(1.minute).of(Time.now))
.with(unsynced_project_in_restricted_group.id, within(1.minute).of(Time.now))
.once
.and_return(spy)
......@@ -117,11 +118,11 @@ describe Geo::RepositoryShardSyncWorker, :geo, :delete, :clean_gitlab_redis_cach
end
it 'does not perform Geo::ProjectSyncWorker for synced projects updated recently that do not belong to selected namespaces to replicate' do
create(:geo_project_registry, :synced, :repository_dirty, project: project_in_synced_group)
create(:geo_project_registry, :synced, :repository_dirty, project: unsynced_project_in_restricted_group)
create(:geo_project_registry, :synced, :repository_dirty, project: unsynced_project)
expect(Geo::ProjectSyncWorker).to receive(:perform_async)
.with(project_in_synced_group.id, within(1.minute).of(Time.now))
.with(unsynced_project_in_restricted_group.id, within(1.minute).of(Time.now))
.once
.and_return(spy)
......@@ -129,13 +130,53 @@ describe Geo::RepositoryShardSyncWorker, :geo, :delete, :clean_gitlab_redis_cach
end
end
context 'repositories that have never been updated' do
let!(:project_list) { create_list(:project, 4, last_repository_updated_at: 2.hours.ago) }
let!(:abandoned_project) { create(:project) }
before do
# Project sync failed but never received an update
create(:geo_project_registry, :repository_sync_failed, project: abandoned_project)
abandoned_project.update_column(:last_repository_updated_at, 1.year.ago)
# Neither of these are needed for this spec
unsynced_project.destroy
unsynced_project_in_restricted_group.destroy
allow_any_instance_of(described_class).to receive(:db_retrieve_batch_size).and_return(2) # Must be >1 because of the Geo::BaseSchedulerWorker#interleave
secondary.update!(repos_max_capacity: 3) # Must be more than db_retrieve_batch_size
project_list.each do |project|
allow(Geo::ProjectSyncWorker)
.to receive(:perform_async)
.with(project.id, anything)
.and_call_original
end
allow_any_instance_of(Geo::ProjectRegistry).to receive(:wiki_sync_due?).and_return(false)
allow_any_instance_of(Geo::RepositorySyncService).to receive(:expire_repository_caches)
end
it 'tries to sync project where last attempt to sync failed' do
expect(Geo::ProjectSyncWorker)
.to receive(:perform_async)
.with(abandoned_project.id, anything)
.at_least(:once)
.and_return(spy)
3.times do
Sidekiq::Testing.inline! { subject.perform(shard_name) }
end
end
end
context 'all repositories fail' do
let!(:project_list) { create_list(:project, 4, :random_last_repository_updated_at) }
before do
# Neither of these are needed for this spec
unsynced_project.destroy
project_in_synced_group.destroy
unsynced_project_in_restricted_group.destroy
allow_any_instance_of(described_class).to receive(:db_retrieve_batch_size).and_return(2) # Must be >1 because of the Geo::BaseSchedulerWorker#interleave
secondary.update!(repos_max_capacity: 3) # Must be more than db_retrieve_batch_size
......@@ -161,14 +202,14 @@ describe Geo::RepositoryShardSyncWorker, :geo, :delete, :clean_gitlab_redis_cach
context 'additional shards' do
it 'skips backfill for projects on unhealthy shards' do
missing_not_synced = create(:project, group: synced_group)
missing_not_synced = create(:project, group: restricted_group)
missing_not_synced.update_column(:repository_storage, 'unknown')
missing_dirty = create(:project, group: synced_group)
missing_dirty = create(:project, group: restricted_group)
missing_dirty.update_column(:repository_storage, 'unknown')
create(:geo_project_registry, :synced, :repository_dirty, project: missing_dirty)
expect(Geo::ProjectSyncWorker).to receive(:perform_async).with(project_in_synced_group.id, anything)
expect(Geo::ProjectSyncWorker).to receive(:perform_async).with(unsynced_project_in_restricted_group.id, anything)
expect(Geo::ProjectSyncWorker).not_to receive(:perform_async).with(missing_not_synced.id, anything)
expect(Geo::ProjectSyncWorker).not_to receive(:perform_async).with(missing_dirty.id, anything)
......
......@@ -508,6 +508,10 @@ module API
expose :end_date
expose :created_at
expose :updated_at
expose :labels do |epic, options|
# Avoids an N+1 query since labels are preloaded
epic.labels.map(&:title).sort
end
end
class EpicIssue < Issue
......
......@@ -5,6 +5,8 @@ describe LabelsFinder do
let(:group_1) { create(:group) }
let(:group_2) { create(:group) }
let(:group_3) { create(:group) }
let(:private_group_1) { create(:group, :private) }
let(:private_subgroup_1) { create(:group, :private, parent: private_group_1) }
let(:project_1) { create(:project, namespace: group_1) }
let(:project_2) { create(:project, namespace: group_2) }
......@@ -20,6 +22,8 @@ describe LabelsFinder do
let!(:group_label_1) { create(:group_label, group: group_1, title: 'Label 1 (group)') }
let!(:group_label_2) { create(:group_label, group: group_1, title: 'Group Label 2') }
let!(:group_label_3) { create(:group_label, group: group_2, title: 'Group Label 3') }
let!(:private_group_label_1) { create(:group_label, group: private_group_1, title: 'Private Group Label 1') }
let!(:private_subgroup_label_1) { create(:group_label, group: private_subgroup_1, title: 'Private Sub Group Label 1') }
let(:user) { create(:user) }
......@@ -66,6 +70,25 @@ describe LabelsFinder do
expect(finder.execute).to eq [group_label_2, group_label_1]
end
end
context 'when including labels from group ancestors', :nested_groups do
it 'returns labels from group and its ancestors' do
private_group_1.add_developer(user)
private_subgroup_1.add_developer(user)
finder = described_class.new(user, group_id: private_subgroup_1.id, only_group_labels: true, include_ancestor_groups: true)
expect(finder.execute).to eq [private_group_label_1, private_subgroup_label_1]
end
it 'ignores labels from groups which user can not read' do
private_subgroup_1.add_developer(user)
finder = described_class.new(user, group_id: private_subgroup_1.id, only_group_labels: true, include_ancestor_groups: true)
expect(finder.execute).to eq [private_subgroup_label_1]
end
end
end
context 'filtering by project_id' do
......
import Vue from 'vue';
import addGitlabSlackApplication from '~/add_gitlab_slack_application/components/add_gitlab_slack_application.vue';
import GitlabSlackService from '~/add_gitlab_slack_application/services/gitlab_slack_service';
import addGitlabSlackApplication from 'ee/add_gitlab_slack_application/components/add_gitlab_slack_application.vue';
import GitlabSlackService from 'ee/add_gitlab_slack_application/services/gitlab_slack_service';
import * as UrlUtility from '~/lib/utils/url_utility';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('AddGitlabSlackApplication', () => {
const redirectLink = '//redirectLink';
......
......@@ -4,7 +4,7 @@
import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import boardNewIssue from '~/boards/components/board_new_issue';
import boardNewIssue from '~/boards/components/board_new_issue.vue';
import '~/boards/models/list';
import { listObj, boardsMockInterceptor, mockBoardService } from './mock_data';
......
......@@ -2,10 +2,10 @@
import MockAdapter from 'axios-mock-adapter';
import Vue from 'vue';
import axios from '~/lib/utils/axios_utils';
import AssigneeSelect from '~/boards/components/assignee_select.vue';
import '~/boards/services/board_service';
import '~/boards/stores/boards_store';
import IssuableContext from '~/issuable_context';
import AssigneeSelect from 'ee/boards/components/assignee_select.vue';
import { boardObj, mockBoardService } from '../mock_data';
let vm;
......
import Vue from 'vue';
import boardForm from '~/boards/components/board_form.vue';
import boardForm from 'ee/boards/components/board_form.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('board_form.vue', () => {
const props = {
......
......@@ -2,9 +2,9 @@
import Vue from 'vue';
import BoardService from '~/boards/services/board_service';
import '~/boards/components/boards_selector';
import setTimeoutPromiseHelper from '../../helpers/set_timeout_promise_helper';
import mountComponent from '../../helpers/vue_mount_component_helper';
import 'ee/boards/components/boards_selector';
import setTimeoutPromiseHelper from 'spec/helpers/set_timeout_promise_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
const throttleDuration = 1;
......
......@@ -4,7 +4,7 @@ import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import '~/labels_select';
import LabelsSelect from '~/boards/components/labels_select.vue';
import LabelsSelect from 'ee/boards/components/labels_select.vue';
import IssuableContext from '~/issuable_context';
let vm;
......
import Vue from 'vue';
import WeightSelect from '~/boards/components/weight_select.vue';
import WeightSelect from 'ee/boards/components/weight_select.vue';
import IssuableContext from '~/issuable_context';
let vm;
......
......@@ -3,7 +3,7 @@
import Vue from 'vue';
import MockAdapater from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import MilestoneSelect from '~/boards/components/milestone_select.vue';
import MilestoneSelect from 'ee/boards/components/milestone_select.vue';
import IssuableContext from '~/issuable_context';
import { boardObj } from './mock_data';
......
import VariableList from '~/ci_variable_list/ci_variable_list';
import getSetTimeoutPromise from '../helpers/set_timeout_promise_helper';
import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper';
describe('VariableList (EE features)', () => {
preloadFixtures('projects/ci_cd_settings.html.raw');
......
import VariableList from '~/ci_variable_list/ci_variable_list';
import getSetTimeoutPromise from '../helpers/set_timeout_promise_helper';
import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper';
const HIDE_CLASS = 'hide';
......
......@@ -7,7 +7,7 @@ import {
REQUEST_SUCCESS,
REQUEST_FAILURE,
} from '~/clusters/constants';
import getSetTimeoutPromise from '../helpers/set_timeout_promise_helper';
import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper';
describe('Clusters', () => {
let cluster;
......
......@@ -12,7 +12,7 @@ import {
REQUEST_FAILURE,
} from '~/clusters/constants';
import applicationRow from '~/clusters/components/application_row.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { DEFAULT_APPLICATION_STATE } from '../services/mock_data';
describe('Application Row', () => {
......
import Vue from 'vue';
import applications from '~/clusters/components/applications.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('Applications', () => {
let vm;
......
......@@ -2,7 +2,7 @@ import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
import mountComponent from '../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('Commit pipeline status component', () => {
let vm;
......
import Vue from 'vue';
import banner from '~/cycle_analytics/components/banner.vue';
import mountComponent from '../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('Cycle analytics banner', () => {
let vm;
......
import Vue from 'vue';
import component from '~/cycle_analytics/components/total_time_component.vue';
import mountComponent from '../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('Total time component', () => {
let vm;
......
import Vue from 'vue';
import DeployBoard from '~/environments/components/deploy_board_component.vue';
import DeployBoard from 'ee/environments/components/deploy_board_component.vue';
import { deployBoardMockData } from './mock_data';
describe('Deploy Board', () => {
......
import Vue from 'vue';
import DeployBoardInstance from '~/environments/components/deploy_board_instance_component.vue';
import DeployBoardInstance from 'ee/environments/components/deploy_board_instance_component.vue';
describe('Deploy Board Instance', () => {
let DeployBoardInstanceComponent;
......
import Vue from 'vue';
import emptyState from '~/environments/components/empty_state.vue';
import mountComponent from '../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('environments empty state', () => {
let vm;
......
import Vue from 'vue';
import environmentTableComp from '~/environments/components/environments_table.vue';
import eventHub from '~/environments/event_hub';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { deployBoardMockData } from './mock_data';
import mountComponent from '../helpers/vue_mount_component_helper';
describe('Environment table', () => {
let Component;
......
import _ from 'underscore';
import Vue from 'vue';
import environmentsComponent from '~/environments/components/environments_app.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { headersInterceptor } from 'spec/helpers/vue_resource_helper';
import { environment, folder } from './mock_data';
import { headersInterceptor } from '../helpers/vue_resource_helper';
import mountComponent from '../helpers/vue_mount_component_helper';
describe('Environment', () => {
const mockData = {
......
import _ from 'underscore';
import Vue from 'vue';
import environmentsFolderViewComponent from '~/environments/folder/environments_folder_view.vue';
import { headersInterceptor } from 'spec/helpers/vue_resource_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { environmentsList } from '../mock_data';
import { headersInterceptor } from '../../helpers/vue_resource_helper';
import mountComponent from '../../helpers/vue_mount_component_helper';
describe('Environments Folder View', () => {
let Component;
......
import Vue from 'vue';
import epicHeader from 'ee/epics/epic_show/components/epic_header.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { headerProps } from '../mock_data';
describe('epicHeader', () => {
......
......@@ -7,9 +7,9 @@ import epicSidebar from 'ee/epics/sidebar/components/sidebar_app.vue';
import issuableApp from '~/issue_show/components/app.vue';
import issuableAppEventHub from '~/issue_show/event_hub';
import * as urlUtils from '~/lib/utils/url_utility';
import mountComponent from '../../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import issueShowData from 'spec/issue_show/mock_data';
import { props } from '../mock_data';
import issueShowData from '../../../issue_show/mock_data';
describe('EpicShowApp', () => {
let mock;
......
......@@ -2,7 +2,7 @@ import Vue from 'vue';
import _ from 'underscore';
import newEpic from 'ee/epics/new_epic/components/new_epic.vue';
import * as urlUtility from '~/lib/utils/url_utility';
import mountComponent from '../../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('newEpic', () => {
let vm;
......
......@@ -2,7 +2,7 @@ import Vue from 'vue';
import _ from 'underscore';
import Cookies from 'js-cookie';
import epicSidebar from 'ee/epics/sidebar/components/sidebar_app.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('epicSidebar', () => {
let vm;
......
......@@ -8,7 +8,7 @@ import {
mouseenter,
inserted,
} from '~/feature_highlight/feature_highlight_helper';
import getSetTimeoutPromise from '../helpers/set_timeout_promise_helper';
import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper';
describe('feature highlight helper', () => {
describe('getSelector', () => {
......
......@@ -6,10 +6,9 @@ import appComponent from 'ee/geo_nodes/components/app.vue';
import eventHub from 'ee/geo_nodes/event_hub';
import GeoNodesStore from 'ee/geo_nodes/store/geo_nodes_store';
import GeoNodesService from 'ee/geo_nodes/service/geo_nodes_service';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { PRIMARY_VERSION, NODE_DETAILS_PATH, mockNodes, rawMockNodeDetails } from '../mock_data';
import mountComponent from '../../helpers/vue_mount_component_helper';
const createComponent = () => {
const Component = Vue.extend(appComponent);
const store = new GeoNodesStore(PRIMARY_VERSION.version, PRIMARY_VERSION.revision);
......
import Vue from 'vue';
import geoNodeActionsComponent from 'ee/geo_nodes/components/geo_node_actions.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockNodes } from '../mock_data';
import mountComponent from '../../helpers/vue_mount_component_helper';
const createComponent = (node = mockNodes[0], nodeEditAllowed = true, nodeMissingOauth = false) => {
const Component = Vue.extend(geoNodeActionsComponent);
......
......@@ -2,10 +2,9 @@ import Vue from 'vue';
import geoNodeDetailItemComponent from 'ee/geo_nodes/components/geo_node_detail_item.vue';
import { VALUE_TYPE, CUSTOM_TYPE } from 'ee/geo_nodes/constants';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { rawMockNodeDetails } from '../mock_data';
import mountComponent from '../../helpers/vue_mount_component_helper';
const createComponent = (config) => {
const Component = Vue.extend(geoNodeDetailItemComponent);
const defaultConfig = Object.assign({
......
import Vue from 'vue';
import geoNodeDetailsComponent from 'ee/geo_nodes/components/geo_node_details.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockNodes, mockNodeDetails } from '../mock_data';
import mountComponent from '../../helpers/vue_mount_component_helper';
const createComponent = (nodeDetails = mockNodeDetails) => {
const Component = Vue.extend(geoNodeDetailsComponent);
......
import Vue from 'vue';
import geoNodeEventStatusComponent from 'ee/geo_nodes/components/geo_node_event_status.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockNodeDetails } from '../mock_data';
import mountComponent from '../../helpers/vue_mount_component_helper';
const createComponent = (
eventId = mockNodeDetails.lastEvent.id,
eventTimeStamp = mockNodeDetails.lastEvent.timeStamp,
......
import Vue from 'vue';
import geoNodeHealthStatusComponent from 'ee/geo_nodes/components/geo_node_health_status.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockNodeDetails } from '../mock_data';
import mountComponent from '../../helpers/vue_mount_component_helper';
const createComponent = (status = mockNodeDetails.health) => {
const Component = Vue.extend(geoNodeHealthStatusComponent);
......
......@@ -2,10 +2,9 @@ import Vue from 'vue';
import geoNodeItemComponent from 'ee/geo_nodes/components/geo_node_item.vue';
import eventHub from 'ee/geo_nodes/event_hub';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockNodes, mockNodeDetails } from '../mock_data';
import mountComponent from '../../helpers/vue_mount_component_helper';
const createComponent = (node = mockNodes[0]) => {
const Component = Vue.extend(geoNodeItemComponent);
......
import Vue from 'vue';
import geoNodeSyncSettingsComponent from 'ee/geo_nodes/components/geo_node_sync_settings.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockNodeDetails } from '../mock_data';
import mountComponent from '../../helpers/vue_mount_component_helper';
const createComponent = (
selectiveSyncType = mockNodeDetails.selectiveSyncType,
lastEvent = mockNodeDetails.lastEvent,
......
import Vue from 'vue';
import geoNodesListComponent from 'ee/geo_nodes/components/geo_nodes_list.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockNodes } from '../mock_data';
import mountComponent from '../../helpers/vue_mount_component_helper';
const createComponent = () => {
const Component = Vue.extend(geoNodesListComponent);
......
......@@ -3,10 +3,9 @@ import * as urlUtils from '~/lib/utils/url_utility';
import groupItemComponent from '~/groups/components/group_item.vue';
import groupFolderComponent from '~/groups/components/group_folder.vue';
import eventHub from '~/groups/event_hub';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockParentGroupItem, mockChildren } from '../mock_data';
import mountComponent from '../../helpers/vue_mount_component_helper';
const createComponent = (group = mockParentGroupItem, parentGroup = mockChildren[0]) => {
const Component = Vue.extend(groupItemComponent);
......
......@@ -4,10 +4,9 @@ import groupsComponent from '~/groups/components/groups.vue';
import groupFolderComponent from '~/groups/components/group_folder.vue';
import groupItemComponent from '~/groups/components/group_item.vue';
import eventHub from '~/groups/event_hub';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockGroups, mockPageInfo } from '../mock_data';
import mountComponent from '../../helpers/vue_mount_component_helper';
const createComponent = (searchEmpty = false) => {
const Component = Vue.extend(groupsComponent);
......
......@@ -2,10 +2,9 @@ import Vue from 'vue';
import itemActionsComponent from '~/groups/components/item_actions.vue';
import eventHub from '~/groups/event_hub';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockParentGroupItem, mockChildren } from '../mock_data';
import mountComponent from '../../helpers/vue_mount_component_helper';
const createComponent = (group = mockParentGroupItem, parentGroup = mockChildren[0]) => {
const Component = Vue.extend(itemActionsComponent);
......
......@@ -2,7 +2,7 @@ import Vue from 'vue';
import itemCaretComponent from '~/groups/components/item_caret.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
const createComponent = (isGroupOpen = false) => {
const Component = Vue.extend(itemCaretComponent);
......
import Vue from 'vue';
import itemStatsComponent from '~/groups/components/item_stats.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import {
mockParentGroupItem,
ITEM_TYPE,
......@@ -9,8 +10,6 @@ import {
PROJECT_VISIBILITY_TYPE,
} from '../mock_data';
import mountComponent from '../../helpers/vue_mount_component_helper';
const createComponent = (item = mockParentGroupItem) => {
const Component = Vue.extend(itemStatsComponent);
......
......@@ -2,7 +2,7 @@ import Vue from 'vue';
import itemStatsValueComponent from '~/groups/components/item_stats_value.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
const createComponent = ({ title, cssClass, iconName, tooltipPlacement, value }) => {
const Component = Vue.extend(itemStatsValueComponent);
......
import Vue from 'vue';
import itemTypeIconComponent from '~/groups/components/item_type_icon.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { ITEM_TYPE } from '../mock_data';
import mountComponent from '../../helpers/vue_mount_component_helper';
const createComponent = (itemType = ITEM_TYPE.GROUP, isGroupOpen = false) => {
const Component = Vue.extend(itemTypeIconComponent);
......
import Vue from 'vue';
import eventHub from '~/issuable/related_issues/event_hub';
import addIssuableForm from '~/issuable/related_issues/components/add_issuable_form.vue';
import eventHub from 'ee/issuable/related_issues/event_hub';
import addIssuableForm from 'ee/issuable/related_issues/components/add_issuable_form.vue';
const issuable1 = {
id: 200,
......
import Vue from 'vue';
import issueItem from '~/issuable/related_issues/components/issue_item.vue';
import eventHub from '~/issuable/related_issues/event_hub';
import mountComponent from '../../../helpers/vue_mount_component_helper';
import issueItem from 'ee/issuable/related_issues/components/issue_item.vue';
import eventHub from 'ee/issuable/related_issues/event_hub';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('issueItem', () => {
let vm;
......
import Vue from 'vue';
import eventHub from '~/issuable/related_issues/event_hub';
import issueToken from '~/issuable/related_issues/components/issue_token.vue';
import eventHub from 'ee/issuable/related_issues/event_hub';
import issueToken from 'ee/issuable/related_issues/components/issue_token.vue';
describe('IssueToken', () => {
const idKey = 200;
......
import Vue from 'vue';
import eventHub from '~/issuable/related_issues/event_hub';
import relatedIssuesBlock from '~/issuable/related_issues/components/related_issues_block.vue';
import eventHub from 'ee/issuable/related_issues/event_hub';
import relatedIssuesBlock from 'ee/issuable/related_issues/components/related_issues_block.vue';
import { issuable1, issuable2, issuable3, issuable4, issuable5 } from '../mock_data';
......
import Vue from 'vue';
import _ from 'underscore';
import relatedIssuesRoot from '~/issuable/related_issues/components/related_issues_root.vue';
import relatedIssuesService from '~/issuable/related_issues/services/related_issues_service';
import relatedIssuesRoot from 'ee/issuable/related_issues/components/related_issues_root.vue';
import relatedIssuesService from 'ee/issuable/related_issues/services/related_issues_service';
import { defaultProps, issuable1, issuable2 } from '../mock_data';
......
import RelatedIssuesStore from '~/issuable/related_issues/stores/related_issues_store';
import RelatedIssuesStore from 'ee/issuable/related_issues/stores/related_issues_store';
import { issuable1, issuable2, issuable3, issuable4, issuable5 } from '../mock_data';
......
......@@ -6,8 +6,8 @@ import '~/render_gfm';
import * as urlUtils from '~/lib/utils/url_utility';
import issuableApp from '~/issue_show/components/app.vue';
import eventHub from '~/issue_show/event_hub';
import setTimeoutPromise from 'spec/helpers/set_timeout_promise_helper';
import issueShowData from '../mock_data';
import setTimeoutPromise from '../../helpers/set_timeout_promise_helper';
function formatText(text) {
return text.trim().replace(/\s\s+/g, ' ');
......
import Vue from 'vue';
import descriptionComponent from '~/issue_show/components/description.vue';
import * as taskList from '~/task_list';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('Description component', () => {
let vm;
......
import Vue from 'vue';
import headerComponent from '~/jobs/components/header.vue';
import mountComponent from '../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('Job details header', () => {
let HeaderComponent;
......
......@@ -4,7 +4,7 @@ import axios from '~/lib/utils/axios_utils';
import stopJobsModal from '~/pages/admin/jobs/index/components/stop_jobs_modal.vue';
import * as urlUtility from '~/lib/utils/url_utility';
import mountComponent from '../../../../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('stop_jobs_modal.vue', () => {
const props = {
......
......@@ -5,7 +5,7 @@ import deleteMilestoneModal from '~/pages/milestones/shared/components/delete_mi
import eventHub from '~/pages/milestones/shared/event_hub';
import * as urlUtility from '~/lib/utils/url_utility';
import mountComponent from '../../../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('delete_milestone_modal.vue', () => {
const Component = Vue.extend(deleteMilestoneModal);
......
import Vue from 'vue';
import jobComponent from '~/pipelines/components/graph/job_component.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('pipeline graph job component', () => {
let JobComponent;
......
import Vue from 'vue';
import LinkedPipelineComponent from '~/pipelines/components/graph/linked_pipeline.vue';
import LinkedPipelineComponent from 'ee/pipelines/components/graph/linked_pipeline.vue';
import mockData from './linked_pipelines_mock_data';
const LinkedPipeline = Vue.extend(LinkedPipelineComponent);
......
import Vue from 'vue';
import LinkedPipelinesColumn from '~/pipelines/components/graph/linked_pipelines_column.vue';
import LinkedPipelinesColumn from 'ee/pipelines/components/graph/linked_pipelines_column.vue';
import mockData from './linked_pipelines_mock_data';
const LinkedPipelinesColumnComponent = Vue.extend(LinkedPipelinesColumn);
......
......@@ -2,7 +2,7 @@ import _ from 'underscore';
import Vue from 'vue';
import pipelinesComp from '~/pipelines/components/pipelines.vue';
import Store from '~/pipelines/stores/pipelines_store';
import mountComponent from '../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('Pipelines', () => {
const jsonFixtureName = 'pipelines/pipelines.json';
......
......@@ -2,7 +2,7 @@ import Vue from 'vue';
import deleteAccountModal from '~/profile/account/components/delete_account_modal.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('DeleteAccountModal component', () => {
const actionUrl = `${gl.TEST_HOST}/delete/user`;
......
import Vue from 'vue';
import eventHub from '~/projects/settings_service_desk/event_hub';
import serviceDeskSetting from '~/projects/settings_service_desk/components/service_desk_setting.vue';
import eventHub from 'ee/projects/settings_service_desk/event_hub';
import serviceDeskSetting from 'ee/projects/settings_service_desk/components/service_desk_setting.vue';
describe('ServiceDeskSetting', () => {
let ServiceDeskSetting;
......
import ServiceDeskStore from '~/projects/settings_service_desk/stores/service_desk_store';
import ServiceDeskStore from 'ee/projects/settings_service_desk/stores/service_desk_store';
describe('ServiceDeskStore', () => {
let store;
......
......@@ -6,7 +6,7 @@ import eventHub from '~/projects_dropdown/event_hub';
import ProjectsStore from '~/projects_dropdown/store/projects_store';
import ProjectsService from '~/projects_dropdown/service/projects_service';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { currentSession, mockProject, mockRawProject } from '../mock_data';
const createComponent = () => {
......
......@@ -2,7 +2,7 @@ import Vue from 'vue';
import projectsListFrequentComponent from '~/projects_dropdown/components/projects_list_frequent.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockFrequents } from '../mock_data';
const createComponent = () => {
......
......@@ -2,7 +2,7 @@ import Vue from 'vue';
import projectsListItemComponent from '~/projects_dropdown/components/projects_list_item.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockProject } from '../mock_data';
const createComponent = () => {
......
......@@ -2,7 +2,7 @@ import Vue from 'vue';
import projectsListSearchComponent from '~/projects_dropdown/components/projects_list_search.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockProject } from '../mock_data';
const createComponent = () => {
......
......@@ -3,7 +3,7 @@ import Vue from 'vue';
import searchComponent from '~/projects_dropdown/components/search.vue';
import eventHub from '~/projects_dropdown/event_hub';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
const createComponent = () => {
const Component = Vue.extend(searchComponent);
......
import _ from 'underscore';
import Vue from 'vue';
import registry from '~/registry/components/app.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { reposServerResponse } from '../mock_data';
describe('Registry List', () => {
......
import Vue from 'vue';
import changedFileIcon from '~/ide/components/changed_file_icon.vue';
import createComponent from '../../helpers/vue_mount_component_helper';
import changedFileIcon from 'ee/ide/components/changed_file_icon.vue';
import createComponent from 'spec/helpers/vue_mount_component_helper';
describe('IDE changed file icon', () => {
let vm;
......
import Vue from 'vue';
import store from '~/ide/stores';
import listCollapsed from '~/ide/components/commit_sidebar/list_collapsed.vue';
import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { file } from '../../helpers';
describe('Multi-file editor commit sidebar list collapsed', () => {
......
import Vue from 'vue';
import listItem from '~/ide/components/commit_sidebar/list_item.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { file } from '../../helpers';
describe('Multi-file editor commit sidebar list item', () => {
......
import Vue from 'vue';
import store from '~/ide/stores';
import commitSidebarList from '~/ide/components/commit_sidebar/list.vue';
import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { file } from '../../helpers';
describe('Multi-file editor commit sidebar list', () => {
......
import Vue from 'vue';
import store from '~/ide/stores';
import ideContextBar from '~/ide/components/ide_context_bar.vue';
import { createComponentWithStore } from '../../helpers/vue_mount_component_helper';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
describe('Multi-file editor right context bar', () => {
let vm;
......
import Vue from 'vue';
import store from '~/ide/stores';
import ideSidebar from '~/ide/components/ide_side_bar.vue';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { resetStore } from '../helpers';
import { createComponentWithStore } from '../../helpers/vue_mount_component_helper';
describe('IdeSidebar', () => {
let vm;
......
import Vue from 'vue';
import store from '~/ide/stores';
import ide from '~/ide/components/ide.vue';
import { createComponentWithStore } from '../../helpers/vue_mount_component_helper';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { file, resetStore } from '../helpers';
describe('ide component', () => {
......
import Vue from 'vue';
import store from '~/ide/stores';
import newBranchForm from '~/ide/components/new_branch_form.vue';
import { createComponentWithStore } from '../../helpers/vue_mount_component_helper';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { resetStore } from '../helpers';
describe('Multi-file editor new branch form', () => {
......
import Vue from 'vue';
import store from '~/ide/stores';
import newDropdown from '~/ide/components/new_dropdown/index.vue';
import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { resetStore } from '../../helpers';
describe('new dropdown component', () => {
......
......@@ -2,7 +2,7 @@ import Vue from 'vue';
import store from '~/ide/stores';
import service from '~/ide/services';
import modal from '~/ide/components/new_dropdown/modal.vue';
import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { file, resetStore } from '../../helpers';
describe('new file modal component', () => {
......
......@@ -2,7 +2,7 @@ import Vue from 'vue';
import upload from '~/ide/components/new_dropdown/upload.vue';
import store from '~/ide/stores';
import service from '~/ide/services';
import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { resetStore } from '../../helpers';
describe('new dropdown upload', () => {
......
......@@ -2,8 +2,8 @@ import Vue from 'vue';
import store from '~/ide/stores';
import service from '~/ide/services';
import repoCommitSection from '~/ide/components/repo_commit_section.vue';
import getSetTimeoutPromise from '../../helpers/set_timeout_promise_helper';
import { createComponentWithStore } from '../../helpers/vue_mount_component_helper';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper';
import { file, resetStore } from '../helpers';
describe('RepoCommitSection', () => {
......
......@@ -7,10 +7,9 @@ import appComponent from 'ee/roadmap/components/app.vue';
import RoadmapStore from 'ee/roadmap/store/roadmap_store';
import RoadmapService from 'ee/roadmap/service/roadmap_service';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockTimeframe, mockGroupId, epicsPath, rawEpics, mockSvgPath } from '../mock_data';
import mountComponent from '../../helpers/vue_mount_component_helper';
const createComponent = () => {
const Component = Vue.extend(appComponent);
const timeframe = mockTimeframe;
......
......@@ -2,10 +2,9 @@ import Vue from 'vue';
import epicItemDetailsComponent from 'ee/roadmap/components/epic_item_details.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockGroupId, mockEpic } from '../mock_data';
import mountComponent from '../../helpers/vue_mount_component_helper';
const createComponent = (epic = mockEpic, currentGroupId = mockGroupId) => {
const Component = Vue.extend(epicItemDetailsComponent);
......
......@@ -2,10 +2,9 @@ import Vue from 'vue';
import epicItemComponent from 'ee/roadmap/components/epic_item.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockTimeframe, mockEpic, mockGroupId, mockShellWidth } from '../mock_data';
import mountComponent from '../../helpers/vue_mount_component_helper';
const createComponent = ({
epic = mockEpic,
timeframe = mockTimeframe,
......
......@@ -3,10 +3,9 @@ import Vue from 'vue';
import epicItemTimelineComponent from 'ee/roadmap/components/epic_item_timeline.vue';
import { TIMELINE_CELL_MIN_WIDTH, TIMELINE_END_OFFSET_FULL, TIMELINE_END_OFFSET_HALF } from 'ee/roadmap/constants';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockTimeframe, mockEpic, mockShellWidth } from '../mock_data';
import mountComponent from '../../helpers/vue_mount_component_helper';
const createComponent = ({
timeframe = mockTimeframe,
timeframeItem = mockTimeframe[0],
......
......@@ -2,10 +2,9 @@ import Vue from 'vue';
import epicsListEmptyComponent from 'ee/roadmap/components/epics_list_empty.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockTimeframe, mockSvgPath } from '../mock_data';
import mountComponent from '../../helpers/vue_mount_component_helper';
const createComponent = () => {
const Component = Vue.extend(epicsListEmptyComponent);
......
......@@ -3,10 +3,9 @@ import Vue from 'vue';
import epicsListSectionComponent from 'ee/roadmap/components/epics_list_section.vue';
import RoadmapStore from 'ee/roadmap/store/roadmap_store';
import eventHub from 'ee/roadmap/event_hub';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { rawEpics, mockTimeframe, mockGroupId, mockShellWidth } from '../mock_data';
import mountComponent from '../../helpers/vue_mount_component_helper';
const store = new RoadmapStore(mockGroupId, mockTimeframe);
store.setEpics(rawEpics);
const mockEpics = store.getEpics();
......
......@@ -2,10 +2,9 @@ import Vue from 'vue';
import roadmapShellComponent from 'ee/roadmap/components/roadmap_shell.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockEpic, mockTimeframe, mockGroupId } from '../mock_data';
import mountComponent from '../../helpers/vue_mount_component_helper';
const createComponent = ({
epics = [mockEpic],
timeframe = mockTimeframe,
......
......@@ -3,10 +3,9 @@ import Vue from 'vue';
import roadmapTimelineSectionComponent from 'ee/roadmap/components/roadmap_timeline_section.vue';
import eventHub from 'ee/roadmap/event_hub';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockEpic, mockTimeframe, mockShellWidth } from '../mock_data';
import mountComponent from '../../helpers/vue_mount_component_helper';
const createComponent = ({
epics = [mockEpic],
timeframe = mockTimeframe,
......
......@@ -3,10 +3,9 @@ import Vue from 'vue';
import timelineHeaderItemComponent from 'ee/roadmap/components/timeline_header_item.vue';
import { TIMELINE_CELL_MIN_WIDTH } from 'ee/roadmap/constants';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockTimeframe, mockShellWidth } from '../mock_data';
import mountComponent from '../../helpers/vue_mount_component_helper';
const mockTimeframeIndex = 0;
const createComponent = ({
......
......@@ -2,10 +2,9 @@ import Vue from 'vue';
import timelineHeaderSubItemComponent from 'ee/roadmap/components/timeline_header_sub_item.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockTimeframe } from '../mock_data';
import mountComponent from '../../helpers/vue_mount_component_helper';
const createComponent = ({
currentDate = mockTimeframe[0],
timeframeItem = mockTimeframe[0],
......
......@@ -3,10 +3,9 @@ import Vue from 'vue';
import timelineTodayIndicatorComponent from 'ee/roadmap/components/timeline_today_indicator.vue';
import eventHub from 'ee/roadmap/event_hub';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockTimeframe } from '../mock_data';
import mountComponent from '../../helpers/vue_mount_component_helper';
const mockCurrentDate = new Date(
mockTimeframe[0].getFullYear(),
mockTimeframe[0].getMonth(),
......
......@@ -2,7 +2,7 @@ import Vue from 'vue';
import CESidebarStore from '~/sidebar/stores/sidebar_store';
import SidebarStore from 'ee/sidebar/stores/sidebar_store';
import sidebarItemEpic from 'ee/sidebar/components/sidebar_item_epic.vue';
import mountComponent from '../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('sidebarItemEpic', () => {
let vm;
......
import Vue from 'vue';
import editFormButtons from '~/sidebar/components/lock/edit_form_buttons.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('EditFormButtons', () => {
let vm1;
......
import Vue from 'vue';
import participants from '~/sidebar/components/participants/participants.vue';
import mountComponent from '../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
const PARTICIPANT = {
id: 1,
......
......@@ -4,8 +4,8 @@ import SidebarAssignees from '~/sidebar/components/assignees/sidebar_assignees';
import SidebarMediator from '~/sidebar/sidebar_mediator';
import SidebarService from '~/sidebar/services/sidebar_service';
import SidebarStore from '~/sidebar/stores/sidebar_store';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import Mock from './mock_data';
import mountComponent from '../helpers/vue_mount_component_helper';
describe('sidebar assignees', () => {
let vm;
......
......@@ -4,7 +4,7 @@ import SidebarMediator from '~/sidebar/sidebar_mediator';
import SidebarService from '~/sidebar/services/sidebar_service';
import SidebarStore from '~/sidebar/stores/sidebar_store';
import eventHub from '~/sidebar/event_hub';
import mountComponent from '../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import Mock from './mock_data';
describe('Sidebar Subscriptions', function () {
......
......@@ -4,7 +4,7 @@ import SidebarMediator from 'ee/sidebar/sidebar_mediator';
import SidebarService from '~/sidebar/services/sidebar_service';
import SidebarStore from 'ee/sidebar/stores/sidebar_store';
import eventHub from '~/sidebar/event_hub';
import mountComponent from '../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import Mock from './ee_mock_data';
describe('Sidebar Weight', function () {
......
import Vue from 'vue';
import subscriptions from '~/sidebar/components/subscriptions/subscriptions.vue';
import mountComponent from '../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('Subscriptions', function () {
let vm;
......
import Vue from 'vue';
import weight from 'ee/sidebar/components/weight/weight.vue';
import eventHub from '~/sidebar/event_hub';
import mountComponent from '../helpers/vue_mount_component_helper';
import getSetTimeoutPromise from '../helpers/set_timeout_promise_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper';
const DEFAULT_PROPS = {
weightOptions: ['No Weight', 1, 2, 3],
......
import Vue from 'vue';
import authorComponent from '~/vue_merge_request_widget/components/mr_widget_author.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('MRWidgetAuthor', () => {
let vm;
......
import Vue from 'vue';
import authorTimeComponent from '~/vue_merge_request_widget/components/mr_widget_author_time.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('MRWidgetAuthorTime', () => {
let vm;
......
import Vue from 'vue';
import headerComponent from '~/vue_merge_request_widget/components/mr_widget_header.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('MRWidgetHeader', () => {
let vm;
......
import Vue from 'vue';
import mergeHelpComponent from '~/vue_merge_request_widget/components/mr_widget_merge_help.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('MRWidgetMergeHelp', () => {
let vm;
......
import Vue from 'vue';
import pipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; // eslint-disable-line import/first
import mockData from '../mock_data';
import mockLinkedPipelines from '../../pipelines/graph/linked_pipelines_mock_data';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mockLinkedPipelines from 'spec/pipelines/graph/linked_pipelines_mock_data'; // eslint-disable-line import/first
describe('MRWidgetPipeline', () => {
let vm;
......
import Vue from 'vue';
import eventHub from '~/vue_merge_request_widget/event_hub';
import component from '~/vue_merge_request_widget/components/states/mr_widget_rebase.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('Merge request widget rebase component', () => {
let Component;
......
import Vue from 'vue';
import relatedLinksComponent from '~/vue_merge_request_widget/components/mr_widget_related_links.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('MRWidgetRelatedLinks', () => {
let vm;
......
import Vue from 'vue';
import mrStatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('MR widget status icon component', () => {
let vm;
......
import Vue from 'vue';
import archivedComponent from '~/vue_merge_request_widget/components/states/mr_widget_archived.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('MRWidgetArchived', () => {
let vm;
......
import Vue from 'vue';
import autoMergeFailedComponent from '~/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue';
import eventHub from '~/vue_merge_request_widget/event_hub';
import mountComponent from '../../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('MRWidgetAutoMergeFailed', () => {
let vm;
......
import Vue from 'vue';
import checkingComponent from '~/vue_merge_request_widget/components/states/mr_widget_checking.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('MRWidgetChecking', () => {
let Component;
......
import Vue from 'vue';
import closedComponent from '~/vue_merge_request_widget/components/states/mr_widget_closed.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('MRWidgetClosed', () => {
let vm;
......
import Vue from 'vue';
import conflictsComponent from '~/vue_merge_request_widget/components/states/mr_widget_conflicts.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('MRWidgetConflicts', () => {
let Component;
......
import Vue from 'vue';
import failedToMergeComponent from '~/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue';
import eventHub from '~/vue_merge_request_widget/event_hub';
import mountComponent from '../../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('MRWidgetFailedToMerge', () => {
let Component;
......
import Vue from 'vue';
import mwpsComponent from '~/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue';
import eventHub from '~/vue_merge_request_widget/event_hub';
import mountComponent from '../../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('MRWidgetMergeWhenPipelineSucceeds', () => {
let vm;
......
import Vue from 'vue';
import mergedComponent from '~/vue_merge_request_widget/components/states/mr_widget_merged.vue';
import eventHub from '~/vue_merge_request_widget/event_hub';
import mountComponent from '../../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('MRWidgetMerged', () => {
let vm;
......
import Vue from 'vue';
import mergingComponent from '~/vue_merge_request_widget/components/states/mr_widget_merging.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('MRWidgetMerging', () => {
let vm;
......
import Vue from 'vue';
import missingBranchComponent from '~/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('MRWidgetMissingBranch', () => {
let vm;
......
import Vue from 'vue';
import notAllowedComponent from '~/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('MRWidgetNotAllowed', () => {
let vm;
......
import Vue from 'vue';
import pipelineBlockedComponent from '~/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('MRWidgetPipelineBlocked', () => {
let vm;
......
......@@ -4,6 +4,7 @@ import axios from '~/lib/utils/axios_utils';
import mrWidgetOptions from 'ee/vue_merge_request_widget/mr_widget_options';
import MRWidgetService from 'ee/vue_merge_request_widget/services/mr_widget_service';
import MRWidgetStore from 'ee/vue_merge_request_widget/stores/mr_widget_store';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import mockData, {
baseIssues,
headIssues,
......@@ -21,8 +22,6 @@ import {
sastHeadAllIssues,
} from '../vue_shared/security_reports/mock_data';
import mountComponent from '../helpers/vue_mount_component_helper';
describe('ee merge request widget options', () => {
let vm;
let Component;
......
......@@ -3,8 +3,8 @@ import mrWidgetOptions from '~/vue_merge_request_widget/mr_widget_options';
import eventHub from '~/vue_merge_request_widget/event_hub';
import notify from '~/lib/utils/notify';
import { stateKey } from '~/vue_merge_request_widget/stores/state_maps';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import mockData from './mock_data';
import mountComponent from '../helpers/vue_mount_component_helper';
const returnPromise = data => new Promise((resolve) => {
resolve({
......
import Vue from 'vue';
import ciBadge from '~/vue_shared/components/ci_badge_link.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('CI Badge Link Component', () => {
let CIBadge;
......
import Vue from 'vue';
import clipboardButton from '~/vue_shared/components/clipboard_button.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('clipboard button', () => {
let vm;
......
import Vue from 'vue';
import expandButton from '~/vue_shared/components/expand_button.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('expand button', () => {
let vm;
......
import Vue from 'vue';
import fileIcon from '~/vue_shared/components/file_icon.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('File Icon component', () => {
let vm;
......
import Vue from 'vue';
import GlModal from '~/vue_shared/components/gl_modal.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
const modalComponent = Vue.extend(GlModal);
......
import Vue from 'vue';
import headerCi from '~/vue_shared/components/header_ci_component.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('Header CI Component', () => {
let HeaderCi;
......
import Vue from 'vue';
import Icon from '~/vue_shared/components/icon.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('Sprite Icon Component', function () {
describe('Initialization', function () {
......
import Vue from 'vue';
import issueWarning from '~/vue_shared/components/issue/issue_warning.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
const IssueWarning = Vue.extend(issueWarning);
......
/* eslint-disable no-restricted-syntax */
import Vue from 'vue';
import linkToMemberAvatar from '~/vue_shared/components/link_to_member_avatar';
import linkToMemberAvatar from 'ee/vue_shared/components/link_to_member_avatar';
(() => {
function initComponent(propsData = {}) {
......
import Vue from 'vue';
import LinkedPipelinesMiniList from '~/vue_shared/components/linked_pipelines_mini_list.vue';
import mockData from '../../pipelines/graph/linked_pipelines_mock_data';
import LinkedPipelinesMiniList from 'ee/vue_shared/components/linked_pipelines_mini_list.vue';
import mockData from 'spec/pipelines/graph/linked_pipelines_mock_data';
const ListComponent = Vue.extend(LinkedPipelinesMiniList);
......
import Vue from 'vue';
import loadingButton from '~/vue_shared/components/loading_button.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
const LABEL = 'Hello';
......
import Vue from 'vue';
import modal from '~/vue_shared/components/modal.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
const modalComponent = Vue.extend(modal);
......
import Vue from 'vue';
import navigationTabs from '~/vue_shared/components/navigation_tabs.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('navigation tabs component', () => {
let vm;
......
import Vue from 'vue';
import placeholderSystemNote from '~/vue_shared/components/notes/placeholder_system_note.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('placeholder system note component', () => {
let PlaceholderSystemNote;
......
import Vue from 'vue';
import panelResizer from '~/vue_shared/components/panel_resizer.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('Panel Resizer component', () => {
let vm;
......
import Vue from 'vue';
import datePicker from '~/vue_shared/components/pikaday.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('datePicker', () => {
let vm;
......
import Vue from 'vue';
import collapsedCalendarIcon from '~/vue_shared/components/sidebar/collapsed_calendar_icon.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('collapsedCalendarIcon', () => {
let vm;
......
import Vue from 'vue';
import collapsedGroupedDatePicker from '~/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('collapsedGroupedDatePicker', () => {
let vm;
......
import Vue from 'vue';
import sidebarDatePicker from '~/vue_shared/components/sidebar/date_picker.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('sidebarDatePicker', () => {
let vm;
......
import Vue from 'vue';
import toggleSidebar from '~/vue_shared/components/sidebar/toggle_sidebar.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('toggleSidebar', () => {
let vm;
......
import Vue from 'vue';
import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('Skeleton loading container', () => {
let vm;
......
......@@ -2,7 +2,7 @@ import Vue from 'vue';
import stackedProgressBarComponent from '~/vue_shared/components/stacked_progress_bar.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
const createComponent = (config) => {
const Component = Vue.extend(stackedProgressBarComponent);
......
import Vue from 'vue';
import toggleButton from '~/vue_shared/components/toggle_button.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('Toggle Button', () => {
let vm;
......
import Vue from 'vue';
import { placeholderImage } from '~/lazy_loader';
import userAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
const DEFAULT_PROPS = {
size: 99,
......
import Vue from 'vue';
import modal from 'ee/vue_shared/security_reports/components/dast_modal.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('mr widget modal', () => {
let vm;
......
import Vue from 'vue';
import reportIssues from 'ee/vue_shared/security_reports/components/report_issues.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import {
codequalityParsedIssues,
} from '../../../vue_mr_widget/mock_data';
} from 'spec/vue_mr_widget/mock_data';
import {
sastParsedIssues,
dockerReportParsed,
......
import Vue from 'vue';
import reportSection from 'ee/vue_shared/security_reports/components/report_section.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
import { codequalityParsedIssues } from '../../../vue_mr_widget/mock_data';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { codequalityParsedIssues } from 'spec/vue_mr_widget/mock_data';
describe('Report section', () => {
let vm;
......
......@@ -1421,7 +1421,7 @@ describe API::Issues do
context 'when source project does not exist' do
it 'returns 404 when trying to move an issue' do
post api("/projects/12345/issues/#{issue.iid}/move", user),
post api("/projects/0/issues/#{issue.iid}/move", user),
to_project_id: target_project.id
expect(response).to have_gitlab_http_status(404)
......@@ -1432,7 +1432,7 @@ describe API::Issues do
context 'when target project does not exist' do
it 'returns 404 when trying to move an issue' do
post api("/projects/#{project.id}/issues/#{issue.iid}/move", user),
to_project_id: 12345
to_project_id: 0
expect(response).to have_gitlab_http_status(404)
end
......
......@@ -1266,7 +1266,7 @@ describe API::V3::Issues do
context 'when source project does not exist' do
it 'returns 404 when trying to move an issue' do
post v3_api("/projects/999999/issues/#{issue.id}/move", user),
post v3_api("/projects/0/issues/#{issue.id}/move", user),
to_project_id: target_project.id
expect(response).to have_gitlab_http_status(404)
......@@ -1277,7 +1277,7 @@ describe API::V3::Issues do
context 'when target project does not exist' do
it 'returns 404 when trying to move an issue' do
post v3_api("/projects/#{project.id}/issues/#{issue.id}/move", user),
to_project_id: 999999
to_project_id: 0
expect(response).to have_gitlab_http_status(404)
end
......
......@@ -15,47 +15,79 @@ describe Labels::FindOrCreateService do
context 'when acting on behalf of a specific user' do
let(:user) { create(:user) }
subject(:service) { described_class.new(user, project, params) }
before do
project.add_developer(user)
end
context 'when label does not exist at group level' do
it 'creates a new label at project level' do
expect { service.execute }.to change(project.labels, :count).by(1)
context 'when finding labels on project level' do
subject(:service) { described_class.new(user, project, params) }
before do
project.add_developer(user)
end
end
context 'when label exists at group level' do
it 'returns the group label' do
group_label = create(:group_label, group: group, title: 'Security')
context 'when label does not exist at group level' do
it 'creates a new label at project level' do
expect { service.execute }.to change(project.labels, :count).by(1)
end
end
expect(service.execute).to eq group_label
context 'when label exists at group level' do
it 'returns the group label' do
group_label = create(:group_label, group: group, title: 'Security')
expect(service.execute).to eq group_label
end
end
context 'when label exists at project level' do
it 'returns the project label' do
project_label = create(:label, project: project, title: 'Security')
expect(service.execute).to eq project_label
end
end
end
context 'when label does not exist at group level' do
it 'creates a new label at project leve' do
expect { service.execute }.to change(project.labels, :count).by(1)
context 'when finding labels on group level' do
subject(:service) { described_class.new(user, group, params) }
before do
group.add_developer(user)
end
context 'when label does not exist at group level' do
it 'creates a new label at group level' do
expect { service.execute }.to change(group.labels, :count).by(1)
end
end
context 'when label exists at group level' do
it 'returns the group label' do
group_label = create(:group_label, group: group, title: 'Security')
expect(service.execute).to eq group_label
end
end
end
end
context 'when authorization is not required' do
context 'when finding labels on project level' do
subject(:service) { described_class.new(nil, project, params) }
context 'when label exists at project level' do
it 'returns the project label' do
project_label = create(:label, project: project, title: 'Security')
expect(service.execute).to eq project_label
expect(service.execute(skip_authorization: true)).to eq project_label
end
end
end
context 'when authorization is not required' do
subject(:service) { described_class.new(nil, project, params) }
context 'when finding labels on group level' do
subject(:service) { described_class.new(nil, group, params) }
it 'returns the project label' do
project_label = create(:label, project: project, title: 'Security')
it 'returns the group label' do
group_label = create(:group_label, group: group, title: 'Security')
expect(service.execute(skip_authorization: true)).to eq project_label
expect(service.execute(skip_authorization: true)).to eq group_label
end
end
end
end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment