Commit 923172c9 authored by Fatih Acet's avatar Fatih Acet

Merge branch 'tz-mr-diff-performance' into 'master'

Performance Improvements to Vue MR page

Closes #48492

See merge request gitlab-org/gitlab-ce!20246
parents 3a3233a5 fb44cb3d
...@@ -145,7 +145,7 @@ export default { ...@@ -145,7 +145,7 @@ export default {
</script> </script>
<template> <template>
<div v-if="shouldShow"> <div v-show="shouldShow">
<div <div
v-if="isLoading" v-if="isLoading"
class="loading" class="loading"
......
...@@ -69,12 +69,23 @@ let { location } = window; ...@@ -69,12 +69,23 @@ let { location } = window;
export default class MergeRequestTabs { export default class MergeRequestTabs {
constructor({ action, setUrl, stubLocation } = {}) { constructor({ action, setUrl, stubLocation } = {}) {
const mergeRequestTabs = document.querySelector('.js-tabs-affix'); this.mergeRequestTabs = document.querySelector('.merge-request-tabs-container');
this.mergeRequestTabsAll =
this.mergeRequestTabs && this.mergeRequestTabs.querySelectorAll
? this.mergeRequestTabs.querySelectorAll('.merge-request-tabs li')
: null;
this.mergeRequestTabPanes = document.querySelector('#diff-notes-app');
this.mergeRequestTabPanesAll =
this.mergeRequestTabPanes && this.mergeRequestTabPanes.querySelectorAll
? this.mergeRequestTabPanes.querySelectorAll('.tab-pane')
: null;
const navbar = document.querySelector('.navbar-gitlab'); const navbar = document.querySelector('.navbar-gitlab');
const peek = document.getElementById('js-peek'); const peek = document.getElementById('js-peek');
const paddingTop = 16; const paddingTop = 16;
this.commitsTab = document.querySelector('.tab-content .commits.tab-pane'); this.commitsTab = document.querySelector('.tab-content .commits.tab-pane');
this.currentTab = null;
this.diffsLoaded = false; this.diffsLoaded = false;
this.pipelinesLoaded = false; this.pipelinesLoaded = false;
this.commitsLoaded = false; this.commitsLoaded = false;
...@@ -84,15 +95,15 @@ export default class MergeRequestTabs { ...@@ -84,15 +95,15 @@ export default class MergeRequestTabs {
this.setUrl = setUrl !== undefined ? setUrl : true; this.setUrl = setUrl !== undefined ? setUrl : true;
this.setCurrentAction = this.setCurrentAction.bind(this); this.setCurrentAction = this.setCurrentAction.bind(this);
this.tabShown = this.tabShown.bind(this); this.tabShown = this.tabShown.bind(this);
this.showTab = this.showTab.bind(this); this.clickTab = this.clickTab.bind(this);
this.stickyTop = navbar ? navbar.offsetHeight - paddingTop : 0; this.stickyTop = navbar ? navbar.offsetHeight - paddingTop : 0;
if (peek) { if (peek) {
this.stickyTop += peek.offsetHeight; this.stickyTop += peek.offsetHeight;
} }
if (mergeRequestTabs) { if (this.mergeRequestTabs) {
this.stickyTop += mergeRequestTabs.offsetHeight; this.stickyTop += this.mergeRequestTabs.offsetHeight;
} }
if (stubLocation) { if (stubLocation) {
...@@ -100,25 +111,22 @@ export default class MergeRequestTabs { ...@@ -100,25 +111,22 @@ export default class MergeRequestTabs {
} }
this.bindEvents(); this.bindEvents();
this.activateTab(action); if (
this.mergeRequestTabs &&
this.mergeRequestTabs.querySelector(`a[data-action='${action}']`) &&
this.mergeRequestTabs.querySelector(`a[data-action='${action}']`).click
)
this.mergeRequestTabs.querySelector(`a[data-action='${action}']`).click();
this.initAffix(); this.initAffix();
} }
bindEvents() { bindEvents() {
$(document) $('.merge-request-tabs a[data-toggle="tabvue"]').on('click', this.clickTab);
.on('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown)
.on('click', '.js-show-tab', this.showTab);
$('.merge-request-tabs a[data-toggle="tab"]').on('click', this.clickTab);
} }
// Used in tests // Used in tests
unbindEvents() { unbindEvents() {
$(document) $('.merge-request-tabs a[data-toggle="tabvue"]').off('click', this.clickTab);
.off('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown)
.off('click', '.js-show-tab', this.showTab);
$('.merge-request-tabs a[data-toggle="tab"]').off('click', this.clickTab);
} }
destroyPipelinesView() { destroyPipelinesView() {
...@@ -130,58 +138,87 @@ export default class MergeRequestTabs { ...@@ -130,58 +138,87 @@ export default class MergeRequestTabs {
} }
} }
showTab(e) {
e.preventDefault();
this.activateTab($(e.target).data('action'));
}
clickTab(e) { clickTab(e) {
if (e.currentTarget && isMetaClick(e)) { if (e.currentTarget) {
const targetLink = e.currentTarget.getAttribute('href');
e.stopImmediatePropagation(); e.stopImmediatePropagation();
e.preventDefault(); e.preventDefault();
window.open(targetLink, '_blank');
const { action } = e.currentTarget.dataset;
if (action) {
const href = e.currentTarget.getAttribute('href');
this.tabShown(action, href);
} else if (isMetaClick(e)) {
const targetLink = e.currentTarget.getAttribute('href');
window.open(targetLink, '_blank');
}
} }
} }
tabShown(e) { tabShown(action, href) {
const $target = $(e.target); if (action !== this.currentTab && this.mergeRequestTabs) {
const action = $target.data('action'); this.currentTab = action;
if (action === 'commits') { if (this.mergeRequestTabPanesAll) {
this.loadCommits($target.attr('href')); this.mergeRequestTabPanesAll.forEach(el => {
this.expandView(); const tabPane = el;
this.resetViewContainer(); tabPane.style.display = 'none';
this.destroyPipelinesView(); });
} else if (this.isDiffAction(action)) {
if (!isInVueNoteablePage()) {
this.loadDiff($target.attr('href'));
}
if (bp.getBreakpointSize() !== 'lg') {
this.shrinkView();
} }
if (this.diffViewType() === 'parallel') {
this.expandViewContainer(); if (this.mergeRequestTabsAll) {
this.mergeRequestTabsAll.forEach(el => {
el.classList.remove('active');
});
} }
this.destroyPipelinesView();
this.commitsTab.classList.remove('active'); const tabPane = this.mergeRequestTabPanes.querySelector(`#${action}`);
} else if (action === 'pipelines') { if (tabPane) tabPane.style.display = 'block';
this.resetViewContainer(); const tab = this.mergeRequestTabs.querySelector(`.${action}-tab`);
this.mountPipelinesView(); if (tab) tab.classList.add('active');
} else {
if (bp.getBreakpointSize() !== 'xs') { if (action === 'commits') {
this.loadCommits(href);
this.expandView();
this.resetViewContainer();
this.destroyPipelinesView();
} else if (action === 'new') {
this.expandView(); this.expandView();
this.resetViewContainer();
this.destroyPipelinesView();
} else if (this.isDiffAction(action)) {
if (!isInVueNoteablePage()) {
this.loadDiff(href);
}
if (bp.getBreakpointSize() !== 'lg') {
this.shrinkView();
}
if (this.diffViewType() === 'parallel') {
this.expandViewContainer();
}
this.destroyPipelinesView();
this.commitsTab.classList.remove('active');
} else if (action === 'pipelines') {
this.resetViewContainer();
this.mountPipelinesView();
} else {
this.mergeRequestTabPanes.querySelector('#notes').style.display = 'block';
this.mergeRequestTabs.querySelector('.notes-tab').classList.add('active');
if (bp.getBreakpointSize() !== 'xs') {
this.expandView();
}
this.resetViewContainer();
this.destroyPipelinesView();
initDiscussionTab();
}
if (this.setUrl) {
this.setCurrentAction(action);
} }
this.resetViewContainer();
this.destroyPipelinesView();
initDiscussionTab(); this.eventHub.$emit('MergeRequestTabChange', this.getCurrentAction());
}
if (this.setUrl) {
this.setCurrentAction(action);
} }
this.eventHub.$emit('MergeRequestTabChange', this.getCurrentAction());
} }
scrollToElement(container) { scrollToElement(container) {
...@@ -194,12 +231,6 @@ export default class MergeRequestTabs { ...@@ -194,12 +231,6 @@ export default class MergeRequestTabs {
} }
} }
// Activate a tab based on the current action
activateTab(action) {
// important note: the .tab('show') method triggers 'shown.bs.tab' event itself
$(`.merge-request-tabs a[data-action='${action}']`).tab('show');
}
// Replaces the current Merge Request-specific action in the URL with a new one // Replaces the current Merge Request-specific action in the URL with a new one
// //
// If the action is "notes", the URL is reset to the standard // If the action is "notes", the URL is reset to the standard
......
...@@ -175,7 +175,7 @@ export default { ...@@ -175,7 +175,7 @@ export default {
<template> <template>
<div <div
v-if="shouldShow" v-show="shouldShow"
id="notes" id="notes"
> >
<ul <ul
......
...@@ -108,7 +108,7 @@ module MergeRequestsHelper ...@@ -108,7 +108,7 @@ module MergeRequestsHelper
data_attrs = { data_attrs = {
action: tab.to_s, action: tab.to_s,
target: "##{tab}", target: "##{tab}",
toggle: options.fetch(:force_link, false) ? '' : 'tab' toggle: options.fetch(:force_link, false) ? '' : 'tabvue'
} }
url = case tab url = case tab
......
...@@ -24,23 +24,28 @@ ...@@ -24,23 +24,28 @@
There are no commits yet. There are no commits yet.
= custom_icon ('illustration_no_commits') = custom_icon ('illustration_no_commits')
- else - else
%ul.merge-request-tabs.nav.nav-tabs.nav-links.no-top.no-bottom .merge-request-tabs-holder{ class: ("js-tabs-affix" unless ENV['RAILS_ENV'] == 'test') }
%li.commits-tab .merge-request-tabs-container
= link_to url_for(safe_params), data: {target: 'div#commits', action: 'new', toggle: 'tab'} do .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
Commits .fade-left= icon('angle-left')
%span.badge.badge-pill= @commits.size .fade-right= icon('angle-right')
- if @pipelines.any? %ul.merge-request-tabs.nav.nav-tabs.nav-links.no-top.no-bottom.js-tabs-affix
%li.builds-tab %li.commits-tab.new-tab
= link_to url_for(safe_params.merge(action: 'pipelines')), data: {target: 'div#pipelines', action: 'pipelines', toggle: 'tab'} do = link_to url_for(safe_params), data: {target: 'div#commits', action: 'new', toggle: 'tabvue'} do
Pipelines Commits
%span.badge.badge-pill= @pipelines.size %span.badge.badge-pill= @commits.size
%li.diffs-tab - if @pipelines.any?
= link_to url_for(safe_params.merge(action: 'diffs')), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do %li.builds-tab
Changes = link_to url_for(safe_params.merge(action: 'pipelines')), data: {target: 'div#pipelines', action: 'pipelines', toggle: 'tabvue'} do
%span.badge.badge-pill= @merge_request.diff_size Pipelines
%span.badge.badge-pill= @pipelines.size
%li.diffs-tab
= link_to url_for(safe_params.merge(action: 'diffs')), data: {target: 'div#diffs', action: 'diffs', toggle: 'tabvue'} do
Changes
%span.badge.badge-pill= @merge_request.diff_size
.tab-content #diff-notes-app.tab-content
#commits.commits.tab-pane.active #new.commits.tab-pane.active
= render "projects/merge_requests/commits" = render "projects/merge_requests/commits"
#diffs.diffs.tab-pane #diffs.diffs.tab-pane
-# This tab is always loaded via AJAX -# This tab is always loaded via AJAX
......
...@@ -54,7 +54,7 @@ describe MergeRequestsHelper do ...@@ -54,7 +54,7 @@ describe MergeRequestsHelper do
let(:options) { { force_link: true } } let(:options) { { force_link: true } }
it 'removes the data-toggle attributes' do it 'removes the data-toggle attributes' do
is_expected.not_to match(/data-toggle="tab"/) is_expected.not_to match(/data-toggle="tabvue"/)
end end
end end
end end
......
...@@ -5,12 +5,16 @@ import { userDataMock, notesDataMock, noteableDataMock } from '../notes/mock_dat ...@@ -5,12 +5,16 @@ import { userDataMock, notesDataMock, noteableDataMock } from '../notes/mock_dat
import diffFileMockData from '../diffs/mock_data/diff_file'; import diffFileMockData from '../diffs/mock_data/diff_file';
export default function initVueMRPage() { export default function initVueMRPage() {
const mrTestEl = document.createElement('div');
mrTestEl.className = 'js-merge-request-test';
document.body.appendChild(mrTestEl);
const diffsAppEndpoint = '/diffs/app/endpoint'; const diffsAppEndpoint = '/diffs/app/endpoint';
const diffsAppProjectPath = 'testproject'; const diffsAppProjectPath = 'testproject';
const mrEl = document.createElement('div'); const mrEl = document.createElement('div');
mrEl.className = 'merge-request fixture-mr'; mrEl.className = 'merge-request fixture-mr';
mrEl.setAttribute('data-mr-action', 'diffs'); mrEl.setAttribute('data-mr-action', 'diffs');
document.body.appendChild(mrEl); mrTestEl.appendChild(mrEl);
const mrDiscussionsEl = document.createElement('div'); const mrDiscussionsEl = document.createElement('div');
mrDiscussionsEl.id = 'js-vue-mr-discussions'; mrDiscussionsEl.id = 'js-vue-mr-discussions';
...@@ -18,18 +22,18 @@ export default function initVueMRPage() { ...@@ -18,18 +22,18 @@ export default function initVueMRPage() {
mrDiscussionsEl.setAttribute('data-noteable-data', JSON.stringify(noteableDataMock)); mrDiscussionsEl.setAttribute('data-noteable-data', JSON.stringify(noteableDataMock));
mrDiscussionsEl.setAttribute('data-notes-data', JSON.stringify(notesDataMock)); mrDiscussionsEl.setAttribute('data-notes-data', JSON.stringify(notesDataMock));
mrDiscussionsEl.setAttribute('data-noteable-type', 'merge-request'); mrDiscussionsEl.setAttribute('data-noteable-type', 'merge-request');
document.body.appendChild(mrDiscussionsEl); mrTestEl.appendChild(mrDiscussionsEl);
const discussionCounterEl = document.createElement('div'); const discussionCounterEl = document.createElement('div');
discussionCounterEl.id = 'js-vue-discussion-counter'; discussionCounterEl.id = 'js-vue-discussion-counter';
document.body.appendChild(discussionCounterEl); mrTestEl.appendChild(discussionCounterEl);
const diffsAppEl = document.createElement('div'); const diffsAppEl = document.createElement('div');
diffsAppEl.id = 'js-diffs-app'; diffsAppEl.id = 'js-diffs-app';
diffsAppEl.setAttribute('data-endpoint', diffsAppEndpoint); diffsAppEl.setAttribute('data-endpoint', diffsAppEndpoint);
diffsAppEl.setAttribute('data-project-path', diffsAppProjectPath); diffsAppEl.setAttribute('data-project-path', diffsAppProjectPath);
diffsAppEl.setAttribute('data-current-user-data', JSON.stringify(userDataMock)); diffsAppEl.setAttribute('data-current-user-data', JSON.stringify(userDataMock));
document.body.appendChild(diffsAppEl); mrTestEl.appendChild(diffsAppEl);
const mock = new MockAdapter(axios); const mock = new MockAdapter(axios);
mock.onGet(diffsAppEndpoint).reply(200, { mock.onGet(diffsAppEndpoint).reply(200, {
......
...@@ -19,9 +19,11 @@ import IssuablesHelper from '~/helpers/issuables_helper'; ...@@ -19,9 +19,11 @@ import IssuablesHelper from '~/helpers/issuables_helper';
spyOn(axios, 'patch').and.callThrough(); spyOn(axios, 'patch').and.callThrough();
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
mock.onPatch(`${gl.TEST_HOST}/frontend-fixtures/merge-requests-project/merge_requests/1.json`).reply(200, {}); mock
.onPatch(`${gl.TEST_HOST}/frontend-fixtures/merge-requests-project/merge_requests/1.json`)
.reply(200, {});
return this.merge = new MergeRequest(); return (this.merge = new MergeRequest());
}); });
afterEach(() => { afterEach(() => {
...@@ -32,17 +34,22 @@ import IssuablesHelper from '~/helpers/issuables_helper'; ...@@ -32,17 +34,22 @@ import IssuablesHelper from '~/helpers/issuables_helper';
spyOn($, 'ajax').and.stub(); spyOn($, 'ajax').and.stub();
const changeEvent = document.createEvent('HTMLEvents'); const changeEvent = document.createEvent('HTMLEvents');
changeEvent.initEvent('change', true, true); changeEvent.initEvent('change', true, true);
$('input[type=checkbox]').attr('checked', true)[0].dispatchEvent(changeEvent); $('input[type=checkbox]')
.attr('checked', true)[0]
.dispatchEvent(changeEvent);
return expect($('.js-task-list-field').val()).toBe('- [x] Task List Item'); return expect($('.js-task-list-field').val()).toBe('- [x] Task List Item');
}); });
it('submits an ajax request on tasklist:changed', (done) => { it('submits an ajax request on tasklist:changed', done => {
$('.js-task-list-field').trigger('tasklist:changed'); $('.js-task-list-field').trigger('tasklist:changed');
setTimeout(() => { setTimeout(() => {
expect(axios.patch).toHaveBeenCalledWith(`${gl.TEST_HOST}/frontend-fixtures/merge-requests-project/merge_requests/1.json`, { expect(axios.patch).toHaveBeenCalledWith(
merge_request: { description: '- [ ] Task List Item' }, `${gl.TEST_HOST}/frontend-fixtures/merge-requests-project/merge_requests/1.json`,
}); {
merge_request: { description: '- [ ] Task List Item' },
},
);
done(); done();
}); });
}); });
...@@ -119,4 +126,4 @@ import IssuablesHelper from '~/helpers/issuables_helper'; ...@@ -119,4 +126,4 @@ import IssuablesHelper from '~/helpers/issuables_helper';
}); });
}); });
}); });
}).call(window); }.call(window));
...@@ -40,6 +40,7 @@ describe('MergeRequestTabs', function() { ...@@ -40,6 +40,7 @@ describe('MergeRequestTabs', function() {
this.class.unbindEvents(); this.class.unbindEvents();
this.class.destroyPipelinesView(); this.class.destroyPipelinesView();
mrPageMock.restore(); mrPageMock.restore();
$('.js-merge-request-test').remove();
}); });
describe('opensInNewTab', function() { describe('opensInNewTab', function() {
......
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