summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/repo/components/repo_file.vue31
-rw-r--r--app/assets/javascripts/repo/components/repo_loading_file.vue41
-rw-r--r--app/assets/javascripts/repo/components/repo_sidebar.vue2
-rw-r--r--app/assets/javascripts/repo/stores/actions.js18
-rw-r--r--app/assets/javascripts/repo/stores/actions/tree.js35
-rw-r--r--app/assets/javascripts/repo/stores/mutations.js2
-rw-r--r--app/assets/javascripts/repo/stores/mutations/tree.js25
-rw-r--r--app/assets/javascripts/repo/stores/utils.js19
-rw-r--r--app/assets/javascripts/vue_shared/components/skeleton_loading_container.vue32
-rw-r--r--app/controllers/projects/refs_controller.rb5
-rw-r--r--spec/javascripts/repo/components/repo_file_spec.js5
-rw-r--r--spec/javascripts/vue_shared/components/skeleton_loading_container_spec.js49
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();
+ });
+ });
+});