Commit 75ead9a4 authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'diff-tree-collapse-directories' into 'master'

Collapses directory structure in merge request tree

Closes #53069

See merge request gitlab-org/gitlab-ce!24392
parents c9504df6 24b98a87
...@@ -32,3 +32,5 @@ export const LINES_TO_BE_RENDERED_DIRECTLY = 100; ...@@ -32,3 +32,5 @@ export const LINES_TO_BE_RENDERED_DIRECTLY = 100;
export const MAX_LINES_TO_BE_RENDERED = 2000; export const MAX_LINES_TO_BE_RENDERED = 2000;
export const MR_TREE_SHOW_KEY = 'mr_tree_show'; export const MR_TREE_SHOW_KEY = 'mr_tree_show';
export const TREE_TYPE = 'tree';
...@@ -5,6 +5,7 @@ import createFlash from '~/flash'; ...@@ -5,6 +5,7 @@ import createFlash from '~/flash';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { handleLocationHash, historyPushState, scrollToElement } from '~/lib/utils/common_utils'; import { handleLocationHash, historyPushState, scrollToElement } from '~/lib/utils/common_utils';
import { mergeUrlParams, getLocationHash } from '~/lib/utils/url_utility'; import { mergeUrlParams, getLocationHash } from '~/lib/utils/url_utility';
import TreeWorker from '../workers/tree_worker';
import eventHub from '../../notes/event_hub'; import eventHub from '../../notes/event_hub';
import { getDiffPositionByLineCode, getNoteFormData } from './utils'; import { getDiffPositionByLineCode, getNoteFormData } from './utils';
import * as types from './mutation_types'; import * as types from './mutation_types';
...@@ -21,17 +22,29 @@ export const setBaseConfig = ({ commit }, options) => { ...@@ -21,17 +22,29 @@ export const setBaseConfig = ({ commit }, options) => {
}; };
export const fetchDiffFiles = ({ state, commit }) => { export const fetchDiffFiles = ({ state, commit }) => {
const worker = new TreeWorker();
commit(types.SET_LOADING, true); commit(types.SET_LOADING, true);
worker.addEventListener('message', ({ data }) => {
commit(types.SET_TREE_DATA, data);
worker.terminate();
});
return axios return axios
.get(state.endpoint) .get(state.endpoint)
.then(res => { .then(res => {
commit(types.SET_LOADING, false); commit(types.SET_LOADING, false);
commit(types.SET_MERGE_REQUEST_DIFFS, res.data.merge_request_diffs || []); commit(types.SET_MERGE_REQUEST_DIFFS, res.data.merge_request_diffs || []);
commit(types.SET_DIFF_DATA, res.data); commit(types.SET_DIFF_DATA, res.data);
worker.postMessage(state.diffFiles);
return Vue.nextTick(); return Vue.nextTick();
}) })
.then(handleLocationHash); .then(handleLocationHash)
.catch(() => worker.terminate());
}; };
export const setHighlightedRow = ({ commit }, lineCode) => { export const setHighlightedRow = ({ commit }, lineCode) => {
......
...@@ -18,3 +18,5 @@ export const OPEN_DIFF_FILE_COMMENT_FORM = 'OPEN_DIFF_FILE_COMMENT_FORM'; ...@@ -18,3 +18,5 @@ export const OPEN_DIFF_FILE_COMMENT_FORM = 'OPEN_DIFF_FILE_COMMENT_FORM';
export const UPDATE_DIFF_FILE_COMMENT_FORM = 'UPDATE_DIFF_FILE_COMMENT_FORM'; export const UPDATE_DIFF_FILE_COMMENT_FORM = 'UPDATE_DIFF_FILE_COMMENT_FORM';
export const CLOSE_DIFF_FILE_COMMENT_FORM = 'CLOSE_DIFF_FILE_COMMENT_FORM'; export const CLOSE_DIFF_FILE_COMMENT_FORM = 'CLOSE_DIFF_FILE_COMMENT_FORM';
export const SET_HIGHLIGHTED_ROW = 'SET_HIGHLIGHTED_ROW'; export const SET_HIGHLIGHTED_ROW = 'SET_HIGHLIGHTED_ROW';
export const SET_TREE_DATA = 'SET_TREE_DATA';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { sortTree } from '~/ide/stores/utils';
import { import {
findDiffFile, findDiffFile,
addLineReferences, addLineReferences,
...@@ -7,7 +6,6 @@ import { ...@@ -7,7 +6,6 @@ import {
addContextLines, addContextLines,
prepareDiffData, prepareDiffData,
isDiscussionApplicableToLine, isDiscussionApplicableToLine,
generateTreeList,
} from './utils'; } from './utils';
import * as types from './mutation_types'; import * as types from './mutation_types';
...@@ -23,12 +21,9 @@ export default { ...@@ -23,12 +21,9 @@ export default {
[types.SET_DIFF_DATA](state, data) { [types.SET_DIFF_DATA](state, data) {
prepareDiffData(data); prepareDiffData(data);
const { tree, treeEntries } = generateTreeList(data.diff_files);
Object.assign(state, { Object.assign(state, {
...convertObjectPropsToCamelCase(data), ...convertObjectPropsToCamelCase(data),
tree: sortTree(tree),
treeEntries,
}); });
}, },
...@@ -239,4 +234,8 @@ export default { ...@@ -239,4 +234,8 @@ export default {
[types.SET_HIGHLIGHTED_ROW](state, lineCode) { [types.SET_HIGHLIGHTED_ROW](state, lineCode) {
state.highlightedRow = lineCode; state.highlightedRow = lineCode;
}, },
[types.SET_TREE_DATA](state, { treeEntries, tree }) {
state.treeEntries = treeEntries;
state.tree = tree;
},
}; };
...@@ -11,6 +11,7 @@ import { ...@@ -11,6 +11,7 @@ import {
MATCH_LINE_TYPE, MATCH_LINE_TYPE,
LINES_TO_BE_RENDERED_DIRECTLY, LINES_TO_BE_RENDERED_DIRECTLY,
MAX_LINES_TO_BE_RENDERED, MAX_LINES_TO_BE_RENDERED,
TREE_TYPE,
} from '../constants'; } from '../constants';
export function findDiffFile(files, hash) { export function findDiffFile(files, hash) {
...@@ -289,8 +290,63 @@ export function isDiscussionApplicableToLine({ discussion, diffPosition, latestD ...@@ -289,8 +290,63 @@ export function isDiscussionApplicableToLine({ discussion, diffPosition, latestD
return latestDiff && discussion.active && line_code === discussion.line_code; return latestDiff && discussion.active && line_code === discussion.line_code;
} }
export const generateTreeList = files => export const getLowestSingleFolder = folder => {
files.reduce( const getFolder = (blob, start = []) =>
blob.tree.reduce(
(acc, file) => {
const shouldGetFolder = file.tree.length === 1 && file.tree[0].type === TREE_TYPE;
const currentFileTypeTree = file.type === TREE_TYPE;
const path = shouldGetFolder || currentFileTypeTree ? acc.path.concat(file.name) : acc.path;
const tree = shouldGetFolder || currentFileTypeTree ? acc.tree.concat(file) : acc.tree;
if (shouldGetFolder) {
const firstFolder = getFolder(file);
path.push(firstFolder.path);
tree.push(...firstFolder.tree);
}
return {
...acc,
path,
tree,
};
},
{ path: start, tree: [] },
);
const { path, tree } = getFolder(folder, [folder.name]);
return {
path: path.join('/'),
treeAcc: tree.length ? tree[tree.length - 1].tree : null,
};
};
export const flattenTree = tree => {
const flatten = blobTree =>
blobTree.reduce((acc, file) => {
const blob = file;
let treeToFlatten = blob.tree;
if (file.type === TREE_TYPE && file.tree.length === 1) {
const { treeAcc, path } = getLowestSingleFolder(file);
if (treeAcc) {
blob.name = path;
treeToFlatten = flatten(treeAcc);
}
}
blob.tree = flatten(treeToFlatten);
return acc.concat(blob);
}, []);
return flatten(tree);
};
export const generateTreeList = files => {
const { treeEntries, tree } = files.reduce(
(acc, file) => { (acc, file) => {
const split = file.new_path.split('/'); const split = file.new_path.split('/');
...@@ -335,6 +391,9 @@ export const generateTreeList = files => ...@@ -335,6 +391,9 @@ export const generateTreeList = files =>
{ treeEntries: {}, tree: [] }, { treeEntries: {}, tree: [] },
); );
return { treeEntries, tree: flattenTree(tree) };
};
export const getDiffMode = diffFile => { export const getDiffMode = diffFile => {
const diffModeKey = Object.keys(diffModes).find(key => diffFile[`${key}_file`]); const diffModeKey = Object.keys(diffModes).find(key => diffFile[`${key}_file`]);
return ( return (
......
import { sortTree } from '~/ide/stores/utils';
import { generateTreeList } from '../store/utils';
// eslint-disable-next-line no-restricted-globals
self.addEventListener('message', e => {
const { data } = e;
const { treeEntries, tree } = generateTreeList(data);
// eslint-disable-next-line no-restricted-globals
self.postMessage({
treeEntries,
tree: sortTree(tree),
});
});
---
title: Collapse directory structure in merge request file tree
merge_request:
author:
type: changed
...@@ -628,4 +628,26 @@ describe('DiffsStoreMutations', () => { ...@@ -628,4 +628,26 @@ describe('DiffsStoreMutations', () => {
expect(file.parallel_diff_lines[1].right.hasForm).toBe(false); expect(file.parallel_diff_lines[1].right.hasForm).toBe(false);
}); });
}); });
describe('SET_TREE_DATA', () => {
it('sets treeEntries and tree in state', () => {
const state = {
treeEntries: {},
tree: [],
};
mutations[types.SET_TREE_DATA](state, {
treeEntries: { file: { name: 'index.js' } },
tree: ['tree'],
});
expect(state.treeEntries).toEqual({
file: {
name: 'index.js',
},
});
expect(state.tree).toEqual(['tree']);
});
});
}); });
...@@ -601,4 +601,123 @@ describe('DiffsStoreUtils', () => { ...@@ -601,4 +601,123 @@ describe('DiffsStoreUtils', () => {
expect(utils.getDiffMode({})).toBe('replaced'); expect(utils.getDiffMode({})).toBe('replaced');
}); });
}); });
describe('getLowestSingleFolder', () => {
it('returns path and tree of lowest single folder tree', () => {
const folder = {
name: 'app',
type: 'tree',
tree: [
{
name: 'javascripts',
type: 'tree',
tree: [
{
type: 'blob',
name: 'index.js',
},
],
},
],
};
const { path, treeAcc } = utils.getLowestSingleFolder(folder);
expect(path).toEqual('app/javascripts');
expect(treeAcc).toEqual([
{
type: 'blob',
name: 'index.js',
},
]);
});
it('returns passed in folders path & tree when more than tree exists', () => {
const folder = {
name: 'app',
type: 'tree',
tree: [
{
name: 'spec',
type: 'blob',
tree: [],
},
],
};
const { path, treeAcc } = utils.getLowestSingleFolder(folder);
expect(path).toEqual('app');
expect(treeAcc).toBeNull();
});
});
describe('flattenTree', () => {
it('returns flattened directory structure', () => {
const tree = [
{
type: 'tree',
name: 'app',
tree: [
{
type: 'tree',
name: 'javascripts',
tree: [
{
type: 'blob',
name: 'index.js',
tree: [],
},
],
},
],
},
{
type: 'tree',
name: 'spec',
tree: [
{
type: 'tree',
name: 'javascripts',
tree: [],
},
{
type: 'blob',
name: 'index_spec.js',
tree: [],
},
],
},
];
const flattened = utils.flattenTree(tree);
expect(flattened).toEqual([
{
type: 'tree',
name: 'app/javascripts',
tree: [
{
type: 'blob',
name: 'index.js',
tree: [],
},
],
},
{
type: 'tree',
name: 'spec',
tree: [
{
type: 'tree',
name: 'javascripts',
tree: [],
},
{
type: 'blob',
name: 'index_spec.js',
tree: [],
},
],
},
]);
});
});
}); });
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