diff options
12 files changed, 180 insertions, 84 deletions
diff --git a/app/assets/javascripts/repo/components/repo_file.vue b/app/assets/javascripts/repo/components/repo_file.vue index 3d2ab90187d..e7a40e06da7 100644 --- a/app/assets/javascripts/repo/components/repo_file.vue +++ b/app/assets/javascripts/repo/components/repo_file.vue @@ -1,11 +1,15 @@ <script> import { mapActions, mapGetters } from 'vuex'; import timeAgoMixin from '../../vue_shared/mixins/timeago'; + import skeletonLoadingContainer from '../../vue_shared/components/skeleton_loading_container.vue'; export default { mixins: [ timeAgoMixin, ], + components: { + skeletonLoadingContainer, + }, props: { file: { type: Object, @@ -87,17 +91,10 @@ > {{ file.lastCommit.message }} </a> - <div + <skeleton-loading-container v-else - class="animation-container animation-container-small" - > - <div - v-for="n in 6" - :key="n" - :class="'skeleton-line-' + n" - > - </div> - </div> + :small="true" + /> </td> <td class="commit-update hidden-xs text-right"> @@ -107,17 +104,11 @@ > {{ timeFormated(file.lastCommit.updatedAt) }} </span> - <div + <skeleton-loading-container v-else - class="animation-container animation-container-small animation-container-right" - > - <div - v-for="n in 6" - :key="n" - :class="'skeleton-line-' + n" - > - </div> - </div> + class="animation-container-right" + :small="true" + /> </td> </template> </tr> diff --git a/app/assets/javascripts/repo/components/repo_loading_file.vue b/app/assets/javascripts/repo/components/repo_loading_file.vue index a77fb8cc462..14c2ddfb615 100644 --- a/app/assets/javascripts/repo/components/repo_loading_file.vue +++ b/app/assets/javascripts/repo/components/repo_loading_file.vue @@ -1,17 +1,16 @@ <script> import { mapGetters } from 'vuex'; + import skeletonLoadingContainer from '../../vue_shared/components/skeleton_loading_container.vue'; export default { + components: { + skeletonLoadingContainer, + }, computed: { ...mapGetters([ 'isCollapsed', ]), }, - methods: { - lineOfCode(n) { - return `skeleton-line-${n}`; - }, - }, }; </script> @@ -21,36 +20,24 @@ aria-label="Loading files" > <td> - <div - class="animation-container animation-container-small"> - <div - v-for="n in 6" - :key="n" - :class="lineOfCode(n)"> - </div> - </div> + <skeleton-loading-container + :small="true" + /> </td> <template v-if="!isCollapsed"> <td class="hidden-sm hidden-xs"> - <div class="animation-container animation-container-small"> - <div - v-for="n in 6" - :key="n" - :class="lineOfCode(n)"> - </div> - </div> + <skeleton-loading-container + :small="true" + /> </td> <td class="hidden-xs"> - <div class="animation-container animation-container-small animation-container-right"> - <div - v-for="n in 6" - :key="n" - :class="lineOfCode(n)"> - </div> - </div> + <skeleton-loading-container + class="animation-container-right" + :small="true" + /> </td> </template> </tr> diff --git a/app/assets/javascripts/repo/components/repo_sidebar.vue b/app/assets/javascripts/repo/components/repo_sidebar.vue index 63c0d70f5c0..8efc1aaa06c 100644 --- a/app/assets/javascripts/repo/components/repo_sidebar.vue +++ b/app/assets/javascripts/repo/components/repo_sidebar.vue @@ -80,7 +80,7 @@ export default { /> <repo-file v-for="(file, index) in treeList" - :key="index" + :key="file.name + file.type" :file="file" /> </tbody> diff --git a/app/assets/javascripts/repo/stores/actions.js b/app/assets/javascripts/repo/stores/actions.js index ca2f2a5ce7a..be290c268b1 100644 --- a/app/assets/javascripts/repo/stores/actions.js +++ b/app/assets/javascripts/repo/stores/actions.js @@ -64,7 +64,7 @@ export const checkCommitStatus = ({ state }) => service.getBranchData( }) .catch(() => flash('Error checking branch data. Please try again.')); -export const commitChanges = ({ commit, state, dispatch }, { payload, newMr }) => +export const commitChanges = ({ commit, state, dispatch, getters }, { payload, newMr }) => service.commit(state.project.id, payload) .then((data) => { const { branch } = payload; @@ -73,12 +73,28 @@ export const commitChanges = ({ commit, state, dispatch }, { payload, newMr }) = return; } + const lastCommit = { + commit_path: `${state.project.url}/commit/${data.id}`, + commit: { + message: data.message, + authored_date: data.committed_date, + }, + }; + flash(`Your changes have been committed. Commit ${data.short_id} with ${data.stats.additions} additions, ${data.stats.deletions} deletions.`, 'notice'); if (newMr) { redirectToUrl(`${state.endpoints.newMergeRequestUrl}${branch}`); } else { commit(types.SET_COMMIT_REF, data.id); + + getters.changedFiles.forEach((entry) => { + commit(types.SET_LAST_COMMIT_DATA, { + entry, + lastCommit, + }); + }); + dispatch('discardAllChanges'); dispatch('closeAllFiles'); dispatch('toggleEditMode'); diff --git a/app/assets/javascripts/repo/stores/actions/tree.js b/app/assets/javascripts/repo/stores/actions/tree.js index 7e8b0b10322..aa830e946a2 100644 --- a/app/assets/javascripts/repo/stores/actions/tree.js +++ b/app/assets/javascripts/repo/stores/actions/tree.js @@ -7,6 +7,7 @@ import { setPageTitle, findEntry, createTemp, + createOrMergeEntry, } from '../utils'; export const getTreeData = ( @@ -24,15 +25,19 @@ export const getTreeData = ( return res.json(); }) .then((data) => { + const prevLastCommitPath = tree.lastCommitPath; if (!state.isInitialRoot) { commit(types.SET_ROOT, data.path === '/'); } - commit(types.SET_DIRECTORY_DATA, { data, tree }); + dispatch('updateDirectoryData', { data, tree }); commit(types.SET_PARENT_TREE_URL, data.parent_tree_url); commit(types.SET_LAST_COMMIT_URL, { tree, url: data.last_commit_path }); commit(types.TOGGLE_LOADING, tree); - dispatch('getLastCommitData', tree); + + if (prevLastCommitPath !== null) { + dispatch('getLastCommitData', tree); + } pushState(endpoint); }) @@ -50,7 +55,7 @@ export const toggleTreeOpen = ({ commit, dispatch }, { endpoint, tree }) => { pushState(tree.parentTreeUrl); commit(types.SET_PREVIOUS_URL, tree.parentTreeUrl); - commit(types.SET_DIRECTORY_DATA, { data, tree }); + dispatch('updateDirectoryData', { data, tree }); } else { commit(types.SET_PREVIOUS_URL, endpoint); dispatch('getTreeData', { endpoint, tree }); @@ -112,11 +117,11 @@ export const createTempTree = ({ state, commit, dispatch }, name) => { }; export const getLastCommitData = ({ state, commit, dispatch, getters }, tree = state) => { - if (tree.lastCommitPath === '' || getters.isCollapsed) return; + if (tree.lastCommitPath === null || getters.isCollapsed) return; service.getTreeLastCommit(tree.lastCommitPath) .then((res) => { - const lastCommitPath = normalizeHeaders(res.headers)['LOG-URL']; + const lastCommitPath = normalizeHeaders(res.headers)['MORE-LOGS-URL'] || null; commit(types.SET_LAST_COMMIT_URL, { tree, url: lastCommitPath }); @@ -135,3 +140,23 @@ export const getLastCommitData = ({ state, commit, dispatch, getters }, tree = s }) .catch(() => flash('Error fetching log data.')); }; + +export const updateDirectoryData = ({ commit, state }, { data, tree }) => { + const level = tree.level !== undefined ? tree.level + 1 : 0; + const parentTreeUrl = data.parent_tree_url ? `${data.parent_tree_url}${data.path}` : state.endpoints.rootUrl; + const createEntry = (entry, type) => createOrMergeEntry({ + tree, + entry, + level, + type, + parentTreeUrl, + }); + + const formattedData = [ + ...data.trees.map(t => createEntry(t, 'tree')), + ...data.submodules.map(m => createEntry(m, 'submodule')), + ...data.blobs.map(b => createEntry(b, 'blob')), + ]; + + commit(types.SET_DIRECTORY_DATA, { tree, data: formattedData }); +}; diff --git a/app/assets/javascripts/repo/stores/mutations.js b/app/assets/javascripts/repo/stores/mutations.js index 7e6c3d6e286..ae2ba5bedf7 100644 --- a/app/assets/javascripts/repo/stores/mutations.js +++ b/app/assets/javascripts/repo/stores/mutations.js @@ -50,7 +50,7 @@ export default { }, [types.SET_LAST_COMMIT_DATA](state, { entry, lastCommit }) { Object.assign(entry.lastCommit, { - url: `${state.project.url}/commit/${lastCommit.commit.id}`, + url: lastCommit.commit_path, message: lastCommit.commit.message, updatedAt: lastCommit.commit.authored_date, }); diff --git a/app/assets/javascripts/repo/stores/mutations/tree.js b/app/assets/javascripts/repo/stores/mutations/tree.js index eb77512a7c4..130221c9fda 100644 --- a/app/assets/javascripts/repo/stores/mutations/tree.js +++ b/app/assets/javascripts/repo/stores/mutations/tree.js @@ -1,5 +1,4 @@ import * as types from '../mutation_types'; -import * as utils from '../utils'; export default { [types.TOGGLE_TREE_OPEN](state, tree) { @@ -8,30 +7,8 @@ export default { }); }, [types.SET_DIRECTORY_DATA](state, { data, tree }) { - const level = tree.level !== undefined ? tree.level + 1 : 0; - const parentTreeUrl = data.parent_tree_url ? `${data.parent_tree_url}${data.path}` : state.endpoints.rootUrl; - Object.assign(tree, { - tree: [ - ...data.trees.map(t => utils.decorateData({ - ...t, - type: 'tree', - parentTreeUrl, - level, - })), - ...data.submodules.map(m => utils.decorateData({ - ...m, - type: 'submodule', - parentTreeUrl, - level, - })), - ...data.blobs.map(b => utils.decorateData({ - ...b, - type: 'blob', - parentTreeUrl, - level, - })), - ], + tree: data, }); }, [types.SET_PARENT_TREE_URL](state, url) { diff --git a/app/assets/javascripts/repo/stores/utils.js b/app/assets/javascripts/repo/stores/utils.js index 18673fe82c7..57c0dd62c41 100644 --- a/app/assets/javascripts/repo/stores/utils.js +++ b/app/assets/javascripts/repo/stores/utils.js @@ -104,3 +104,22 @@ export const createTemp = ({ name, path, type, level, changed, content, base64 } renderError: base64, }); }; + +export const createOrMergeEntry = ({ tree, entry, type, parentTreeUrl, level }) => { + const found = findEntry(tree, type, entry.name); + + if (found) { + return Object.assign({}, found, { + id: entry.id, + url: entry.url, + tempFile: false, + }); + } + + return decorateData({ + ...entry, + type, + parentTreeUrl, + level, + }); +}; diff --git a/app/assets/javascripts/vue_shared/components/skeleton_loading_container.vue b/app/assets/javascripts/vue_shared/components/skeleton_loading_container.vue new file mode 100644 index 00000000000..4e759fb6c6c --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/skeleton_loading_container.vue @@ -0,0 +1,32 @@ +<script> + export default { + props: { + small: { + type: Boolean, + required: false, + default: false, + }, + lines: { + type: Number, + required: false, + default: 6, + }, + }, + }; +</script> + +<template> + <div + class="animation-container" + :class="{ + 'animation-container-small': small, + }" + > + <div + v-for="line in lines" + :key="line" + :class="'skeleton-line-' + line" + > + </div> + </div> +</template> diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb index e5285ca8fba..ff79f88afc3 100644 --- a/app/controllers/projects/refs_controller.rb +++ b/app/controllers/projects/refs_controller.rb @@ -59,7 +59,8 @@ class Projects::RefsController < Projects::ApplicationController { file_name: content.name, commit: last_commit, - type: content.type + type: content.type, + commit_path: project_commit_path(@project, last_commit) } end end @@ -72,7 +73,7 @@ class Projects::RefsController < Projects::ApplicationController respond_to do |format| format.html { render_404 } format.json do - response.headers["Log-Url"] = @more_log_url + response.headers["More-Logs-Url"] = @more_log_url render json: @logs end diff --git a/spec/javascripts/repo/components/repo_file_spec.js b/spec/javascripts/repo/components/repo_file_spec.js index c45f8a18d1f..bf9181fb09c 100644 --- a/spec/javascripts/repo/components/repo_file_spec.js +++ b/spec/javascripts/repo/components/repo_file_spec.js @@ -20,7 +20,7 @@ describe('RepoFile', () => { resetStore(vm.$store); }); - it('renders link, icon, name and last commit details', () => { + it('renders link, icon and name', () => { const RepoFile = Vue.extend(repoFile); vm = new RepoFile({ store, @@ -37,10 +37,9 @@ describe('RepoFile', () => { expect(vm.$el.querySelector(`.${vm.file.icon}`).style.marginLeft).toEqual('0px'); expect(name.href).toMatch(`/${vm.file.url}`); expect(name.textContent.trim()).toEqual(vm.file.name); - expect(vm.$el.querySelector('.commit-message').textContent.trim()).toBe(vm.file.lastCommit.message); - expect(vm.$el.querySelector('.commit-update').textContent.trim()).toBe(updated); expect(fileIcon.classList.contains(vm.file.icon)).toBeTruthy(); expect(fileIcon.style.marginLeft).toEqual(`${vm.file.level * 10}px`); + expect(vm.$el.querySelectorAll('.animation-container').length).toBe(2); }); it('does render if hasFiles is true and is loading tree', () => { diff --git a/spec/javascripts/vue_shared/components/skeleton_loading_container_spec.js b/spec/javascripts/vue_shared/components/skeleton_loading_container_spec.js new file mode 100644 index 00000000000..a5db0b2c59e --- /dev/null +++ b/spec/javascripts/vue_shared/components/skeleton_loading_container_spec.js @@ -0,0 +1,49 @@ +import Vue from 'vue'; +import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('Skeleton loading container', () => { + let vm; + + beforeEach(() => { + const component = Vue.extend(skeletonLoadingContainer); + vm = mountComponent(component); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders 6 skeleton lines by default', () => { + expect(vm.$el.querySelector('.skeleton-line-6')).not.toBeNull(); + }); + + it('renders in full mode by default', () => { + expect(vm.$el.classList.contains('animation-container-small')).toBeFalsy(); + }); + + describe('small', () => { + beforeEach((done) => { + vm.small = true; + + Vue.nextTick(done); + }); + + it('renders in small mode', () => { + expect(vm.$el.classList.contains('animation-container-small')).toBeTruthy(); + }); + }); + + describe('lines', () => { + beforeEach((done) => { + vm.lines = 5; + + Vue.nextTick(done); + }); + + it('renders 5 lines', () => { + expect(vm.$el.querySelector('.skeleton-line-5')).not.toBeNull(); + expect(vm.$el.querySelector('.skeleton-line-6')).toBeNull(); + }); + }); +}); |