Commit cd776400 authored by Heinrich Lee Yu's avatar Heinrich Lee Yu

Load milestone issues tab asynchronously

We currently do not paginate these issues so this can be very long. This
is not so simple to paginate because there are 3 columns. We move this
to an async request for now so that we do not block the other parts of
the page.
parent fb0b4a31
...@@ -7,11 +7,6 @@ import { __ } from './locale'; ...@@ -7,11 +7,6 @@ import { __ } from './locale';
export default class Milestone { export default class Milestone {
constructor() { constructor() {
this.bindTabsSwitching(); this.bindTabsSwitching();
// Load merge request tab if it is active
// merge request tab is active based on different conditions in the backend
this.loadTab($('.js-milestone-tabs .active a'));
this.loadInitialTab(); this.loadInitialTab();
} }
...@@ -23,12 +18,14 @@ export default class Milestone { ...@@ -23,12 +18,14 @@ export default class Milestone {
this.loadTab($target); this.loadTab($target);
}); });
} }
// eslint-disable-next-line class-methods-use-this
loadInitialTab() { loadInitialTab() {
const $target = $(`.js-milestone-tabs a[href="${window.location.hash}"]`); const $target = $(`.js-milestone-tabs a:not(.active)[href="${window.location.hash}"]`);
if ($target.length) { if ($target.length) {
$target.tab('show'); $target.tab('show');
} else {
this.loadTab($('.js-milestone-tabs a.active'));
} }
} }
// eslint-disable-next-line class-methods-use-this // eslint-disable-next-line class-methods-use-this
......
...@@ -3,13 +3,25 @@ ...@@ -3,13 +3,25 @@
module MilestoneActions module MilestoneActions
extend ActiveSupport::Concern extend ActiveSupport::Concern
def issues
respond_to do |format|
format.html { redirect_to milestone_redirect_path }
format.json do
render json: tabs_json("shared/milestones/_issues_tab", {
issues: @milestone.sorted_issues(current_user), # rubocop:disable Gitlab/ModuleWithInstanceVariables
show_project_name: Gitlab::Utils.to_boolean(params[:show_project_name])
})
end
end
end
def merge_requests def merge_requests
respond_to do |format| respond_to do |format|
format.html { redirect_to milestone_redirect_path } format.html { redirect_to milestone_redirect_path }
format.json do format.json do
render json: tabs_json("shared/milestones/_merge_requests_tab", { render json: tabs_json("shared/milestones/_merge_requests_tab", {
merge_requests: @milestone.sorted_merge_requests(current_user), # rubocop:disable Gitlab/ModuleWithInstanceVariables merge_requests: @milestone.sorted_merge_requests(current_user), # rubocop:disable Gitlab/ModuleWithInstanceVariables
show_project_name: true show_project_name: Gitlab::Utils.to_boolean(params[:show_project_name])
}) })
end end
end end
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
class Groups::MilestonesController < Groups::ApplicationController class Groups::MilestonesController < Groups::ApplicationController
include MilestoneActions include MilestoneActions
before_action :milestone, only: [:edit, :show, :update, :merge_requests, :participants, :labels, :destroy] before_action :milestone, only: [:edit, :show, :update, :issues, :merge_requests, :participants, :labels, :destroy]
before_action :authorize_admin_milestones!, only: [:edit, :new, :create, :update, :destroy] before_action :authorize_admin_milestones!, only: [:edit, :new, :create, :update, :destroy]
before_action do before_action do
push_frontend_feature_flag(:burnup_charts, @group) push_frontend_feature_flag(:burnup_charts, @group)
......
...@@ -5,7 +5,7 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -5,7 +5,7 @@ class Projects::MilestonesController < Projects::ApplicationController
include MilestoneActions include MilestoneActions
before_action :check_issuables_available! before_action :check_issuables_available!
before_action :milestone, only: [:edit, :update, :destroy, :show, :merge_requests, :participants, :labels, :promote] before_action :milestone, only: [:edit, :update, :destroy, :show, :issues, :merge_requests, :participants, :labels, :promote]
before_action do before_action do
push_frontend_feature_flag(:burnup_charts, @project) push_frontend_feature_flag(:burnup_charts, @project)
end end
...@@ -14,7 +14,7 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -14,7 +14,7 @@ class Projects::MilestonesController < Projects::ApplicationController
before_action :authorize_read_milestone! before_action :authorize_read_milestone!
# Allow admin milestone # Allow admin milestone
before_action :authorize_admin_milestone!, except: [:index, :show, :merge_requests, :participants, :labels] before_action :authorize_admin_milestone!, except: [:index, :show, :issues, :merge_requests, :participants, :labels]
# Allow to promote milestone # Allow to promote milestone
before_action :authorize_promote_milestone!, only: :promote before_action :authorize_promote_milestone!, only: :promote
......
...@@ -228,8 +228,8 @@ module TimeboxesHelper ...@@ -228,8 +228,8 @@ module TimeboxesHelper
end end
alias_method :milestone_date_range, :timebox_date_range alias_method :milestone_date_range, :timebox_date_range
def milestone_tab_path(milestone, tab) def milestone_tab_path(milestone, tab, params = {})
url_for(action: tab, format: :json) url_for(params.merge(action: tab, format: :json))
end end
def update_milestone_path(milestone, params = {}) def update_milestone_path(milestone, params = {})
......
...@@ -10,8 +10,6 @@ ...@@ -10,8 +10,6 @@
%span %span
- if show_project_name - if show_project_name
%strong #{project.name} &middot; %strong #{project.name} &middot;
- elsif show_full_project_name
%strong #{project.full_name} &middot;
- if issuable.is_a?(Issue) - if issuable.is_a?(Issue)
= confidential_icon(issuable) = confidential_icon(issuable)
= link_to issuable.title, issuable_url_args, title: issuable.title = link_to issuable.title, issuable_url_args, title: issuable.title
......
...@@ -15,4 +15,4 @@ ...@@ -15,4 +15,4 @@
= render partial: 'shared/milestones/issuable', = render partial: 'shared/milestones/issuable',
collection: issuables, collection: issuables,
as: :issuable, as: :issuable,
locals: { show_project_name: show_project_name, show_full_project_name: show_full_project_name } locals: { show_project_name: show_project_name }
- args = { show_project_name: local_assigns.fetch(:show_project_name, false), - args = { show_project_name: local_assigns.fetch(:show_project_name, false) }
show_full_project_name: local_assigns.fetch(:show_full_project_name, false) }
- if display_issues_count_warning?(@milestone) - if display_issues_count_warning?(@milestone)
.flash-container .flash-container
......
- args = { show_project_name: local_assigns.fetch(:show_project_name, false), - args = { show_project_name: local_assigns.fetch(:show_project_name, false) }
show_full_project_name: local_assigns.fetch(:show_full_project_name, false) }
.row.gl-mt-3 .row.gl-mt-3
.col-md-3 .col-md-3
......
- show_project_name = local_assigns.fetch(:show_project_name, false)
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
.fade-left= sprite_icon('chevron-lg-left', size: 12) .fade-left= sprite_icon('chevron-lg-left', size: 12)
.fade-right= sprite_icon('chevron-lg-right', size: 12) .fade-right= sprite_icon('chevron-lg-right', size: 12)
%ul.nav-links.scrolling-tabs.js-milestone-tabs.nav.nav-tabs %ul.nav-links.scrolling-tabs.js-milestone-tabs.nav.nav-tabs
%li.nav-item %li.nav-item
= link_to '#tab-issues', class: 'nav-link active', data: { toggle: 'tab', show: '.tab-issues-buttons' } do = link_to '#tab-issues', class: 'nav-link active', data: { toggle: 'tab', endpoint: milestone_tab_path(milestone, 'issues', show_project_name: show_project_name) } do
= _('Issues') = _('Issues')
%span.badge.badge-pill= milestone.issues_visible_to_user(current_user).size %span.badge.badge-pill= milestone.issues_visible_to_user(current_user).size
- if milestone.merge_requests_enabled? - if milestone.merge_requests_enabled?
%li.nav-item %li.nav-item
= link_to '#tab-merge-requests', class: 'nav-link', data: { toggle: 'tab', endpoint: milestone_tab_path(milestone, 'merge_requests') } do = link_to '#tab-merge-requests', class: 'nav-link', data: { toggle: 'tab', endpoint: milestone_tab_path(milestone, 'merge_requests', show_project_name: show_project_name) } do
= _('Merge Requests') = _('Merge Requests')
%span.badge.badge-pill= milestone.merge_requests_visible_to_user(current_user).size %span.badge.badge-pill= milestone.merge_requests_visible_to_user(current_user).size
%li.nav-item %li.nav-item
...@@ -20,20 +22,13 @@ ...@@ -20,20 +22,13 @@
= _('Labels') = _('Labels')
%span.badge.badge-pill= milestone.issue_labels_visible_by_user(current_user).count %span.badge.badge-pill= milestone.issue_labels_visible_by_user(current_user).count
- issues = milestone.sorted_issues(current_user)
- show_project_name = local_assigns.fetch(:show_project_name, false)
- show_full_project_name = local_assigns.fetch(:show_full_project_name, false)
.tab-content.milestone-content .tab-content.milestone-content
.tab-pane.active#tab-issues{ data: { sort_endpoint: (sort_issues_project_milestone_path(@project, @milestone) if @project && current_user) } } .tab-pane.active#tab-issues
= render 'shared/milestones/issues_tab', issues: issues, show_project_name: show_project_name, show_full_project_name: show_full_project_name = render "shared/milestones/tab_loading"
- if milestone.merge_requests_enabled? - if milestone.merge_requests_enabled?
.tab-pane#tab-merge-requests .tab-pane#tab-merge-requests
-# loaded async
= render "shared/milestones/tab_loading" = render "shared/milestones/tab_loading"
.tab-pane#tab-participants .tab-pane#tab-participants
-# loaded async
= render "shared/milestones/tab_loading" = render "shared/milestones/tab_loading"
.tab-pane#tab-labels .tab-pane#tab-labels
-# loaded async
= render "shared/milestones/tab_loading" = render "shared/milestones/tab_loading"
---
title: Load issues tab in the milestone page asynchronously
merge_request: 42473
author:
type: performance
...@@ -61,6 +61,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do ...@@ -61,6 +61,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
resources :milestones, constraints: { id: %r{[^/]+} } do resources :milestones, constraints: { id: %r{[^/]+} } do
member do member do
get :issues
get :merge_requests get :merge_requests
get :participants get :participants
get :labels get :labels
......
...@@ -161,8 +161,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -161,8 +161,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
resources :milestones, constraints: { id: /\d+/ } do resources :milestones, constraints: { id: /\d+/ } do
member do member do
post :promote post :promote
put :sort_issues get :issues
put :sort_merge_requests
get :merge_requests get :merge_requests
get :participants get :participants
get :labels get :labels
......
...@@ -9,7 +9,6 @@ RSpec.describe Groups::MilestonesController do ...@@ -9,7 +9,6 @@ RSpec.describe Groups::MilestonesController do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:title) { '肯定不是中文的问题' } let(:title) { '肯定不是中文的问题' }
let(:milestone) { create(:milestone, project: project) } let(:milestone) { create(:milestone, project: project) }
let(:milestone_path) { group_milestone_path(group, milestone.safe_title, title: milestone.title) }
let(:milestone_params) do let(:milestone_params) do
{ {
...@@ -25,6 +24,12 @@ RSpec.describe Groups::MilestonesController do ...@@ -25,6 +24,12 @@ RSpec.describe Groups::MilestonesController do
project.add_maintainer(user) project.add_maintainer(user)
end end
it_behaves_like 'milestone tabs' do
let(:milestone) { create(:milestone, group: group) }
let(:milestone_path) { group_milestone_path(group, milestone.iid) }
let(:request_params) { { group_id: group, id: milestone.iid } }
end
describe '#index' do describe '#index' do
describe 'as HTML' do describe 'as HTML' do
render_views render_views
......
...@@ -17,7 +17,9 @@ RSpec.describe Projects::MilestonesController do ...@@ -17,7 +17,9 @@ RSpec.describe Projects::MilestonesController do
controller.instance_variable_set(:@project, project) controller.instance_variable_set(:@project, project)
end end
it_behaves_like 'milestone tabs' it_behaves_like 'milestone tabs' do
let(:request_params) { { namespace_id: project.namespace, project_id: project, id: milestone.iid } }
end
describe "#show" do describe "#show" do
render_views render_views
......
...@@ -4,12 +4,16 @@ require 'spec_helper' ...@@ -4,12 +4,16 @@ require 'spec_helper'
RSpec.describe "User views milestone" do RSpec.describe "User views milestone" do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) } let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, :repository, group: group) }
let_it_be(:milestone) { create(:milestone, project: project) } let_it_be(:milestone) { create(:milestone, project: project) }
let_it_be(:labels) { create_list(:label, 2, project: project) } let_it_be(:labels) { create_list(:label, 2, project: project) }
before do before_all do
project.add_developer(user) project.add_developer(user)
end
before do
sign_in(user) sign_in(user)
end end
...@@ -25,7 +29,7 @@ RSpec.describe "User views milestone" do ...@@ -25,7 +29,7 @@ RSpec.describe "User views milestone" do
expect { visit_milestone }.not_to exceed_query_limit(control) expect { visit_milestone }.not_to exceed_query_limit(control)
end end
context 'limiting milestone issues' do context 'issues list', :js do
before_all do before_all do
2.times do 2.times do
create(:issue, milestone: milestone, project: project) create(:issue, milestone: milestone, project: project)
...@@ -34,6 +38,28 @@ RSpec.describe "User views milestone" do ...@@ -34,6 +38,28 @@ RSpec.describe "User views milestone" do
end end
end end
context 'for a project milestone' do
it 'does not show the project name' do
visit(project_milestone_path(project, milestone))
wait_for_requests
expect(page.find('#tab-issues')).not_to have_text(project.name)
end
end
context 'for a group milestone' do
let(:group_milestone) { create(:milestone, group: group) }
it 'shows the project name' do
create(:issue, project: project, milestone: group_milestone)
visit(group_milestone_path(group, group_milestone))
expect(page.find('#tab-issues')).to have_text(project.name)
end
end
context 'when issues on milestone are over DISPLAY_ISSUES_LIMIT' do context 'when issues on milestone are over DISPLAY_ISSUES_LIMIT' do
it "limits issues to display and shows warning" do it "limits issues to display and shows warning" do
stub_const('Milestoneish::DISPLAY_ISSUES_LIMIT', 3) stub_const('Milestoneish::DISPLAY_ISSUES_LIMIT', 3)
...@@ -56,6 +82,40 @@ RSpec.describe "User views milestone" do ...@@ -56,6 +82,40 @@ RSpec.describe "User views milestone" do
end end
end end
context 'merge requests list', :js do
context 'for a project milestone' do
it 'does not show the project name' do
create(:merge_request, source_project: project, milestone: milestone)
visit(project_milestone_path(project, milestone))
within('.js-milestone-tabs') do
click_link('Merge Requests')
end
wait_for_requests
expect(page.find('#tab-merge-requests')).not_to have_text(project.name)
end
end
context 'for a group milestone' do
let(:group_milestone) { create(:milestone, group: group) }
it 'shows the project name' do
create(:merge_request, source_project: project, milestone: group_milestone)
visit(group_milestone_path(group, group_milestone))
within('.js-milestone-tabs') do
click_link('Merge Requests')
end
expect(page.find('#tab-merge-requests')).to have_text(project.name)
end
end
end
private private
def visit_milestone def visit_milestone
......
...@@ -21,7 +21,7 @@ RSpec.describe "User views milestones" do ...@@ -21,7 +21,7 @@ RSpec.describe "User views milestones" do
.and have_content("Merge Requests") .and have_content("Merge Requests")
end end
context "with issues" do context "with issues", :js do
let_it_be(:issue) { create(:issue, project: project, milestone: milestone) } let_it_be(:issue) { create(:issue, project: project, milestone: milestone) }
let_it_be(:closed_issue) { create(:closed_issue, project: project, milestone: milestone) } let_it_be(:closed_issue) { create(:closed_issue, project: project, milestone: milestone) }
...@@ -33,7 +33,6 @@ RSpec.describe "User views milestones" do ...@@ -33,7 +33,6 @@ RSpec.describe "User views milestones" do
.and have_selector("#tab-issues li.issuable-row", count: 2) .and have_selector("#tab-issues li.issuable-row", count: 2)
.and have_content(issue.title) .and have_content(issue.title)
.and have_content(closed_issue.title) .and have_content(closed_issue.title)
.and have_selector("#tab-merge-requests")
end end
end end
......
...@@ -2,9 +2,28 @@ ...@@ -2,9 +2,28 @@
RSpec.shared_examples 'milestone tabs' do RSpec.shared_examples 'milestone tabs' do
def go(path, extra_params = {}) def go(path, extra_params = {})
params = { namespace_id: project.namespace.to_param, project_id: project, id: milestone.iid } get path, params: request_params.merge(extra_params)
end
describe '#issues' do
context 'as html' do
before do
go(:issues, format: 'html')
end
get path, params: params.merge(extra_params) it 'redirects to milestone#show' do
expect(response).to redirect_to(milestone_path)
end
end
context 'as json' do
it 'renders the issues tab template to a string' do
go(:issues, format: 'json')
expect(response).to render_template('shared/milestones/_issues_tab')
expect(json_response).to have_key('html')
end
end
end end
describe '#merge_requests' do describe '#merge_requests' do
......
...@@ -6,8 +6,7 @@ RSpec.describe 'shared/milestones/_issuables.html.haml' do ...@@ -6,8 +6,7 @@ RSpec.describe 'shared/milestones/_issuables.html.haml' do
let(:issuables_size) { 100 } let(:issuables_size) { 100 }
before do before do
allow(view).to receive_messages(title: nil, id: nil, show_project_name: nil, allow(view).to receive_messages(title: nil, id: nil, show_project_name: nil, dom_class: '',
show_full_project_name: nil, dom_class: '',
issuables: double(length: issuables_size).as_null_object) issuables: double(length: issuables_size).as_null_object)
stub_template 'shared/milestones/_issuable.html.haml' => '' stub_template 'shared/milestones/_issuable.html.haml' => ''
......
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