Commit ecc821a9 authored by Phil Hughes's avatar Phil Hughes

Merge branch '44833-ide-clean-up-status-bar' into 'master'

Resolve "Clean up bottom status bar Web IDE"

Closes #44833

See merge request gitlab-org/gitlab-ce!18756
parents 68b71df6 b7f3d747
......@@ -65,61 +65,63 @@ export default {
</script>
<template>
<div
class="ide-view"
>
<find-file
v-show="fileFindVisible"
/>
<ide-sidebar />
<article class="ide">
<div
class="multi-file-edit-pane"
class="ide-view"
>
<template
v-if="activeFile"
>
<repo-tabs
:active-file="activeFile"
:files="openFiles"
:viewer="viewer"
:has-changes="hasChanges"
:merge-request-id="currentMergeRequestId"
/>
<repo-editor
class="multi-file-edit-pane-content"
:file="activeFile"
/>
<ide-status-bar
:file="activeFile"
/>
</template>
<template
v-else
<find-file
v-show="fileFindVisible"
/>
<ide-sidebar />
<div
class="multi-file-edit-pane"
>
<div
v-once
class="ide-empty-state"
<template
v-if="activeFile"
>
<repo-tabs
:active-file="activeFile"
:files="openFiles"
:viewer="viewer"
:has-changes="hasChanges"
:merge-request-id="currentMergeRequestId"
/>
<repo-editor
class="multi-file-edit-pane-content"
:file="activeFile"
/>
</template>
<template
v-else
>
<div class="row js-empty-state">
<div class="col-xs-12">
<div class="svg-content svg-250">
<img :src="emptyStateSvgPath" />
<div
v-once
class="ide-empty-state"
>
<div class="row js-empty-state">
<div class="col-xs-12">
<div class="svg-content svg-250">
<img :src="emptyStateSvgPath" />
</div>
</div>
</div>
<div class="col-xs-12">
<div class="text-content text-center">
<h4>
Welcome to the GitLab IDE
</h4>
<p>
You can select a file in the left sidebar to begin
editing and use the right sidebar to commit your changes.
</p>
<div class="col-xs-12">
<div class="text-content text-center">
<h4>
Welcome to the GitLab IDE
</h4>
<p>
You can select a file in the left sidebar to begin
editing and use the right sidebar to commit your changes.
</p>
</div>
</div>
</div>
</div>
</div>
</template>
</template>
</div>
</div>
</div>
<ide-status-bar
:file="activeFile"
/>
</article>
</template>
<script>
import { mapGetters } from 'vuex';
import icon from '~/vue_shared/components/icon.vue';
import tooltip from '~/vue_shared/directives/tooltip';
import timeAgoMixin from '~/vue_shared/mixins/timeago';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
export default {
components: {
icon,
userAvatarImage,
},
directives: {
tooltip,
......@@ -14,40 +17,93 @@ export default {
props: {
file: {
type: Object,
required: true,
required: false,
default: null,
},
},
data() {
return {
lastCommitFormatedAge: null,
};
},
computed: {
...mapGetters(['currentProject', 'lastCommit']),
},
mounted() {
this.startTimer();
},
beforeDestroy() {
if (this.intervalId) {
clearInterval(this.intervalId);
}
},
methods: {
startTimer() {
this.intervalId = setInterval(() => {
this.commitAgeUpdate();
}, 1000);
},
commitAgeUpdate() {
if (this.lastCommit) {
this.lastCommitFormatedAge = this.timeFormated(this.lastCommit.committed_date);
}
},
getCommitPath(shortSha) {
return `${this.currentProject.web_url}/commit/${shortSha}`;
},
},
};
</script>
<template>
<div class="ide-status-bar">
<div>
<div v-if="file.lastCommit && file.lastCommit.id">
Last commit:
<a
v-tooltip
:title="file.lastCommit.message"
:href="file.lastCommit.url"
>
{{ timeFormated(file.lastCommit.updatedAt) }} by
{{ file.lastCommit.author }}
</a>
</div>
<footer class="ide-status-bar">
<div
class="ide-status-branch"
v-if="lastCommit && lastCommitFormatedAge"
>
<icon
name="commit"
/>
<a
v-tooltip
class="commit-sha"
:title="lastCommit.message"
:href="getCommitPath(lastCommit.short_id)"
>{{ lastCommit.short_id }}</a>
by
{{ lastCommit.author_name }}
<time
v-tooltip
data-placement="top"
data-container="body"
:datetime="lastCommit.committed_date"
:title="tooltipTitle(lastCommit.committed_date)"
>
{{ lastCommitFormatedAge }}
</time>
</div>
<div class="text-right">
<div
v-if="file"
class="ide-status-file"
>
{{ file.name }}
</div>
<div class="text-right">
<div
v-if="file"
class="ide-status-file"
>
{{ file.eol }}
</div>
<div
class="text-right"
v-if="!file.binary">
class="ide-status-file"
v-if="file && !file.binary">
{{ file.editorRow }}:{{ file.editorColumn }}
</div>
<div class="text-right">
<div
v-if="file"
class="ide-status-file"
>
{{ file.fileLanguage }}
</div>
</div>
</footer>
</template>
......@@ -72,3 +72,26 @@ export const getBranchData = (
resolve(state.projects[`${projectId}`].branches[branchId]);
}
});
export const refreshLastCommitData = (
{ commit, state, dispatch },
{ projectId, branchId } = {},
) => service
.getBranchData(projectId, branchId)
.then(({ data }) => {
commit(types.SET_BRANCH_COMMIT, {
projectId,
branchId,
commit: data.commit,
});
})
.catch(() => {
flash(
'Error loading last commit.',
'alert',
document,
null,
false,
true,
);
});
......@@ -81,5 +81,11 @@ export const getUnstagedFilesCountForPath = state => path =>
export const getStagedFilesCountForPath = state => path =>
getChangesCountForFiles(state.stagedFiles, path);
export const lastCommit = (state, getters) => {
const branch = getters.currentProject && getters.currentProject.branches[state.currentBranchId];
return branch ? branch.commit : null;
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
......@@ -210,7 +210,11 @@ export const commitChanges = ({ commit, state, getters, dispatch, rootState, roo
);
}
})
.then(() => dispatch('updateCommitAction', consts.COMMIT_TO_CURRENT_BRANCH));
.then(() => dispatch('updateCommitAction', consts.COMMIT_TO_CURRENT_BRANCH))
.then(() => dispatch('refreshLastCommitData', {
projectId: rootState.currentProjectId,
branchId: rootState.currentBranchId,
}, { root: true }));
})
.catch(err => {
let errMsg = __('Error committing changes. Please try again.');
......
......@@ -20,6 +20,7 @@ export const SET_MERGE_REQUEST_VERSIONS = 'SET_MERGE_REQUEST_VERSIONS';
// Branch Mutation Types
export const SET_BRANCH = 'SET_BRANCH';
export const SET_BRANCH_COMMIT = 'SET_BRANCH_COMMIT';
export const SET_BRANCH_WORKING_REFERENCE = 'SET_BRANCH_WORKING_REFERENCE';
export const TOGGLE_BRANCH_OPEN = 'TOGGLE_BRANCH_OPEN';
......
......@@ -23,4 +23,9 @@ export default {
workingReference: reference,
});
},
[types.SET_BRANCH_COMMIT](state, { projectId, branchId, commit }) {
Object.assign(state.projects[projectId].branches[branchId], {
commit,
});
},
};
......@@ -230,6 +230,7 @@ $row-hover: $blue-50;
$row-hover-border: $blue-200;
$progress-color: #c0392b;
$header-height: 40px;
$ide-statusbar-height: 27px;
$fixed-layout-width: 1280px;
$limited-layout-width: 990px;
$limited-layout-width-sm: 790px;
......
......@@ -23,6 +23,7 @@
margin-top: 0;
border-top: 1px solid $white-dark;
border-bottom: 1px solid $white-dark;
padding-bottom: $ide-statusbar-height;
&.is-collapsed {
.ide-file-list {
......@@ -375,7 +376,13 @@
padding: $gl-bar-padding $gl-padding;
background: $white-light;
display: flex;
justify-content: flex-end;
justify-content: space-between;
height: $ide-statusbar-height;
position: absolute;
bottom: 0;
left: 0;
width: 100%;
> div + div {
padding-left: $gl-padding;
......@@ -386,6 +393,14 @@
}
}
.ide-status-file {
text-align: right;
.ide-status-branch + &,
&:first-child {
margin-left: auto;
}
}
// Not great, but this is to deal with our current output
.multi-file-preview-holder {
height: 100%;
......
---
title: Clean up WebIDE status bar and add useful info
merge_request:
author:
type: changed
import Vue from 'vue';
import store from '~/ide/stores';
import ideStatusBar from '~/ide/components/ide_status_bar.vue';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { resetStore } from '../helpers';
import { projectData } from '../mock_data';
describe('ideStatusBar', () => {
let vm;
beforeEach(() => {
const Component = Vue.extend(ideStatusBar);
store.state.currentProjectId = 'abcproject';
store.state.projects.abcproject = projectData;
vm = createComponentWithStore(Component, store).$mount();
});
afterEach(() => {
vm.$destroy();
resetStore(vm.$store);
});
it('renders the statusbar', () => {
expect(vm.$el.className).toBe('ide-status-bar');
});
describe('mounted', () => {
it('triggers a setInterval', () => {
expect(vm.intervalId).not.toBe(null);
});
});
describe('commitAgeUpdate', () => {
beforeEach(function() {
jasmine.clock().install();
spyOn(vm, 'commitAgeUpdate').and.callFake(() => {});
vm.startTimer();
});
afterEach(function() {
jasmine.clock().uninstall();
});
it('gets called every second', () => {
expect(vm.commitAgeUpdate).not.toHaveBeenCalled();
jasmine.clock().tick(1100);
expect(vm.commitAgeUpdate.calls.count()).toEqual(1);
jasmine.clock().tick(1000);
expect(vm.commitAgeUpdate.calls.count()).toEqual(2);
});
});
describe('getCommitPath', () => {
it('returns the path to the commit details', () => {
expect(vm.getCommitPath('abc123de')).toBe('/commit/abc123de');
});
});
});
import {
refreshLastCommitData,
} from '~/ide/stores/actions';
import store from '~/ide/stores';
import service from '~/ide/services';
import { resetStore } from '../../helpers';
import testAction from '../../../helpers/vuex_action_helper';
describe('IDE store project actions', () => {
beforeEach(() => {
store.state.projects.abcproject = {};
});
afterEach(() => {
resetStore(store);
});
describe('refreshLastCommitData', () => {
beforeEach(() => {
store.state.currentProjectId = 'abcproject';
store.state.currentBranchId = 'master';
store.state.projects.abcproject = {
branches: {
master: {
commit: null,
},
},
};
});
it('calls the service', done => {
spyOn(service, 'getBranchData').and.returnValue(
Promise.resolve({
data: {
commit: { id: '123' },
},
}),
);
store
.dispatch('refreshLastCommitData', {
projectId: store.state.currentProjectId,
branchId: store.state.currentBranchId,
})
.then(() => {
expect(service.getBranchData).toHaveBeenCalledWith('abcproject', 'master');
done();
})
.catch(done.fail);
});
it('commits getBranchData', done => {
testAction(
refreshLastCommitData,
{},
{},
[{
type: 'SET_BRANCH_COMMIT',
payload: {
projectId: 'abcproject',
branchId: 'master',
commit: { id: '123' },
},
}], // mutations
[], // action
done,
);
});
});
});
......@@ -141,4 +141,24 @@ describe('IDE store getters', () => {
expect(getters.getChangesInFolder(localState)('test')).toBe(2);
});
});
describe('lastCommit', () => {
it('returns the last commit of the current branch on the current project', () => {
const commitTitle = 'Example commit title';
const localGetters = {
currentProject: {
branches: {
'example-branch': {
commit: {
title: commitTitle,
},
},
},
},
};
localState.currentBranchId = 'example-branch';
expect(getters.lastCommit(localState, localGetters).title).toBe(commitTitle);
});
});
});
......@@ -15,4 +15,26 @@ describe('Multi-file store branch mutations', () => {
expect(localState.currentBranchId).toBe('master');
});
});
describe('SET_BRANCH_COMMIT', () => {
it('sets the last commit on current project', () => {
localState.projects = {
Example: {
branches: {
master: {},
},
},
};
mutations.SET_BRANCH_COMMIT(localState, {
projectId: 'Example',
branchId: 'master',
commit: {
title: 'Example commit',
},
});
expect(localState.projects.Example.branches.master.commit.title).toBe('Example commit');
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment