Commit 6c3038e8 authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'master' into 30985-cancel-pipelines

* master:
  Remove special naming of pipelines folder
  remove changelog (not needed)
  Fix active user count
  add spec and changelog
  Add migration to remove orphaned notification settings
  Improve container registry repository path specs
  Fix duplicated container repository names
  update textarea height and refocus when attaching files
  Remove IIFEs in filtered_search_bundle.js
  Remove IIFEs from diff_notes_bundle.js
parents dbbcb32c a9da3743
import Vue from 'vue'; import Vue from 'vue';
import Visibility from 'visibilityjs'; import Visibility from 'visibilityjs';
import PipelinesTableComponent from '../../vue_shared/components/pipelines_table'; import PipelinesTableComponent from '../../vue_shared/components/pipelines_table';
import PipelinesService from '../../vue_pipelines_index/services/pipelines_service'; import PipelinesService from '../../pipelines/services/pipelines_service';
import PipelineStore from '../../vue_pipelines_index/stores/pipelines_store'; import PipelineStore from '../../pipelines/stores/pipelines_store';
import eventHub from '../../vue_pipelines_index/event_hub'; import eventHub from '../../pipelines/event_hub';
import EmptyState from '../../vue_pipelines_index/components/empty_state.vue'; import EmptyState from '../../pipelines/components/empty_state.vue';
import ErrorState from '../../vue_pipelines_index/components/error_state.vue'; import ErrorState from '../../pipelines/components/error_state.vue';
import '../../lib/utils/common_utils'; import '../../lib/utils/common_utils';
import '../../vue_shared/vue_resource_interceptor'; import '../../vue_shared/vue_resource_interceptor';
import Poll from '../../lib/utils/poll'; import Poll from '../../lib/utils/poll';
......
...@@ -3,8 +3,7 @@ ...@@ -3,8 +3,7 @@
import Vue from 'vue'; import Vue from 'vue';
(() => { const CommentAndResolveBtn = Vue.extend({
const CommentAndResolveBtn = Vue.extend({
props: { props: {
discussionId: String, discussionId: String,
}, },
...@@ -61,7 +60,6 @@ import Vue from 'vue'; ...@@ -61,7 +60,6 @@ import Vue from 'vue';
$(`.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`).off('input.comment-and-resolve-btn'); $(`.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`).off('input.comment-and-resolve-btn');
} }
}); });
Vue.component('comment-and-resolve-btn', CommentAndResolveBtn); Vue.component('comment-and-resolve-btn', CommentAndResolveBtn);
})(window);
...@@ -4,8 +4,7 @@ ...@@ -4,8 +4,7 @@
import Vue from 'vue'; import Vue from 'vue';
import collapseIcon from '../icons/collapse_icon.svg'; import collapseIcon from '../icons/collapse_icon.svg';
(() => { const DiffNoteAvatars = Vue.extend({
const DiffNoteAvatars = Vue.extend({
props: ['discussionId'], props: ['discussionId'],
data() { data() {
return { return {
...@@ -152,7 +151,6 @@ import collapseIcon from '../icons/collapse_icon.svg'; ...@@ -152,7 +151,6 @@ import collapseIcon from '../icons/collapse_icon.svg';
this.isVisible = $(`.diffs .notes[data-discussion-id="${this.discussion.id}"]`).is(':visible'); this.isVisible = $(`.diffs .notes[data-discussion-id="${this.discussion.id}"]`).is(':visible');
}, },
}, },
}); });
Vue.component('diff-note-avatars', DiffNoteAvatars); Vue.component('diff-note-avatars', DiffNoteAvatars);
})();
...@@ -4,8 +4,7 @@ ...@@ -4,8 +4,7 @@
import Vue from 'vue'; import Vue from 'vue';
(() => { const JumpToDiscussion = Vue.extend({
const JumpToDiscussion = Vue.extend({
mixins: [DiscussionMixins], mixins: [DiscussionMixins],
props: { props: {
discussionId: String discussionId: String
...@@ -189,7 +188,6 @@ import Vue from 'vue'; ...@@ -189,7 +188,6 @@ import Vue from 'vue';
created() { created() {
this.discussion = this.discussions[this.discussionId]; this.discussion = this.discussions[this.discussionId];
}, },
}); });
Vue.component('jump-to-discussion', JumpToDiscussion); Vue.component('jump-to-discussion', JumpToDiscussion);
})();
...@@ -2,8 +2,7 @@ ...@@ -2,8 +2,7 @@
import Vue from 'vue'; import Vue from 'vue';
(() => { const NewIssueForDiscussion = Vue.extend({
const NewIssueForDiscussion = Vue.extend({
props: { props: {
discussionId: { discussionId: {
type: String, type: String,
...@@ -24,7 +23,6 @@ import Vue from 'vue'; ...@@ -24,7 +23,6 @@ import Vue from 'vue';
return false; return false;
}, },
}, },
}); });
Vue.component('new-issue-for-discussion-btn', NewIssueForDiscussion); Vue.component('new-issue-for-discussion-btn', NewIssueForDiscussion);
})();
...@@ -5,8 +5,7 @@ ...@@ -5,8 +5,7 @@
import Vue from 'vue'; import Vue from 'vue';
(() => { const ResolveBtn = Vue.extend({
const ResolveBtn = Vue.extend({
props: { props: {
noteId: Number, noteId: Number,
discussionId: String, discussionId: String,
...@@ -115,7 +114,6 @@ import Vue from 'vue'; ...@@ -115,7 +114,6 @@ import Vue from 'vue';
noteTruncated: this.noteTruncated, noteTruncated: this.noteTruncated,
}); });
} }
}); });
Vue.component('resolve-btn', ResolveBtn); Vue.component('resolve-btn', ResolveBtn);
})();
...@@ -4,8 +4,7 @@ ...@@ -4,8 +4,7 @@
import Vue from 'vue'; import Vue from 'vue';
((w) => { window.ResolveCount = Vue.extend({
w.ResolveCount = Vue.extend({
mixins: [DiscussionMixins], mixins: [DiscussionMixins],
props: { props: {
loggedOut: Boolean loggedOut: Boolean
...@@ -23,5 +22,4 @@ import Vue from 'vue'; ...@@ -23,5 +22,4 @@ import Vue from 'vue';
return this.discussionCount === 1 ? 'discussion' : 'discussions'; return this.discussionCount === 1 ? 'discussion' : 'discussions';
} }
} }
}); });
})(window);
...@@ -4,8 +4,7 @@ ...@@ -4,8 +4,7 @@
import Vue from 'vue'; import Vue from 'vue';
(() => { const ResolveDiscussionBtn = Vue.extend({
const ResolveDiscussionBtn = Vue.extend({
props: { props: {
discussionId: String, discussionId: String,
mergeRequestId: Number, mergeRequestId: Number,
...@@ -56,7 +55,6 @@ import Vue from 'vue'; ...@@ -56,7 +55,6 @@ import Vue from 'vue';
this.discussion = CommentsStore.state[this.discussionId]; this.discussion = CommentsStore.state[this.discussionId];
} }
}); });
Vue.component('resolve-discussion-btn', ResolveDiscussionBtn); Vue.component('resolve-discussion-btn', ResolveDiscussionBtn);
})();
/* eslint-disable object-shorthand, func-names, guard-for-in, no-restricted-syntax, comma-dangle, no-param-reassign, max-len */ /* eslint-disable object-shorthand, func-names, guard-for-in, no-restricted-syntax, comma-dangle, no-param-reassign, max-len */
((w) => { window.DiscussionMixins = {
w.DiscussionMixins = {
computed: { computed: {
discussionCount: function () { discussionCount: function () {
return Object.keys(this.discussions).length; return Object.keys(this.discussions).length;
...@@ -33,5 +32,4 @@ ...@@ -33,5 +32,4 @@
return unresolvedCount; return unresolvedCount;
} }
} }
}; };
})(window);
...@@ -9,10 +9,9 @@ require('../../vue_shared/vue_resource_interceptor'); ...@@ -9,10 +9,9 @@ require('../../vue_shared/vue_resource_interceptor');
Vue.use(VueResource); Vue.use(VueResource);
(() => { window.gl = window.gl || {};
window.gl = window.gl || {};
class ResolveServiceClass { class ResolveServiceClass {
constructor(root) { constructor(root) {
this.noteResource = Vue.resource(`${root}/notes{/noteId}/resolve`); this.noteResource = Vue.resource(`${root}/notes{/noteId}/resolve`);
this.discussionResource = Vue.resource(`${root}/merge_requests{/mergeRequestId}/discussions{/discussionId}/resolve`); this.discussionResource = Vue.resource(`${root}/merge_requests{/mergeRequestId}/discussions{/discussionId}/resolve`);
...@@ -78,7 +77,6 @@ Vue.use(VueResource); ...@@ -78,7 +77,6 @@ Vue.use(VueResource);
discussionId discussionId
}, {}); }, {});
} }
} }
gl.DiffNotesResolveServiceClass = ResolveServiceClass; gl.DiffNotesResolveServiceClass = ResolveServiceClass;
})();
...@@ -3,8 +3,7 @@ ...@@ -3,8 +3,7 @@
import Vue from 'vue'; import Vue from 'vue';
((w) => { window.CommentsStore = {
w.CommentsStore = {
state: {}, state: {},
get: function (discussionId, noteId) { get: function (discussionId, noteId) {
return this.state[discussionId].getNote(noteId); return this.state[discussionId].getNote(noteId);
...@@ -54,5 +53,4 @@ import Vue from 'vue'; ...@@ -54,5 +53,4 @@ import Vue from 'vue';
return ids; return ids;
} }
}; };
})(window);
...@@ -130,13 +130,15 @@ window.DropzoneInput = (function() { ...@@ -130,13 +130,15 @@ window.DropzoneInput = (function() {
var afterSelection, beforeSelection, caretEnd, caretStart, textEnd; var afterSelection, beforeSelection, caretEnd, caretStart, textEnd;
var formattedText = text; var formattedText = text;
if (shouldPad) formattedText += "\n\n"; if (shouldPad) formattedText += "\n\n";
caretStart = $(child)[0].selectionStart; const textarea = child.get(0);
caretEnd = $(child)[0].selectionEnd; caretStart = textarea.selectionStart;
caretEnd = textarea.selectionEnd;
textEnd = $(child).val().length; textEnd = $(child).val().length;
beforeSelection = $(child).val().substring(0, caretStart); beforeSelection = $(child).val().substring(0, caretStart);
afterSelection = $(child).val().substring(caretEnd, textEnd); afterSelection = $(child).val().substring(caretEnd, textEnd);
$(child).val(beforeSelection + formattedText + afterSelection); $(child).val(beforeSelection + formattedText + afterSelection);
child.get(0).setSelectionRange(caretStart + formattedText.length, caretEnd + formattedText.length); textarea.setSelectionRange(caretStart + formattedText.length, caretEnd + formattedText.length);
textarea.style.height = `${textarea.scrollHeight}px`;
return form_textarea.trigger("input"); return form_textarea.trigger("input");
}; };
getFilename = function(e) { getFilename = function(e) {
...@@ -180,7 +182,7 @@ window.DropzoneInput = (function() { ...@@ -180,7 +182,7 @@ window.DropzoneInput = (function() {
}; };
insertToTextArea = function(filename, url) { insertToTextArea = function(filename, url) {
return $(child).val(function(index, val) { return $(child).val(function(index, val) {
return val.replace("{{" + filename + "}}", url + "\n"); return val.replace("{{" + filename + "}}", url);
}); });
}; };
appendToTextArea = function(url) { appendToTextArea = function(url) {
...@@ -215,6 +217,7 @@ window.DropzoneInput = (function() { ...@@ -215,6 +217,7 @@ window.DropzoneInput = (function() {
form.find(".markdown-selector").click(function(e) { form.find(".markdown-selector").click(function(e) {
e.preventDefault(); e.preventDefault();
$(this).closest('.gfm-form').find('.div-dropzone').click(); $(this).closest('.gfm-form').find('.div-dropzone').click();
form_textarea.focus();
}); });
} }
......
...@@ -2,8 +2,7 @@ import Filter from '~/droplab/plugins/filter'; ...@@ -2,8 +2,7 @@ import Filter from '~/droplab/plugins/filter';
require('./filtered_search_dropdown'); require('./filtered_search_dropdown');
(() => { class DropdownHint extends gl.FilteredSearchDropdown {
class DropdownHint extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) { constructor(droplab, dropdown, input, filter) {
super(droplab, dropdown, input, filter); super(droplab, dropdown, input, filter);
this.config = { this.config = {
...@@ -76,8 +75,7 @@ require('./filtered_search_dropdown'); ...@@ -76,8 +75,7 @@ require('./filtered_search_dropdown');
init() { init() {
this.droplab.addHook(this.input, this.dropdown, [Filter], this.config).init(); this.droplab.addHook(this.input, this.dropdown, [Filter], this.config).init();
} }
} }
window.gl = window.gl || {}; window.gl = window.gl || {};
gl.DropdownHint = DropdownHint; gl.DropdownHint = DropdownHint;
})();
...@@ -5,8 +5,7 @@ import Filter from '~/droplab/plugins/filter'; ...@@ -5,8 +5,7 @@ import Filter from '~/droplab/plugins/filter';
require('./filtered_search_dropdown'); require('./filtered_search_dropdown');
(() => { class DropdownNonUser extends gl.FilteredSearchDropdown {
class DropdownNonUser extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter, endpoint, symbol) { constructor(droplab, dropdown, input, filter, endpoint, symbol) {
super(droplab, dropdown, input, filter); super(droplab, dropdown, input, filter);
this.symbol = symbol; this.symbol = symbol;
...@@ -45,8 +44,7 @@ require('./filtered_search_dropdown'); ...@@ -45,8 +44,7 @@ require('./filtered_search_dropdown');
this.droplab this.droplab
.addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init(); .addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init();
} }
} }
window.gl = window.gl || {}; window.gl = window.gl || {};
gl.DropdownNonUser = DropdownNonUser; gl.DropdownNonUser = DropdownNonUser;
})();
...@@ -4,8 +4,7 @@ import AjaxFilter from '~/droplab/plugins/ajax_filter'; ...@@ -4,8 +4,7 @@ import AjaxFilter from '~/droplab/plugins/ajax_filter';
require('./filtered_search_dropdown'); require('./filtered_search_dropdown');
(() => { class DropdownUser extends gl.FilteredSearchDropdown {
class DropdownUser extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) { constructor(droplab, dropdown, input, filter) {
super(droplab, dropdown, input, filter); super(droplab, dropdown, input, filter);
this.config = { this.config = {
...@@ -65,8 +64,7 @@ require('./filtered_search_dropdown'); ...@@ -65,8 +64,7 @@ require('./filtered_search_dropdown');
init() { init() {
this.droplab.addHook(this.input, this.dropdown, [AjaxFilter], this.config).init(); this.droplab.addHook(this.input, this.dropdown, [AjaxFilter], this.config).init();
} }
} }
window.gl = window.gl || {}; window.gl = window.gl || {};
gl.DropdownUser = DropdownUser; gl.DropdownUser = DropdownUser;
})();
import FilteredSearchContainer from './container'; import FilteredSearchContainer from './container';
(() => { class DropdownUtils {
class DropdownUtils {
static getEscapedText(text) { static getEscapedText(text) {
let escapedText = text; let escapedText = text;
const hasSpace = text.indexOf(' ') !== -1; const hasSpace = text.indexOf(' ') !== -1;
...@@ -176,8 +175,7 @@ import FilteredSearchContainer from './container'; ...@@ -176,8 +175,7 @@ import FilteredSearchContainer from './container';
right, right,
}; };
} }
} }
window.gl = window.gl || {}; window.gl = window.gl || {};
gl.DropdownUtils = DropdownUtils; gl.DropdownUtils = DropdownUtils;
})();
(() => { const DATA_DROPDOWN_TRIGGER = 'data-dropdown-trigger';
const DATA_DROPDOWN_TRIGGER = 'data-dropdown-trigger';
class FilteredSearchDropdown { class FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) { constructor(droplab, dropdown, input, filter) {
this.droplab = droplab; this.droplab = droplab;
this.hookId = input && input.id; this.hookId = input && input.id;
...@@ -117,8 +116,7 @@ ...@@ -117,8 +116,7 @@
hook.list.render(results); hook.list.render(results);
} }
} }
} }
window.gl = window.gl || {}; window.gl = window.gl || {};
gl.FilteredSearchDropdown = FilteredSearchDropdown; gl.FilteredSearchDropdown = FilteredSearchDropdown;
})();
import DropLab from '~/droplab/drop_lab'; import DropLab from '~/droplab/drop_lab';
import FilteredSearchContainer from './container'; import FilteredSearchContainer from './container';
(() => { class FilteredSearchDropdownManager {
class FilteredSearchDropdownManager {
constructor(baseEndpoint = '', page) { constructor(baseEndpoint = '', page) {
this.container = FilteredSearchContainer.container; this.container = FilteredSearchContainer.container;
this.baseEndpoint = baseEndpoint.replace(/\/$/, ''); this.baseEndpoint = baseEndpoint.replace(/\/$/, '');
...@@ -184,8 +183,7 @@ import FilteredSearchContainer from './container'; ...@@ -184,8 +183,7 @@ import FilteredSearchContainer from './container';
destroyDroplab() { destroyDroplab() {
this.droplab.destroy(); this.droplab.destroy();
} }
} }
window.gl = window.gl || {}; window.gl = window.gl || {};
gl.FilteredSearchDropdownManager = FilteredSearchDropdownManager; gl.FilteredSearchDropdownManager = FilteredSearchDropdownManager;
})();
...@@ -6,8 +6,7 @@ import RecentSearchesStore from './stores/recent_searches_store'; ...@@ -6,8 +6,7 @@ import RecentSearchesStore from './stores/recent_searches_store';
import RecentSearchesService from './services/recent_searches_service'; import RecentSearchesService from './services/recent_searches_service';
import eventHub from './event_hub'; import eventHub from './event_hub';
(() => { class FilteredSearchManager {
class FilteredSearchManager {
constructor(page) { constructor(page) {
this.container = FilteredSearchContainer.container; this.container = FilteredSearchContainer.container;
this.filteredSearchInput = this.container.querySelector('.filtered-search'); this.filteredSearchInput = this.container.querySelector('.filtered-search');
...@@ -487,8 +486,7 @@ import eventHub from './event_hub'; ...@@ -487,8 +486,7 @@ import eventHub from './event_hub';
this.filteredSearchInput.dispatchEvent(new CustomEvent('input')); this.filteredSearchInput.dispatchEvent(new CustomEvent('input'));
this.search(); this.search();
} }
} }
window.gl = window.gl || {}; window.gl = window.gl || {};
gl.FilteredSearchManager = FilteredSearchManager; gl.FilteredSearchManager = FilteredSearchManager;
})();
(() => { const tokenKeys = [{
const tokenKeys = [{
key: 'author', key: 'author',
type: 'string', type: 'string',
param: 'username', param: 'username',
symbol: '@', symbol: '@',
}, { }, {
key: 'assignee', key: 'assignee',
type: 'string', type: 'string',
param: 'username', param: 'username',
symbol: '@', symbol: '@',
}, { }, {
key: 'milestone', key: 'milestone',
type: 'string', type: 'string',
param: 'title', param: 'title',
symbol: '%', symbol: '%',
}, { }, {
key: 'label', key: 'label',
type: 'array', type: 'array',
param: 'name[]', param: 'name[]',
symbol: '~', symbol: '~',
}]; }];
const alternativeTokenKeys = [{ const alternativeTokenKeys = [{
key: 'label', key: 'label',
type: 'string', type: 'string',
param: 'name', param: 'name',
symbol: '~', symbol: '~',
}]; }];
const tokenKeysWithAlternative = tokenKeys.concat(alternativeTokenKeys); const tokenKeysWithAlternative = tokenKeys.concat(alternativeTokenKeys);
const conditions = [{ const conditions = [{
url: 'assignee_id=0', url: 'assignee_id=0',
tokenKey: 'assignee', tokenKey: 'assignee',
value: 'none', value: 'none',
}, { }, {
url: 'milestone_title=No+Milestone', url: 'milestone_title=No+Milestone',
tokenKey: 'milestone', tokenKey: 'milestone',
value: 'none', value: 'none',
}, { }, {
url: 'milestone_title=%23upcoming', url: 'milestone_title=%23upcoming',
tokenKey: 'milestone', tokenKey: 'milestone',
value: 'upcoming', value: 'upcoming',
}, { }, {
url: 'milestone_title=%23started', url: 'milestone_title=%23started',
tokenKey: 'milestone', tokenKey: 'milestone',
value: 'started', value: 'started',
}, { }, {
url: 'label_name[]=No+Label', url: 'label_name[]=No+Label',
tokenKey: 'label', tokenKey: 'label',
value: 'none', value: 'none',
}]; }];
class FilteredSearchTokenKeys { class FilteredSearchTokenKeys {
static get() { static get() {
return tokenKeys; return tokenKeys;
} }
...@@ -93,8 +92,7 @@ ...@@ -93,8 +92,7 @@
return conditions return conditions
.find(condition => condition.tokenKey === key && condition.value === value) || null; .find(condition => condition.tokenKey === key && condition.value === value) || null;
} }
} }
window.gl = window.gl || {}; window.gl = window.gl || {};
gl.FilteredSearchTokenKeys = FilteredSearchTokenKeys; gl.FilteredSearchTokenKeys = FilteredSearchTokenKeys;
})();
require('./filtered_search_token_keys'); require('./filtered_search_token_keys');
(() => { class FilteredSearchTokenizer {
class FilteredSearchTokenizer {
static processTokens(input) { static processTokens(input) {
const allowedKeys = gl.FilteredSearchTokenKeys.get().map(i => i.key); const allowedKeys = gl.FilteredSearchTokenKeys.get().map(i => i.key);
// Regex extracts `(token):(symbol)(value)` // Regex extracts `(token):(symbol)(value)`
...@@ -51,8 +50,7 @@ require('./filtered_search_token_keys'); ...@@ -51,8 +50,7 @@ require('./filtered_search_token_keys');
searchToken, searchToken,
}; };
} }
} }
window.gl = window.gl || {}; window.gl = window.gl || {};
gl.FilteredSearchTokenizer = FilteredSearchTokenizer; gl.FilteredSearchTokenizer = FilteredSearchTokenizer;
})();
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
import AsyncButtonComponent from '../../vue_pipelines_index/components/async_button.vue'; import AsyncButtonComponent from '../../pipelines/components/async_button.vue';
import PipelinesActionsComponent from '../../vue_pipelines_index/components/pipelines_actions'; import PipelinesActionsComponent from '../../pipelines/components/pipelines_actions';
import PipelinesArtifactsComponent from '../../vue_pipelines_index/components/pipelines_artifacts'; import PipelinesArtifactsComponent from '../../pipelines/components/pipelines_artifacts';
import PipelinesStatusComponent from '../../vue_pipelines_index/components/status'; import PipelinesStatusComponent from '../../pipelines/components/status';
import PipelinesStageComponent from '../../vue_pipelines_index/components/stage'; import PipelinesStageComponent from '../../pipelines/components/stage';
import PipelinesUrlComponent from '../../vue_pipelines_index/components/pipeline_url'; import PipelinesUrlComponent from '../../pipelines/components/pipeline_url';
import PipelinesTimeagoComponent from '../../vue_pipelines_index/components/time_ago'; import PipelinesTimeagoComponent from '../../pipelines/components/time_ago';
import CommitComponent from './commit'; import CommitComponent from './commit';
/** /**
......
...@@ -197,7 +197,7 @@ class User < ActiveRecord::Base ...@@ -197,7 +197,7 @@ class User < ActiveRecord::Base
scope :admins, -> { where(admin: true) } scope :admins, -> { where(admin: true) }
scope :blocked, -> { with_states(:blocked, :ldap_blocked) } scope :blocked, -> { with_states(:blocked, :ldap_blocked) }
scope :external, -> { where(external: true) } scope :external, -> { where(external: true) }
scope :active, -> { with_state(:active) } scope :active, -> { with_state(:active).non_internal }
scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all } scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all }
scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members WHERE user_id IS NOT NULL AND requested_at IS NULL)') } scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members WHERE user_id IS NOT NULL AND requested_at IS NULL)') }
scope :todo_authors, ->(user_id, state) { where(id: Todo.where(user_id: user_id, state: state).select(:author_id)) } scope :todo_authors, ->(user_id, state) { where(id: Todo.where(user_id: user_id, state: state).select(:author_id)) }
......
...@@ -17,4 +17,4 @@ ...@@ -17,4 +17,4 @@
"ci-lint-path" => ci_lint_path } } "ci-lint-path" => ci_lint_path } }
= page_specific_javascript_bundle_tag('common_vue') = page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('vue_pipelines') = page_specific_javascript_bundle_tag('pipelines')
---
title: refocus textarea after attaching a file
merge_request:
author:
---
title: Removed orphaned notification settings without a namespace
merge_request:
author:
...@@ -19,12 +19,11 @@ var WEBPACK_REPORT = process.env.WEBPACK_REPORT; ...@@ -19,12 +19,11 @@ var WEBPACK_REPORT = process.env.WEBPACK_REPORT;
var config = { var config = {
context: path.join(ROOT_PATH, 'app/assets/javascripts'), context: path.join(ROOT_PATH, 'app/assets/javascripts'),
entry: { entry: {
blob: './blob_edit/blob_bundle.js',
boards: './boards/boards_bundle.js',
common: './commons/index.js', common: './commons/index.js',
common_vue: ['vue', './vue_shared/common_vue.js'], common_vue: ['vue', './vue_shared/common_vue.js'],
common_d3: ['d3'], common_d3: ['d3'],
main: './main.js',
blob: './blob_edit/blob_bundle.js',
boards: './boards/boards_bundle.js',
cycle_analytics: './cycle_analytics/cycle_analytics_bundle.js', cycle_analytics: './cycle_analytics/cycle_analytics_bundle.js',
commit_pipelines: './commit/pipelines/pipelines_bundle.js', commit_pipelines: './commit/pipelines/pipelines_bundle.js',
diff_notes: './diff_notes/diff_notes_bundle.js', diff_notes: './diff_notes/diff_notes_bundle.js',
...@@ -32,26 +31,27 @@ var config = { ...@@ -32,26 +31,27 @@ var config = {
environments_folder: './environments/folder/environments_folder_bundle.js', environments_folder: './environments/folder/environments_folder_bundle.js',
filtered_search: './filtered_search/filtered_search_bundle.js', filtered_search: './filtered_search/filtered_search_bundle.js',
graphs: './graphs/graphs_bundle.js', graphs: './graphs/graphs_bundle.js',
group: './group.js',
groups_list: './groups_list.js', groups_list: './groups_list.js',
issuable: './issuable/issuable_bundle.js', issuable: './issuable/issuable_bundle.js',
issue_show: './issue_show/index.js',
main: './main.js',
merge_conflicts: './merge_conflicts/merge_conflicts_bundle.js', merge_conflicts: './merge_conflicts/merge_conflicts_bundle.js',
merge_request_widget: './merge_request_widget/ci_bundle.js', merge_request_widget: './merge_request_widget/ci_bundle.js',
monitoring: './monitoring/monitoring_bundle.js', monitoring: './monitoring/monitoring_bundle.js',
network: './network/network_bundle.js', network: './network/network_bundle.js',
notebook_viewer: './blob/notebook_viewer.js', notebook_viewer: './blob/notebook_viewer.js',
sketch_viewer: './blob/sketch_viewer.js',
pdf_viewer: './blob/pdf_viewer.js', pdf_viewer: './blob/pdf_viewer.js',
pipelines: './pipelines/index.js',
profile: './profile/profile_bundle.js', profile: './profile/profile_bundle.js',
protected_branches: './protected_branches/protected_branches_bundle.js', protected_branches: './protected_branches/protected_branches_bundle.js',
protected_tags: './protected_tags', protected_tags: './protected_tags',
snippet: './snippet/snippet_bundle.js', snippet: './snippet/snippet_bundle.js',
sketch_viewer: './blob/sketch_viewer.js',
stl_viewer: './blob/stl_viewer.js', stl_viewer: './blob/stl_viewer.js',
terminal: './terminal/terminal_bundle.js', terminal: './terminal/terminal_bundle.js',
u2f: ['vendor/u2f'], u2f: ['vendor/u2f'],
users: './users/users_bundle.js', users: './users/users_bundle.js',
vue_pipelines: './vue_pipelines_index/index.js',
issue_show: './issue_show/index.js',
group: './group.js',
}, },
output: { output: {
...@@ -121,11 +121,11 @@ var config = { ...@@ -121,11 +121,11 @@ var config = {
'environments', 'environments',
'environments_folder', 'environments_folder',
'issuable', 'issuable',
'issue_show',
'merge_conflicts', 'merge_conflicts',
'notebook_viewer', 'notebook_viewer',
'pdf_viewer', 'pdf_viewer',
'vue_pipelines', 'pipelines',
'issue_show',
], ],
minChunks: function(module, count) { minChunks: function(module, count) {
return module.resource && (/vue_shared/).test(module.resource); return module.resource && (/vue_shared/).test(module.resource);
......
class DeleteOrphanNotificationSettings < ActiveRecord::Migration
DOWNTIME = false
def up
execute("DELETE FROM notification_settings WHERE EXISTS (SELECT true FROM (#{orphan_notification_settings}) AS ns WHERE ns.id = notification_settings.id)")
end
def down
# This is a no-op method to make the migration reversible.
# If someone is trying to rollback for other reasons, we should not throw an Exception.
# raise ActiveRecord::IrreversibleMigration
end
def orphan_notification_settings
<<-SQL
SELECT notification_settings.id
FROM notification_settings
LEFT OUTER JOIN namespaces
ON namespaces.id = notification_settings.source_id
WHERE notification_settings.source_type = 'Namespace'
AND namespaces.id IS NULL
SQL
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20170408033905) do ActiveRecord::Schema.define(version: 20170418103908) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
......
...@@ -48,7 +48,7 @@ module ContainerRegistry ...@@ -48,7 +48,7 @@ module ContainerRegistry
end end
def root_repository? def root_repository?
@path == repository_project.full_path @path == project_path
end end
def repository_project def repository_project
...@@ -60,7 +60,13 @@ module ContainerRegistry ...@@ -60,7 +60,13 @@ module ContainerRegistry
def repository_name def repository_name
return unless has_project? return unless has_project?
@path.remove(%r(^#{Regexp.escape(repository_project.full_path)}/?)) @path.remove(%r(^#{Regexp.escape(project_path)}/?))
end
def project_path
return unless has_project?
repository_project.full_path.downcase
end end
def to_s def to_s
......
...@@ -5,8 +5,7 @@ require('~/diff_notes/models/discussion'); ...@@ -5,8 +5,7 @@ require('~/diff_notes/models/discussion');
require('~/diff_notes/models/note'); require('~/diff_notes/models/note');
require('~/diff_notes/stores/comments'); require('~/diff_notes/stores/comments');
(() => { function createDiscussion(noteId = 1, resolved = true) {
function createDiscussion(noteId = 1, resolved = true) {
CommentsStore.create({ CommentsStore.create({
discussionId: 'a', discussionId: 'a',
noteId, noteId,
...@@ -17,13 +16,13 @@ require('~/diff_notes/stores/comments'); ...@@ -17,13 +16,13 @@ require('~/diff_notes/stores/comments');
authorAvatar: 'test', authorAvatar: 'test',
noteTruncated: 'test...', noteTruncated: 'test...',
}); });
} }
beforeEach(() => { beforeEach(() => {
CommentsStore.state = {}; CommentsStore.state = {};
}); });
describe('New discussion', () => { describe('New discussion', () => {
it('creates new discussion', () => { it('creates new discussion', () => {
expect(Object.keys(CommentsStore.state).length).toBe(0); expect(Object.keys(CommentsStore.state).length).toBe(0);
createDiscussion(); createDiscussion();
...@@ -37,9 +36,9 @@ require('~/diff_notes/stores/comments'); ...@@ -37,9 +36,9 @@ require('~/diff_notes/stores/comments');
const discussion = CommentsStore.state['a']; const discussion = CommentsStore.state['a'];
expect(Object.keys(discussion.notes).length).toBe(2); expect(Object.keys(discussion.notes).length).toBe(2);
}); });
}); });
describe('Get note', () => { describe('Get note', () => {
beforeEach(() => { beforeEach(() => {
expect(Object.keys(CommentsStore.state).length).toBe(0); expect(Object.keys(CommentsStore.state).length).toBe(0);
createDiscussion(); createDiscussion();
...@@ -50,9 +49,9 @@ require('~/diff_notes/stores/comments'); ...@@ -50,9 +49,9 @@ require('~/diff_notes/stores/comments');
expect(note).toBeDefined(); expect(note).toBeDefined();
expect(note.id).toBe(1); expect(note.id).toBe(1);
}); });
}); });
describe('Delete discussion', () => { describe('Delete discussion', () => {
beforeEach(() => { beforeEach(() => {
expect(Object.keys(CommentsStore.state).length).toBe(0); expect(Object.keys(CommentsStore.state).length).toBe(0);
createDiscussion(); createDiscussion();
...@@ -73,9 +72,9 @@ require('~/diff_notes/stores/comments'); ...@@ -73,9 +72,9 @@ require('~/diff_notes/stores/comments');
CommentsStore.delete('a', 2); CommentsStore.delete('a', 2);
expect(Object.keys(CommentsStore.state).length).toBe(0); expect(Object.keys(CommentsStore.state).length).toBe(0);
}); });
}); });
describe('Update note', () => { describe('Update note', () => {
beforeEach(() => { beforeEach(() => {
expect(Object.keys(CommentsStore.state).length).toBe(0); expect(Object.keys(CommentsStore.state).length).toBe(0);
createDiscussion(); createDiscussion();
...@@ -87,9 +86,9 @@ require('~/diff_notes/stores/comments'); ...@@ -87,9 +86,9 @@ require('~/diff_notes/stores/comments');
const note = CommentsStore.get('a', 1); const note = CommentsStore.get('a', 1);
expect(note.resolved).toBe(false); expect(note.resolved).toBe(false);
}); });
}); });
describe('Discussion resolved', () => { describe('Discussion resolved', () => {
beforeEach(() => { beforeEach(() => {
expect(Object.keys(CommentsStore.state).length).toBe(0); expect(Object.keys(CommentsStore.state).length).toBe(0);
createDiscussion(); createDiscussion();
...@@ -129,5 +128,4 @@ require('~/diff_notes/stores/comments'); ...@@ -129,5 +128,4 @@ require('~/diff_notes/stores/comments');
discussion.unResolveAllNotes(); discussion.unResolveAllNotes();
expect(discussion.isResolved()).toBe(false); expect(discussion.isResolved()).toBe(false);
}); });
}); });
})();
...@@ -3,8 +3,7 @@ require('~/filtered_search/filtered_search_tokenizer'); ...@@ -3,8 +3,7 @@ require('~/filtered_search/filtered_search_tokenizer');
require('~/filtered_search/filtered_search_dropdown'); require('~/filtered_search/filtered_search_dropdown');
require('~/filtered_search/dropdown_user'); require('~/filtered_search/dropdown_user');
(() => { describe('Dropdown User', () => {
describe('Dropdown User', () => {
describe('getSearchInput', () => { describe('getSearchInput', () => {
let dropdownUser; let dropdownUser;
...@@ -67,5 +66,4 @@ require('~/filtered_search/dropdown_user'); ...@@ -67,5 +66,4 @@ require('~/filtered_search/dropdown_user');
window.gon = {}; window.gon = {};
}); });
}); });
}); });
})();
...@@ -3,8 +3,7 @@ require('~/filtered_search/dropdown_utils'); ...@@ -3,8 +3,7 @@ require('~/filtered_search/dropdown_utils');
require('~/filtered_search/filtered_search_tokenizer'); require('~/filtered_search/filtered_search_tokenizer');
require('~/filtered_search/filtered_search_dropdown_manager'); require('~/filtered_search/filtered_search_dropdown_manager');
(() => { describe('Dropdown Utils', () => {
describe('Dropdown Utils', () => {
describe('getEscapedText', () => { describe('getEscapedText', () => {
it('should return same word when it has no space', () => { it('should return same word when it has no space', () => {
const escaped = gl.DropdownUtils.getEscapedText('textWithoutSpace'); const escaped = gl.DropdownUtils.getEscapedText('textWithoutSpace');
...@@ -306,5 +305,4 @@ require('~/filtered_search/filtered_search_dropdown_manager'); ...@@ -306,5 +305,4 @@ require('~/filtered_search/filtered_search_dropdown_manager');
}); });
}); });
}); });
}); });
})();
...@@ -3,8 +3,7 @@ require('~/filtered_search/filtered_search_visual_tokens'); ...@@ -3,8 +3,7 @@ require('~/filtered_search/filtered_search_visual_tokens');
require('~/filtered_search/filtered_search_tokenizer'); require('~/filtered_search/filtered_search_tokenizer');
require('~/filtered_search/filtered_search_dropdown_manager'); require('~/filtered_search/filtered_search_dropdown_manager');
(() => { describe('Filtered Search Dropdown Manager', () => {
describe('Filtered Search Dropdown Manager', () => {
describe('addWordToInput', () => { describe('addWordToInput', () => {
function getInputValue() { function getInputValue() {
return document.querySelector('.filtered-search').value; return document.querySelector('.filtered-search').value;
...@@ -97,5 +96,4 @@ require('~/filtered_search/filtered_search_dropdown_manager'); ...@@ -97,5 +96,4 @@ require('~/filtered_search/filtered_search_dropdown_manager');
}); });
}); });
}); });
}); });
})();
...@@ -6,8 +6,7 @@ require('~/filtered_search/filtered_search_dropdown_manager'); ...@@ -6,8 +6,7 @@ require('~/filtered_search/filtered_search_dropdown_manager');
require('~/filtered_search/filtered_search_manager'); require('~/filtered_search/filtered_search_manager');
const FilteredSearchSpecHelper = require('../helpers/filtered_search_spec_helper'); const FilteredSearchSpecHelper = require('../helpers/filtered_search_spec_helper');
(() => { describe('Filtered Search Manager', () => {
describe('Filtered Search Manager', () => {
let input; let input;
let manager; let manager;
let tokensContainer; let tokensContainer;
...@@ -272,5 +271,4 @@ const FilteredSearchSpecHelper = require('../helpers/filtered_search_spec_helper ...@@ -272,5 +271,4 @@ const FilteredSearchSpecHelper = require('../helpers/filtered_search_spec_helper
expect(document.querySelector('.filtered-search-box').classList.contains('focus')).toEqual(false); expect(document.querySelector('.filtered-search-box').classList.contains('focus')).toEqual(false);
}); });
}); });
}); });
})();
require('~/extensions/array'); require('~/extensions/array');
require('~/filtered_search/filtered_search_token_keys'); require('~/filtered_search/filtered_search_token_keys');
(() => { describe('Filtered Search Token Keys', () => {
describe('Filtered Search Token Keys', () => {
describe('get', () => { describe('get', () => {
let tokenKeys; let tokenKeys;
...@@ -106,5 +105,4 @@ require('~/filtered_search/filtered_search_token_keys'); ...@@ -106,5 +105,4 @@ require('~/filtered_search/filtered_search_token_keys');
expect(result).toEqual(conditions[0]); expect(result).toEqual(conditions[0]);
}); });
}); });
}); });
})();
...@@ -2,8 +2,7 @@ require('~/extensions/array'); ...@@ -2,8 +2,7 @@ require('~/extensions/array');
require('~/filtered_search/filtered_search_token_keys'); require('~/filtered_search/filtered_search_token_keys');
require('~/filtered_search/filtered_search_tokenizer'); require('~/filtered_search/filtered_search_tokenizer');
(() => { describe('Filtered Search Tokenizer', () => {
describe('Filtered Search Tokenizer', () => {
describe('processTokens', () => { describe('processTokens', () => {
it('returns for input containing only search value', () => { it('returns for input containing only search value', () => {
const results = gl.FilteredSearchTokenizer.processTokens('searchTerm'); const results = gl.FilteredSearchTokenizer.processTokens('searchTerm');
...@@ -131,5 +130,4 @@ require('~/filtered_search/filtered_search_tokenizer'); ...@@ -131,5 +130,4 @@ require('~/filtered_search/filtered_search_tokenizer');
expect(results.tokens[0].symbol).toBe('~'); expect(results.tokens[0].symbol).toBe('~');
}); });
}); });
}); });
})();
import Vue from 'vue'; import Vue from 'vue';
import asyncButtonComp from '~/vue_pipelines_index/components/async_button.vue'; import asyncButtonComp from '~/pipelines/components/async_button.vue';
describe('Pipelines Async Button', () => { describe('Pipelines Async Button', () => {
let component; let component;
......
import Vue from 'vue'; import Vue from 'vue';
import emptyStateComp from '~/vue_pipelines_index/components/empty_state.vue'; import emptyStateComp from '~/pipelines/components/empty_state.vue';
describe('Pipelines Empty State', () => { describe('Pipelines Empty State', () => {
let component; let component;
......
import Vue from 'vue'; import Vue from 'vue';
import errorStateComp from '~/vue_pipelines_index/components/error_state.vue'; import errorStateComp from '~/pipelines/components/error_state.vue';
describe('Pipelines Error State', () => { describe('Pipelines Error State', () => {
let component; let component;
......
import Vue from 'vue'; import Vue from 'vue';
import navControlsComp from '~/vue_pipelines_index/components/nav_controls'; import navControlsComp from '~/pipelines/components/nav_controls';
describe('Pipelines Nav Controls', () => { describe('Pipelines Nav Controls', () => {
let NavControlsComponent; let NavControlsComponent;
......
import Vue from 'vue'; import Vue from 'vue';
import pipelineUrlComp from '~/vue_pipelines_index/components/pipeline_url'; import pipelineUrlComp from '~/pipelines/components/pipeline_url';
describe('Pipeline Url Component', () => { describe('Pipeline Url Component', () => {
let PipelineUrlComponent; let PipelineUrlComponent;
......
import Vue from 'vue'; import Vue from 'vue';
import pipelinesActionsComp from '~/vue_pipelines_index/components/pipelines_actions'; import pipelinesActionsComp from '~/pipelines/components/pipelines_actions';
describe('Pipelines Actions dropdown', () => { describe('Pipelines Actions dropdown', () => {
let component; let component;
......
import Vue from 'vue'; import Vue from 'vue';
import artifactsComp from '~/vue_pipelines_index/components/pipelines_artifacts'; import artifactsComp from '~/pipelines/components/pipelines_artifacts';
describe('Pipelines Artifacts dropdown', () => { describe('Pipelines Artifacts dropdown', () => {
let component; let component;
......
import Vue from 'vue'; import Vue from 'vue';
import pipelinesComp from '~/vue_pipelines_index/pipelines'; import pipelinesComp from '~/pipelines/pipelines';
import Store from '~/vue_pipelines_index/stores/pipelines_store'; import Store from '~/pipelines/stores/pipelines_store';
import pipelinesData from './mock_data'; import pipelinesData from './mock_data';
describe('Pipelines', () => { describe('Pipelines', () => {
......
import PipelineStore from '~/vue_pipelines_index/stores/pipelines_store'; import PipelineStore from '~/pipelines/stores/pipelines_store';
describe('Pipelines Store', () => { describe('Pipelines Store', () => {
let store; let store;
......
import Vue from 'vue'; import Vue from 'vue';
import { SUCCESS_SVG } from '~/ci_status_icons'; import { SUCCESS_SVG } from '~/ci_status_icons';
import Stage from '~/vue_pipelines_index/components/stage'; import Stage from '~/pipelines/components/stage';
function minify(string) { function minify(string) {
return string.replace(/\s/g, ''); return string.replace(/\s/g, '');
......
...@@ -189,15 +189,10 @@ describe ContainerRegistry::Path do ...@@ -189,15 +189,10 @@ describe ContainerRegistry::Path do
end end
context 'when project exists' do context 'when project exists' do
let(:group) { create(:group, path: 'some_group') } let(:group) { create(:group, path: 'Some_Group') }
let(:project) do
create(:empty_project, group: group, name: 'some_project')
end
before do before do
allow(path).to receive(:repository_project) create(:empty_project, group: group, name: 'some_project')
.and_return(project)
end end
context 'when project path equal repository path' do context 'when project path equal repository path' do
...@@ -225,4 +220,27 @@ describe ContainerRegistry::Path do ...@@ -225,4 +220,27 @@ describe ContainerRegistry::Path do
end end
end end
end end
describe '#project_path' do
context 'when project does not exist' do
let(:path) { 'some/name' }
it 'returns nil' do
expect(subject.project_path).to be_nil
end
end
context 'when project with uppercase characters in path exists' do
let(:path) { 'somegroup/myproject/my/image' }
let(:group) { create(:group, path: 'SomeGroup') }
before do
create(:empty_project, group: group, name: 'MyProject')
end
it 'returns downcased project path' do
expect(subject.project_path).to eq 'somegroup/myproject'
end
end
end
end end
...@@ -1631,4 +1631,16 @@ describe User, models: true do ...@@ -1631,4 +1631,16 @@ describe User, models: true do
end end
end end
end end
context '.active' do
before do
User.ghost
create(:user, name: 'user', state: 'active')
create(:user, name: 'user', state: 'blocked')
end
it 'only counts active and non internal users' do
expect(User.active.count).to eq(1)
end
end
end end
...@@ -7,6 +7,7 @@ describe Groups::DestroyService, services: true do ...@@ -7,6 +7,7 @@ describe Groups::DestroyService, services: true do
let!(:group) { create(:group) } let!(:group) { create(:group) }
let!(:nested_group) { create(:group, parent: group) } let!(:nested_group) { create(:group, parent: group) }
let!(:project) { create(:empty_project, namespace: group) } let!(:project) { create(:empty_project, namespace: group) }
let!(:notification_setting) { create(:notification_setting, source: group)}
let!(:gitlab_shell) { Gitlab::Shell.new } let!(:gitlab_shell) { Gitlab::Shell.new }
let!(:remove_path) { group.path + "+#{group.id}+deleted" } let!(:remove_path) { group.path + "+#{group.id}+deleted" }
...@@ -23,6 +24,7 @@ describe Groups::DestroyService, services: true do ...@@ -23,6 +24,7 @@ describe Groups::DestroyService, services: true do
it { expect(Group.unscoped.all).not_to include(group) } it { expect(Group.unscoped.all).not_to include(group) }
it { expect(Group.unscoped.all).not_to include(nested_group) } it { expect(Group.unscoped.all).not_to include(nested_group) }
it { expect(Project.unscoped.all).not_to include(project) } it { expect(Project.unscoped.all).not_to include(project) }
it { expect(NotificationSetting.unscoped.all).not_to include(notification_setting) }
end end
context 'file system' do context 'file system' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment