Merge branch 'tz-ide-open-mr' into 'master'
Basic Setup for MR Showing Closes #44840 and #44839 See merge request gitlab-org/gitlab-ce!17952
This commit is contained in:
commit
6b89ab1161
41 changed files with 1376 additions and 569 deletions
|
@ -10,6 +10,9 @@ const Api = {
|
|||
projectsPath: '/api/:version/projects.json',
|
||||
projectPath: '/api/:version/projects/:id',
|
||||
projectLabelsPath: '/:namespace_path/:project_path/labels',
|
||||
mergeRequestPath: '/api/:version/projects/:id/merge_requests/:mrid',
|
||||
mergeRequestChangesPath: '/api/:version/projects/:id/merge_requests/:mrid/changes',
|
||||
mergeRequestVersionsPath: '/api/:version/projects/:id/merge_requests/:mrid/versions',
|
||||
groupLabelsPath: '/groups/:namespace_path/-/labels',
|
||||
licensePath: '/api/:version/templates/licenses/:key',
|
||||
gitignorePath: '/api/:version/templates/gitignores/:key',
|
||||
|
@ -22,25 +25,27 @@ const Api = {
|
|||
createBranchPath: '/api/:version/projects/:id/repository/branches',
|
||||
|
||||
group(groupId, callback) {
|
||||
const url = Api.buildUrl(Api.groupPath)
|
||||
.replace(':id', groupId);
|
||||
return axios.get(url)
|
||||
.then(({ data }) => {
|
||||
callback(data);
|
||||
const url = Api.buildUrl(Api.groupPath).replace(':id', groupId);
|
||||
return axios.get(url).then(({ data }) => {
|
||||
callback(data);
|
||||
|
||||
return data;
|
||||
});
|
||||
return data;
|
||||
});
|
||||
},
|
||||
|
||||
// Return groups list. Filtered by query
|
||||
groups(query, options, callback = $.noop) {
|
||||
const url = Api.buildUrl(Api.groupsPath);
|
||||
return axios.get(url, {
|
||||
params: Object.assign({
|
||||
search: query,
|
||||
per_page: 20,
|
||||
}, options),
|
||||
})
|
||||
return axios
|
||||
.get(url, {
|
||||
params: Object.assign(
|
||||
{
|
||||
search: query,
|
||||
per_page: 20,
|
||||
},
|
||||
options,
|
||||
),
|
||||
})
|
||||
.then(({ data }) => {
|
||||
callback(data);
|
||||
|
||||
|
@ -51,12 +56,13 @@ const Api = {
|
|||
// Return namespaces list. Filtered by query
|
||||
namespaces(query, callback) {
|
||||
const url = Api.buildUrl(Api.namespacesPath);
|
||||
return axios.get(url, {
|
||||
params: {
|
||||
search: query,
|
||||
per_page: 20,
|
||||
},
|
||||
})
|
||||
return axios
|
||||
.get(url, {
|
||||
params: {
|
||||
search: query,
|
||||
per_page: 20,
|
||||
},
|
||||
})
|
||||
.then(({ data }) => callback(data));
|
||||
},
|
||||
|
||||
|
@ -73,9 +79,10 @@ const Api = {
|
|||
defaults.membership = true;
|
||||
}
|
||||
|
||||
return axios.get(url, {
|
||||
params: Object.assign(defaults, options),
|
||||
})
|
||||
return axios
|
||||
.get(url, {
|
||||
params: Object.assign(defaults, options),
|
||||
})
|
||||
.then(({ data }) => {
|
||||
callback(data);
|
||||
|
||||
|
@ -85,8 +92,32 @@ const Api = {
|
|||
|
||||
// Return single project
|
||||
project(projectPath) {
|
||||
const url = Api.buildUrl(Api.projectPath)
|
||||
.replace(':id', encodeURIComponent(projectPath));
|
||||
const url = Api.buildUrl(Api.projectPath).replace(':id', encodeURIComponent(projectPath));
|
||||
|
||||
return axios.get(url);
|
||||
},
|
||||
|
||||
// Return Merge Request for project
|
||||
mergeRequest(projectPath, mergeRequestId) {
|
||||
const url = Api.buildUrl(Api.mergeRequestPath)
|
||||
.replace(':id', encodeURIComponent(projectPath))
|
||||
.replace(':mrid', mergeRequestId);
|
||||
|
||||
return axios.get(url);
|
||||
},
|
||||
|
||||
mergeRequestChanges(projectPath, mergeRequestId) {
|
||||
const url = Api.buildUrl(Api.mergeRequestChangesPath)
|
||||
.replace(':id', encodeURIComponent(projectPath))
|
||||
.replace(':mrid', mergeRequestId);
|
||||
|
||||
return axios.get(url);
|
||||
},
|
||||
|
||||
mergeRequestVersions(projectPath, mergeRequestId) {
|
||||
const url = Api.buildUrl(Api.mergeRequestVersionsPath)
|
||||
.replace(':id', encodeURIComponent(projectPath))
|
||||
.replace(':mrid', mergeRequestId);
|
||||
|
||||
return axios.get(url);
|
||||
},
|
||||
|
@ -102,30 +133,30 @@ const Api = {
|
|||
url = Api.buildUrl(Api.groupLabelsPath).replace(':namespace_path', namespacePath);
|
||||
}
|
||||
|
||||
return axios.post(url, {
|
||||
label: data,
|
||||
})
|
||||
return axios
|
||||
.post(url, {
|
||||
label: data,
|
||||
})
|
||||
.then(res => callback(res.data))
|
||||
.catch(e => callback(e.response.data));
|
||||
},
|
||||
|
||||
// Return group projects list. Filtered by query
|
||||
groupProjects(groupId, query, callback) {
|
||||
const url = Api.buildUrl(Api.groupProjectsPath)
|
||||
.replace(':id', groupId);
|
||||
return axios.get(url, {
|
||||
params: {
|
||||
search: query,
|
||||
per_page: 20,
|
||||
},
|
||||
})
|
||||
const url = Api.buildUrl(Api.groupProjectsPath).replace(':id', groupId);
|
||||
return axios
|
||||
.get(url, {
|
||||
params: {
|
||||
search: query,
|
||||
per_page: 20,
|
||||
},
|
||||
})
|
||||
.then(({ data }) => callback(data));
|
||||
},
|
||||
|
||||
commitMultiple(id, data) {
|
||||
// see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
|
||||
const url = Api.buildUrl(Api.commitPath)
|
||||
.replace(':id', encodeURIComponent(id));
|
||||
const url = Api.buildUrl(Api.commitPath).replace(':id', encodeURIComponent(id));
|
||||
return axios.post(url, JSON.stringify(data), {
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
|
@ -136,39 +167,34 @@ const Api = {
|
|||
branchSingle(id, branch) {
|
||||
const url = Api.buildUrl(Api.branchSinglePath)
|
||||
.replace(':id', encodeURIComponent(id))
|
||||
.replace(':branch', branch);
|
||||
.replace(':branch', encodeURIComponent(branch));
|
||||
|
||||
return axios.get(url);
|
||||
},
|
||||
|
||||
// Return text for a specific license
|
||||
licenseText(key, data, callback) {
|
||||
const url = Api.buildUrl(Api.licensePath)
|
||||
.replace(':key', key);
|
||||
return axios.get(url, {
|
||||
params: data,
|
||||
})
|
||||
const url = Api.buildUrl(Api.licensePath).replace(':key', key);
|
||||
return axios
|
||||
.get(url, {
|
||||
params: data,
|
||||
})
|
||||
.then(res => callback(res.data));
|
||||
},
|
||||
|
||||
gitignoreText(key, callback) {
|
||||
const url = Api.buildUrl(Api.gitignorePath)
|
||||
.replace(':key', key);
|
||||
return axios.get(url)
|
||||
.then(({ data }) => callback(data));
|
||||
const url = Api.buildUrl(Api.gitignorePath).replace(':key', key);
|
||||
return axios.get(url).then(({ data }) => callback(data));
|
||||
},
|
||||
|
||||
gitlabCiYml(key, callback) {
|
||||
const url = Api.buildUrl(Api.gitlabCiYmlPath)
|
||||
.replace(':key', key);
|
||||
return axios.get(url)
|
||||
.then(({ data }) => callback(data));
|
||||
const url = Api.buildUrl(Api.gitlabCiYmlPath).replace(':key', key);
|
||||
return axios.get(url).then(({ data }) => callback(data));
|
||||
},
|
||||
|
||||
dockerfileYml(key, callback) {
|
||||
const url = Api.buildUrl(Api.dockerfilePath).replace(':key', key);
|
||||
return axios.get(url)
|
||||
.then(({ data }) => callback(data));
|
||||
return axios.get(url).then(({ data }) => callback(data));
|
||||
},
|
||||
|
||||
issueTemplate(namespacePath, projectPath, key, type, callback) {
|
||||
|
@ -177,7 +203,8 @@ const Api = {
|
|||
.replace(':type', type)
|
||||
.replace(':project_path', projectPath)
|
||||
.replace(':namespace_path', namespacePath);
|
||||
return axios.get(url)
|
||||
return axios
|
||||
.get(url)
|
||||
.then(({ data }) => callback(null, data))
|
||||
.catch(callback);
|
||||
},
|
||||
|
@ -185,10 +212,13 @@ const Api = {
|
|||
users(query, options) {
|
||||
const url = Api.buildUrl(this.usersPath);
|
||||
return axios.get(url, {
|
||||
params: Object.assign({
|
||||
search: query,
|
||||
per_page: 20,
|
||||
}, options),
|
||||
params: Object.assign(
|
||||
{
|
||||
search: query,
|
||||
per_page: 20,
|
||||
},
|
||||
options,
|
||||
),
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
<script>
|
||||
import icon from '~/vue_shared/components/icon.vue';
|
||||
import icon from '~/vue_shared/components/icon.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
icon,
|
||||
export default {
|
||||
components: {
|
||||
icon,
|
||||
},
|
||||
props: {
|
||||
file: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
props: {
|
||||
file: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
changedIcon() {
|
||||
return this.file.tempFile ? 'file-addition' : 'file-modified';
|
||||
},
|
||||
computed: {
|
||||
changedIcon() {
|
||||
return this.file.tempFile ? 'file-addition' : 'file-modified';
|
||||
},
|
||||
changedIconClass() {
|
||||
return `multi-${this.changedIcon}`;
|
||||
},
|
||||
changedIconClass() {
|
||||
return `multi-${this.changedIcon}`;
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -1,31 +1,44 @@
|
|||
<script>
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import { __, sprintf } from '~/locale';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Icon,
|
||||
export default {
|
||||
components: {
|
||||
Icon,
|
||||
},
|
||||
props: {
|
||||
hasChanges: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
props: {
|
||||
hasChanges: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
viewer: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
showShadow: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
mergeRequestId: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
methods: {
|
||||
changeMode(mode) {
|
||||
this.$emit('click', mode);
|
||||
},
|
||||
viewer: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
};
|
||||
showShadow: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
mergeReviewLine() {
|
||||
return sprintf(__('Reviewing (merge request !%{mergeRequestId})'), {
|
||||
mergeRequestId: this.mergeRequestId,
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
changeMode(mode) {
|
||||
this.$emit('click', mode);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -43,7 +56,10 @@
|
|||
}"
|
||||
data-toggle="dropdown"
|
||||
>
|
||||
<template v-if="viewer === 'editor'">
|
||||
<template v-if="viewer === 'mrdiff' && mergeRequestId">
|
||||
{{ mergeReviewLine }}
|
||||
</template>
|
||||
<template v-else-if="viewer === 'editor'">
|
||||
{{ __('Editing') }}
|
||||
</template>
|
||||
<template v-else>
|
||||
|
@ -57,6 +73,29 @@
|
|||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-selectable dropdown-open-left">
|
||||
<ul>
|
||||
<template v-if="mergeRequestId">
|
||||
<li>
|
||||
<a
|
||||
href="#"
|
||||
@click.prevent="changeMode('mrdiff')"
|
||||
:class="{
|
||||
'is-active': viewer === 'mrdiff',
|
||||
}"
|
||||
>
|
||||
<strong class="dropdown-menu-inner-title">
|
||||
{{ mergeReviewLine }}
|
||||
</strong>
|
||||
<span class="dropdown-menu-inner-content">
|
||||
{{ __('Compare changes with the merge request target branch') }}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
role="separator"
|
||||
class="divider"
|
||||
>
|
||||
</li>
|
||||
</template>
|
||||
<li>
|
||||
<a
|
||||
href="#"
|
||||
|
|
|
@ -1,51 +1,51 @@
|
|||
<script>
|
||||
import { mapState, mapGetters } from 'vuex';
|
||||
import ideSidebar from './ide_side_bar.vue';
|
||||
import ideContextbar from './ide_context_bar.vue';
|
||||
import repoTabs from './repo_tabs.vue';
|
||||
import repoFileButtons from './repo_file_buttons.vue';
|
||||
import ideStatusBar from './ide_status_bar.vue';
|
||||
import repoEditor from './repo_editor.vue';
|
||||
import { mapState, mapGetters } from 'vuex';
|
||||
import ideSidebar from './ide_side_bar.vue';
|
||||
import ideContextbar from './ide_context_bar.vue';
|
||||
import repoTabs from './repo_tabs.vue';
|
||||
import repoFileButtons from './repo_file_buttons.vue';
|
||||
import ideStatusBar from './ide_status_bar.vue';
|
||||
import repoEditor from './repo_editor.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ideSidebar,
|
||||
ideContextbar,
|
||||
repoTabs,
|
||||
repoFileButtons,
|
||||
ideStatusBar,
|
||||
repoEditor,
|
||||
export default {
|
||||
components: {
|
||||
ideSidebar,
|
||||
ideContextbar,
|
||||
repoTabs,
|
||||
repoFileButtons,
|
||||
ideStatusBar,
|
||||
repoEditor,
|
||||
},
|
||||
props: {
|
||||
emptyStateSvgPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
props: {
|
||||
emptyStateSvgPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
noChangesStateSvgPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
committedStateSvgPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
noChangesStateSvgPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
computed: {
|
||||
...mapState(['changedFiles', 'openFiles', 'viewer']),
|
||||
...mapGetters(['activeFile', 'hasChanges']),
|
||||
committedStateSvgPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
mounted() {
|
||||
const returnValue = 'Are you sure you want to lose unsaved changes?';
|
||||
window.onbeforeunload = e => {
|
||||
if (!this.changedFiles.length) return undefined;
|
||||
},
|
||||
computed: {
|
||||
...mapState(['changedFiles', 'openFiles', 'viewer', 'currentMergeRequestId']),
|
||||
...mapGetters(['activeFile', 'hasChanges']),
|
||||
},
|
||||
mounted() {
|
||||
const returnValue = 'Are you sure you want to lose unsaved changes?';
|
||||
window.onbeforeunload = e => {
|
||||
if (!this.changedFiles.length) return undefined;
|
||||
|
||||
Object.assign(e, {
|
||||
returnValue,
|
||||
});
|
||||
return returnValue;
|
||||
};
|
||||
},
|
||||
};
|
||||
Object.assign(e, {
|
||||
returnValue,
|
||||
});
|
||||
return returnValue;
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -63,6 +63,7 @@
|
|||
:files="openFiles"
|
||||
:viewer="viewer"
|
||||
:has-changes="hasChanges"
|
||||
:merge-request-id="currentMergeRequestId"
|
||||
/>
|
||||
<repo-editor
|
||||
class="multi-file-edit-pane-content"
|
||||
|
|
23
app/assets/javascripts/ide/components/mr_file_icon.vue
Normal file
23
app/assets/javascripts/ide/components/mr_file_icon.vue
Normal file
|
@ -0,0 +1,23 @@
|
|||
<script>
|
||||
import icon from '~/vue_shared/components/icon.vue';
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
icon,
|
||||
},
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<icon
|
||||
name="git-merge"
|
||||
v-tooltip
|
||||
title="__('Part of merge request changes')"
|
||||
css-classes="ide-file-changed-icon"
|
||||
:size="12"
|
||||
/>
|
||||
</template>
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
/* global monaco */
|
||||
import { mapState, mapActions } from 'vuex';
|
||||
import { mapState, mapGetters, mapActions } from 'vuex';
|
||||
import flash from '~/flash';
|
||||
import monacoLoader from '../monaco_loader';
|
||||
import Editor from '../lib/editor';
|
||||
|
@ -13,12 +13,8 @@ export default {
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
'leftPanelCollapsed',
|
||||
'rightPanelCollapsed',
|
||||
'viewer',
|
||||
'delayViewerUpdated',
|
||||
]),
|
||||
...mapState(['leftPanelCollapsed', 'rightPanelCollapsed', 'viewer', 'delayViewerUpdated']),
|
||||
...mapGetters(['currentMergeRequest']),
|
||||
shouldHideEditor() {
|
||||
return this.file && this.file.binary && !this.file.raw;
|
||||
},
|
||||
|
@ -68,9 +64,14 @@ export default {
|
|||
|
||||
this.editor.clearEditor();
|
||||
|
||||
this.getRawFileData(this.file)
|
||||
this.getRawFileData({
|
||||
path: this.file.path,
|
||||
baseSha: this.currentMergeRequest ? this.currentMergeRequest.baseCommitSha : '',
|
||||
})
|
||||
.then(() => {
|
||||
const viewerPromise = this.delayViewerUpdated ? this.updateViewer('editor') : Promise.resolve();
|
||||
const viewerPromise = this.delayViewerUpdated
|
||||
? this.updateViewer('editor')
|
||||
: Promise.resolve();
|
||||
|
||||
return viewerPromise;
|
||||
})
|
||||
|
@ -78,7 +79,7 @@ export default {
|
|||
this.updateDelayViewerUpdated(false);
|
||||
this.createEditorInstance();
|
||||
})
|
||||
.catch((err) => {
|
||||
.catch(err => {
|
||||
flash('Error setting up monaco. Please try again.', 'alert', document, null, false, true);
|
||||
throw err;
|
||||
});
|
||||
|
@ -101,9 +102,13 @@ export default {
|
|||
|
||||
this.model = this.editor.createModel(this.file);
|
||||
|
||||
this.editor.attachModel(this.model);
|
||||
if (this.viewer === 'mrdiff') {
|
||||
this.editor.attachMergeRequestModel(this.model);
|
||||
} else {
|
||||
this.editor.attachModel(this.model);
|
||||
}
|
||||
|
||||
this.model.onChange((model) => {
|
||||
this.model.onChange(model => {
|
||||
const { file } = model;
|
||||
|
||||
if (file.active) {
|
||||
|
|
|
@ -6,6 +6,7 @@ import router from '../ide_router';
|
|||
import newDropdown from './new_dropdown/index.vue';
|
||||
import fileStatusIcon from './repo_file_status_icon.vue';
|
||||
import changedFileIcon from './changed_file_icon.vue';
|
||||
import mrFileIcon from './mr_file_icon.vue';
|
||||
|
||||
export default {
|
||||
name: 'RepoFile',
|
||||
|
@ -15,6 +16,7 @@ export default {
|
|||
fileStatusIcon,
|
||||
fileIcon,
|
||||
changedFileIcon,
|
||||
mrFileIcon,
|
||||
},
|
||||
props: {
|
||||
file: {
|
||||
|
@ -56,10 +58,7 @@ export default {
|
|||
...mapActions(['toggleTreeOpen', 'updateDelayViewerUpdated']),
|
||||
clickFile() {
|
||||
// Manual Action if a tree is selected/opened
|
||||
if (
|
||||
this.isTree &&
|
||||
this.$router.currentRoute.path === `/project${this.file.url}`
|
||||
) {
|
||||
if (this.isTree && this.$router.currentRoute.path === `/project${this.file.url}`) {
|
||||
this.toggleTreeOpen(this.file.path);
|
||||
}
|
||||
|
||||
|
@ -102,11 +101,15 @@ export default {
|
|||
:file="file"
|
||||
/>
|
||||
</span>
|
||||
<changed-file-icon
|
||||
:file="file"
|
||||
v-if="file.changed || file.tempFile"
|
||||
class="prepend-top-5 pull-right"
|
||||
/>
|
||||
<span class="pull-right">
|
||||
<mr-file-icon
|
||||
v-if="file.mrChange"
|
||||
/>
|
||||
<changed-file-icon
|
||||
:file="file"
|
||||
v-if="file.changed || file.tempFile"
|
||||
/>
|
||||
</span>
|
||||
<new-dropdown
|
||||
v-if="isTree"
|
||||
:project-id="file.projectId"
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
<script>
|
||||
import icon from '~/vue_shared/components/icon.vue';
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
import '~/lib/utils/datetime_utility';
|
||||
import icon from '~/vue_shared/components/icon.vue';
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
import '~/lib/utils/datetime_utility';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
icon,
|
||||
export default {
|
||||
components: {
|
||||
icon,
|
||||
},
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
props: {
|
||||
file: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
computed: {
|
||||
lockTooltip() {
|
||||
return `Locked by ${this.file.file_lock.user.name}`;
|
||||
},
|
||||
props: {
|
||||
file: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
lockTooltip() {
|
||||
return `Locked by ${this.file.file_lock.user.name}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -1,42 +1,46 @@
|
|||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
import RepoTab from './repo_tab.vue';
|
||||
import EditorMode from './editor_mode_dropdown.vue';
|
||||
import { mapActions } from 'vuex';
|
||||
import RepoTab from './repo_tab.vue';
|
||||
import EditorMode from './editor_mode_dropdown.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
RepoTab,
|
||||
EditorMode,
|
||||
export default {
|
||||
components: {
|
||||
RepoTab,
|
||||
EditorMode,
|
||||
},
|
||||
props: {
|
||||
files: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
props: {
|
||||
files: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
viewer: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
hasChanges: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
viewer: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showShadow: false,
|
||||
};
|
||||
hasChanges: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
updated() {
|
||||
if (!this.$refs.tabsScroller) return;
|
||||
mergeRequestId: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showShadow: false,
|
||||
};
|
||||
},
|
||||
updated() {
|
||||
if (!this.$refs.tabsScroller) return;
|
||||
|
||||
this.showShadow =
|
||||
this.$refs.tabsScroller.scrollWidth > this.$refs.tabsScroller.offsetWidth;
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['updateViewer']),
|
||||
},
|
||||
};
|
||||
this.showShadow = this.$refs.tabsScroller.scrollWidth > this.$refs.tabsScroller.offsetWidth;
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['updateViewer']),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -55,6 +59,7 @@
|
|||
:viewer="viewer"
|
||||
:show-shadow="showShadow"
|
||||
:has-changes="hasChanges"
|
||||
:merge-request-id="mergeRequestId"
|
||||
@click="updateViewer"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -44,7 +44,7 @@ const router = new VueRouter({
|
|||
component: EmptyRouterComponent,
|
||||
},
|
||||
{
|
||||
path: 'mr/:mrid',
|
||||
path: 'merge_requests/:mrid',
|
||||
component: EmptyRouterComponent,
|
||||
},
|
||||
],
|
||||
|
@ -76,9 +76,7 @@ router.beforeEach((to, from, next) => {
|
|||
.then(() => {
|
||||
if (to.params[0]) {
|
||||
const path =
|
||||
to.params[0].slice(-1) === '/'
|
||||
? to.params[0].slice(0, -1)
|
||||
: to.params[0];
|
||||
to.params[0].slice(-1) === '/' ? to.params[0].slice(0, -1) : to.params[0];
|
||||
const treeEntry = store.state.entries[path];
|
||||
if (treeEntry) {
|
||||
store.dispatch('handleTreeEntryAction', treeEntry);
|
||||
|
@ -96,6 +94,60 @@ router.beforeEach((to, from, next) => {
|
|||
);
|
||||
throw e;
|
||||
});
|
||||
} else if (to.params.mrid) {
|
||||
store.dispatch('updateViewer', 'mrdiff');
|
||||
|
||||
store
|
||||
.dispatch('getMergeRequestData', {
|
||||
projectId: fullProjectId,
|
||||
mergeRequestId: to.params.mrid,
|
||||
})
|
||||
.then(mr => {
|
||||
store.dispatch('getBranchData', {
|
||||
projectId: fullProjectId,
|
||||
branchId: mr.source_branch,
|
||||
});
|
||||
|
||||
return store.dispatch('getFiles', {
|
||||
projectId: fullProjectId,
|
||||
branchId: mr.source_branch,
|
||||
});
|
||||
})
|
||||
.then(() =>
|
||||
store.dispatch('getMergeRequestVersions', {
|
||||
projectId: fullProjectId,
|
||||
mergeRequestId: to.params.mrid,
|
||||
}),
|
||||
)
|
||||
.then(() =>
|
||||
store.dispatch('getMergeRequestChanges', {
|
||||
projectId: fullProjectId,
|
||||
mergeRequestId: to.params.mrid,
|
||||
}),
|
||||
)
|
||||
.then(mrChanges => {
|
||||
mrChanges.changes.forEach((change, ind) => {
|
||||
const changeTreeEntry = store.state.entries[change.new_path];
|
||||
|
||||
if (changeTreeEntry) {
|
||||
store.dispatch('setFileMrChange', {
|
||||
file: changeTreeEntry,
|
||||
mrChange: change,
|
||||
});
|
||||
|
||||
if (ind < 10) {
|
||||
store.dispatch('getFileData', {
|
||||
path: change.new_path,
|
||||
makeFileActive: ind === 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
flash('Error while loading the merge request. Please try again.');
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
|
|
|
@ -21,6 +21,15 @@ export default class Model {
|
|||
new this.monaco.Uri(null, null, this.file.path),
|
||||
)),
|
||||
);
|
||||
if (this.file.mrChange) {
|
||||
this.disposable.add(
|
||||
(this.baseModel = this.monaco.editor.createModel(
|
||||
this.file.baseRaw,
|
||||
undefined,
|
||||
new this.monaco.Uri(null, null, `target/${this.file.path}`),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
this.events = new Map();
|
||||
|
||||
|
@ -28,10 +37,7 @@ export default class Model {
|
|||
this.dispose = this.dispose.bind(this);
|
||||
|
||||
eventHub.$on(`editor.update.model.dispose.${this.file.path}`, this.dispose);
|
||||
eventHub.$on(
|
||||
`editor.update.model.content.${this.file.path}`,
|
||||
this.updateContent,
|
||||
);
|
||||
eventHub.$on(`editor.update.model.content.${this.file.path}`, this.updateContent);
|
||||
}
|
||||
|
||||
get url() {
|
||||
|
@ -58,6 +64,10 @@ export default class Model {
|
|||
return this.originalModel;
|
||||
}
|
||||
|
||||
getBaseModel() {
|
||||
return this.baseModel;
|
||||
}
|
||||
|
||||
setValue(value) {
|
||||
this.getModel().setValue(value);
|
||||
}
|
||||
|
@ -78,13 +88,7 @@ export default class Model {
|
|||
this.disposable.dispose();
|
||||
this.events.clear();
|
||||
|
||||
eventHub.$off(
|
||||
`editor.update.model.dispose.${this.file.path}`,
|
||||
this.dispose,
|
||||
);
|
||||
eventHub.$off(
|
||||
`editor.update.model.content.${this.file.path}`,
|
||||
this.updateContent,
|
||||
);
|
||||
eventHub.$off(`editor.update.model.dispose.${this.file.path}`, this.dispose);
|
||||
eventHub.$off(`editor.update.model.content.${this.file.path}`, this.updateContent);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,11 +109,19 @@ export default class Editor {
|
|||
if (this.dirtyDiffController) this.dirtyDiffController.reDecorate(model);
|
||||
}
|
||||
|
||||
attachMergeRequestModel(model) {
|
||||
this.instance.setModel({
|
||||
original: model.getBaseModel(),
|
||||
modified: model.getModel(),
|
||||
});
|
||||
|
||||
this.monaco.editor.createDiffNavigator(this.instance, {
|
||||
alwaysRevealFirst: true,
|
||||
});
|
||||
}
|
||||
|
||||
setupMonacoTheme() {
|
||||
this.monaco.editor.defineTheme(
|
||||
gitlabTheme.themeName,
|
||||
gitlabTheme.monacoTheme,
|
||||
);
|
||||
this.monaco.editor.defineTheme(gitlabTheme.themeName, gitlabTheme.monacoTheme);
|
||||
|
||||
this.monaco.editor.setTheme('gitlab');
|
||||
}
|
||||
|
@ -161,8 +169,6 @@ export default class Editor {
|
|||
onPositionChange(cb) {
|
||||
if (!this.instance.onDidChangeCursorPosition) return;
|
||||
|
||||
this.disposable.add(
|
||||
this.instance.onDidChangeCursorPosition(e => cb(this.instance, e)),
|
||||
);
|
||||
this.disposable.add(this.instance.onDidChangeCursorPosition(e => cb(this.instance, e)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,12 +20,35 @@ export default {
|
|||
return Promise.resolve(file.raw);
|
||||
}
|
||||
|
||||
return Vue.http.get(file.rawPath, { params: { format: 'json' } })
|
||||
return Vue.http.get(file.rawPath, { params: { format: 'json' } }).then(res => res.text());
|
||||
},
|
||||
getBaseRawFileData(file, sha) {
|
||||
if (file.tempFile) {
|
||||
return Promise.resolve(file.baseRaw);
|
||||
}
|
||||
|
||||
if (file.baseRaw) {
|
||||
return Promise.resolve(file.baseRaw);
|
||||
}
|
||||
|
||||
return Vue.http
|
||||
.get(file.rawPath.replace(`/raw/${file.branchId}/${file.path}`, `/raw/${sha}/${file.path}`), {
|
||||
params: { format: 'json' },
|
||||
})
|
||||
.then(res => res.text());
|
||||
},
|
||||
getProjectData(namespace, project) {
|
||||
return Api.project(`${namespace}/${project}`);
|
||||
},
|
||||
getProjectMergeRequestData(projectId, mergeRequestId) {
|
||||
return Api.mergeRequest(projectId, mergeRequestId);
|
||||
},
|
||||
getProjectMergeRequestChanges(projectId, mergeRequestId) {
|
||||
return Api.mergeRequestChanges(projectId, mergeRequestId);
|
||||
},
|
||||
getProjectMergeRequestVersions(projectId, mergeRequestId) {
|
||||
return Api.mergeRequestVersions(projectId, mergeRequestId);
|
||||
},
|
||||
getBranchData(projectId, currentBranchId) {
|
||||
return Api.branchSingle(projectId, currentBranchId);
|
||||
},
|
||||
|
|
|
@ -6,8 +6,7 @@ import FilesDecoratorWorker from './workers/files_decorator_worker';
|
|||
|
||||
export const redirectToUrl = (_, url) => visitUrl(url);
|
||||
|
||||
export const setInitialData = ({ commit }, data) =>
|
||||
commit(types.SET_INITIAL_DATA, data);
|
||||
export const setInitialData = ({ commit }, data) => commit(types.SET_INITIAL_DATA, data);
|
||||
|
||||
export const discardAllChanges = ({ state, commit, dispatch }) => {
|
||||
state.changedFiles.forEach(file => {
|
||||
|
@ -43,14 +42,11 @@ export const createTempEntry = (
|
|||
) =>
|
||||
new Promise(resolve => {
|
||||
const worker = new FilesDecoratorWorker();
|
||||
const fullName =
|
||||
name.slice(-1) !== '/' && type === 'tree' ? `${name}/` : name;
|
||||
const fullName = name.slice(-1) !== '/' && type === 'tree' ? `${name}/` : name;
|
||||
|
||||
if (state.entries[name]) {
|
||||
flash(
|
||||
`The name "${name
|
||||
.split('/')
|
||||
.pop()}" is already taken in this directory.`,
|
||||
`The name "${name.split('/').pop()}" is already taken in this directory.`,
|
||||
'alert',
|
||||
document,
|
||||
null,
|
||||
|
@ -119,3 +115,4 @@ export const updateDelayViewerUpdated = ({ commit }, delay) => {
|
|||
export * from './actions/tree';
|
||||
export * from './actions/file';
|
||||
export * from './actions/project';
|
||||
export * from './actions/merge_request';
|
||||
|
|
|
@ -46,53 +46,63 @@ export const setFileActive = ({ commit, state, getters, dispatch }, path) => {
|
|||
commit(types.SET_CURRENT_BRANCH, file.branchId);
|
||||
};
|
||||
|
||||
export const getFileData = ({ state, commit, dispatch }, file) => {
|
||||
export const getFileData = ({ state, commit, dispatch }, { path, makeFileActive = true }) => {
|
||||
const file = state.entries[path];
|
||||
commit(types.TOGGLE_LOADING, { entry: file });
|
||||
|
||||
return service
|
||||
.getFileData(file.url)
|
||||
.then(res => {
|
||||
const pageTitle = decodeURI(normalizeHeaders(res.headers)['PAGE-TITLE']);
|
||||
|
||||
setPageTitle(pageTitle);
|
||||
|
||||
return res.json();
|
||||
})
|
||||
.then(data => {
|
||||
commit(types.SET_FILE_DATA, { data, file });
|
||||
commit(types.TOGGLE_FILE_OPEN, file.path);
|
||||
dispatch('setFileActive', file.path);
|
||||
commit(types.TOGGLE_FILE_OPEN, path);
|
||||
if (makeFileActive) dispatch('setFileActive', path);
|
||||
commit(types.TOGGLE_LOADING, { entry: file });
|
||||
})
|
||||
.catch(() => {
|
||||
commit(types.TOGGLE_LOADING, { entry: file });
|
||||
flash(
|
||||
'Error loading file data. Please try again.',
|
||||
'alert',
|
||||
document,
|
||||
null,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
flash('Error loading file data. Please try again.', 'alert', document, null, false, true);
|
||||
});
|
||||
};
|
||||
|
||||
export const getRawFileData = ({ commit, dispatch }, file) =>
|
||||
service
|
||||
.getRawFileData(file)
|
||||
.then(raw => {
|
||||
commit(types.SET_FILE_RAW_DATA, { file, raw });
|
||||
})
|
||||
.catch(() =>
|
||||
flash(
|
||||
'Error loading file content. Please try again.',
|
||||
'alert',
|
||||
document,
|
||||
null,
|
||||
false,
|
||||
true,
|
||||
),
|
||||
);
|
||||
export const setFileMrChange = ({ state, commit }, { file, mrChange }) => {
|
||||
commit(types.SET_FILE_MERGE_REQUEST_CHANGE, { file, mrChange });
|
||||
};
|
||||
|
||||
export const getRawFileData = ({ state, commit, dispatch }, { path, baseSha }) => {
|
||||
const file = state.entries[path];
|
||||
return new Promise((resolve, reject) => {
|
||||
service
|
||||
.getRawFileData(file)
|
||||
.then(raw => {
|
||||
commit(types.SET_FILE_RAW_DATA, { file, raw });
|
||||
if (file.mrChange && file.mrChange.new_file === false) {
|
||||
service
|
||||
.getBaseRawFileData(file, baseSha)
|
||||
.then(baseRaw => {
|
||||
commit(types.SET_FILE_BASE_RAW_DATA, {
|
||||
file,
|
||||
baseRaw,
|
||||
});
|
||||
resolve(raw);
|
||||
})
|
||||
.catch(e => {
|
||||
reject(e);
|
||||
});
|
||||
} else {
|
||||
resolve(raw);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
flash('Error loading file content. Please try again.');
|
||||
reject();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const changeFileContent = ({ state, commit }, { path, content }) => {
|
||||
const file = state.entries[path];
|
||||
|
@ -119,10 +129,7 @@ export const setFileEOL = ({ getters, commit }, { eol }) => {
|
|||
}
|
||||
};
|
||||
|
||||
export const setEditorPosition = (
|
||||
{ getters, commit },
|
||||
{ editorRow, editorColumn },
|
||||
) => {
|
||||
export const setEditorPosition = ({ getters, commit }, { editorRow, editorColumn }) => {
|
||||
if (getters.activeFile) {
|
||||
commit(types.SET_FILE_POSITION, {
|
||||
file: getters.activeFile,
|
||||
|
|
84
app/assets/javascripts/ide/stores/actions/merge_request.js
Normal file
84
app/assets/javascripts/ide/stores/actions/merge_request.js
Normal file
|
@ -0,0 +1,84 @@
|
|||
import flash from '~/flash';
|
||||
import service from '../../services';
|
||||
import * as types from '../mutation_types';
|
||||
|
||||
export const getMergeRequestData = (
|
||||
{ commit, state, dispatch },
|
||||
{ projectId, mergeRequestId, force = false } = {},
|
||||
) =>
|
||||
new Promise((resolve, reject) => {
|
||||
if (!state.projects[projectId].mergeRequests[mergeRequestId] || force) {
|
||||
service
|
||||
.getProjectMergeRequestData(projectId, mergeRequestId)
|
||||
.then(res => res.data)
|
||||
.then(data => {
|
||||
commit(types.SET_MERGE_REQUEST, {
|
||||
projectPath: projectId,
|
||||
mergeRequestId,
|
||||
mergeRequest: data,
|
||||
});
|
||||
if (!state.currentMergeRequestId) {
|
||||
commit(types.SET_CURRENT_MERGE_REQUEST, mergeRequestId);
|
||||
}
|
||||
resolve(data);
|
||||
})
|
||||
.catch(() => {
|
||||
flash('Error loading merge request data. Please try again.');
|
||||
reject(new Error(`Merge Request not loaded ${projectId}`));
|
||||
});
|
||||
} else {
|
||||
resolve(state.projects[projectId].mergeRequests[mergeRequestId]);
|
||||
}
|
||||
});
|
||||
|
||||
export const getMergeRequestChanges = (
|
||||
{ commit, state, dispatch },
|
||||
{ projectId, mergeRequestId, force = false } = {},
|
||||
) =>
|
||||
new Promise((resolve, reject) => {
|
||||
if (!state.projects[projectId].mergeRequests[mergeRequestId].changes.length || force) {
|
||||
service
|
||||
.getProjectMergeRequestChanges(projectId, mergeRequestId)
|
||||
.then(res => res.data)
|
||||
.then(data => {
|
||||
commit(types.SET_MERGE_REQUEST_CHANGES, {
|
||||
projectPath: projectId,
|
||||
mergeRequestId,
|
||||
changes: data,
|
||||
});
|
||||
resolve(data);
|
||||
})
|
||||
.catch(() => {
|
||||
flash('Error loading merge request changes. Please try again.');
|
||||
reject(new Error(`Merge Request Changes not loaded ${projectId}`));
|
||||
});
|
||||
} else {
|
||||
resolve(state.projects[projectId].mergeRequests[mergeRequestId].changes);
|
||||
}
|
||||
});
|
||||
|
||||
export const getMergeRequestVersions = (
|
||||
{ commit, state, dispatch },
|
||||
{ projectId, mergeRequestId, force = false } = {},
|
||||
) =>
|
||||
new Promise((resolve, reject) => {
|
||||
if (!state.projects[projectId].mergeRequests[mergeRequestId].versions.length || force) {
|
||||
service
|
||||
.getProjectMergeRequestVersions(projectId, mergeRequestId)
|
||||
.then(res => res.data)
|
||||
.then(data => {
|
||||
commit(types.SET_MERGE_REQUEST_VERSIONS, {
|
||||
projectPath: projectId,
|
||||
mergeRequestId,
|
||||
versions: data,
|
||||
});
|
||||
resolve(data);
|
||||
})
|
||||
.catch(() => {
|
||||
flash('Error loading merge request versions. Please try again.');
|
||||
reject(new Error(`Merge Request Versions not loaded ${projectId}`));
|
||||
});
|
||||
} else {
|
||||
resolve(state.projects[projectId].mergeRequests[mergeRequestId].versions);
|
||||
}
|
||||
});
|
|
@ -2,9 +2,7 @@ import { normalizeHeaders } from '~/lib/utils/common_utils';
|
|||
import flash from '~/flash';
|
||||
import service from '../../services';
|
||||
import * as types from '../mutation_types';
|
||||
import {
|
||||
findEntry,
|
||||
} from '../utils';
|
||||
import { findEntry } from '../utils';
|
||||
import FilesDecoratorWorker from '../workers/files_decorator_worker';
|
||||
|
||||
export const toggleTreeOpen = ({ commit, dispatch }, path) => {
|
||||
|
@ -21,23 +19,24 @@ export const handleTreeEntryAction = ({ commit, dispatch }, row) => {
|
|||
|
||||
dispatch('setFileActive', row.path);
|
||||
} else {
|
||||
dispatch('getFileData', row);
|
||||
dispatch('getFileData', { path: row.path });
|
||||
}
|
||||
};
|
||||
|
||||
export const getLastCommitData = ({ state, commit, dispatch, getters }, tree = state) => {
|
||||
if (!tree || tree.lastCommitPath === null || !tree.lastCommitPath) return;
|
||||
|
||||
service.getTreeLastCommit(tree.lastCommitPath)
|
||||
.then((res) => {
|
||||
service
|
||||
.getTreeLastCommit(tree.lastCommitPath)
|
||||
.then(res => {
|
||||
const lastCommitPath = normalizeHeaders(res.headers)['MORE-LOGS-URL'] || null;
|
||||
|
||||
commit(types.SET_LAST_COMMIT_URL, { tree, url: lastCommitPath });
|
||||
|
||||
return res.json();
|
||||
})
|
||||
.then((data) => {
|
||||
data.forEach((lastCommit) => {
|
||||
.then(data => {
|
||||
data.forEach(lastCommit => {
|
||||
const entry = findEntry(tree.tree, lastCommit.type, lastCommit.file_name);
|
||||
|
||||
if (entry) {
|
||||
|
@ -50,44 +49,47 @@ export const getLastCommitData = ({ state, commit, dispatch, getters }, tree = s
|
|||
.catch(() => flash('Error fetching log data.', 'alert', document, null, false, true));
|
||||
};
|
||||
|
||||
export const getFiles = (
|
||||
{ state, commit, dispatch },
|
||||
{ projectId, branchId } = {},
|
||||
) => new Promise((resolve, reject) => {
|
||||
if (!state.trees[`${projectId}/${branchId}`]) {
|
||||
const selectedProject = state.projects[projectId];
|
||||
commit(types.CREATE_TREE, { treePath: `${projectId}/${branchId}` });
|
||||
export const getFiles = ({ state, commit, dispatch }, { projectId, branchId } = {}) =>
|
||||
new Promise((resolve, reject) => {
|
||||
if (!state.trees[`${projectId}/${branchId}`]) {
|
||||
const selectedProject = state.projects[projectId];
|
||||
commit(types.CREATE_TREE, { treePath: `${projectId}/${branchId}` });
|
||||
|
||||
service
|
||||
.getFiles(selectedProject.web_url, branchId)
|
||||
.then(res => res.json())
|
||||
.then((data) => {
|
||||
const worker = new FilesDecoratorWorker();
|
||||
worker.addEventListener('message', (e) => {
|
||||
const { entries, treeList } = e.data;
|
||||
const selectedTree = state.trees[`${projectId}/${branchId}`];
|
||||
service
|
||||
.getFiles(selectedProject.web_url, branchId)
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
const worker = new FilesDecoratorWorker();
|
||||
worker.addEventListener('message', e => {
|
||||
const { entries, treeList } = e.data;
|
||||
const selectedTree = state.trees[`${projectId}/${branchId}`];
|
||||
|
||||
commit(types.SET_ENTRIES, entries);
|
||||
commit(types.SET_DIRECTORY_DATA, { treePath: `${projectId}/${branchId}`, data: treeList });
|
||||
commit(types.TOGGLE_LOADING, { entry: selectedTree, forceValue: false });
|
||||
commit(types.SET_ENTRIES, entries);
|
||||
commit(types.SET_DIRECTORY_DATA, {
|
||||
treePath: `${projectId}/${branchId}`,
|
||||
data: treeList,
|
||||
});
|
||||
commit(types.TOGGLE_LOADING, {
|
||||
entry: selectedTree,
|
||||
forceValue: false,
|
||||
});
|
||||
|
||||
worker.terminate();
|
||||
worker.terminate();
|
||||
|
||||
resolve();
|
||||
resolve();
|
||||
});
|
||||
|
||||
worker.postMessage({
|
||||
data,
|
||||
projectId,
|
||||
branchId,
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
flash('Error loading tree data. Please try again.', 'alert', document, null, false, true);
|
||||
reject(e);
|
||||
});
|
||||
|
||||
worker.postMessage({
|
||||
data,
|
||||
projectId,
|
||||
branchId,
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
flash('Error loading tree data. Please try again.', 'alert', document, null, false, true);
|
||||
reject(e);
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
export const activeFile = state =>
|
||||
state.openFiles.find(file => file.active) || null;
|
||||
export const activeFile = state => state.openFiles.find(file => file.active) || null;
|
||||
|
||||
export const addedFiles = state => state.changedFiles.filter(f => f.tempFile);
|
||||
|
||||
export const modifiedFiles = state =>
|
||||
state.changedFiles.filter(f => !f.tempFile);
|
||||
export const modifiedFiles = state => state.changedFiles.filter(f => !f.tempFile);
|
||||
|
||||
export const projectsWithTrees = state =>
|
||||
Object.keys(state.projects).map(projectId => {
|
||||
|
@ -23,8 +21,17 @@ export const projectsWithTrees = state =>
|
|||
};
|
||||
});
|
||||
|
||||
export const currentMergeRequest = state => {
|
||||
if (state.projects[state.currentProjectId]) {
|
||||
return state.projects[state.currentProjectId].mergeRequests[state.currentMergeRequestId];
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-confusing-arrow
|
||||
export const currentIcon = state =>
|
||||
state.rightPanelCollapsed ? 'angle-double-left' : 'angle-double-right';
|
||||
|
||||
export const hasChanges = state => !!state.changedFiles.length;
|
||||
|
||||
export const hasMergeRequest = state => !!state.currentMergeRequestId;
|
||||
|
|
|
@ -11,6 +11,12 @@ export const SET_PROJECT = 'SET_PROJECT';
|
|||
export const SET_CURRENT_PROJECT = 'SET_CURRENT_PROJECT';
|
||||
export const TOGGLE_PROJECT_OPEN = 'TOGGLE_PROJECT_OPEN';
|
||||
|
||||
// Merge Request Mutation Types
|
||||
export const SET_MERGE_REQUEST = 'SET_MERGE_REQUEST';
|
||||
export const SET_CURRENT_MERGE_REQUEST = 'SET_CURRENT_MERGE_REQUEST';
|
||||
export const SET_MERGE_REQUEST_CHANGES = 'SET_MERGE_REQUEST_CHANGES';
|
||||
export const SET_MERGE_REQUEST_VERSIONS = 'SET_MERGE_REQUEST_VERSIONS';
|
||||
|
||||
// Branch Mutation Types
|
||||
export const SET_BRANCH = 'SET_BRANCH';
|
||||
export const SET_BRANCH_WORKING_REFERENCE = 'SET_BRANCH_WORKING_REFERENCE';
|
||||
|
@ -28,6 +34,7 @@ export const SET_FILE_DATA = 'SET_FILE_DATA';
|
|||
export const TOGGLE_FILE_OPEN = 'TOGGLE_FILE_OPEN';
|
||||
export const SET_FILE_ACTIVE = 'SET_FILE_ACTIVE';
|
||||
export const SET_FILE_RAW_DATA = 'SET_FILE_RAW_DATA';
|
||||
export const SET_FILE_BASE_RAW_DATA = 'SET_FILE_BASE_RAW_DATA';
|
||||
export const UPDATE_FILE_CONTENT = 'UPDATE_FILE_CONTENT';
|
||||
export const SET_FILE_LANGUAGE = 'SET_FILE_LANGUAGE';
|
||||
export const SET_FILE_POSITION = 'SET_FILE_POSITION';
|
||||
|
@ -39,5 +46,6 @@ export const TOGGLE_FILE_CHANGED = 'TOGGLE_FILE_CHANGED';
|
|||
export const SET_CURRENT_BRANCH = 'SET_CURRENT_BRANCH';
|
||||
export const SET_ENTRIES = 'SET_ENTRIES';
|
||||
export const CREATE_TMP_ENTRY = 'CREATE_TMP_ENTRY';
|
||||
export const SET_FILE_MERGE_REQUEST_CHANGE = 'SET_FILE_MERGE_REQUEST_CHANGE';
|
||||
export const UPDATE_VIEWER = 'UPDATE_VIEWER';
|
||||
export const UPDATE_DELAY_VIEWER_CHANGE = 'UPDATE_DELAY_VIEWER_CHANGE';
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import * as types from './mutation_types';
|
||||
import projectMutations from './mutations/project';
|
||||
import mergeRequestMutation from './mutations/merge_request';
|
||||
import fileMutations from './mutations/file';
|
||||
import treeMutations from './mutations/tree';
|
||||
import branchMutations from './mutations/branch';
|
||||
|
@ -11,10 +12,7 @@ export default {
|
|||
[types.TOGGLE_LOADING](state, { entry, forceValue = undefined }) {
|
||||
if (entry.path) {
|
||||
Object.assign(state.entries[entry.path], {
|
||||
loading:
|
||||
forceValue !== undefined
|
||||
? forceValue
|
||||
: !state.entries[entry.path].loading,
|
||||
loading: forceValue !== undefined ? forceValue : !state.entries[entry.path].loading,
|
||||
});
|
||||
} else {
|
||||
Object.assign(entry, {
|
||||
|
@ -83,9 +81,7 @@ export default {
|
|||
|
||||
if (!foundEntry) {
|
||||
Object.assign(state.trees[`${projectId}/${branchId}`], {
|
||||
tree: state.trees[`${projectId}/${branchId}`].tree.concat(
|
||||
data.treeList,
|
||||
),
|
||||
tree: state.trees[`${projectId}/${branchId}`].tree.concat(data.treeList),
|
||||
});
|
||||
}
|
||||
},
|
||||
|
@ -100,6 +96,7 @@ export default {
|
|||
});
|
||||
},
|
||||
...projectMutations,
|
||||
...mergeRequestMutation,
|
||||
...fileMutations,
|
||||
...treeMutations,
|
||||
...branchMutations,
|
||||
|
|
|
@ -28,6 +28,8 @@ export default {
|
|||
rawPath: data.raw_path,
|
||||
binary: data.binary,
|
||||
renderError: data.render_error,
|
||||
raw: null,
|
||||
baseRaw: null,
|
||||
});
|
||||
},
|
||||
[types.SET_FILE_RAW_DATA](state, { file, raw }) {
|
||||
|
@ -35,6 +37,11 @@ export default {
|
|||
raw,
|
||||
});
|
||||
},
|
||||
[types.SET_FILE_BASE_RAW_DATA](state, { file, baseRaw }) {
|
||||
Object.assign(state.entries[file.path], {
|
||||
baseRaw,
|
||||
});
|
||||
},
|
||||
[types.UPDATE_FILE_CONTENT](state, { path, content }) {
|
||||
const changed = content !== state.entries[path].raw;
|
||||
|
||||
|
@ -59,6 +66,11 @@ export default {
|
|||
editorColumn,
|
||||
});
|
||||
},
|
||||
[types.SET_FILE_MERGE_REQUEST_CHANGE](state, { file, mrChange }) {
|
||||
Object.assign(state.entries[file.path], {
|
||||
mrChange,
|
||||
});
|
||||
},
|
||||
[types.DISCARD_FILE_CHANGES](state, path) {
|
||||
Object.assign(state.entries[path], {
|
||||
content: state.entries[path].raw,
|
||||
|
|
33
app/assets/javascripts/ide/stores/mutations/merge_request.js
Normal file
33
app/assets/javascripts/ide/stores/mutations/merge_request.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
import * as types from '../mutation_types';
|
||||
|
||||
export default {
|
||||
[types.SET_CURRENT_MERGE_REQUEST](state, currentMergeRequestId) {
|
||||
Object.assign(state, {
|
||||
currentMergeRequestId,
|
||||
});
|
||||
},
|
||||
[types.SET_MERGE_REQUEST](state, { projectPath, mergeRequestId, mergeRequest }) {
|
||||
Object.assign(state.projects[projectPath], {
|
||||
mergeRequests: {
|
||||
[mergeRequestId]: {
|
||||
...mergeRequest,
|
||||
active: true,
|
||||
changes: [],
|
||||
versions: [],
|
||||
baseCommitSha: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
[types.SET_MERGE_REQUEST_CHANGES](state, { projectPath, mergeRequestId, changes }) {
|
||||
Object.assign(state.projects[projectPath].mergeRequests[mergeRequestId], {
|
||||
changes,
|
||||
});
|
||||
},
|
||||
[types.SET_MERGE_REQUEST_VERSIONS](state, { projectPath, mergeRequestId, versions }) {
|
||||
Object.assign(state.projects[projectPath].mergeRequests[mergeRequestId], {
|
||||
versions,
|
||||
baseCommitSha: versions.length ? versions[0].base_commit_sha : null,
|
||||
});
|
||||
},
|
||||
};
|
|
@ -11,6 +11,7 @@ export default {
|
|||
Object.assign(project, {
|
||||
tree: [],
|
||||
branches: {},
|
||||
mergeRequests: {},
|
||||
active: true,
|
||||
});
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
export default () => ({
|
||||
currentProjectId: '',
|
||||
currentBranchId: '',
|
||||
currentMergeRequestId: '',
|
||||
changedFiles: [],
|
||||
endpoints: {},
|
||||
lastCommitMsg: '',
|
||||
|
|
|
@ -38,7 +38,7 @@ export const dataStructure = () => ({
|
|||
eol: '',
|
||||
});
|
||||
|
||||
export const decorateData = (entity) => {
|
||||
export const decorateData = entity => {
|
||||
const {
|
||||
id,
|
||||
projectId,
|
||||
|
@ -57,7 +57,6 @@ export const decorateData = (entity) => {
|
|||
base64 = false,
|
||||
|
||||
file_lock,
|
||||
|
||||
} = entity;
|
||||
|
||||
return {
|
||||
|
@ -80,17 +79,15 @@ export const decorateData = (entity) => {
|
|||
base64,
|
||||
|
||||
file_lock,
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
export const findEntry = (tree, type, name, prop = 'name') => tree.find(
|
||||
f => f.type === type && f[prop] === name,
|
||||
);
|
||||
export const findEntry = (tree, type, name, prop = 'name') =>
|
||||
tree.find(f => f.type === type && f[prop] === name);
|
||||
|
||||
export const findIndexOfFile = (state, file) => state.findIndex(f => f.path === file.path);
|
||||
|
||||
export const setPageTitle = (title) => {
|
||||
export const setPageTitle = title => {
|
||||
document.title = title;
|
||||
};
|
||||
|
||||
|
@ -120,6 +117,11 @@ const sortTreesByTypeAndName = (a, b) => {
|
|||
return 0;
|
||||
};
|
||||
|
||||
export const sortTree = sortedTree => sortedTree.map(entity => Object.assign(entity, {
|
||||
tree: entity.tree.length ? sortTree(entity.tree) : [],
|
||||
})).sort(sortTreesByTypeAndName);
|
||||
export const sortTree = sortedTree =>
|
||||
sortedTree
|
||||
.map(entity =>
|
||||
Object.assign(entity, {
|
||||
tree: entity.tree.length ? sortTree(entity.tree) : [],
|
||||
}),
|
||||
)
|
||||
.sort(sortTreesByTypeAndName);
|
||||
|
|
|
@ -51,7 +51,7 @@ export function removeParams(params) {
|
|||
const url = document.createElement('a');
|
||||
url.href = window.location.href;
|
||||
|
||||
params.forEach((param) => {
|
||||
params.forEach(param => {
|
||||
url.search = removeParamQueryString(url.search, param);
|
||||
});
|
||||
|
||||
|
@ -83,3 +83,11 @@ export function refreshCurrentPage() {
|
|||
export function redirectTo(url) {
|
||||
return window.location.assign(url);
|
||||
}
|
||||
|
||||
export function webIDEUrl(route = undefined) {
|
||||
let returnUrl = `${gon.relative_url_root}/-/ide/`;
|
||||
if (route) {
|
||||
returnUrl += `project${route}`;
|
||||
}
|
||||
return returnUrl;
|
||||
}
|
||||
|
|
|
@ -1,53 +1,57 @@
|
|||
<script>
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
import { n__ } from '~/locale';
|
||||
import icon from '~/vue_shared/components/icon.vue';
|
||||
import clipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
import { n__ } from '~/locale';
|
||||
import { webIDEUrl } from '~/lib/utils/url_utility';
|
||||
import icon from '~/vue_shared/components/icon.vue';
|
||||
import clipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
|
||||
export default {
|
||||
name: 'MRWidgetHeader',
|
||||
directives: {
|
||||
tooltip,
|
||||
export default {
|
||||
name: 'MRWidgetHeader',
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
components: {
|
||||
icon,
|
||||
clipboardButton,
|
||||
},
|
||||
props: {
|
||||
mr: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
components: {
|
||||
icon,
|
||||
clipboardButton,
|
||||
},
|
||||
computed: {
|
||||
shouldShowCommitsBehindText() {
|
||||
return this.mr.divergedCommitsCount > 0;
|
||||
},
|
||||
props: {
|
||||
mr: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
commitsText() {
|
||||
return n__('%d commit behind', '%d commits behind', this.mr.divergedCommitsCount);
|
||||
},
|
||||
computed: {
|
||||
shouldShowCommitsBehindText() {
|
||||
return this.mr.divergedCommitsCount > 0;
|
||||
},
|
||||
commitsText() {
|
||||
return n__('%d commit behind', '%d commits behind', this.mr.divergedCommitsCount);
|
||||
},
|
||||
branchNameClipboardData() {
|
||||
// This supports code in app/assets/javascripts/copy_to_clipboard.js that
|
||||
// works around ClipboardJS limitations to allow the context-specific
|
||||
// copy/pasting of plain text or GFM.
|
||||
return JSON.stringify({
|
||||
text: this.mr.sourceBranch,
|
||||
gfm: `\`${this.mr.sourceBranch}\``,
|
||||
});
|
||||
},
|
||||
isSourceBranchLong() {
|
||||
return this.isBranchTitleLong(this.mr.sourceBranch);
|
||||
},
|
||||
isTargetBranchLong() {
|
||||
return this.isBranchTitleLong(this.mr.targetBranch);
|
||||
},
|
||||
branchNameClipboardData() {
|
||||
// This supports code in app/assets/javascripts/copy_to_clipboard.js that
|
||||
// works around ClipboardJS limitations to allow the context-specific
|
||||
// copy/pasting of plain text or GFM.
|
||||
return JSON.stringify({
|
||||
text: this.mr.sourceBranch,
|
||||
gfm: `\`${this.mr.sourceBranch}\``,
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
isBranchTitleLong(branchTitle) {
|
||||
return branchTitle.length > 32;
|
||||
},
|
||||
isSourceBranchLong() {
|
||||
return this.isBranchTitleLong(this.mr.sourceBranch);
|
||||
},
|
||||
};
|
||||
isTargetBranchLong() {
|
||||
return this.isBranchTitleLong(this.mr.targetBranch);
|
||||
},
|
||||
webIdePath() {
|
||||
return webIDEUrl(this.mr.statusPath.replace('.json', ''));
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isBranchTitleLong(branchTitle) {
|
||||
return branchTitle.length > 32;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="mr-source-target">
|
||||
|
@ -96,6 +100,13 @@
|
|||
</div>
|
||||
|
||||
<div v-if="mr.isOpen">
|
||||
<a
|
||||
v-if="!mr.sourceBranchRemoved"
|
||||
:href="webIdePath"
|
||||
class="btn btn-sm btn-default inline js-web-ide"
|
||||
>
|
||||
{{ s__("mrWidget|Web IDE") }}
|
||||
</a>
|
||||
<button
|
||||
data-target="#modal_merge_info"
|
||||
data-toggle="modal"
|
||||
|
|
|
@ -53,6 +53,7 @@
|
|||
flex: 1;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
max-width: inherit;
|
||||
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
|
|
|
@ -35,14 +35,14 @@ describe('Api', () => {
|
|||
});
|
||||
|
||||
describe('group', () => {
|
||||
it('fetches a group', (done) => {
|
||||
it('fetches a group', done => {
|
||||
const groupId = '123456';
|
||||
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}`;
|
||||
mock.onGet(expectedUrl).reply(200, {
|
||||
name: 'test',
|
||||
});
|
||||
|
||||
Api.group(groupId, (response) => {
|
||||
Api.group(groupId, response => {
|
||||
expect(response.name).toBe('test');
|
||||
done();
|
||||
});
|
||||
|
@ -50,15 +50,17 @@ describe('Api', () => {
|
|||
});
|
||||
|
||||
describe('groups', () => {
|
||||
it('fetches groups', (done) => {
|
||||
it('fetches groups', done => {
|
||||
const query = 'dummy query';
|
||||
const options = { unused: 'option' };
|
||||
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups.json`;
|
||||
mock.onGet(expectedUrl).reply(200, [{
|
||||
name: 'test',
|
||||
}]);
|
||||
mock.onGet(expectedUrl).reply(200, [
|
||||
{
|
||||
name: 'test',
|
||||
},
|
||||
]);
|
||||
|
||||
Api.groups(query, options, (response) => {
|
||||
Api.groups(query, options, response => {
|
||||
expect(response.length).toBe(1);
|
||||
expect(response[0].name).toBe('test');
|
||||
done();
|
||||
|
@ -67,14 +69,16 @@ describe('Api', () => {
|
|||
});
|
||||
|
||||
describe('namespaces', () => {
|
||||
it('fetches namespaces', (done) => {
|
||||
it('fetches namespaces', done => {
|
||||
const query = 'dummy query';
|
||||
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/namespaces.json`;
|
||||
mock.onGet(expectedUrl).reply(200, [{
|
||||
name: 'test',
|
||||
}]);
|
||||
mock.onGet(expectedUrl).reply(200, [
|
||||
{
|
||||
name: 'test',
|
||||
},
|
||||
]);
|
||||
|
||||
Api.namespaces(query, (response) => {
|
||||
Api.namespaces(query, response => {
|
||||
expect(response.length).toBe(1);
|
||||
expect(response[0].name).toBe('test');
|
||||
done();
|
||||
|
@ -83,31 +87,35 @@ describe('Api', () => {
|
|||
});
|
||||
|
||||
describe('projects', () => {
|
||||
it('fetches projects with membership when logged in', (done) => {
|
||||
it('fetches projects with membership when logged in', done => {
|
||||
const query = 'dummy query';
|
||||
const options = { unused: 'option' };
|
||||
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json`;
|
||||
window.gon.current_user_id = 1;
|
||||
mock.onGet(expectedUrl).reply(200, [{
|
||||
name: 'test',
|
||||
}]);
|
||||
mock.onGet(expectedUrl).reply(200, [
|
||||
{
|
||||
name: 'test',
|
||||
},
|
||||
]);
|
||||
|
||||
Api.projects(query, options, (response) => {
|
||||
Api.projects(query, options, response => {
|
||||
expect(response.length).toBe(1);
|
||||
expect(response[0].name).toBe('test');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fetches projects without membership when not logged in', (done) => {
|
||||
it('fetches projects without membership when not logged in', done => {
|
||||
const query = 'dummy query';
|
||||
const options = { unused: 'option' };
|
||||
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json`;
|
||||
mock.onGet(expectedUrl).reply(200, [{
|
||||
name: 'test',
|
||||
}]);
|
||||
mock.onGet(expectedUrl).reply(200, [
|
||||
{
|
||||
name: 'test',
|
||||
},
|
||||
]);
|
||||
|
||||
Api.projects(query, options, (response) => {
|
||||
Api.projects(query, options, response => {
|
||||
expect(response.length).toBe(1);
|
||||
expect(response[0].name).toBe('test');
|
||||
done();
|
||||
|
@ -115,8 +123,65 @@ describe('Api', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('mergerequest', () => {
|
||||
it('fetches a merge request', done => {
|
||||
const projectPath = 'abc';
|
||||
const mergeRequestId = '123456';
|
||||
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}`;
|
||||
mock.onGet(expectedUrl).reply(200, {
|
||||
title: 'test',
|
||||
});
|
||||
|
||||
Api.mergeRequest(projectPath, mergeRequestId)
|
||||
.then(({ data }) => {
|
||||
expect(data.title).toBe('test');
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mergerequest changes', () => {
|
||||
it('fetches the changes of a merge request', done => {
|
||||
const projectPath = 'abc';
|
||||
const mergeRequestId = '123456';
|
||||
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}/changes`;
|
||||
mock.onGet(expectedUrl).reply(200, {
|
||||
title: 'test',
|
||||
});
|
||||
|
||||
Api.mergeRequestChanges(projectPath, mergeRequestId)
|
||||
.then(({ data }) => {
|
||||
expect(data.title).toBe('test');
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mergerequest versions', () => {
|
||||
it('fetches the versions of a merge request', done => {
|
||||
const projectPath = 'abc';
|
||||
const mergeRequestId = '123456';
|
||||
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}/versions`;
|
||||
mock.onGet(expectedUrl).reply(200, [
|
||||
{
|
||||
id: 123,
|
||||
},
|
||||
]);
|
||||
|
||||
Api.mergeRequestVersions(projectPath, mergeRequestId)
|
||||
.then(({ data }) => {
|
||||
expect(data.length).toBe(1);
|
||||
expect(data[0].id).toBe(123);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('newLabel', () => {
|
||||
it('creates a new label', (done) => {
|
||||
it('creates a new label', done => {
|
||||
const namespace = 'some namespace';
|
||||
const project = 'some project';
|
||||
const labelData = { some: 'data' };
|
||||
|
@ -124,36 +189,42 @@ describe('Api', () => {
|
|||
const expectedData = {
|
||||
label: labelData,
|
||||
};
|
||||
mock.onPost(expectedUrl).reply((config) => {
|
||||
mock.onPost(expectedUrl).reply(config => {
|
||||
expect(config.data).toBe(JSON.stringify(expectedData));
|
||||
|
||||
return [200, {
|
||||
name: 'test',
|
||||
}];
|
||||
return [
|
||||
200,
|
||||
{
|
||||
name: 'test',
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
Api.newLabel(namespace, project, labelData, (response) => {
|
||||
Api.newLabel(namespace, project, labelData, response => {
|
||||
expect(response.name).toBe('test');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a group label', (done) => {
|
||||
it('creates a group label', done => {
|
||||
const namespace = 'group/subgroup';
|
||||
const labelData = { some: 'data' };
|
||||
const expectedUrl = `${dummyUrlRoot}/groups/${namespace}/-/labels`;
|
||||
const expectedData = {
|
||||
label: labelData,
|
||||
};
|
||||
mock.onPost(expectedUrl).reply((config) => {
|
||||
mock.onPost(expectedUrl).reply(config => {
|
||||
expect(config.data).toBe(JSON.stringify(expectedData));
|
||||
|
||||
return [200, {
|
||||
name: 'test',
|
||||
}];
|
||||
return [
|
||||
200,
|
||||
{
|
||||
name: 'test',
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
Api.newLabel(namespace, undefined, labelData, (response) => {
|
||||
Api.newLabel(namespace, undefined, labelData, response => {
|
||||
expect(response.name).toBe('test');
|
||||
done();
|
||||
});
|
||||
|
@ -161,15 +232,17 @@ describe('Api', () => {
|
|||
});
|
||||
|
||||
describe('groupProjects', () => {
|
||||
it('fetches group projects', (done) => {
|
||||
it('fetches group projects', done => {
|
||||
const groupId = '123456';
|
||||
const query = 'dummy query';
|
||||
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}/projects.json`;
|
||||
mock.onGet(expectedUrl).reply(200, [{
|
||||
name: 'test',
|
||||
}]);
|
||||
mock.onGet(expectedUrl).reply(200, [
|
||||
{
|
||||
name: 'test',
|
||||
},
|
||||
]);
|
||||
|
||||
Api.groupProjects(groupId, query, (response) => {
|
||||
Api.groupProjects(groupId, query, response => {
|
||||
expect(response.length).toBe(1);
|
||||
expect(response[0].name).toBe('test');
|
||||
done();
|
||||
|
@ -178,13 +251,13 @@ describe('Api', () => {
|
|||
});
|
||||
|
||||
describe('licenseText', () => {
|
||||
it('fetches a license text', (done) => {
|
||||
it('fetches a license text', done => {
|
||||
const licenseKey = "driver's license";
|
||||
const data = { unused: 'option' };
|
||||
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/templates/licenses/${licenseKey}`;
|
||||
mock.onGet(expectedUrl).reply(200, 'test');
|
||||
|
||||
Api.licenseText(licenseKey, data, (response) => {
|
||||
Api.licenseText(licenseKey, data, response => {
|
||||
expect(response).toBe('test');
|
||||
done();
|
||||
});
|
||||
|
@ -192,12 +265,12 @@ describe('Api', () => {
|
|||
});
|
||||
|
||||
describe('gitignoreText', () => {
|
||||
it('fetches a gitignore text', (done) => {
|
||||
it('fetches a gitignore text', done => {
|
||||
const gitignoreKey = 'ignore git';
|
||||
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/templates/gitignores/${gitignoreKey}`;
|
||||
mock.onGet(expectedUrl).reply(200, 'test');
|
||||
|
||||
Api.gitignoreText(gitignoreKey, (response) => {
|
||||
Api.gitignoreText(gitignoreKey, response => {
|
||||
expect(response).toBe('test');
|
||||
done();
|
||||
});
|
||||
|
@ -205,12 +278,12 @@ describe('Api', () => {
|
|||
});
|
||||
|
||||
describe('gitlabCiYml', () => {
|
||||
it('fetches a .gitlab-ci.yml', (done) => {
|
||||
it('fetches a .gitlab-ci.yml', done => {
|
||||
const gitlabCiYmlKey = 'Y CI ML';
|
||||
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/templates/gitlab_ci_ymls/${gitlabCiYmlKey}`;
|
||||
mock.onGet(expectedUrl).reply(200, 'test');
|
||||
|
||||
Api.gitlabCiYml(gitlabCiYmlKey, (response) => {
|
||||
Api.gitlabCiYml(gitlabCiYmlKey, response => {
|
||||
expect(response).toBe('test');
|
||||
done();
|
||||
});
|
||||
|
@ -218,12 +291,12 @@ describe('Api', () => {
|
|||
});
|
||||
|
||||
describe('dockerfileYml', () => {
|
||||
it('fetches a Dockerfile', (done) => {
|
||||
it('fetches a Dockerfile', done => {
|
||||
const dockerfileYmlKey = 'a giant whale';
|
||||
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/templates/dockerfiles/${dockerfileYmlKey}`;
|
||||
mock.onGet(expectedUrl).reply(200, 'test');
|
||||
|
||||
Api.dockerfileYml(dockerfileYmlKey, (response) => {
|
||||
Api.dockerfileYml(dockerfileYmlKey, response => {
|
||||
expect(response).toBe('test');
|
||||
done();
|
||||
});
|
||||
|
@ -231,12 +304,14 @@ describe('Api', () => {
|
|||
});
|
||||
|
||||
describe('issueTemplate', () => {
|
||||
it('fetches an issue template', (done) => {
|
||||
it('fetches an issue template', done => {
|
||||
const namespace = 'some namespace';
|
||||
const project = 'some project';
|
||||
const templateKey = ' template #%?.key ';
|
||||
const templateType = 'template type';
|
||||
const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/templates/${templateType}/${encodeURIComponent(templateKey)}`;
|
||||
const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/templates/${templateType}/${encodeURIComponent(
|
||||
templateKey,
|
||||
)}`;
|
||||
mock.onGet(expectedUrl).reply(200, 'test');
|
||||
|
||||
Api.issueTemplate(namespace, project, templateKey, templateType, (error, response) => {
|
||||
|
@ -247,13 +322,15 @@ describe('Api', () => {
|
|||
});
|
||||
|
||||
describe('users', () => {
|
||||
it('fetches users', (done) => {
|
||||
it('fetches users', done => {
|
||||
const query = 'dummy query';
|
||||
const options = { unused: 'option' };
|
||||
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/users.json`;
|
||||
mock.onGet(expectedUrl).reply(200, [{
|
||||
name: 'test',
|
||||
}]);
|
||||
mock.onGet(expectedUrl).reply(200, [
|
||||
{
|
||||
name: 'test',
|
||||
},
|
||||
]);
|
||||
|
||||
Api.users(query, options)
|
||||
.then(({ data }) => {
|
||||
|
|
|
@ -11,6 +11,7 @@ describe('IDE changed file icon', () => {
|
|||
vm = createComponent(component, {
|
||||
file: {
|
||||
tempFile: false,
|
||||
changed: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -20,7 +21,7 @@ describe('IDE changed file icon', () => {
|
|||
});
|
||||
|
||||
describe('changedIcon', () => {
|
||||
it('equals file-modified when not a temp file', () => {
|
||||
it('equals file-modified when not a temp file and has changes', () => {
|
||||
expect(vm.changedIcon).toBe('file-modified');
|
||||
});
|
||||
|
||||
|
|
|
@ -89,6 +89,20 @@ describe('RepoEditor', () => {
|
|||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls createDiffInstance when viewer is a merge request diff', done => {
|
||||
vm.$store.state.viewer = 'mrdiff';
|
||||
|
||||
spyOn(vm.editor, 'createDiffInstance');
|
||||
|
||||
vm.createEditorInstance();
|
||||
|
||||
vm.$nextTick(() => {
|
||||
expect(vm.editor.createDiffInstance).toHaveBeenCalled();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setupEditor', () => {
|
||||
|
@ -134,4 +148,48 @@ describe('RepoEditor', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setup editor for merge request viewing', () => {
|
||||
beforeEach(done => {
|
||||
// Resetting as the main test setup has already done it
|
||||
vm.$destroy();
|
||||
resetStore(vm.$store);
|
||||
Editor.editorInstance.modelManager.dispose();
|
||||
|
||||
const f = {
|
||||
...file(),
|
||||
active: true,
|
||||
tempFile: true,
|
||||
html: 'testing',
|
||||
mrChange: { diff: 'ABC' },
|
||||
baseRaw: 'testing',
|
||||
content: 'test',
|
||||
};
|
||||
const RepoEditor = Vue.extend(repoEditor);
|
||||
vm = createComponentWithStore(RepoEditor, store, {
|
||||
file: f,
|
||||
});
|
||||
|
||||
vm.$store.state.openFiles.push(f);
|
||||
vm.$store.state.entries[f.path] = f;
|
||||
|
||||
vm.$store.state.viewer = 'mrdiff';
|
||||
|
||||
vm.monaco = true;
|
||||
|
||||
vm.$mount();
|
||||
|
||||
monacoLoader(['vs/editor/editor.main'], () => {
|
||||
setTimeout(done, 0);
|
||||
});
|
||||
});
|
||||
|
||||
it('attaches merge request model to editor when merge request diff', () => {
|
||||
spyOn(vm.editor, 'attachMergeRequestModel').and.callThrough();
|
||||
|
||||
vm.setupEditor();
|
||||
|
||||
expect(vm.editor.attachMergeRequestModel).toHaveBeenCalledWith(vm.model);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,6 +17,7 @@ describe('RepoTabs', () => {
|
|||
files: openedFiles,
|
||||
viewer: 'editor',
|
||||
hasChanges: false,
|
||||
hasMergeRequest: false,
|
||||
});
|
||||
openedFiles[0].active = true;
|
||||
|
||||
|
@ -56,6 +57,7 @@ describe('RepoTabs', () => {
|
|||
files: [],
|
||||
viewer: 'editor',
|
||||
hasChanges: false,
|
||||
hasMergeRequest: false,
|
||||
},
|
||||
'#test-app',
|
||||
);
|
||||
|
|
|
@ -11,7 +11,10 @@ describe('Multi-file editor library model', () => {
|
|||
spyOn(eventHub, '$on').and.callThrough();
|
||||
|
||||
monacoLoader(['vs/editor/editor.main'], () => {
|
||||
model = new Model(monaco, file('path'));
|
||||
const f = file('path');
|
||||
f.mrChange = { diff: 'ABC' };
|
||||
f.baseRaw = 'test';
|
||||
model = new Model(monaco, f);
|
||||
|
||||
done();
|
||||
});
|
||||
|
@ -21,9 +24,10 @@ describe('Multi-file editor library model', () => {
|
|||
model.dispose();
|
||||
});
|
||||
|
||||
it('creates original model & new model', () => {
|
||||
it('creates original model & base model & new model', () => {
|
||||
expect(model.originalModel).not.toBeNull();
|
||||
expect(model.model).not.toBeNull();
|
||||
expect(model.baseModel).not.toBeNull();
|
||||
});
|
||||
|
||||
it('adds eventHub listener', () => {
|
||||
|
@ -51,6 +55,12 @@ describe('Multi-file editor library model', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('getBaseModel', () => {
|
||||
it('returns base model', () => {
|
||||
expect(model.getBaseModel()).toBe(model.baseModel);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setValue', () => {
|
||||
it('updates models value', () => {
|
||||
model.setValue('testing 123');
|
||||
|
|
|
@ -143,6 +143,31 @@ describe('Multi-file editor library', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('attachMergeRequestModel', () => {
|
||||
let model;
|
||||
|
||||
beforeEach(() => {
|
||||
instance.createDiffInstance(document.createElement('div'));
|
||||
|
||||
const f = file();
|
||||
f.mrChanges = { diff: 'ABC' };
|
||||
f.baseRaw = 'testing';
|
||||
|
||||
model = instance.createModel(f);
|
||||
});
|
||||
|
||||
it('sets original & modified', () => {
|
||||
spyOn(instance.instance, 'setModel');
|
||||
|
||||
instance.attachMergeRequestModel(model);
|
||||
|
||||
expect(instance.instance.setModel).toHaveBeenCalledWith({
|
||||
original: model.getBaseModel(),
|
||||
modified: model.getModel(),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('clearEditor', () => {
|
||||
it('resets the editor model', () => {
|
||||
instance.createInstance(document.createElement('div'));
|
||||
|
|
|
@ -5,7 +5,7 @@ import router from '~/ide/ide_router';
|
|||
import eventHub from '~/ide/eventhub';
|
||||
import { file, resetStore } from '../../helpers';
|
||||
|
||||
describe('Multi-file store file actions', () => {
|
||||
describe('IDE store file actions', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(router, 'push');
|
||||
});
|
||||
|
@ -189,7 +189,7 @@ describe('Multi-file store file actions', () => {
|
|||
|
||||
it('calls the service', done => {
|
||||
store
|
||||
.dispatch('getFileData', localFile)
|
||||
.dispatch('getFileData', { path: localFile.path })
|
||||
.then(() => {
|
||||
expect(service.getFileData).toHaveBeenCalledWith('getFileDataURL');
|
||||
|
||||
|
@ -200,7 +200,7 @@ describe('Multi-file store file actions', () => {
|
|||
|
||||
it('sets the file data', done => {
|
||||
store
|
||||
.dispatch('getFileData', localFile)
|
||||
.dispatch('getFileData', { path: localFile.path })
|
||||
.then(() => {
|
||||
expect(localFile.blamePath).toBe('blame_path');
|
||||
|
||||
|
@ -211,7 +211,7 @@ describe('Multi-file store file actions', () => {
|
|||
|
||||
it('sets document title', done => {
|
||||
store
|
||||
.dispatch('getFileData', localFile)
|
||||
.dispatch('getFileData', { path: localFile.path })
|
||||
.then(() => {
|
||||
expect(document.title).toBe('testing getFileData');
|
||||
|
||||
|
@ -222,7 +222,7 @@ describe('Multi-file store file actions', () => {
|
|||
|
||||
it('sets the file as active', done => {
|
||||
store
|
||||
.dispatch('getFileData', localFile)
|
||||
.dispatch('getFileData', { path: localFile.path })
|
||||
.then(() => {
|
||||
expect(localFile.active).toBeTruthy();
|
||||
|
||||
|
@ -231,9 +231,20 @@ describe('Multi-file store file actions', () => {
|
|||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('sets the file not as active if we pass makeFileActive false', done => {
|
||||
store
|
||||
.dispatch('getFileData', { path: localFile.path, makeFileActive: false })
|
||||
.then(() => {
|
||||
expect(localFile.active).toBeFalsy();
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('adds the file to open files', done => {
|
||||
store
|
||||
.dispatch('getFileData', localFile)
|
||||
.dispatch('getFileData', { path: localFile.path })
|
||||
.then(() => {
|
||||
expect(store.state.openFiles.length).toBe(1);
|
||||
expect(store.state.openFiles[0].name).toBe(localFile.name);
|
||||
|
@ -256,7 +267,7 @@ describe('Multi-file store file actions', () => {
|
|||
|
||||
it('calls getRawFileData service method', done => {
|
||||
store
|
||||
.dispatch('getRawFileData', tmpFile)
|
||||
.dispatch('getRawFileData', { path: tmpFile.path })
|
||||
.then(() => {
|
||||
expect(service.getRawFileData).toHaveBeenCalledWith(tmpFile);
|
||||
|
||||
|
@ -267,7 +278,7 @@ describe('Multi-file store file actions', () => {
|
|||
|
||||
it('updates file raw data', done => {
|
||||
store
|
||||
.dispatch('getRawFileData', tmpFile)
|
||||
.dispatch('getRawFileData', { path: tmpFile.path })
|
||||
.then(() => {
|
||||
expect(tmpFile.raw).toBe('raw');
|
||||
|
||||
|
@ -275,6 +286,22 @@ describe('Multi-file store file actions', () => {
|
|||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('calls also getBaseRawFileData service method', done => {
|
||||
spyOn(service, 'getBaseRawFileData').and.returnValue(Promise.resolve('baseraw'));
|
||||
|
||||
tmpFile.mrChange = { new_file: false };
|
||||
|
||||
store
|
||||
.dispatch('getRawFileData', { path: tmpFile.path, baseSha: 'SHA' })
|
||||
.then(() => {
|
||||
expect(service.getBaseRawFileData).toHaveBeenCalledWith(tmpFile, 'SHA');
|
||||
expect(tmpFile.baseRaw).toBe('baseraw');
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('changeFileContent', () => {
|
||||
|
|
110
spec/javascripts/ide/stores/actions/merge_request_spec.js
Normal file
110
spec/javascripts/ide/stores/actions/merge_request_spec.js
Normal file
|
@ -0,0 +1,110 @@
|
|||
import store from '~/ide/stores';
|
||||
import service from '~/ide/services';
|
||||
import { resetStore } from '../../helpers';
|
||||
|
||||
describe('IDE store merge request actions', () => {
|
||||
beforeEach(() => {
|
||||
store.state.projects.abcproject = {
|
||||
mergeRequests: {},
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetStore(store);
|
||||
});
|
||||
|
||||
describe('getMergeRequestData', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(service, 'getProjectMergeRequestData').and.returnValue(
|
||||
Promise.resolve({ data: { title: 'mergerequest' } }),
|
||||
);
|
||||
});
|
||||
|
||||
it('calls getProjectMergeRequestData service method', done => {
|
||||
store
|
||||
.dispatch('getMergeRequestData', { projectId: 'abcproject', mergeRequestId: 1 })
|
||||
.then(() => {
|
||||
expect(service.getProjectMergeRequestData).toHaveBeenCalledWith('abcproject', 1);
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('sets the Merge Request Object', done => {
|
||||
store
|
||||
.dispatch('getMergeRequestData', { projectId: 'abcproject', mergeRequestId: 1 })
|
||||
.then(() => {
|
||||
expect(store.state.projects.abcproject.mergeRequests['1'].title).toBe('mergerequest');
|
||||
expect(store.state.currentMergeRequestId).toBe(1);
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMergeRequestChanges', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(service, 'getProjectMergeRequestChanges').and.returnValue(
|
||||
Promise.resolve({ data: { title: 'mergerequest' } }),
|
||||
);
|
||||
|
||||
store.state.projects.abcproject.mergeRequests['1'] = { changes: [] };
|
||||
});
|
||||
|
||||
it('calls getProjectMergeRequestChanges service method', done => {
|
||||
store
|
||||
.dispatch('getMergeRequestChanges', { projectId: 'abcproject', mergeRequestId: 1 })
|
||||
.then(() => {
|
||||
expect(service.getProjectMergeRequestChanges).toHaveBeenCalledWith('abcproject', 1);
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('sets the Merge Request Changes Object', done => {
|
||||
store
|
||||
.dispatch('getMergeRequestChanges', { projectId: 'abcproject', mergeRequestId: 1 })
|
||||
.then(() => {
|
||||
expect(store.state.projects.abcproject.mergeRequests['1'].changes.title).toBe(
|
||||
'mergerequest',
|
||||
);
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMergeRequestVersions', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(service, 'getProjectMergeRequestVersions').and.returnValue(
|
||||
Promise.resolve({ data: [{ id: 789 }] }),
|
||||
);
|
||||
|
||||
store.state.projects.abcproject.mergeRequests['1'] = { versions: [] };
|
||||
});
|
||||
|
||||
it('calls getProjectMergeRequestVersions service method', done => {
|
||||
store
|
||||
.dispatch('getMergeRequestVersions', { projectId: 'abcproject', mergeRequestId: 1 })
|
||||
.then(() => {
|
||||
expect(service.getProjectMergeRequestVersions).toHaveBeenCalledWith('abcproject', 1);
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('sets the Merge Request Versions Object', done => {
|
||||
store
|
||||
.dispatch('getMergeRequestVersions', { projectId: 'abcproject', mergeRequestId: 1 })
|
||||
.then(() => {
|
||||
expect(store.state.projects.abcproject.mergeRequests['1'].versions.length).toBe(1);
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -68,9 +68,7 @@ describe('Multi-file store tree actions', () => {
|
|||
expect(projectTree.tree[0].tree[1].name).toBe('fileinfolder.js');
|
||||
expect(projectTree.tree[1].type).toBe('blob');
|
||||
expect(projectTree.tree[0].tree[0].tree[0].type).toBe('blob');
|
||||
expect(projectTree.tree[0].tree[0].tree[0].name).toBe(
|
||||
'fileinsubfolder.js',
|
||||
);
|
||||
expect(projectTree.tree[0].tree[0].tree[0].name).toBe('fileinsubfolder.js');
|
||||
|
||||
done();
|
||||
})
|
||||
|
@ -132,9 +130,7 @@ describe('Multi-file store tree actions', () => {
|
|||
store
|
||||
.dispatch('getLastCommitData', projectTree)
|
||||
.then(() => {
|
||||
expect(service.getTreeLastCommit).toHaveBeenCalledWith(
|
||||
'lastcommitpath',
|
||||
);
|
||||
expect(service.getTreeLastCommit).toHaveBeenCalledWith('lastcommitpath');
|
||||
|
||||
done();
|
||||
})
|
||||
|
@ -160,9 +156,7 @@ describe('Multi-file store tree actions', () => {
|
|||
.dispatch('getLastCommitData', projectTree)
|
||||
.then(Vue.nextTick)
|
||||
.then(() => {
|
||||
expect(projectTree.tree[0].lastCommit.message).not.toBe(
|
||||
'commit message',
|
||||
);
|
||||
expect(projectTree.tree[0].lastCommit.message).not.toBe('commit message');
|
||||
|
||||
done();
|
||||
})
|
||||
|
|
|
@ -2,7 +2,7 @@ import * as getters from '~/ide/stores/getters';
|
|||
import state from '~/ide/stores/state';
|
||||
import { file } from '../helpers';
|
||||
|
||||
describe('Multi-file store getters', () => {
|
||||
describe('IDE store getters', () => {
|
||||
let localState;
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -52,4 +52,24 @@ describe('Multi-file store getters', () => {
|
|||
expect(modifiedFiles[0].name).toBe('added');
|
||||
});
|
||||
});
|
||||
|
||||
describe('currentMergeRequest', () => {
|
||||
it('returns Current Merge Request', () => {
|
||||
localState.currentProjectId = 'abcproject';
|
||||
localState.currentMergeRequestId = 1;
|
||||
localState.projects.abcproject = {
|
||||
mergeRequests: {
|
||||
1: { mergeId: 1 },
|
||||
},
|
||||
};
|
||||
|
||||
expect(getters.currentMergeRequest(localState).mergeId).toBe(1);
|
||||
});
|
||||
|
||||
it('returns null if no active Merge Request was found', () => {
|
||||
localState.currentProjectId = 'otherproject';
|
||||
|
||||
expect(getters.currentMergeRequest(localState)).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,7 +2,7 @@ import mutations from '~/ide/stores/mutations/file';
|
|||
import state from '~/ide/stores/state';
|
||||
import { file } from '../../helpers';
|
||||
|
||||
describe('Multi-file store file mutations', () => {
|
||||
describe('IDE store file mutations', () => {
|
||||
let localState;
|
||||
let localFile;
|
||||
|
||||
|
@ -62,6 +62,8 @@ describe('Multi-file store file mutations', () => {
|
|||
expect(localFile.rawPath).toBe('raw');
|
||||
expect(localFile.binary).toBeTruthy();
|
||||
expect(localFile.renderError).toBe('render_error');
|
||||
expect(localFile.raw).toBeNull();
|
||||
expect(localFile.baseRaw).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -76,6 +78,17 @@ describe('Multi-file store file mutations', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('SET_FILE_BASE_RAW_DATA', () => {
|
||||
it('sets raw data from base branch', () => {
|
||||
mutations.SET_FILE_BASE_RAW_DATA(localState, {
|
||||
file: localFile,
|
||||
baseRaw: 'testing',
|
||||
});
|
||||
|
||||
expect(localFile.baseRaw).toBe('testing');
|
||||
});
|
||||
});
|
||||
|
||||
describe('UPDATE_FILE_CONTENT', () => {
|
||||
beforeEach(() => {
|
||||
localFile.raw = 'test';
|
||||
|
@ -112,6 +125,17 @@ describe('Multi-file store file mutations', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('SET_FILE_MERGE_REQUEST_CHANGE', () => {
|
||||
it('sets file mr change', () => {
|
||||
mutations.SET_FILE_MERGE_REQUEST_CHANGE(localState, {
|
||||
file: localFile,
|
||||
mrChange: { diff: 'ABC' },
|
||||
});
|
||||
|
||||
expect(localFile.mrChange.diff).toBe('ABC');
|
||||
});
|
||||
});
|
||||
|
||||
describe('DISCARD_FILE_CHANGES', () => {
|
||||
beforeEach(() => {
|
||||
localFile.content = 'test';
|
||||
|
|
65
spec/javascripts/ide/stores/mutations/merge_request_spec.js
Normal file
65
spec/javascripts/ide/stores/mutations/merge_request_spec.js
Normal file
|
@ -0,0 +1,65 @@
|
|||
import mutations from '~/ide/stores/mutations/merge_request';
|
||||
import state from '~/ide/stores/state';
|
||||
|
||||
describe('IDE store merge request mutations', () => {
|
||||
let localState;
|
||||
|
||||
beforeEach(() => {
|
||||
localState = state();
|
||||
localState.projects = { abcproject: { mergeRequests: {} } };
|
||||
|
||||
mutations.SET_MERGE_REQUEST(localState, {
|
||||
projectPath: 'abcproject',
|
||||
mergeRequestId: 1,
|
||||
mergeRequest: {
|
||||
title: 'mr',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('SET_CURRENT_MERGE_REQUEST', () => {
|
||||
it('sets current merge request', () => {
|
||||
mutations.SET_CURRENT_MERGE_REQUEST(localState, 2);
|
||||
|
||||
expect(localState.currentMergeRequestId).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('SET_MERGE_REQUEST', () => {
|
||||
it('setsmerge request data', () => {
|
||||
const newMr = localState.projects.abcproject.mergeRequests[1];
|
||||
|
||||
expect(newMr.title).toBe('mr');
|
||||
expect(newMr.active).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('SET_MERGE_REQUEST_CHANGES', () => {
|
||||
it('sets merge request changes', () => {
|
||||
mutations.SET_MERGE_REQUEST_CHANGES(localState, {
|
||||
projectPath: 'abcproject',
|
||||
mergeRequestId: 1,
|
||||
changes: {
|
||||
diff: 'abc',
|
||||
},
|
||||
});
|
||||
|
||||
const newMr = localState.projects.abcproject.mergeRequests[1];
|
||||
expect(newMr.changes.diff).toBe('abc');
|
||||
});
|
||||
});
|
||||
|
||||
describe('SET_MERGE_REQUEST_VERSIONS', () => {
|
||||
it('sets merge request versions', () => {
|
||||
mutations.SET_MERGE_REQUEST_VERSIONS(localState, {
|
||||
projectPath: 'abcproject',
|
||||
mergeRequestId: 1,
|
||||
versions: [{ id: 123 }],
|
||||
});
|
||||
|
||||
const newMr = localState.projects.abcproject.mergeRequests[1];
|
||||
expect(newMr.versions.length).toBe(1);
|
||||
expect(newMr.versions[0].id).toBe(123);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -17,46 +17,58 @@ describe('MRWidgetHeader', () => {
|
|||
describe('computed', () => {
|
||||
describe('shouldShowCommitsBehindText', () => {
|
||||
it('return true when there are divergedCommitsCount', () => {
|
||||
vm = mountComponent(Component, { mr: {
|
||||
divergedCommitsCount: 12,
|
||||
sourceBranch: 'mr-widget-refactor',
|
||||
sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">Link</a>',
|
||||
targetBranch: 'master',
|
||||
} });
|
||||
vm = mountComponent(Component, {
|
||||
mr: {
|
||||
divergedCommitsCount: 12,
|
||||
sourceBranch: 'mr-widget-refactor',
|
||||
sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">Link</a>',
|
||||
targetBranch: 'master',
|
||||
statusPath: 'abc',
|
||||
},
|
||||
});
|
||||
|
||||
expect(vm.shouldShowCommitsBehindText).toEqual(true);
|
||||
});
|
||||
|
||||
it('returns false where there are no divergedComits count', () => {
|
||||
vm = mountComponent(Component, { mr: {
|
||||
divergedCommitsCount: 0,
|
||||
sourceBranch: 'mr-widget-refactor',
|
||||
sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">Link</a>',
|
||||
targetBranch: 'master',
|
||||
} });
|
||||
vm = mountComponent(Component, {
|
||||
mr: {
|
||||
divergedCommitsCount: 0,
|
||||
sourceBranch: 'mr-widget-refactor',
|
||||
sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">Link</a>',
|
||||
targetBranch: 'master',
|
||||
statusPath: 'abc',
|
||||
},
|
||||
});
|
||||
expect(vm.shouldShowCommitsBehindText).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('commitsText', () => {
|
||||
it('returns singular when there is one commit', () => {
|
||||
vm = mountComponent(Component, { mr: {
|
||||
divergedCommitsCount: 1,
|
||||
sourceBranch: 'mr-widget-refactor',
|
||||
sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">Link</a>',
|
||||
targetBranch: 'master',
|
||||
} });
|
||||
vm = mountComponent(Component, {
|
||||
mr: {
|
||||
divergedCommitsCount: 1,
|
||||
sourceBranch: 'mr-widget-refactor',
|
||||
sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">Link</a>',
|
||||
targetBranch: 'master',
|
||||
statusPath: 'abc',
|
||||
},
|
||||
});
|
||||
|
||||
expect(vm.commitsText).toEqual('1 commit behind');
|
||||
});
|
||||
|
||||
it('returns plural when there is more than one commit', () => {
|
||||
vm = mountComponent(Component, { mr: {
|
||||
divergedCommitsCount: 2,
|
||||
sourceBranch: 'mr-widget-refactor',
|
||||
sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">Link</a>',
|
||||
targetBranch: 'master',
|
||||
} });
|
||||
vm = mountComponent(Component, {
|
||||
mr: {
|
||||
divergedCommitsCount: 2,
|
||||
sourceBranch: 'mr-widget-refactor',
|
||||
sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">Link</a>',
|
||||
targetBranch: 'master',
|
||||
statusPath: 'abc',
|
||||
},
|
||||
});
|
||||
|
||||
expect(vm.commitsText).toEqual('2 commits behind');
|
||||
});
|
||||
|
@ -66,24 +78,27 @@ describe('MRWidgetHeader', () => {
|
|||
describe('template', () => {
|
||||
describe('common elements', () => {
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(Component, { mr: {
|
||||
divergedCommitsCount: 12,
|
||||
sourceBranch: 'mr-widget-refactor',
|
||||
sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
|
||||
sourceBranchRemoved: false,
|
||||
targetBranchPath: 'foo/bar/commits-path',
|
||||
targetBranchTreePath: 'foo/bar/tree/path',
|
||||
targetBranch: 'master',
|
||||
isOpen: true,
|
||||
emailPatchesPath: '/mr/email-patches',
|
||||
plainDiffPath: '/mr/plainDiffPath',
|
||||
} });
|
||||
vm = mountComponent(Component, {
|
||||
mr: {
|
||||
divergedCommitsCount: 12,
|
||||
sourceBranch: 'mr-widget-refactor',
|
||||
sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
|
||||
sourceBranchRemoved: false,
|
||||
targetBranchPath: 'foo/bar/commits-path',
|
||||
targetBranchTreePath: 'foo/bar/tree/path',
|
||||
targetBranch: 'master',
|
||||
isOpen: true,
|
||||
emailPatchesPath: '/mr/email-patches',
|
||||
plainDiffPath: '/mr/plainDiffPath',
|
||||
statusPath: 'abc',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('renders source branch link', () => {
|
||||
expect(
|
||||
vm.$el.querySelector('.js-source-branch').innerHTML,
|
||||
).toEqual('<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>');
|
||||
expect(vm.$el.querySelector('.js-source-branch').innerHTML).toEqual(
|
||||
'<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
|
||||
);
|
||||
});
|
||||
|
||||
it('renders clipboard button', () => {
|
||||
|
@ -101,18 +116,21 @@ describe('MRWidgetHeader', () => {
|
|||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(Component, { mr: {
|
||||
divergedCommitsCount: 12,
|
||||
sourceBranch: 'mr-widget-refactor',
|
||||
sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
|
||||
sourceBranchRemoved: false,
|
||||
targetBranchPath: 'foo/bar/commits-path',
|
||||
targetBranchTreePath: 'foo/bar/tree/path',
|
||||
targetBranch: 'master',
|
||||
isOpen: true,
|
||||
emailPatchesPath: '/mr/email-patches',
|
||||
plainDiffPath: '/mr/plainDiffPath',
|
||||
} });
|
||||
vm = mountComponent(Component, {
|
||||
mr: {
|
||||
divergedCommitsCount: 12,
|
||||
sourceBranch: 'mr-widget-refactor',
|
||||
sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
|
||||
sourceBranchRemoved: false,
|
||||
targetBranchPath: 'foo/bar/commits-path',
|
||||
targetBranchTreePath: 'foo/bar/tree/path',
|
||||
targetBranch: 'master',
|
||||
isOpen: true,
|
||||
emailPatchesPath: '/mr/email-patches',
|
||||
plainDiffPath: '/mr/plainDiffPath',
|
||||
statusPath: 'abc',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('renders checkout branch button with modal trigger', () => {
|
||||
|
@ -123,39 +141,49 @@ describe('MRWidgetHeader', () => {
|
|||
expect(button.getAttribute('data-toggle')).toEqual('modal');
|
||||
});
|
||||
|
||||
it('renders web ide button', () => {
|
||||
const button = vm.$el.querySelector('.js-web-ide');
|
||||
|
||||
expect(button.textContent.trim()).toEqual('Web IDE');
|
||||
expect(button.getAttribute('href')).toEqual('undefined/-/ide/projectabc');
|
||||
});
|
||||
|
||||
it('renders download dropdown with links', () => {
|
||||
expect(
|
||||
vm.$el.querySelector('.js-download-email-patches').textContent.trim(),
|
||||
).toEqual('Email patches');
|
||||
expect(vm.$el.querySelector('.js-download-email-patches').textContent.trim()).toEqual(
|
||||
'Email patches',
|
||||
);
|
||||
|
||||
expect(
|
||||
vm.$el.querySelector('.js-download-email-patches').getAttribute('href'),
|
||||
).toEqual('/mr/email-patches');
|
||||
expect(vm.$el.querySelector('.js-download-email-patches').getAttribute('href')).toEqual(
|
||||
'/mr/email-patches',
|
||||
);
|
||||
|
||||
expect(
|
||||
vm.$el.querySelector('.js-download-plain-diff').textContent.trim(),
|
||||
).toEqual('Plain diff');
|
||||
expect(vm.$el.querySelector('.js-download-plain-diff').textContent.trim()).toEqual(
|
||||
'Plain diff',
|
||||
);
|
||||
|
||||
expect(
|
||||
vm.$el.querySelector('.js-download-plain-diff').getAttribute('href'),
|
||||
).toEqual('/mr/plainDiffPath');
|
||||
expect(vm.$el.querySelector('.js-download-plain-diff').getAttribute('href')).toEqual(
|
||||
'/mr/plainDiffPath',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with a closed merge request', () => {
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(Component, { mr: {
|
||||
divergedCommitsCount: 12,
|
||||
sourceBranch: 'mr-widget-refactor',
|
||||
sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
|
||||
sourceBranchRemoved: false,
|
||||
targetBranchPath: 'foo/bar/commits-path',
|
||||
targetBranchTreePath: 'foo/bar/tree/path',
|
||||
targetBranch: 'master',
|
||||
isOpen: false,
|
||||
emailPatchesPath: '/mr/email-patches',
|
||||
plainDiffPath: '/mr/plainDiffPath',
|
||||
} });
|
||||
vm = mountComponent(Component, {
|
||||
mr: {
|
||||
divergedCommitsCount: 12,
|
||||
sourceBranch: 'mr-widget-refactor',
|
||||
sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
|
||||
sourceBranchRemoved: false,
|
||||
targetBranchPath: 'foo/bar/commits-path',
|
||||
targetBranchTreePath: 'foo/bar/tree/path',
|
||||
targetBranch: 'master',
|
||||
isOpen: false,
|
||||
emailPatchesPath: '/mr/email-patches',
|
||||
plainDiffPath: '/mr/plainDiffPath',
|
||||
statusPath: 'abc',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('does not render checkout branch button with modal trigger', () => {
|
||||
|
@ -165,30 +193,29 @@ describe('MRWidgetHeader', () => {
|
|||
});
|
||||
|
||||
it('does not render download dropdown with links', () => {
|
||||
expect(
|
||||
vm.$el.querySelector('.js-download-email-patches'),
|
||||
).toEqual(null);
|
||||
expect(vm.$el.querySelector('.js-download-email-patches')).toEqual(null);
|
||||
|
||||
expect(
|
||||
vm.$el.querySelector('.js-download-plain-diff'),
|
||||
).toEqual(null);
|
||||
expect(vm.$el.querySelector('.js-download-plain-diff')).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('without diverged commits', () => {
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(Component, { mr: {
|
||||
divergedCommitsCount: 0,
|
||||
sourceBranch: 'mr-widget-refactor',
|
||||
sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
|
||||
sourceBranchRemoved: false,
|
||||
targetBranchPath: 'foo/bar/commits-path',
|
||||
targetBranchTreePath: 'foo/bar/tree/path',
|
||||
targetBranch: 'master',
|
||||
isOpen: true,
|
||||
emailPatchesPath: '/mr/email-patches',
|
||||
plainDiffPath: '/mr/plainDiffPath',
|
||||
} });
|
||||
vm = mountComponent(Component, {
|
||||
mr: {
|
||||
divergedCommitsCount: 0,
|
||||
sourceBranch: 'mr-widget-refactor',
|
||||
sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
|
||||
sourceBranchRemoved: false,
|
||||
targetBranchPath: 'foo/bar/commits-path',
|
||||
targetBranchTreePath: 'foo/bar/tree/path',
|
||||
targetBranch: 'master',
|
||||
isOpen: true,
|
||||
emailPatchesPath: '/mr/email-patches',
|
||||
plainDiffPath: '/mr/plainDiffPath',
|
||||
statusPath: 'abc',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('does not render diverged commits info', () => {
|
||||
|
@ -198,22 +225,27 @@ describe('MRWidgetHeader', () => {
|
|||
|
||||
describe('with diverged commits', () => {
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(Component, { mr: {
|
||||
divergedCommitsCount: 12,
|
||||
sourceBranch: 'mr-widget-refactor',
|
||||
sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
|
||||
sourceBranchRemoved: false,
|
||||
targetBranchPath: 'foo/bar/commits-path',
|
||||
targetBranchTreePath: 'foo/bar/tree/path',
|
||||
targetBranch: 'master',
|
||||
isOpen: true,
|
||||
emailPatchesPath: '/mr/email-patches',
|
||||
plainDiffPath: '/mr/plainDiffPath',
|
||||
} });
|
||||
vm = mountComponent(Component, {
|
||||
mr: {
|
||||
divergedCommitsCount: 12,
|
||||
sourceBranch: 'mr-widget-refactor',
|
||||
sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
|
||||
sourceBranchRemoved: false,
|
||||
targetBranchPath: 'foo/bar/commits-path',
|
||||
targetBranchTreePath: 'foo/bar/tree/path',
|
||||
targetBranch: 'master',
|
||||
isOpen: true,
|
||||
emailPatchesPath: '/mr/email-patches',
|
||||
plainDiffPath: '/mr/plainDiffPath',
|
||||
statusPath: 'abc',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('renders diverged commits info', () => {
|
||||
expect(vm.$el.querySelector('.diverged-commits-count').textContent.trim()).toEqual('(12 commits behind)');
|
||||
expect(vm.$el.querySelector('.diverged-commits-count').textContent.trim()).toEqual(
|
||||
'(12 commits behind)',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue