Merge branch '57668-create-file-from-url' into 'master'

Resolve "Support creating new file from URL in the Web IDE"

Closes #57668

See merge request gitlab-org/gitlab-ce!26622
This commit is contained in:
Phil Hughes 2019-04-05 13:41:03 +00:00
commit 9fb1dfa870
8 changed files with 271 additions and 17 deletions

View file

@ -147,6 +147,11 @@ export const openBranch = ({ dispatch, state }, { projectId, branchId, basePath
if (treeEntry) { if (treeEntry) {
dispatch('handleTreeEntryAction', treeEntry); dispatch('handleTreeEntryAction', treeEntry);
} else {
dispatch('createTempEntry', {
name: path,
type: 'blob',
});
} }
} }
}) })

View file

@ -1,5 +1,5 @@
import * as types from '../mutation_types'; import * as types from '../mutation_types';
import { sortTree } from '../utils'; import { sortTree, mergeTrees } from '../utils';
export default { export default {
[types.TOGGLE_TREE_OPEN](state, path) { [types.TOGGLE_TREE_OPEN](state, path) {
@ -23,9 +23,15 @@ export default {
}); });
}, },
[types.SET_DIRECTORY_DATA](state, { data, treePath }) { [types.SET_DIRECTORY_DATA](state, { data, treePath }) {
Object.assign(state.trees[treePath], { const selectedTree = state.trees[treePath];
tree: data,
}); // If we opened files while loading the tree, we need to merge them
// Otherwise, simply overwrite the tree
const tree = !selectedTree.tree.length
? data
: selectedTree.loading && mergeTrees(selectedTree.tree, data);
Object.assign(selectedTree, { tree });
}, },
[types.SET_LAST_COMMIT_URL](state, { tree = state, url }) { [types.SET_LAST_COMMIT_URL](state, { tree = state, url }) {
Object.assign(tree, { Object.assign(tree, {

View file

@ -170,3 +170,31 @@ export const filePathMatches = (filePath, path) => filePath.indexOf(`${path}/`)
export const getChangesCountForFiles = (files, path) => export const getChangesCountForFiles = (files, path) =>
files.filter(f => filePathMatches(f.path, path)).length; files.filter(f => filePathMatches(f.path, path)).length;
export const mergeTrees = (fromTree, toTree) => {
if (!fromTree || !fromTree.length) {
return toTree;
}
const recurseTree = (n, t) => {
if (!n) {
return t;
}
const existingTreeNode = t.find(el => el.path === n.path);
if (existingTreeNode && n.tree.length > 0) {
existingTreeNode.opened = true;
recurseTree(n.tree[0], existingTreeNode.tree);
} else if (!existingTreeNode) {
const sorted = sortTree(t.concat(n));
t.splice(0, t.length + 1, ...sorted);
}
return t;
};
for (let i = 0, l = fromTree.length; i < l; i += 1) {
recurseTree(fromTree[i], toTree);
}
return toTree;
};

View file

@ -0,0 +1,5 @@
---
title: Implemented support for creation of new files from URL in Web IDE
merge_request: 26622
author:
type: added

View file

@ -279,5 +279,22 @@ describe('IDE store project actions', () => {
.then(done) .then(done)
.catch(done.fail); .catch(done.fail);
}); });
it('creates a new file supplied via URL if the file does not exist yet', done => {
openBranch(store, { ...branch, basePath: 'not-existent.md' })
.then(() => {
expect(store.dispatch).not.toHaveBeenCalledWith(
'handleTreeEntryAction',
jasmine.anything(),
);
expect(store.dispatch).toHaveBeenCalledWith('createTempEntry', {
name: 'not-existent.md',
type: 'blob',
});
})
.then(done)
.catch(done.fail);
});
}); });
}); });

View file

@ -1,6 +1,6 @@
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import testAction from 'spec/helpers/vuex_action_helper'; import testAction from 'spec/helpers/vuex_action_helper';
import { showTreeEntry, getFiles } from '~/ide/stores/actions/tree'; import { showTreeEntry, getFiles, setDirectoryData } from '~/ide/stores/actions/tree';
import * as types from '~/ide/stores/mutation_types'; import * as types from '~/ide/stores/mutation_types';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import store from '~/ide/stores'; import store from '~/ide/stores';
@ -206,4 +206,35 @@ describe('Multi-file store tree actions', () => {
); );
}); });
}); });
describe('setDirectoryData', () => {
it('sets tree correctly if there are no opened files yet', done => {
const treeFile = file({ name: 'README.md' });
store.state.trees['abcproject/master'] = {};
testAction(
setDirectoryData,
{ projectId: 'abcproject', branchId: 'master', treeList: [treeFile] },
store.state,
[
{
type: types.SET_DIRECTORY_DATA,
payload: {
treePath: 'abcproject/master',
data: [treeFile],
},
},
{
type: types.TOGGLE_LOADING,
payload: {
entry: {},
forceValue: false,
},
},
],
[],
done,
);
});
});
}); });

View file

@ -26,17 +26,11 @@ describe('Multi-file store tree mutations', () => {
}); });
describe('SET_DIRECTORY_DATA', () => { describe('SET_DIRECTORY_DATA', () => {
const data = [ let data;
{
name: 'tree', beforeEach(() => {
}, data = [file('tree'), file('foo'), file('blob')];
{ });
name: 'submodule',
},
{
name: 'blob',
},
];
it('adds directory data', () => { it('adds directory data', () => {
localState.trees['project/master'] = { localState.trees['project/master'] = {
@ -52,7 +46,7 @@ describe('Multi-file store tree mutations', () => {
expect(tree.tree.length).toBe(3); expect(tree.tree.length).toBe(3);
expect(tree.tree[0].name).toBe('tree'); expect(tree.tree[0].name).toBe('tree');
expect(tree.tree[1].name).toBe('submodule'); expect(tree.tree[1].name).toBe('foo');
expect(tree.tree[2].name).toBe('blob'); expect(tree.tree[2].name).toBe('blob');
}); });
@ -65,6 +59,49 @@ describe('Multi-file store tree mutations', () => {
expect(localState.trees['project/master'].loading).toBe(true); expect(localState.trees['project/master'].loading).toBe(true);
}); });
it('does not override tree already in state, but merges the two with correct order', () => {
const openedFile = file('new');
localState.trees['project/master'] = {
loading: true,
tree: [openedFile],
};
mutations.SET_DIRECTORY_DATA(localState, {
data,
treePath: 'project/master',
});
const { tree } = localState.trees['project/master'];
expect(tree.length).toBe(4);
expect(tree[0].name).toBe('blob');
expect(tree[1].name).toBe('foo');
expect(tree[2].name).toBe('new');
expect(tree[3].name).toBe('tree');
});
it('returns tree unchanged if the opened file is already in the tree', () => {
const openedFile = file('foo');
localState.trees['project/master'] = {
loading: true,
tree: [openedFile],
};
mutations.SET_DIRECTORY_DATA(localState, {
data,
treePath: 'project/master',
});
const { tree } = localState.trees['project/master'];
expect(tree.length).toBe(3);
expect(tree[0].name).toBe('tree');
expect(tree[1].name).toBe('foo');
expect(tree[2].name).toBe('blob');
});
}); });
describe('REMOVE_ALL_CHANGES_FILES', () => { describe('REMOVE_ALL_CHANGES_FILES', () => {

View file

@ -235,4 +235,129 @@ describe('Multi-file store utils', () => {
]); ]);
}); });
}); });
describe('mergeTrees', () => {
let fromTree;
let toTree;
beforeEach(() => {
fromTree = [file('foo')];
toTree = [file('bar')];
});
it('merges simple trees with sorting the result', () => {
toTree = [file('beta'), file('alpha'), file('gamma')];
const res = utils.mergeTrees(fromTree, toTree);
expect(res.length).toEqual(4);
expect(res[0].name).toEqual('alpha');
expect(res[1].name).toEqual('beta');
expect(res[2].name).toEqual('foo');
expect(res[3].name).toEqual('gamma');
expect(res[2]).toBe(fromTree[0]);
});
it('handles edge cases', () => {
expect(utils.mergeTrees({}, []).length).toEqual(0);
let res = utils.mergeTrees({}, toTree);
expect(res.length).toEqual(1);
expect(res[0].name).toEqual('bar');
res = utils.mergeTrees(fromTree, []);
expect(res.length).toEqual(1);
expect(res[0].name).toEqual('foo');
expect(res[0]).toBe(fromTree[0]);
});
it('merges simple trees without producing duplicates', () => {
toTree.push(file('foo'));
const res = utils.mergeTrees(fromTree, toTree);
expect(res.length).toEqual(2);
expect(res[0].name).toEqual('bar');
expect(res[1].name).toEqual('foo');
expect(res[1]).not.toBe(fromTree[0]);
});
it('merges nested tree into the main one without duplicates', () => {
fromTree[0].tree.push({
...file('alpha'),
path: 'foo/alpha',
tree: [
{
...file('beta.md'),
path: 'foo/alpha/beta.md',
},
],
});
toTree.push({
...file('foo'),
tree: [
{
...file('alpha'),
path: 'foo/alpha',
tree: [
{
...file('gamma.md'),
path: 'foo/alpha/gamma.md',
},
],
},
],
});
const res = utils.mergeTrees(fromTree, toTree);
expect(res.length).toEqual(2);
expect(res[1].name).toEqual('foo');
const finalBranch = res[1].tree[0].tree;
expect(finalBranch.length).toEqual(2);
expect(finalBranch[0].name).toEqual('beta.md');
expect(finalBranch[1].name).toEqual('gamma.md');
});
it('marks correct folders as opened as the parsing goes on', () => {
fromTree[0].tree.push({
...file('alpha'),
path: 'foo/alpha',
tree: [
{
...file('beta.md'),
path: 'foo/alpha/beta.md',
},
],
});
toTree.push({
...file('foo'),
tree: [
{
...file('alpha'),
path: 'foo/alpha',
tree: [
{
...file('gamma.md'),
path: 'foo/alpha/gamma.md',
},
],
},
],
});
const res = utils.mergeTrees(fromTree, toTree);
expect(res[1].name).toEqual('foo');
expect(res[1].opened).toEqual(true);
expect(res[1].tree[0].name).toEqual('alpha');
expect(res[1].tree[0].opened).toEqual(true);
});
});
}); });