Commit 43a6b985 authored by Simon Knox's avatar Simon Knox

Merge branch '343000-lazy-load-first-batch' into 'master'

Request visible batch of commit data first when lazy loading

See merge request gitlab-org/gitlab!75609
parents 4ae8feda 6f5f68e2
...@@ -52,14 +52,9 @@ export const loadCommits = async (projectPath, path, ref, offset) => { ...@@ -52,14 +52,9 @@ export const loadCommits = async (projectPath, path, ref, offset) => {
} }
// We fetch in batches of 25, so this ensures we don't refetch // We fetch in batches of 25, so this ensures we don't refetch
Array.from(Array(COMMIT_BATCH_SIZE)).forEach((_, i) => { Array.from(Array(COMMIT_BATCH_SIZE)).forEach((_, i) => addRequestedOffset(offset + i));
addRequestedOffset(offset - i);
addRequestedOffset(offset + i);
});
// Since a user could scroll either up or down, we want to support lazy loading in both directions const commits = await fetchData(projectPath, path, ref, offset);
const commitsBatchUp = await fetchData(projectPath, path, ref, offset - COMMIT_BATCH_SIZE);
const commitsBatchDown = await fetchData(projectPath, path, ref, offset);
return commitsBatchUp.concat(commitsBatchDown); return commits;
}; };
...@@ -13,7 +13,7 @@ import { ...@@ -13,7 +13,7 @@ import {
import { escapeRegExp } from 'lodash'; import { escapeRegExp } from 'lodash';
import paginatedTreeQuery from 'shared_queries/repository/paginated_tree.query.graphql'; import paginatedTreeQuery from 'shared_queries/repository/paginated_tree.query.graphql';
import { escapeFileUrl } from '~/lib/utils/url_utility'; import { escapeFileUrl } from '~/lib/utils/url_utility';
import { TREE_PAGE_SIZE } from '~/repository/constants'; import { TREE_PAGE_SIZE, ROW_APPEAR_DELAY } from '~/repository/constants';
import FileIcon from '~/vue_shared/components/file_icon.vue'; import FileIcon from '~/vue_shared/components/file_icon.vue';
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
...@@ -128,6 +128,7 @@ export default { ...@@ -128,6 +128,7 @@ export default {
return { return {
commit: null, commit: null,
hasRowAppeared: false, hasRowAppeared: false,
delayedRowAppear: null,
}; };
}, },
computed: { computed: {
...@@ -202,14 +203,19 @@ export default { ...@@ -202,14 +203,19 @@ export default {
rowAppeared() { rowAppeared() {
this.hasRowAppeared = true; this.hasRowAppeared = true;
if (this.commitInfo) {
return;
}
if (this.glFeatures.lazyLoadCommits) { if (this.glFeatures.lazyLoadCommits) {
this.$emit('row-appear', { this.delayedRowAppear = setTimeout(
rowNumber: this.rowNumber, () => this.$emit('row-appear', this.rowNumber),
hasCommit: Boolean(this.commitInfo), ROW_APPEAR_DELAY,
}); );
} }
}, },
rowDisappeared() { rowDisappeared() {
clearTimeout(this.delayedRowAppear);
this.hasRowAppeared = false; this.hasRowAppeared = false;
}, },
}, },
......
...@@ -3,7 +3,12 @@ import paginatedTreeQuery from 'shared_queries/repository/paginated_tree.query.g ...@@ -3,7 +3,12 @@ import paginatedTreeQuery from 'shared_queries/repository/paginated_tree.query.g
import createFlash from '~/flash'; import createFlash from '~/flash';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { __ } from '../../locale'; import { __ } from '../../locale';
import { TREE_PAGE_SIZE, TREE_INITIAL_FETCH_COUNT, TREE_PAGE_LIMIT } from '../constants'; import {
TREE_PAGE_SIZE,
TREE_INITIAL_FETCH_COUNT,
TREE_PAGE_LIMIT,
COMMIT_BATCH_SIZE,
} from '../constants';
import getRefMixin from '../mixins/get_ref'; import getRefMixin from '../mixins/get_ref';
import projectPathQuery from '../queries/project_path.query.graphql'; import projectPathQuery from '../queries/project_path.query.graphql';
import { readmeFile } from '../utils/readme'; import { readmeFile } from '../utils/readme';
...@@ -151,11 +156,19 @@ export default { ...@@ -151,11 +156,19 @@ export default {
.concat(data.trees.pageInfo, data.submodules.pageInfo, data.blobs.pageInfo) .concat(data.trees.pageInfo, data.submodules.pageInfo, data.blobs.pageInfo)
.find(({ hasNextPage }) => hasNextPage); .find(({ hasNextPage }) => hasNextPage);
}, },
loadCommitData({ rowNumber = 0, hasCommit } = {}) { handleRowAppear(rowNumber) {
if (!this.glFeatures.lazyLoadCommits || hasCommit || isRequested(rowNumber)) { if (!this.glFeatures.lazyLoadCommits || isRequested(rowNumber)) {
return; return;
} }
// Since a user could scroll either up or down, we want to support lazy loading in both directions
this.loadCommitData(rowNumber);
if (rowNumber - COMMIT_BATCH_SIZE >= 0) {
this.loadCommitData(rowNumber - COMMIT_BATCH_SIZE);
}
},
loadCommitData(rowNumber) {
loadCommits(this.projectPath, this.path, this.ref, rowNumber) loadCommits(this.projectPath, this.path, this.ref, rowNumber)
.then(this.setCommitData) .then(this.setCommitData)
.catch(() => {}); .catch(() => {});
...@@ -182,7 +195,7 @@ export default { ...@@ -182,7 +195,7 @@ export default {
:has-more="hasShowMore" :has-more="hasShowMore"
:commits="commits" :commits="commits"
@showMore="handleShowMore" @showMore="handleShowMore"
@row-appear="loadCommitData" @row-appear="handleRowAppear"
/> />
<file-preview v-if="readme" :blob="readme" /> <file-preview v-if="readme" :blob="readme" />
</div> </div>
......
...@@ -23,3 +23,5 @@ export const I18N_COMMIT_DATA_FETCH_ERROR = __('An error occurred while fetching ...@@ -23,3 +23,5 @@ export const I18N_COMMIT_DATA_FETCH_ERROR = __('An error occurred while fetching
export const PDF_MAX_FILE_SIZE = 10000000; // 10 MB export const PDF_MAX_FILE_SIZE = 10000000; // 10 MB
export const PDF_MAX_PAGE_LIMIT = 50; export const PDF_MAX_PAGE_LIMIT = 50;
export const ROW_APPEAR_DELAY = 150;
...@@ -52,13 +52,6 @@ describe('commits service', () => { ...@@ -52,13 +52,6 @@ describe('commits service', () => {
expect(axios.get.mock.calls.length).toEqual(1); expect(axios.get.mock.calls.length).toEqual(1);
}); });
it('calls axios get twice if an offset is larger than 25', async () => {
await requestCommits(100);
expect(axios.get.mock.calls[0][1]).toEqual({ params: { format: 'json', offset: 75 } });
expect(axios.get.mock.calls[1][1]).toEqual({ params: { format: 'json', offset: 100 } });
});
it('updates the list of requested offsets', async () => { it('updates the list of requested offsets', async () => {
await requestCommits(200); await requestCommits(200);
......
...@@ -4,6 +4,7 @@ import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; ...@@ -4,6 +4,7 @@ import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import TableRow from '~/repository/components/table/row.vue'; import TableRow from '~/repository/components/table/row.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue'; import FileIcon from '~/vue_shared/components/file_icon.vue';
import { FILE_SYMLINK_MODE } from '~/vue_shared/constants'; import { FILE_SYMLINK_MODE } from '~/vue_shared/constants';
import { ROW_APPEAR_DELAY } from '~/repository/constants';
const COMMIT_MOCK = { lockLabel: 'Locked by Root', committedDate: '2019-01-01' }; const COMMIT_MOCK = { lockLabel: 'Locked by Root', committedDate: '2019-01-01' };
...@@ -17,12 +18,12 @@ function factory(propsData = {}) { ...@@ -17,12 +18,12 @@ function factory(propsData = {}) {
vm = shallowMount(TableRow, { vm = shallowMount(TableRow, {
propsData: { propsData: {
commitInfo: COMMIT_MOCK,
...propsData, ...propsData,
name: propsData.path, name: propsData.path,
projectPath: 'gitlab-org/gitlab-ce', projectPath: 'gitlab-org/gitlab-ce',
url: `https://test.com`, url: `https://test.com`,
totalEntries: 10, totalEntries: 10,
commitInfo: COMMIT_MOCK,
rowNumber: 123, rowNumber: 123,
}, },
directives: { directives: {
...@@ -251,6 +252,8 @@ describe('Repository table row component', () => { ...@@ -251,6 +252,8 @@ describe('Repository table row component', () => {
}); });
describe('row visibility', () => { describe('row visibility', () => {
beforeAll(() => jest.useFakeTimers());
beforeEach(() => { beforeEach(() => {
factory({ factory({
id: '1', id: '1',
...@@ -258,18 +261,20 @@ describe('Repository table row component', () => { ...@@ -258,18 +261,20 @@ describe('Repository table row component', () => {
path: 'test', path: 'test',
type: 'tree', type: 'tree',
currentPath: '/', currentPath: '/',
commitInfo: null,
}); });
}); });
it('emits a `row-appear` event', () => {
afterAll(() => jest.useRealTimers());
it('emits a `row-appear` event', async () => {
findIntersectionObserver().vm.$emit('appear'); findIntersectionObserver().vm.$emit('appear');
expect(vm.emitted('row-appear')).toEqual([
[ jest.runAllTimers();
{
hasCommit: true, expect(setTimeout).toHaveBeenCalledTimes(1);
rowNumber: 123, expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), ROW_APPEAR_DELAY);
}, expect(vm.emitted('row-appear')).toEqual([[123]]);
],
]);
}); });
}); });
}); });
...@@ -190,14 +190,28 @@ describe('Repository table component', () => { ...@@ -190,14 +190,28 @@ describe('Repository table component', () => {
}); });
}); });
it('loads commit data when row-appear event is emitted', () => { describe('commit data', () => {
const path = 'some/path'; const path = 'some/path';
const rowNumber = 1;
factory(path); it('loads commit data for both top and bottom batches when row-appear event is emitted', () => {
findFileTable().vm.$emit('row-appear', { hasCommit: false, rowNumber }); const rowNumber = 50;
expect(isRequested).toHaveBeenCalledWith(rowNumber); factory(path);
expect(loadCommits).toHaveBeenCalledWith('', path, '', rowNumber); findFileTable().vm.$emit('row-appear', rowNumber);
expect(isRequested).toHaveBeenCalledWith(rowNumber);
expect(loadCommits.mock.calls).toEqual([
['', path, '', rowNumber],
['', path, '', rowNumber - 25],
]);
});
it('loads commit data once if rowNumber is zero', () => {
factory(path);
findFileTable().vm.$emit('row-appear', 0);
expect(loadCommits.mock.calls).toEqual([['', path, '', 0]]);
});
}); });
}); });
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