Merge branch 'fe-issue-reorder' into 'master'
Bring Manual Ordering on Issue List See merge request gitlab-org/gitlab-ce!29410
This commit is contained in:
commit
2b9ddc2f99
|
|
@ -0,0 +1,58 @@
|
||||||
|
import Sortable from 'sortablejs';
|
||||||
|
import { s__ } from '~/locale';
|
||||||
|
import createFlash from '~/flash';
|
||||||
|
import {
|
||||||
|
getBoardSortableDefaultOptions,
|
||||||
|
sortableStart,
|
||||||
|
} from '~/boards/mixins/sortable_default_options';
|
||||||
|
import axios from '~/lib/utils/axios_utils';
|
||||||
|
|
||||||
|
const updateIssue = (url, issueList, { move_before_id, move_after_id }) =>
|
||||||
|
axios
|
||||||
|
.put(`${url}/reorder`, {
|
||||||
|
move_before_id,
|
||||||
|
move_after_id,
|
||||||
|
group_full_path: issueList.dataset.groupFullPath,
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
createFlash(s__("ManualOrdering|Couldn't save the order of the issues"));
|
||||||
|
});
|
||||||
|
|
||||||
|
const initManualOrdering = () => {
|
||||||
|
const issueList = document.querySelector('.manual-ordering');
|
||||||
|
|
||||||
|
if (!issueList || !(gon.features && gon.features.manualSorting)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Sortable.create(
|
||||||
|
issueList,
|
||||||
|
getBoardSortableDefaultOptions({
|
||||||
|
scroll: true,
|
||||||
|
dataIdAttr: 'data-id',
|
||||||
|
fallbackOnBody: false,
|
||||||
|
group: {
|
||||||
|
name: 'issues',
|
||||||
|
},
|
||||||
|
draggable: 'li.issue',
|
||||||
|
onStart: () => {
|
||||||
|
sortableStart();
|
||||||
|
},
|
||||||
|
onUpdate: event => {
|
||||||
|
const el = event.item;
|
||||||
|
|
||||||
|
const url = el.getAttribute('url');
|
||||||
|
|
||||||
|
const prev = el.previousElementSibling;
|
||||||
|
const next = el.nextElementSibling;
|
||||||
|
|
||||||
|
const beforeId = prev && parseInt(prev.dataset.id, 10);
|
||||||
|
const afterId = next && parseInt(next.dataset.id, 10);
|
||||||
|
|
||||||
|
updateIssue(url, issueList, { move_after_id: afterId, move_before_id: beforeId });
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default initManualOrdering;
|
||||||
|
|
@ -2,6 +2,7 @@ import projectSelect from '~/project_select';
|
||||||
import initFilteredSearch from '~/pages/search/init_filtered_search';
|
import initFilteredSearch from '~/pages/search/init_filtered_search';
|
||||||
import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
|
import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
|
||||||
import { FILTERED_SEARCH } from '~/pages/constants';
|
import { FILTERED_SEARCH } from '~/pages/constants';
|
||||||
|
import initManualOrdering from '~/manual_ordering';
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
initFilteredSearch({
|
initFilteredSearch({
|
||||||
|
|
@ -10,4 +11,5 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
projectSelect();
|
projectSelect();
|
||||||
|
initManualOrdering();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import projectSelect from '~/project_select';
|
||||||
import initFilteredSearch from '~/pages/search/init_filtered_search';
|
import initFilteredSearch from '~/pages/search/init_filtered_search';
|
||||||
import { FILTERED_SEARCH } from '~/pages/constants';
|
import { FILTERED_SEARCH } from '~/pages/constants';
|
||||||
import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys';
|
import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys';
|
||||||
|
import initManualOrdering from '~/manual_ordering';
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
IssuableFilteredSearchTokenKeys.addExtraTokensForIssues();
|
IssuableFilteredSearchTokenKeys.addExtraTokensForIssues();
|
||||||
|
|
@ -12,4 +13,5 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys,
|
filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys,
|
||||||
});
|
});
|
||||||
projectSelect();
|
projectSelect();
|
||||||
|
initManualOrdering();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import initFilteredSearch from '~/pages/search/init_filtered_search';
|
||||||
import { FILTERED_SEARCH } from '~/pages/constants';
|
import { FILTERED_SEARCH } from '~/pages/constants';
|
||||||
import { ISSUABLE_INDEX } from '~/pages/projects/constants';
|
import { ISSUABLE_INDEX } from '~/pages/projects/constants';
|
||||||
import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys';
|
import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys';
|
||||||
|
import initManualOrdering from '~/manual_ordering';
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
IssuableFilteredSearchTokenKeys.addExtraTokensForIssues();
|
IssuableFilteredSearchTokenKeys.addExtraTokensForIssues();
|
||||||
|
|
@ -19,4 +20,5 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
|
||||||
new ShortcutsNavigation();
|
new ShortcutsNavigation();
|
||||||
new UsersSelect();
|
new UsersSelect();
|
||||||
|
initManualOrdering();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,18 @@
|
||||||
.issues-list {
|
.issues-list {
|
||||||
|
&.manual-ordering {
|
||||||
|
background-color: $gray-light;
|
||||||
|
border-radius: $border-radius-default;
|
||||||
|
padding: $gl-padding-8;
|
||||||
|
|
||||||
|
.issue {
|
||||||
|
background-color: $white-light;
|
||||||
|
margin-bottom: $gl-padding-8;
|
||||||
|
border-radius: $border-radius-default;
|
||||||
|
border: 1px solid $gray-100;
|
||||||
|
box-shadow: 0 1px 2px $issue-boards-card-shadow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.issue {
|
.issue {
|
||||||
padding: 10px 0 10px $gl-padding;
|
padding: 10px 0 10px $gl-padding;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,10 @@ class GroupsController < Groups::ApplicationController
|
||||||
include PreviewMarkdown
|
include PreviewMarkdown
|
||||||
include RecordUserLastActivity
|
include RecordUserLastActivity
|
||||||
|
|
||||||
|
before_action do
|
||||||
|
push_frontend_feature_flag(:manual_sorting)
|
||||||
|
end
|
||||||
|
|
||||||
respond_to :html
|
respond_to :html
|
||||||
|
|
||||||
prepend_before_action(only: [:show, :issues]) { authenticate_sessionless_user!(:rss) }
|
prepend_before_action(only: [:show, :issues]) { authenticate_sessionless_user!(:rss) }
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,10 @@ class Projects::IssuesController < Projects::ApplicationController
|
||||||
include SpammableActions
|
include SpammableActions
|
||||||
include RecordUserLastActivity
|
include RecordUserLastActivity
|
||||||
|
|
||||||
|
before_action do
|
||||||
|
push_frontend_feature_flag(:manual_sorting)
|
||||||
|
end
|
||||||
|
|
||||||
def issue_except_actions
|
def issue_except_actions
|
||||||
%i[index calendar new create bulk_update import_csv]
|
%i[index calendar new create bulk_update import_csv]
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ module IssuesHelper
|
||||||
classes = ["issue"]
|
classes = ["issue"]
|
||||||
classes << "closed" if issue.closed?
|
classes << "closed" if issue.closed?
|
||||||
classes << "today" if issue.today?
|
classes << "today" if issue.today?
|
||||||
|
classes << "user-can-drag" if @sort == 'relative_position'
|
||||||
classes.join(' ')
|
classes.join(' ')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
- empty_state_path = local_assigns.fetch(:empty_state_path, 'shared/empty_states/issues')
|
- empty_state_path = local_assigns.fetch(:empty_state_path, 'shared/empty_states/issues')
|
||||||
|
|
||||||
%ul.content-list.issues-list.issuable-list
|
%ul.content-list.issues-list.issuable-list{ class: ("manual-ordering" if @sort == 'relative_position') }
|
||||||
= render partial: "projects/issues/issue", collection: @issues
|
= render partial: "projects/issues/issue", collection: @issues
|
||||||
- if @issues.blank?
|
- if @issues.blank?
|
||||||
= render empty_state_path
|
= render empty_state_path
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
- if @issues.to_a.any?
|
- if @issues.to_a.any?
|
||||||
.card.card-small.card-without-border
|
.card.card-small.card-without-border
|
||||||
%ul.content-list.issues-list.issuable-list
|
%ul.content-list.issues-list.issuable-list{ class: ("manual-ordering" if @sort == 'relative_position'), data: { group_full_path: @group&.full_path } }
|
||||||
= render partial: 'projects/issues/issue', collection: @issues
|
= render partial: 'projects/issues/issue', collection: @issues
|
||||||
= paginate @issues, theme: "gitlab"
|
= paginate @issues, theme: "gitlab"
|
||||||
- else
|
- else
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
- sort_value = @sort
|
- sort_value = @sort
|
||||||
- sort_title = issuable_sort_option_title(sort_value)
|
- sort_title = issuable_sort_option_title(sort_value)
|
||||||
- viewing_issues = controller.controller_name == 'issues' || controller.action_name == 'issues'
|
- viewing_issues = controller.controller_name == 'issues' || controller.action_name == 'issues'
|
||||||
|
- manual_sorting = viewing_issues && controller.controller_name != 'dashboard' && Feature.enabled?(:manual_sorting)
|
||||||
|
|
||||||
.dropdown.inline.prepend-left-10.issue-sort-dropdown
|
.dropdown.inline.prepend-left-10.issue-sort-dropdown
|
||||||
.btn-group{ role: 'group' }
|
.btn-group{ role: 'group' }
|
||||||
|
|
@ -17,6 +18,6 @@
|
||||||
= sortable_item(sort_title_due_date, page_filter_path(sort: sort_value_due_date), sort_title) if viewing_issues
|
= sortable_item(sort_title_due_date, page_filter_path(sort: sort_value_due_date), sort_title) if viewing_issues
|
||||||
= sortable_item(sort_title_popularity, page_filter_path(sort: sort_value_popularity), sort_title)
|
= sortable_item(sort_title_popularity, page_filter_path(sort: sort_value_popularity), sort_title)
|
||||||
= sortable_item(sort_title_label_priority, page_filter_path(sort: sort_value_label_priority), sort_title)
|
= sortable_item(sort_title_label_priority, page_filter_path(sort: sort_value_label_priority), sort_title)
|
||||||
= sortable_item(sort_title_relative_position, page_filter_path(sort: sort_value_relative_position), sort_title) if viewing_issues && Feature.enabled?(:manual_sorting)
|
= sortable_item(sort_title_relative_position, page_filter_path(sort: sort_value_relative_position), sort_title) if manual_sorting
|
||||||
= render_if_exists('shared/ee/issuable/sort_dropdown', viewing_issues: viewing_issues, sort_title: sort_title)
|
= render_if_exists('shared/ee/issuable/sort_dropdown', viewing_issues: viewing_issues, sort_title: sort_title)
|
||||||
= issuable_sort_direction_button(sort_value)
|
= issuable_sort_direction_button(sort_value)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Bring Manual Ordering on Issue List
|
||||||
|
merge_request: 29410
|
||||||
|
author:
|
||||||
|
type: added
|
||||||
|
|
@ -6011,6 +6011,9 @@ msgstr ""
|
||||||
msgid "Manual job"
|
msgid "Manual job"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "ManualOrdering|Couldn't save the order of the issues"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Map a FogBugz account ID to a GitLab user"
|
msgid "Map a FogBugz account ID to a GitLab user"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ require 'spec_helper'
|
||||||
|
|
||||||
describe 'Group issues page' do
|
describe 'Group issues page' do
|
||||||
include FilteredSearchHelpers
|
include FilteredSearchHelpers
|
||||||
|
include DragTo
|
||||||
|
|
||||||
let(:group) { create(:group) }
|
let(:group) { create(:group) }
|
||||||
let(:project) { create(:project, :public, group: group)}
|
let(:project) { create(:project, :public, group: group)}
|
||||||
|
|
@ -99,4 +100,49 @@ describe 'Group issues page' do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'manual ordering' do
|
||||||
|
let!(:issue1) { create(:issue, project: project, title: 'Issue #1') }
|
||||||
|
let!(:issue2) { create(:issue, project: project, title: 'Issue #2') }
|
||||||
|
let!(:issue3) { create(:issue, project: project, title: 'Issue #3') }
|
||||||
|
|
||||||
|
it 'displays all issues' do
|
||||||
|
visit issues_group_path(group, sort: 'relative_position')
|
||||||
|
|
||||||
|
page.within('.issues-list') do
|
||||||
|
expect(page).to have_selector('li.issue', count: 3)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has manual-ordering css applied' do
|
||||||
|
visit issues_group_path(group, sort: 'relative_position')
|
||||||
|
|
||||||
|
expect(page).to have_selector('.manual-ordering')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'each issue item has a user-can-drag css applied' do
|
||||||
|
visit issues_group_path(group, sort: 'relative_position')
|
||||||
|
|
||||||
|
page.within('.manual-ordering') do
|
||||||
|
expect(page).to have_selector('.issue.user-can-drag', count: 3)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'issues should be draggable and persist order', :js do
|
||||||
|
visit issues_group_path(group, sort: 'relative_position')
|
||||||
|
|
||||||
|
drag_to(selector: '.manual-ordering',
|
||||||
|
scrollable: '#board-app',
|
||||||
|
list_from_index: 0,
|
||||||
|
from_index: 0,
|
||||||
|
to_index: 2,
|
||||||
|
list_to_index: 0)
|
||||||
|
|
||||||
|
page.within('.manual-ordering') do
|
||||||
|
expect(find('.issue:nth-child(1) .title')).to have_content('Issue #2')
|
||||||
|
expect(find('.issue:nth-child(2) .title')).to have_content('Issue #1')
|
||||||
|
expect(find('.issue:nth-child(3) .title')).to have_content('Issue #3')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue