Commit d0125202 authored by Phil Hughes's avatar Phil Hughes

Merge branch 'master' into 'ph-webpack-bundle-tags'

# Conflicts:
#   app/assets/javascripts/pages/projects/merge_requests/show/index.js
parents 6358004b a4885b8f
......@@ -397,9 +397,9 @@ For issues related to the open source stewardship of GitLab,
there is the ~"stewardship" label.
This label is to be used for issues in which the stewardship of GitLab
is a topic of discussion. For instance if GitLab Inc. is planning to remove
features from GitLab CE to make exclusive in GitLab EE, related issues
would be labelled with ~"stewardship".
is a topic of discussion. For instance if GitLab Inc. is planning to add
features from GitLab EE to GitLab CE, related issues would be labelled with
~"stewardship".
A recent example of this was the issue for
[bringing the time tracking API to GitLab CE][time-tracking-issue].
......
......@@ -411,7 +411,7 @@ group :ed25519 do
end
# Gitaly GRPC client
gem 'gitaly-proto', '~> 0.84.0', require: 'gitaly'
gem 'gitaly-proto', '~> 0.85.0', require: 'gitaly'
# Locked until https://github.com/google/protobuf/issues/4210 is closed
gem 'google-protobuf', '= 3.5.1'
......
......@@ -285,7 +285,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
gitaly-proto (0.84.0)
gitaly-proto (0.85.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (5.3.3)
......@@ -1057,7 +1057,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0)
gitaly-proto (~> 0.84.0)
gitaly-proto (~> 0.85.0)
github-linguist (~> 5.3.3)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.6.2)
......
<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 ListIssue from '../models/issue';
const Store = gl.issueBoards.BoardsStore;
......@@ -17,6 +18,9 @@ export default {
error: false,
};
},
mounted() {
this.$refs.input.focus();
},
methods: {
submit(e) {
e.preventDefault();
......@@ -59,42 +63,51 @@ export default {
eventHub.$emit(`hide-issue-form-${this.list.id}`);
},
},
mounted() {
this.$refs.input.focus();
},
template: `
};
</script>
<template>
<div class="card board-new-issue-form">
<form @submit="submit($event)">
<div class="flash-container"
v-if="error">
<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'">
<label
class="label-light"
:for="list.id + '-title'"
>
Title
</label>
<input class="form-control"
<input
class="form-control"
type="text"
v-model="title"
ref="input"
autocomplete="off"
:id="list.id + '-title'" />
:id="list.id + '-title'"
/>
<div class="clearfix prepend-top-10">
<button class="btn btn-success pull-left"
<button
class="btn btn-success pull-left"
type="submit"
:disabled="title === ''"
ref="submit-button">
ref="submit-button"
>
Submit issue
</button>
<button class="btn btn-default pull-right"
<button
class="btn btn-default pull-right"
type="button"
@click="cancel">
@click="cancel"
>
Cancel
</button>
</div>
</form>
</div>
`,
};
</template>
......@@ -2,11 +2,13 @@
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';
......@@ -22,7 +24,7 @@ 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
export default () => {
const $boardApp = document.getElementById('board-app');
......
......@@ -110,3 +110,5 @@ class ListIssue {
}
window.ListIssue = ListIssue;
export default ListIssue;
......@@ -2,7 +2,7 @@
/* global List */
import _ from 'underscore';
import Cookies from 'js-cookie';
import { getUrlParamsArray } from '../../lib/utils/common_utils';
import { getUrlParamsArray } from '~/lib/utils/common_utils';
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};
......
......@@ -15,7 +15,7 @@ const CommitPipelinesTable = Vue.extend(commitPipelinesTable);
window.gl = window.gl || {};
window.gl.CommitPipelinesTable = CommitPipelinesTable;
document.addEventListener('DOMContentLoaded', () => {
export default () => {
const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view');
if (pipelineTableViewEl) {
......@@ -43,4 +43,4 @@ document.addEventListener('DOMContentLoaded', () => {
pipelineTableViewEl.appendChild(table.$el);
}
}
});
};
......@@ -6,43 +6,21 @@ 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;
function initSearch() {
// Only when search form is present
if ($('.search').length) {
return new SearchAutocomplete();
}
}
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,
});
function initFieldErrors() {
$('.gl-show-field-errors').each((i, form) => {
new GlFieldErrors(form);
});
}
const shortcutHandlerPages = [
function initPageShortcuts(page) {
const pagesWithCustomShortcuts = [
'projects:activity',
'projects:artifacts:browse',
'projects:artifacts:file',
......@@ -66,117 +44,42 @@ var Dispatcher;
'groups:show',
];
if (shortcutHandlerPages.indexOf(page) !== -1) {
shortcut_handler = true;
}
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) {
if (pagesWithCustomShortcuts.indexOf(page) === -1) {
new Shortcuts();
}
}
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,
});
});
}
function initPerformanceBar() {
if (document.querySelector('#peek')) {
import('./performance_bar')
.then(m => new m.default({ container: '#peek' })) // eslint-disable-line new-cap
.catch(fail);
.catch(() => Flash('Error loading performance bar module'));
}
};
Dispatcher.prototype.initSearch = function() {
// Only when search form is present
if ($('.search').length) {
return new SearchAutocomplete();
}
};
Dispatcher.prototype.initFieldErrors = function() {
$('.gl-show-field-errors').each((i, form) => {
new GlFieldErrors(form);
});
};
}
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,8 +2,8 @@
/**
* 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';
export default {
components: {
......
......@@ -5,7 +5,7 @@ import Translate from '../../vue_shared/translate';
Vue.use(Translate);
document.addEventListener('DOMContentLoaded', () => new Vue({
export default () => new Vue({
el: '#environments-folder-list-view',
components: {
environmentsFolderApp,
......@@ -32,4 +32,4 @@ document.addEventListener('DOMContentLoaded', () => new Vue({
},
});
},
}));
});
<script>
import { mapState } from 'vuex';
import timeAgoMixin from '../../vue_shared/mixins/timeago';
import skeletonLoadingContainer from '../../vue_shared/components/skeleton_loading_container.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';
export default {
components: {
......
<script>
import { mapActions } from 'vuex';
import fileIcon from '../../vue_shared/components/file_icon.vue';
import fileIcon from '~/vue_shared/components/file_icon.vue';
export default {
components: {
......
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 MiniPipelineGraph from '~/mini_pipeline_graph_dropdown';
import initPipelines from '~/commit/pipelines/pipelines_bundle';
document.addEventListener('DOMContentLoaded', () => {
new MiniPipelineGraph({
container: '.js-commit-pipeline-graph',
}).bindEvents();
$('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath);
initPipelines();
});
import initCompareAutocomplete from '~/compare_autocomplete';
export default () => {
initCompareAutocomplete();
};
document.addEventListener('DOMContentLoaded', initCompareAutocomplete);
import initEnvironmentsFolderBundle from '~/environments/folder/environments_folder_bundle';
document.addEventListener('DOMContentLoaded', initEnvironmentsFolderBundle);
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
};
});
import Compare from '~/compare';
import MergeRequest from '~/merge_request';
import initPipelines from '~/commit/pipelines/pipelines_bundle';
document.addEventListener('DOMContentLoaded', () => {
const mrNewCompareNode = document.querySelector('.js-merge-request-new-compare');
......@@ -14,5 +15,6 @@ document.addEventListener('DOMContentLoaded', () => {
new MergeRequest({ // eslint-disable-line no-new
action: mrNewSubmitNode.dataset.mrSubmitAction,
});
initPipelines();
}
});
......@@ -7,6 +7,7 @@ import ShortcutsIssuable from '~/shortcuts_issuable';
import Diff from '~/diff';
import { handleLocationHash } from '~/lib/utils/common_utils';
import howToMerge from '~/how_to_merge';
import initPipelines from '~/commit/pipelines/pipelines_bundle';
import initWidget from '../../../../vue_merge_request_widget';
document.addEventListener('DOMContentLoaded', () => {
......@@ -16,6 +17,7 @@ document.addEventListener('DOMContentLoaded', () => {
initIssuableSidebar();
initNotes();
initDiffNotes();
initPipelines();
const mrShowNode = document.querySelector('.merge-request');
......
......@@ -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
};
});
......@@ -9,13 +9,12 @@ export default class ShortcutsIssuable extends Shortcuts {
super();
this.$replyField = isMergeRequest ? $('.js-main-target-form #note_note') : $('.js-main-target-form .js-vue-comment-form');
this.editBtn = document.querySelector('.js-issuable-edit');
Mousetrap.bind('a', () => ShortcutsIssuable.openSidebarDropdown('assignee'));
Mousetrap.bind('m', () => ShortcutsIssuable.openSidebarDropdown('milestone'));
Mousetrap.bind('l', () => ShortcutsIssuable.openSidebarDropdown('labels'));
Mousetrap.bind('r', this.replyWithSelectedText.bind(this));
Mousetrap.bind('e', this.editIssue.bind(this));
Mousetrap.bind('e', ShortcutsIssuable.editIssue);
if (isMergeRequest) {
this.enabledHelp.push('.hidden-shortcut.merge_requests');
......@@ -58,10 +57,10 @@ export default class ShortcutsIssuable extends Shortcuts {
return false;
}
editIssue() {
static editIssue() {
// Need to click the element as on issues, editing is inline
// on merge request, editing is on a different page
this.editBtn.click();
document.querySelector('.js-issuable-edit').click();
return false;
}
......
<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 pipelineStage from '~/pipelines/components/stage.vue';
import ciIcon from '~/vue_shared/components/ci_icon.vue';
import icon from '~/vue_shared/components/icon.vue';
export default {
name: 'MRWidgetPipeline',
......
......@@ -196,17 +196,9 @@
@media (min-width: $screen-sm-min) {
font-size: 0;
div {
display: inline;
}
.fa-spinner {
font-size: 12px;
}
span {
font-size: 6px;
}
}
.ci-status-link {
......
......@@ -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
......@@ -6,7 +6,10 @@ module Ci
belongs_to :group
validates :key, uniqueness: { scope: :group_id }
validates :key, uniqueness: {
scope: :group_id,
message: "(%{value}) has already been taken"
}
scope :unprotected, -> { where(protected: false) }
end
......
......@@ -6,7 +6,10 @@ module Ci
belongs_to :project
validates :key, uniqueness: { scope: [:project_id, :environment_scope] }
validates :key, uniqueness: {
scope: [:project_id, :environment_scope],
message: "(%{value}) has already been taken"
}
scope :unprotected, -> { where(protected: false) }
end
......
......@@ -9,10 +9,9 @@ class Tree
@repository = repository
@sha = sha
@path = path
@recursive = recursive
git_repo = @repository.raw_repository
@entries = get_entries(git_repo, @sha, @path, recursive: @recursive)
@entries = Gitlab::Git::Tree.where(git_repo, @sha, @path, recursive)
end
def readme
......@@ -58,21 +57,4 @@ class Tree
def sorted_entries
trees + blobs + submodules
end
private
def get_entries(git_repo, sha, path, recursive: false)
current_path_entries = Gitlab::Git::Tree.where(git_repo, sha, path)
ordered_entries = []
current_path_entries.each do |entry|
ordered_entries << entry
if recursive && entry.dir?
ordered_entries.concat(get_entries(git_repo, sha, entry.path, recursive: true))
end
end
ordered_entries
end
end
......@@ -77,8 +77,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
......@@ -102,7 +106,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)
......@@ -303,4 +307,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
......@@ -5,6 +5,8 @@
# - Use `validates :xxx, uniqueness: { scope: :xxx_id }` in a child model
class VariableDuplicatesValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return if record.errors.include?(:"#{attribute}.key")
if options[:scope]
scoped = value.group_by do |variable|
Array(options[:scope]).map { |attr| variable.send(attr) } # rubocop:disable GitlabSecurity/PublicSend
......
......@@ -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')
......@@ -9,4 +9,3 @@
- content_for :page_specific_javascripts do
= webpack_bundle_tag('common_vue')
= webpack_bundle_tag('commit_pipelines')
......@@ -20,7 +20,7 @@
.avatar-cell.hidden-xs
= author_avatar(commit, size: 36)
.commit-detail
.commit-detail.flex-list
.commit-content
= link_to_markdown_field(commit, :title, link, class: "commit-row-message item-title")
%span.commit-row-message.visible-xs-inline
......
......@@ -3,7 +3,6 @@
- content_for :page_specific_javascripts do
= webpack_bundle_tag('common_vue')
= webpack_bundle_tag("environments_folder")
#environments-folder-list-view{ data: { endpoint: folder_project_environments_path(@project, @folder, format: :json),
"folder-name" => @folder,
......
%h4
= s_('PrometheusService|Auto configuration')
- if service.manual_configuration?
.well
= s_('PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below')
- else
.container-fluid
.row
- if service.prometheus_installed?
.col-sm-2
.svg-container
= image_tag 'illustrations/monitoring/getting_started.svg'
.col-sm-10
%p.text-success.prepend-top-default
= s_('PrometheusService|Prometheus is being automatically managed on your clusters')
= link_to s_('PrometheusService|Manage clusters'), project_clusters_path(project), class: 'btn'
- else
.col-sm-2
= image_tag 'illustrations/monitoring/loading.svg'
.col-sm-10
%p.prepend-top-default
= s_('PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments')
= link_to s_('PrometheusService|Install Prometheus on clusters'), project_clusters_path(project), class: 'btn btn-success'
%hr
%h4
= s_('PrometheusService|Auto configuration')
- if @service.manual_configuration?
.well
= s_('PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below')
- else
.container-fluid
.row
- if @service.prometheus_installed?
.col-sm-2
.svg-container
= image_tag 'illustrations/monitoring/getting_started.svg'
.col-sm-10
%p.text-success.prepend-top-default
= s_('PrometheusService|Prometheus is being automatically managed on your clusters')
= link_to s_('PrometheusService|Manage clusters'), project_clusters_path(@project), class: 'btn'
- else
.col-sm-2
= image_tag 'illustrations/monitoring/loading.svg'
.col-sm-10
%p.prepend-top-default
= s_('PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments')
= link_to s_('PrometheusService|Install Prometheus on clusters'), project_clusters_path(@project), class: 'btn btn-success'
%hr
- if @project
= render 'projects/services/prometheus/configuration_banner', project: @project, service: @service
%h4.append-bottom-default
= s_('PrometheusService|Manual configuration')
......
---
title: Allow Prometheus application to be installed from Cluster applications
merge_request: 17372
author:
type: fixed
---
title: Remove duplicated error message on duplicate variable validation
merge_request: 17135
author:
type: fixed
---
title: Fixes gpg popover layout
merge_request: 17323
author:
type: fixed
---
title: Fixes Prometheus admin configuration page
merge_request: 17377
author:
type: fixed
---
title: Fixed issue edit shortcut not opening edit form
merge_request:
author:
type: fixed
---
title: Move BoardNewIssue vue component
merge_request: 16947
author: George Tsiolis
type: performance
......@@ -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}`;
}
}
pageEntries.forEach(( path ) => generateAutoEntries(path));
......@@ -51,14 +45,11 @@ var config = {
context: path.join(ROOT_PATH, 'app/assets/javascripts'),
entry: {
balsamiq_viewer: './blob/balsamiq_viewer.js',
blob: './blob_edit/blob_bundle.js',
common: './commons/index.js',
common_vue: './vue_shared/vue_resource_interceptor.js',
cycle_analytics: './cycle_analytics/cycle_analytics_bundle.js',
commit_pipelines: './commit/pipelines/pipelines_bundle.js',
diff_notes: './diff_notes/diff_notes_bundle.js',
environments: './environments/environments_bundle.js',
environments_folder: './environments/folder/environments_folder_bundle.js',
filtered_search: './filtered_search/filtered_search_bundle.js',
help: './help/help.js',
merge_conflicts: './merge_conflicts/merge_conflicts_bundle.js',
......@@ -242,12 +233,10 @@ var config = {
name: 'common_vue',
chunks: [
'boards',
'commit_pipelines',
'cycle_analytics',
'deploy_keys',
'diff_notes',
'environments',
'environments_folder',
'filtered_search',
'groups',
'merge_conflicts',
......@@ -307,6 +296,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'),
}
}
}
......
......@@ -14,14 +14,14 @@ module Gitlab
# Uses rugged for raw objects
#
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/320
def where(repository, sha, path = nil)
def where(repository, sha, path = nil, recursive = false)
path = nil if path == '' || path == '/'
Gitlab::GitalyClient.migrate(:tree_entries) do |is_enabled|
if is_enabled
repository.gitaly_commit_client.tree_entries(repository, sha, path)
repository.gitaly_commit_client.tree_entries(repository, sha, path, recursive)
else
tree_entries_from_rugged(repository, sha, path)
tree_entries_from_rugged(repository, sha, path, recursive)
end
end
end
......@@ -57,7 +57,22 @@ module Gitlab
end
end
def tree_entries_from_rugged(repository, sha, path)
def tree_entries_from_rugged(repository, sha, path, recursive)
current_path_entries = get_tree_entries_from_rugged(repository, sha, path)
ordered_entries = []
current_path_entries.each do |entry|
ordered_entries << entry
if recursive && entry.dir?
ordered_entries.concat(tree_entries_from_rugged(repository, sha, entry.path, true))
end
end
ordered_entries
end
def get_tree_entries_from_rugged(repository, sha, path)
commit = repository.lookup(sha)
root_tree = commit.tree
......
......@@ -105,11 +105,12 @@ module Gitlab
entry unless entry.oid.blank?
end
def tree_entries(repository, revision, path)
def tree_entries(repository, revision, path, recursive)
request = Gitaly::GetTreeEntriesRequest.new(
repository: @gitaly_repo,
revision: encode_binary(revision),
path: path.present? ? encode_binary(path) : '.'
path: path.present? ? encode_binary(path) : '.',
recursive: recursive
)
response = GitalyClient.call(@repository.storage, :commit_service, :get_tree_entries, request, timeout: GitalyClient.medium_timeout)
......
require 'spec_helper'
describe 'Admin activates Prometheus' do
let(:admin) { create(:user, :admin) }
before do
sign_in(admin)
visit(admin_application_settings_services_path)
click_link('Prometheus')
end
it 'activates service' do
check('Active')
fill_in('API URL', with: 'http://prometheus.example.com')
click_button('Save')
expect(page).to have_content('Application settings saved successfully')
end
end
......@@ -271,6 +271,18 @@ describe 'New/edit issue', :js do
end
end
context 'inline edit' do
before do
visit project_issue_path(project, issue)
end
it 'opens inline edit form with shortcut' do
find('body').send_keys('e')
expect(page).to have_selector('.detail-page-description form')
end
end
describe 'sub-group project' do
let(:group) { create(:group) }
let(:nested_group_1) { create(:group, parent: group) }
......
require 'spec_helper'
describe 'User activates Prometheus' do
let(:project) { create(:project) }
let(:user) { create(:user) }
before do
project.add_master(user)
sign_in(user)
visit(project_settings_integrations_path(project))
click_link('Prometheus')
end
it 'activates service' do
check('Active')
fill_in('API URL', with: 'http://prometheus.example.com')
click_button('Save changes')
expect(page).to have_content('Prometheus activated.')
end
end
......@@ -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
......
......@@ -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';
......
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 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 mountComponent from '../helpers/vue_mount_component_helper';
import mountComponent from 'spec/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;
......
......@@ -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', () => {
......
......@@ -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);
......
......@@ -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;
......
......@@ -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`;
......
......@@ -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 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;
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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