Skip to content
Snippets Groups Projects
Commit ea4762d4 authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 68b6846f
No related branches found
No related tags found
No related merge requests found
Showing
with 189 additions and 127 deletions
Loading
Loading
@@ -5,6 +5,8 @@ import { joinPaths } from './lib/utils/url_utility';
import flash from '~/flash';
import { __ } from '~/locale';
 
const DEFAULT_PER_PAGE = 20;
const Api = {
groupsPath: '/api/:version/groups.json',
groupPath: '/api/:version/groups/:id',
Loading
Loading
@@ -66,7 +68,7 @@ const Api = {
params: Object.assign(
{
search: query,
per_page: 20,
per_page: DEFAULT_PER_PAGE,
},
options,
),
Loading
Loading
@@ -90,7 +92,7 @@ const Api = {
.get(url, {
params: {
search: query,
per_page: 20,
per_page: DEFAULT_PER_PAGE,
},
})
.then(({ data }) => callback(data));
Loading
Loading
@@ -101,7 +103,7 @@ const Api = {
const url = Api.buildUrl(Api.projectsPath);
const defaults = {
search: query,
per_page: 20,
per_page: DEFAULT_PER_PAGE,
simple: true,
};
 
Loading
Loading
@@ -126,7 +128,7 @@ const Api = {
.get(url, {
params: {
search: query,
per_page: 20,
per_page: DEFAULT_PER_PAGE,
...options,
},
})
Loading
Loading
@@ -235,7 +237,7 @@ const Api = {
const url = Api.buildUrl(Api.groupProjectsPath).replace(':id', groupId);
const defaults = {
search: query,
per_page: 20,
per_page: DEFAULT_PER_PAGE,
};
return axios
.get(url, {
Loading
Loading
@@ -325,7 +327,7 @@ const Api = {
params: Object.assign(
{
search: query,
per_page: 20,
per_page: DEFAULT_PER_PAGE,
},
options,
),
Loading
Loading
@@ -355,7 +357,7 @@ const Api = {
const url = Api.buildUrl(Api.userProjectsPath).replace(':id', userId);
const defaults = {
search: query,
per_page: 20,
per_page: DEFAULT_PER_PAGE,
};
return axios
.get(url, {
Loading
Loading
@@ -371,7 +373,7 @@ const Api = {
return axios.get(url, {
params: {
search: query,
per_page: 20,
per_page: DEFAULT_PER_PAGE,
...options,
},
});
Loading
Loading
@@ -403,10 +405,15 @@ const Api = {
return axios.post(url);
},
 
releases(id) {
releases(id, options = {}) {
const url = Api.buildUrl(this.releasesPath).replace(':id', encodeURIComponent(id));
 
return axios.get(url);
return axios.get(url, {
params: {
per_page: DEFAULT_PER_PAGE,
...options,
},
});
},
 
release(projectPath, tagName) {
Loading
Loading
Loading
Loading
@@ -19,6 +19,7 @@ export default {
'resolvableDiscussionsCount',
'firstUnresolvedDiscussionId',
'unresolvedDiscussionsCount',
'getDiscussion',
]),
isLoggedIn() {
return this.getUserData.id;
Loading
Loading
@@ -40,9 +41,10 @@ export default {
...mapActions(['expandDiscussion']),
jumpToFirstUnresolvedDiscussion() {
const diffTab = window.mrTabs.currentAction === 'diffs';
const discussionId = this.firstUnresolvedDiscussionId(diffTab);
this.jumpToDiscussion(discussionId);
const discussionId =
this.firstUnresolvedDiscussionId(diffTab) || this.firstUnresolvedDiscussionId();
const firstDiscussion = this.getDiscussion(discussionId);
this.jumpToDiscussion(firstDiscussion);
},
},
};
Loading
Loading
Loading
Loading
@@ -19,7 +19,11 @@ export default {
};
},
computed: {
...mapGetters(['nextUnresolvedDiscussionId', 'previousUnresolvedDiscussionId']),
...mapGetters([
'nextUnresolvedDiscussionId',
'previousUnresolvedDiscussionId',
'getDiscussion',
]),
},
mounted() {
Mousetrap.bind('n', () => this.jumpToNextDiscussion());
Loading
Loading
@@ -33,14 +37,14 @@ export default {
...mapActions(['expandDiscussion']),
jumpToNextDiscussion() {
const nextId = this.nextUnresolvedDiscussionId(this.currentDiscussionId, this.isDiffView);
this.jumpToDiscussion(nextId);
const nextDiscussion = this.getDiscussion(nextId);
this.jumpToDiscussion(nextDiscussion);
this.currentDiscussionId = nextId;
},
jumpToPreviousDiscussion() {
const prevId = this.previousUnresolvedDiscussionId(this.currentDiscussionId, this.isDiffView);
this.jumpToDiscussion(prevId);
const prevDiscussion = this.getDiscussion(prevId);
this.jumpToDiscussion(prevDiscussion);
this.currentDiscussionId = prevId;
},
},
Loading
Loading
Loading
Loading
@@ -84,6 +84,7 @@ export default {
'hasUnresolvedDiscussions',
'showJumpToNextDiscussion',
'getUserData',
'getDiscussion',
]),
currentUser() {
return this.getUserData;
Loading
Loading
@@ -221,8 +222,9 @@ export default {
this.discussion.id,
this.discussionsByDiffOrder,
);
const nextDiscussion = this.getDiscussion(nextId);
 
this.jumpToDiscussion(nextId);
this.jumpToDiscussion(nextDiscussion);
},
deleteNoteHandler(note) {
this.$emit('noteDeleted', this.discussion, note);
Loading
Loading
Loading
Loading
@@ -35,20 +35,26 @@ export default {
 
return false;
},
jumpToDiscussion(id) {
switchToDiscussionsTabAndJumpTo(id) {
window.mrTabs.eventHub.$once('MergeRequestTabChange', () => {
setTimeout(() => this.discussionJump(id), 0);
});
window.mrTabs.tabShown('show');
},
jumpToDiscussion(discussion) {
const { id, diff_discussion: isDiffDiscussion } = discussion;
if (id) {
const activeTab = window.mrTabs.currentAction;
 
if (activeTab === 'diffs') {
if (activeTab === 'diffs' && isDiffDiscussion) {
this.diffsJump(id);
} else if (activeTab === 'commits' || activeTab === 'pipelines') {
window.mrTabs.eventHub.$once('MergeRequestTabChange', () => {
setTimeout(() => this.discussionJump(id), 0);
});
window.mrTabs.tabShown('show');
} else {
} else if (activeTab === 'show') {
this.discussionJump(id);
} else {
this.switchToDiscussionsTabAndJumpTo(id);
}
}
},
Loading
Loading
<script>
import { mapState, mapActions } from 'vuex';
import { GlSkeletonLoading, GlEmptyState } from '@gitlab/ui';
import {
getParameterByName,
historyPushState,
buildUrlWithCurrentLocation,
} from '~/lib/utils/common_utils';
import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
import ReleaseBlock from './release_block.vue';
 
export default {
Loading
Loading
@@ -9,6 +15,7 @@ export default {
GlSkeletonLoading,
GlEmptyState,
ReleaseBlock,
TablePagination,
},
props: {
projectId: {
Loading
Loading
@@ -25,7 +32,7 @@ export default {
},
},
computed: {
...mapState(['isLoading', 'releases', 'hasError']),
...mapState(['isLoading', 'releases', 'hasError', 'pageInfo']),
shouldRenderEmptyState() {
return !this.releases.length && !this.hasError && !this.isLoading;
},
Loading
Loading
@@ -34,10 +41,17 @@ export default {
},
},
created() {
this.fetchReleases(this.projectId);
this.fetchReleases({
page: getParameterByName('page'),
projectId: this.projectId,
});
},
methods: {
...mapActions(['fetchReleases']),
onChangePage(page) {
historyPushState(buildUrlWithCurrentLocation(`?page=${page}`));
this.fetchReleases({ page, projectId: this.projectId });
},
},
};
</script>
Loading
Loading
@@ -67,6 +81,8 @@ export default {
:class="{ 'linked-card': releases.length > 1 && index !== releases.length - 1 }"
/>
</div>
<table-pagination v-if="!isLoading" :change="onChangePage" :page-info="pageInfo" />
</div>
</template>
<style>
Loading
Loading
Loading
Loading
@@ -2,6 +2,7 @@ import * as types from './mutation_types';
import createFlash from '~/flash';
import { __ } from '~/locale';
import api from '~/api';
import { normalizeHeaders, parseIntPagination } from '~/lib/utils/common_utils';
 
/**
* Commits a mutation to update the state while the main endpoint is being requested.
Loading
Loading
@@ -16,17 +17,19 @@ export const requestReleases = ({ commit }) => commit(types.REQUEST_RELEASES);
*
* @param {String} projectId
*/
export const fetchReleases = ({ dispatch }, projectId) => {
export const fetchReleases = ({ dispatch }, { page = '1', projectId }) => {
dispatch('requestReleases');
 
api
.releases(projectId)
.then(({ data }) => dispatch('receiveReleasesSuccess', data))
.releases(projectId, { page })
.then(response => dispatch('receiveReleasesSuccess', response))
.catch(() => dispatch('receiveReleasesError'));
};
 
export const receiveReleasesSuccess = ({ commit }, data) =>
commit(types.RECEIVE_RELEASES_SUCCESS, data);
export const receiveReleasesSuccess = ({ commit }, { data, headers }) => {
const pageInfo = parseIntPagination(normalizeHeaders(headers));
commit(types.RECEIVE_RELEASES_SUCCESS, { data, pageInfo });
};
 
export const receiveReleasesError = ({ commit }) => {
commit(types.RECEIVE_RELEASES_ERROR);
Loading
Loading
Loading
Loading
@@ -13,13 +13,15 @@ export default {
* Sets isLoading to false.
* Sets hasError to false.
* Sets the received data
* Sets the received pagination information
* @param {Object} state
* @param {Object} data
* @param {Object} resp
*/
[types.RECEIVE_RELEASES_SUCCESS](state, data) {
[types.RECEIVE_RELEASES_SUCCESS](state, { data, pageInfo }) {
state.hasError = false;
state.isLoading = false;
state.releases = data;
state.pageInfo = pageInfo;
},
 
/**
Loading
Loading
Loading
Loading
@@ -2,4 +2,5 @@ export default () => ({
isLoading: false,
hasError: false,
releases: [],
pageInfo: {},
});
/* eslint-disable func-names, one-var, no-var, prefer-rest-params, vars-on-top, consistent-return, no-shadow, no-else-return, no-self-compare, no-unused-expressions, yoda, prefer-spread, camelcase, no-param-reassign */
/* eslint-disable func-names, prefer-rest-params, consistent-return, no-shadow, no-else-return, no-self-compare, no-unused-expressions, yoda, prefer-spread, camelcase, no-param-reassign */
/* global Issuable */
/* global emitSidebarEvent */
 
Loading
Loading
@@ -13,7 +13,7 @@ import { parseBoolean } from './lib/utils/common_utils';
window.emitSidebarEvent = window.emitSidebarEvent || $.noop;
 
function UsersSelect(currentUser, els, options = {}) {
var $els;
const $els = $(els || '.js-user-search');
this.users = this.users.bind(this);
this.user = this.user.bind(this);
this.usersPath = '/autocomplete/users.json';
Loading
Loading
@@ -28,36 +28,11 @@ function UsersSelect(currentUser, els, options = {}) {
 
const { handleClick } = options;
 
$els = $(els);
if (!els) {
$els = $('.js-user-search');
}
$els.each(
(function(_this) {
return function(i, dropdown) {
var options = {};
var $block,
$collapsedSidebar,
$dropdown,
$loading,
$selectbox,
$value,
abilityName,
assignTo,
assigneeTemplate,
collapsedAssigneeTemplate,
defaultLabel,
defaultNullUser,
firstUser,
issueURL,
selectedId,
selectedIdDefault,
showAnyUser,
showNullUser,
showMenuAbove;
$dropdown = $(dropdown);
const options = {};
const $dropdown = $(dropdown);
options.projectId = $dropdown.data('projectId');
options.groupId = $dropdown.data('groupId');
options.showCurrentUser = $dropdown.data('currentUser');
Loading
Loading
@@ -65,22 +40,25 @@ function UsersSelect(currentUser, els, options = {}) {
options.todoStateFilter = $dropdown.data('todoStateFilter');
options.iid = $dropdown.data('iid');
options.issuableType = $dropdown.data('issuableType');
showNullUser = $dropdown.data('nullUser');
defaultNullUser = $dropdown.data('nullUserDefault');
showMenuAbove = $dropdown.data('showMenuAbove');
showAnyUser = $dropdown.data('anyUser');
firstUser = $dropdown.data('firstUser');
const showNullUser = $dropdown.data('nullUser');
const defaultNullUser = $dropdown.data('nullUserDefault');
const showMenuAbove = $dropdown.data('showMenuAbove');
const showAnyUser = $dropdown.data('anyUser');
const firstUser = $dropdown.data('firstUser');
options.authorId = $dropdown.data('authorId');
defaultLabel = $dropdown.data('defaultLabel');
issueURL = $dropdown.data('issueUpdate');
$selectbox = $dropdown.closest('.selectbox');
$block = $selectbox.closest('.block');
abilityName = $dropdown.data('abilityName');
$value = $block.find('.value');
$collapsedSidebar = $block.find('.sidebar-collapsed-user');
$loading = $block.find('.block-loading').fadeOut();
selectedIdDefault = defaultNullUser && showNullUser ? 0 : null;
selectedId = $dropdown.data('selected');
const defaultLabel = $dropdown.data('defaultLabel');
const issueURL = $dropdown.data('issueUpdate');
const $selectbox = $dropdown.closest('.selectbox');
let $block = $selectbox.closest('.block');
const abilityName = $dropdown.data('abilityName');
let $value = $block.find('.value');
const $collapsedSidebar = $block.find('.sidebar-collapsed-user');
const $loading = $block.find('.block-loading').fadeOut();
const selectedIdDefault = defaultNullUser && showNullUser ? 0 : null;
let selectedId = $dropdown.data('selected');
let assignTo;
let assigneeTemplate;
let collapsedAssigneeTemplate;
 
if (selectedId === undefined) {
selectedId = selectedIdDefault;
Loading
Loading
@@ -207,15 +185,15 @@ function UsersSelect(currentUser, els, options = {}) {
});
 
assignTo = function(selected) {
var data;
data = {};
const data = {};
data[abilityName] = {};
data[abilityName].assignee_id = selected != null ? selected : null;
$loading.removeClass('hidden').fadeIn();
$dropdown.trigger('loading.gl.dropdown');
 
return axios.put(issueURL, data).then(({ data }) => {
var user, tooltipTitle;
let user = {};
let tooltipTitle = user.name;
$dropdown.trigger('loaded.gl.dropdown');
$loading.fadeOut();
if (data.assignee) {
Loading
Loading
@@ -471,10 +449,9 @@ function UsersSelect(currentUser, els, options = {}) {
}
}
 
var isIssueIndex, isMRIndex, page, selected;
page = $('body').attr('data-page');
isIssueIndex = page === 'projects:issues:index';
isMRIndex = page === page && page === 'projects:merge_requests:index';
const page = $('body').attr('data-page');
const isIssueIndex = page === 'projects:issues:index';
const isMRIndex = page === page && page === 'projects:merge_requests:index';
if (
$dropdown.hasClass('js-filter-bulk-update') ||
$dropdown.hasClass('js-issuable-form-dropdown')
Loading
Loading
@@ -501,7 +478,7 @@ function UsersSelect(currentUser, els, options = {}) {
} else if ($dropdown.hasClass('js-filter-submit')) {
return $dropdown.closest('form').submit();
} else if (!$dropdown.hasClass('js-multiselect')) {
selected = $dropdown
const selected = $dropdown
.closest('.selectbox')
.find(`input[name='${$dropdown.data('fieldName')}']`)
.val();
Loading
Loading
@@ -544,9 +521,8 @@ function UsersSelect(currentUser, els, options = {}) {
},
updateLabel: $dropdown.data('dropdownTitle'),
renderRow(user) {
var avatar, img, username;
username = user.username ? `@${user.username}` : '';
avatar = user.avatar_url ? user.avatar_url : gon.default_avatar_url;
const username = user.username ? `@${user.username}` : '';
const avatar = user.avatar_url ? user.avatar_url : gon.default_avatar_url;
 
let selected = false;
 
Loading
Loading
@@ -565,7 +541,7 @@ function UsersSelect(currentUser, els, options = {}) {
selected = user.id === selectedId;
}
 
img = '';
let img = '';
if (user.beforeDivider != null) {
`<li><a href='#' class='${selected === true ? 'is-active' : ''}'>${_.escape(
user.name,
Loading
Loading
@@ -586,35 +562,34 @@ function UsersSelect(currentUser, els, options = {}) {
$('.ajax-users-select').each(
(function(_this) {
return function(i, select) {
var firstUser, showAnyUser, showEmailUser, showNullUser;
var options = {};
const options = {};
options.skipLdap = $(select).hasClass('skip_ldap');
options.projectId = $(select).data('projectId');
options.groupId = $(select).data('groupId');
options.showCurrentUser = $(select).data('currentUser');
options.authorId = $(select).data('authorId');
options.skipUsers = $(select).data('skipUsers');
showNullUser = $(select).data('nullUser');
showAnyUser = $(select).data('anyUser');
showEmailUser = $(select).data('emailUser');
firstUser = $(select).data('firstUser');
const showNullUser = $(select).data('nullUser');
const showAnyUser = $(select).data('anyUser');
const showEmailUser = $(select).data('emailUser');
const firstUser = $(select).data('firstUser');
return $(select).select2({
placeholder: __('Search for a user'),
multiple: $(select).hasClass('multiselect'),
minimumInputLength: 0,
query(query) {
return _this.users(query.term, options, users => {
var anyUser, data, emailUser, index, len, name, nullUser, obj, ref;
data = {
let name;
const data = {
results: users,
};
if (query.term.length === 0) {
if (firstUser) {
// Move current user to the front of the list
ref = data.results;
const ref = data.results;
 
for (index = 0, len = ref.length; index < len; index += 1) {
obj = ref[index];
for (let index = 0, len = ref.length; index < len; index += 1) {
const obj = ref[index];
if (obj.username === firstUser) {
data.results.splice(index, 1);
data.results.unshift(obj);
Loading
Loading
@@ -623,7 +598,7 @@ function UsersSelect(currentUser, els, options = {}) {
}
}
if (showNullUser) {
nullUser = {
const nullUser = {
name: s__('UsersSelect|Unassigned'),
id: 0,
};
Loading
Loading
@@ -634,7 +609,7 @@ function UsersSelect(currentUser, els, options = {}) {
if (name === true) {
name = s__('UsersSelect|Any User');
}
anyUser = {
const anyUser = {
name,
id: null,
};
Loading
Loading
@@ -646,8 +621,8 @@ function UsersSelect(currentUser, els, options = {}) {
data.results.length === 0 &&
query.term.match(/^[^@]+@[^@]+$/)
) {
var trimmed = query.term.trim();
emailUser = {
const trimmed = query.term.trim();
const emailUser = {
name: sprintf(__('Invite "%{trimmed}" by email'), { trimmed }),
username: trimmed,
id: trimmed,
Loading
Loading
@@ -659,18 +634,15 @@ function UsersSelect(currentUser, els, options = {}) {
});
},
initSelection() {
var args;
args = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
const args = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
return _this.initSelection.apply(_this, args);
},
formatResult() {
var args;
args = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
const args = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
return _this.formatResult.apply(_this, args);
},
formatSelection() {
var args;
args = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
const args = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
return _this.formatSelection.apply(_this, args);
},
dropdownCssClass: 'ajax-users-dropdown',
Loading
Loading
@@ -687,10 +659,9 @@ function UsersSelect(currentUser, els, options = {}) {
}
 
UsersSelect.prototype.initSelection = function(element, callback) {
var id, nullUser;
id = $(element).val();
const id = $(element).val();
if (id === '0') {
nullUser = {
const nullUser = {
name: s__('UsersSelect|Unassigned'),
};
return callback(nullUser);
Loading
Loading
@@ -700,11 +671,9 @@ UsersSelect.prototype.initSelection = function(element, callback) {
};
 
UsersSelect.prototype.formatResult = function(user) {
var avatar;
let avatar = gon.default_avatar_url;
if (user.avatar_url) {
avatar = user.avatar_url;
} else {
avatar = gon.default_avatar_url;
}
return `
<div class='user-result'>
Loading
Loading
@@ -732,8 +701,7 @@ UsersSelect.prototype.user = function(user_id, callback) {
return false;
}
 
var url;
url = this.buildUrl(this.userPath);
let url = this.buildUrl(this.userPath);
url = url.replace(':id', user_id);
return axios.get(url).then(({ data }) => {
callback(data);
Loading
Loading
Loading
Loading
@@ -38,7 +38,8 @@ module CycleAnalyticsParams
end
 
def to_utc_time(field)
Date.parse(field).to_time.utc
date = field.is_a?(Date) ? field : Date.parse(field)
date.to_time.utc
end
end
 
Loading
Loading
Loading
Loading
@@ -44,7 +44,7 @@ class Projects::IssuesController < Projects::ApplicationController
 
before_action do
push_frontend_feature_flag(:vue_issuable_sidebar, project.group)
push_frontend_feature_flag(:release_search_filter, project)
push_frontend_feature_flag(:release_search_filter, project, default_enabled: true)
end
 
respond_to :html
Loading
Loading
Loading
Loading
@@ -24,7 +24,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
 
before_action do
push_frontend_feature_flag(:vue_issuable_sidebar, @project.group)
push_frontend_feature_flag(:release_search_filter, @project)
push_frontend_feature_flag(:release_search_filter, @project, default_enabled: true)
end
 
around_action :allow_gitaly_ref_name_caching, only: [:index, :show, :discussions]
Loading
Loading
Loading
Loading
@@ -13,6 +13,7 @@
# group_id: integer
# project_id: integer
# milestone_title: string
# release_tag: string
# author_id: integer
# author_username: string
# assignee_id: integer or 'None' or 'Any'
Loading
Loading
@@ -59,6 +60,7 @@ class IssuableFinder
author_username
label_name
milestone_title
release_tag
my_reaction_emoji
search
in
Loading
Loading
@@ -126,6 +128,7 @@ class IssuableFinder
items = by_non_archived(items)
items = by_iids(items)
items = by_milestone(items)
items = by_release(items)
items = by_label(items)
by_my_reaction_emoji(items)
end
Loading
Loading
@@ -364,6 +367,10 @@ class IssuableFinder
end
end
 
def releases?
params[:release_tag].present?
end
private
 
def force_cte?
Loading
Loading
@@ -570,6 +577,18 @@ class IssuableFinder
end
# rubocop: enable CodeReuse/ActiveRecord
 
def by_release(items)
return items unless releases?
if filter_by_no_release?
items.without_release
elsif filter_by_any_release?
items.any_release
else
items.with_release(params[:release_tag], params[:project_id])
end
end
def filter_by_no_milestone?
# Accepts `No Milestone` for compatibility
params[:milestone_title].to_s.downcase == FILTER_NONE || params[:milestone_title] == Milestone::None.title
Loading
Loading
@@ -588,6 +607,14 @@ class IssuableFinder
params[:milestone_title] == Milestone::Started.name
end
 
def filter_by_no_release?
params[:release_tag].to_s.downcase == FILTER_NONE
end
def filter_by_any_release?
params[:release_tag].to_s.downcase == FILTER_ANY
end
def by_label(items)
return items unless labels?
 
Loading
Loading
Loading
Loading
@@ -12,6 +12,7 @@
# group_id: integer
# project_id: integer
# milestone_title: string
# release_tag: string
# author_id: integer
# assignee_id: integer
# search: string
Loading
Loading
Loading
Loading
@@ -3,7 +3,7 @@
class PipelinesFinder
attr_reader :project, :pipelines, :params, :current_user
 
ALLOWED_INDEXED_COLUMNS = %w[id status ref user_id].freeze
ALLOWED_INDEXED_COLUMNS = %w[id status ref updated_at user_id].freeze
 
def initialize(project, current_user, params = {})
@project = project
Loading
Loading
Loading
Loading
@@ -99,6 +99,8 @@ module Issuable
scope :of_milestones, ->(ids) { where(milestone_id: ids) }
scope :any_milestone, -> { where('milestone_id IS NOT NULL') }
scope :with_milestone, ->(title) { left_joins_milestones.where(milestones: { title: title }) }
scope :any_release, -> { joins_milestone_releases }
scope :with_release, -> (tag, project_id) { joins_milestone_releases.where( milestones: { releases: { tag: tag, project_id: project_id } } ) }
scope :opened, -> { with_state(:opened) }
scope :only_opened, -> { with_state(:opened) }
scope :closed, -> { with_state(:closed) }
Loading
Loading
@@ -120,6 +122,16 @@ module Issuable
scope :order_milestone_due_desc, -> { left_joins_milestones.reorder(Arel.sql('milestones.due_date IS NULL, milestones.id IS NULL, milestones.due_date DESC')) }
scope :order_milestone_due_asc, -> { left_joins_milestones.reorder(Arel.sql('milestones.due_date IS NULL, milestones.id IS NULL, milestones.due_date ASC')) }
 
scope :without_release, -> do
joins("LEFT OUTER JOIN milestone_releases ON #{table_name}.milestone_id = milestone_releases.milestone_id")
.where('milestone_releases.release_id IS NULL')
end
scope :joins_milestone_releases, -> do
joins("JOIN milestone_releases ON issues.milestone_id = milestone_releases.milestone_id
JOIN releases ON milestone_releases.release_id = releases.id").distinct
end
scope :without_label, -> { joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{name}' AND label_links.target_id = #{table_name}.id").where(label_links: { id: nil }) }
scope :any_label, -> { joins(:label_links).group(:id) }
scope :join_project, -> { joins(:project) }
Loading
Loading
---
title: Replacing incorrect icon for Retry in Pipeline list page
title: Replacing incorrect icon in security dashboard.
merge_request: 20510
author:
type: changed
---
title: Allow order_by updated_at in Pipelines API
merge_request: 19886
author:
type: added
---
title: Implement pagination for project releases page
merge_request: 19912
author: Fabio Huser
type: added
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment