Commit 7b077bef authored by Phil Hughes's avatar Phil Hughes

Merge branch '268225-defer0-components' into 'master'

Defer loading non-essential components

See merge request gitlab-org/gitlab!47029
parents 7a752db3 e3fc5c9e
<script> <script>
import { mapActions, mapGetters, mapState } from 'vuex'; import { mapActions, mapGetters, mapState } from 'vuex';
import { GlButton, GlLoadingIcon } from '@gitlab/ui';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { import {
WEBIDE_MARK_APP_START, WEBIDE_MARK_APP_START,
...@@ -14,15 +13,8 @@ import { ...@@ -14,15 +13,8 @@ import {
import { performanceMarkAndMeasure } from '~/performance/utils'; import { performanceMarkAndMeasure } from '~/performance/utils';
import { modalTypes } from '../constants'; import { modalTypes } from '../constants';
import eventHub from '../eventhub'; import eventHub from '../eventhub';
import FindFile from '~/vue_shared/components/file_finder/index.vue';
import NewModal from './new_dropdown/modal.vue';
import IdeSidebar from './ide_side_bar.vue'; import IdeSidebar from './ide_side_bar.vue';
import RepoTabs from './repo_tabs.vue';
import IdeStatusBar from './ide_status_bar.vue';
import RepoEditor from './repo_editor.vue'; import RepoEditor from './repo_editor.vue';
import RightPane from './panes/right.vue';
import ErrorMessage from './error_message.vue';
import CommitEditorHeader from './commit_sidebar/editor_header.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { measurePerformance } from '../utils'; import { measurePerformance } from '../utils';
...@@ -43,19 +35,24 @@ eventHub.$on(WEBIDE_MEASURE_FILE_AFTER_INTERACTION, () => ...@@ -43,19 +35,24 @@ eventHub.$on(WEBIDE_MEASURE_FILE_AFTER_INTERACTION, () =>
export default { export default {
components: { components: {
NewModal,
IdeSidebar, IdeSidebar,
RepoTabs,
IdeStatusBar,
RepoEditor, RepoEditor,
FindFile, 'error-message': () => import('./error_message.vue'),
ErrorMessage, 'gl-button': () => import('@gitlab/ui/src/components/base/button/button.vue'),
CommitEditorHeader, 'gl-loading-icon': () => import('@gitlab/ui/src/components/base/loading_icon/loading_icon.vue'),
GlButton, 'commit-editor-header': () => import('./commit_sidebar/editor_header.vue'),
GlLoadingIcon, 'repo-tabs': () => import('./repo_tabs.vue'),
RightPane, 'ide-status-bar': () => import('./ide_status_bar.vue'),
'find-file': () => import('~/vue_shared/components/file_finder/index.vue'),
'right-pane': () => import('./panes/right.vue'),
'new-modal': () => import('./new_dropdown/modal.vue'),
}, },
mixins: [glFeatureFlagsMixin()], mixins: [glFeatureFlagsMixin()],
data() {
return {
loadDeferred: false,
};
},
computed: { computed: {
...mapState([ ...mapState([
'openFiles', 'openFiles',
...@@ -107,6 +104,9 @@ export default { ...@@ -107,6 +104,9 @@ export default {
createNewFile() { createNewFile() {
this.$refs.newModal.open(modalTypes.blob); this.$refs.newModal.open(modalTypes.blob);
}, },
loadDeferredComponents() {
this.loadDeferred = true;
},
}, },
}; };
</script> </script>
...@@ -118,6 +118,7 @@ export default { ...@@ -118,6 +118,7 @@ export default {
> >
<error-message v-if="errorMessage" :message="errorMessage" /> <error-message v-if="errorMessage" :message="errorMessage" />
<div class="ide-view flex-grow d-flex"> <div class="ide-view flex-grow d-flex">
<template v-if="loadDeferred">
<find-file <find-file
v-show="fileFindVisible" v-show="fileFindVisible"
:files="allBlobs" :files="allBlobs"
...@@ -126,11 +127,14 @@ export default { ...@@ -126,11 +127,14 @@ export default {
@toggle="toggleFileFinder" @toggle="toggleFileFinder"
@click="openFile" @click="openFile"
/> />
<ide-sidebar /> </template>
<ide-sidebar @tree-ready="loadDeferredComponents" />
<div class="multi-file-edit-pane"> <div class="multi-file-edit-pane">
<template v-if="activeFile"> <template v-if="activeFile">
<template v-if="loadDeferred">
<commit-editor-header v-if="isCommitModeActive" :active-file="activeFile" /> <commit-editor-header v-if="isCommitModeActive" :active-file="activeFile" />
<repo-tabs v-else :active-file="activeFile" :files="openFiles" :viewer="viewer" /> <repo-tabs v-else :active-file="activeFile" :files="openFiles" :viewer="viewer" />
</template>
<repo-editor :file="activeFile" class="multi-file-edit-pane-content" /> <repo-editor :file="activeFile" class="multi-file-edit-pane-content" />
</template> </template>
<template v-else> <template v-else>
...@@ -177,9 +181,13 @@ export default { ...@@ -177,9 +181,13 @@ export default {
</div> </div>
</template> </template>
</div> </div>
<template v-if="loadDeferred">
<right-pane v-if="currentProjectId" /> <right-pane v-if="currentProjectId" />
</template>
</div> </div>
<template v-if="loadDeferred">
<ide-status-bar /> <ide-status-bar />
<new-modal ref="newModal" /> <new-modal ref="newModal" />
</template>
</article> </article>
</template> </template>
...@@ -4,21 +4,19 @@ import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui'; ...@@ -4,21 +4,19 @@ import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import IdeTree from './ide_tree.vue'; import IdeTree from './ide_tree.vue';
import ResizablePanel from './resizable_panel.vue'; import ResizablePanel from './resizable_panel.vue';
import ActivityBar from './activity_bar.vue'; import ActivityBar from './activity_bar.vue';
import RepoCommitSection from './repo_commit_section.vue';
import CommitForm from './commit_sidebar/form.vue'; import CommitForm from './commit_sidebar/form.vue';
import IdeReview from './ide_review.vue';
import IdeProjectHeader from './ide_project_header.vue'; import IdeProjectHeader from './ide_project_header.vue';
import { SIDEBAR_INIT_WIDTH } from '../constants'; import { SIDEBAR_INIT_WIDTH, leftSidebarViews } from '../constants';
export default { export default {
components: { components: {
GlSkeletonLoading, GlSkeletonLoading,
ResizablePanel, ResizablePanel,
ActivityBar, ActivityBar,
RepoCommitSection,
IdeTree, IdeTree,
[leftSidebarViews.review.name]: () => import('./ide_review.vue'),
[leftSidebarViews.commit.name]: () => import('./repo_commit_section.vue'),
CommitForm, CommitForm,
IdeReview,
IdeProjectHeader, IdeProjectHeader,
}, },
computed: { computed: {
...@@ -49,7 +47,7 @@ export default { ...@@ -49,7 +47,7 @@ export default {
<div class="multi-file-commit-panel-inner" data-testid="ide-side-bar-inner"> <div class="multi-file-commit-panel-inner" data-testid="ide-side-bar-inner">
<div class="multi-file-commit-panel-inner-content"> <div class="multi-file-commit-panel-inner-content">
<keep-alive> <keep-alive>
<component :is="currentActivityView" /> <component :is="currentActivityView" @tree-ready="$emit('tree-ready')" />
</keep-alive> </keep-alive>
</div> </div>
<commit-form /> <commit-form />
......
...@@ -51,7 +51,7 @@ export default { ...@@ -51,7 +51,7 @@ export default {
</script> </script>
<template> <template>
<ide-tree-list> <ide-tree-list @tree-ready="$emit('tree-ready')">
<template #header> <template #header>
{{ __('Edit') }} {{ __('Edit') }}
<div class="ide-tree-actions ml-auto d-flex" data-testid="ide-root-actions"> <div class="ide-tree-actions ml-auto d-flex" data-testid="ide-root-actions">
......
...@@ -32,6 +32,13 @@ export default { ...@@ -32,6 +32,13 @@ export default {
return !this.currentTree || this.currentTree.loading; return !this.currentTree || this.currentTree.loading;
}, },
}, },
watch: {
showLoading(newVal) {
if (!newVal) {
this.$emit('tree-ready');
}
},
},
beforeCreate() { beforeCreate() {
performanceMarkAndMeasure({ mark: WEBIDE_MARK_TREE_START }); performanceMarkAndMeasure({ mark: WEBIDE_MARK_TREE_START });
}, },
......
import { mount, createLocalVue } from '@vue/test-utils'; import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex'; import Vuex from 'vuex';
import { GlSkeletonLoading } from '@gitlab/ui'; import { GlSkeletonLoading } from '@gitlab/ui';
import waitForPromises from 'helpers/wait_for_promises';
import { createStore } from '~/ide/stores'; import { createStore } from '~/ide/stores';
import IdeSidebar from '~/ide/components/ide_side_bar.vue'; import IdeSidebar from '~/ide/components/ide_side_bar.vue';
import IdeTree from '~/ide/components/ide_tree.vue'; import IdeTree from '~/ide/components/ide_tree.vue';
import RepoCommitSection from '~/ide/components/repo_commit_section.vue'; import RepoCommitSection from '~/ide/components/repo_commit_section.vue';
import IdeReview from '~/ide/components/ide_review.vue';
import { leftSidebarViews } from '~/ide/constants'; import { leftSidebarViews } from '~/ide/constants';
import { projectData } from '../mock_data'; import { projectData } from '../mock_data';
...@@ -15,11 +17,12 @@ describe('IdeSidebar', () => { ...@@ -15,11 +17,12 @@ describe('IdeSidebar', () => {
let wrapper; let wrapper;
let store; let store;
function createComponent() { function createComponent({ view = leftSidebarViews.edit.name } = {}) {
store = createStore(); store = createStore();
store.state.currentProjectId = 'abcproject'; store.state.currentProjectId = 'abcproject';
store.state.projects.abcproject = projectData; store.state.projects.abcproject = projectData;
store.state.currentActivityView = view;
return mount(IdeSidebar, { return mount(IdeSidebar, {
store, store,
...@@ -48,22 +51,46 @@ describe('IdeSidebar', () => { ...@@ -48,22 +51,46 @@ describe('IdeSidebar', () => {
expect(wrapper.findAll(GlSkeletonLoading)).toHaveLength(3); expect(wrapper.findAll(GlSkeletonLoading)).toHaveLength(3);
}); });
describe('activityBarComponent', () => { describe('deferred rendering components', () => {
it('renders tree component', () => { it('fetches components on demand', async () => {
wrapper = createComponent(); wrapper = createComponent();
expect(wrapper.find(IdeTree).exists()).toBe(true); expect(wrapper.find(IdeTree).exists()).toBe(true);
}); expect(wrapper.find(IdeReview).exists()).toBe(false);
expect(wrapper.find(RepoCommitSection).exists()).toBe(false);
it('renders commit component', async () => { store.state.currentActivityView = leftSidebarViews.review.name;
wrapper = createComponent(); await waitForPromises();
await wrapper.vm.$nextTick();
store.state.currentActivityView = leftSidebarViews.commit.name; expect(wrapper.find(IdeTree).exists()).toBe(false);
expect(wrapper.find(IdeReview).exists()).toBe(true);
expect(wrapper.find(RepoCommitSection).exists()).toBe(false);
store.state.currentActivityView = leftSidebarViews.commit.name;
await waitForPromises();
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
expect(wrapper.find(IdeTree).exists()).toBe(false);
expect(wrapper.find(IdeReview).exists()).toBe(false);
expect(wrapper.find(RepoCommitSection).exists()).toBe(true); expect(wrapper.find(RepoCommitSection).exists()).toBe(true);
}); });
it.each`
view | tree | review | commit
${leftSidebarViews.edit.name} | ${true} | ${false} | ${false}
${leftSidebarViews.review.name} | ${false} | ${true} | ${false}
${leftSidebarViews.commit.name} | ${false} | ${false} | ${true}
`('renders correct panels for $view', async ({ view, tree, review, commit } = {}) => {
wrapper = createComponent({
view,
});
await waitForPromises();
await wrapper.vm.$nextTick();
expect(wrapper.find(IdeTree).exists()).toBe(tree);
expect(wrapper.find(IdeReview).exists()).toBe(review);
expect(wrapper.find(RepoCommitSection).exists()).toBe(commit);
});
}); });
it('keeps the current activity view components alive', async () => { it('keeps the current activity view components alive', async () => {
...@@ -72,7 +99,7 @@ describe('IdeSidebar', () => { ...@@ -72,7 +99,7 @@ describe('IdeSidebar', () => {
const ideTreeComponent = wrapper.find(IdeTree).element; const ideTreeComponent = wrapper.find(IdeTree).element;
store.state.currentActivityView = leftSidebarViews.commit.name; store.state.currentActivityView = leftSidebarViews.commit.name;
await waitForPromises();
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
expect(wrapper.find(IdeTree).exists()).toBe(false); expect(wrapper.find(IdeTree).exists()).toBe(false);
...@@ -80,6 +107,7 @@ describe('IdeSidebar', () => { ...@@ -80,6 +107,7 @@ describe('IdeSidebar', () => {
store.state.currentActivityView = leftSidebarViews.edit.name; store.state.currentActivityView = leftSidebarViews.edit.name;
await waitForPromises();
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
// reference to the elements remains the same, meaning the components were kept alive // reference to the elements remains the same, meaning the components were kept alive
......
import Vue from 'vue'; import Vue from 'vue';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createStore } from '~/ide/stores'; import { createStore } from '~/ide/stores';
import ide from '~/ide/components/ide.vue'; import ide from '~/ide/components/ide.vue';
import { file } from '../helpers'; import { file } from '../helpers';
...@@ -63,18 +64,17 @@ describe('ide component, non-empty repo', () => { ...@@ -63,18 +64,17 @@ describe('ide component, non-empty repo', () => {
vm.$destroy(); vm.$destroy();
}); });
it('shows error message when set', done => { it('shows error message when set', async () => {
expect(vm.$el.querySelector('.gl-alert')).toBe(null); expect(vm.$el.querySelector('.gl-alert')).toBe(null);
vm.$store.state.errorMessage = { vm.$store.state.errorMessage = {
text: 'error', text: 'error',
}; };
vm.$nextTick(() => { await waitForPromises();
expect(vm.$el.querySelector('.gl-alert')).not.toBe(null); await vm.$nextTick();
done(); expect(vm.$el.querySelector('.gl-alert')).not.toBe(null);
});
}); });
describe('onBeforeUnload', () => { describe('onBeforeUnload', () => {
......
...@@ -8,12 +8,6 @@ exports[`WebIDE runs 1`] = ` ...@@ -8,12 +8,6 @@ exports[`WebIDE runs 1`] = `
<div <div
class="ide-view flex-grow d-flex" class="ide-view flex-grow d-flex"
> >
<div
class="file-finder-overlay"
style="display: none;"
>
(jest: contents hidden)
</div>
<div <div
class="gl-relative multi-file-commit-panel flex-column" class="gl-relative multi-file-commit-panel flex-column"
style="width: 340px;" style="width: 340px;"
...@@ -109,28 +103,12 @@ exports[`WebIDE runs 1`] = ` ...@@ -109,28 +103,12 @@ exports[`WebIDE runs 1`] = `
<h4> <h4>
Make and review changes in the browser with the Web IDE Make and review changes in the browser with the Web IDE
</h4> </h4>
<div
class="gl-spinner-container"
>
<span
aria-label="Loading"
class="align-text-bottom gl-spinner gl-spinner-dark gl-spinner-md"
/>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
<footer
class="ide-status-bar"
>
<div
class="ide-status-list d-flex ml-auto"
>
</div>
</footer>
</article> </article>
</div> </div>
`; `;
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