Merge branch 'ide-tree-changes-count' into 'master'
Added changes count to web IDE folder See merge request gitlab-org/gitlab-ce!18452
This commit is contained in:
commit
bb3beb5e9e
|
@ -43,7 +43,7 @@ export default {
|
|||
return `${this.changedIcon}-solid`;
|
||||
},
|
||||
changedIconClass() {
|
||||
return `multi-${this.changedIcon} prepend-left-5 pull-left`;
|
||||
return `multi-${this.changedIcon} pull-left`;
|
||||
},
|
||||
tooltipTitle() {
|
||||
if (!this.showTooltip) return undefined;
|
||||
|
@ -79,13 +79,7 @@ export default {
|
|||
class="ide-file-changed-icon"
|
||||
>
|
||||
<icon
|
||||
v-if="file.staged && showStagedIcon"
|
||||
:name="stagedIcon"
|
||||
:size="12"
|
||||
:css-classes="changedIconClass"
|
||||
/>
|
||||
<icon
|
||||
v-if="file.changed || file.tempFile || (file.staged && !showStagedIcon)"
|
||||
v-if="file.changed || file.tempFile || file.staged"
|
||||
:name="changedIcon"
|
||||
:size="12"
|
||||
:css-classes="changedIconClass"
|
||||
|
|
|
@ -36,7 +36,7 @@ export default {
|
|||
return this.file.tempFile ? `file-addition${prefix}` : `file-modified${prefix}`;
|
||||
},
|
||||
iconClass() {
|
||||
return `multi-file-${this.file.tempFile ? 'additions' : 'modified'} append-right-8`;
|
||||
return `multi-file-${this.file.tempFile ? 'addition' : 'modified'} append-right-8`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -1,22 +1,29 @@
|
|||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
|
||||
import fileIcon from '~/vue_shared/components/file_icon.vue';
|
||||
import { mapActions, mapGetters } from 'vuex';
|
||||
import { n__, __, sprintf } from '~/locale';
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import FileIcon from '~/vue_shared/components/file_icon.vue';
|
||||
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';
|
||||
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',
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
components: {
|
||||
skeletonLoadingContainer,
|
||||
newDropdown,
|
||||
fileStatusIcon,
|
||||
fileIcon,
|
||||
changedFileIcon,
|
||||
mrFileIcon,
|
||||
SkeletonLoadingContainer,
|
||||
NewDropdown,
|
||||
FileStatusIcon,
|
||||
FileIcon,
|
||||
ChangedFileIcon,
|
||||
MrFileIcon,
|
||||
Icon,
|
||||
},
|
||||
props: {
|
||||
file: {
|
||||
|
@ -29,6 +36,34 @@ export default {
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'getChangesInFolder',
|
||||
'getUnstagedFilesCountForPath',
|
||||
'getStagedFilesCountForPath',
|
||||
]),
|
||||
folderUnstagedCount() {
|
||||
return this.getUnstagedFilesCountForPath(this.file.path);
|
||||
},
|
||||
folderStagedCount() {
|
||||
return this.getStagedFilesCountForPath(this.file.path);
|
||||
},
|
||||
changesCount() {
|
||||
return this.getChangesInFolder(this.file.path);
|
||||
},
|
||||
folderChangesTooltip() {
|
||||
if (this.changesCount === 0) return undefined;
|
||||
|
||||
if (this.folderUnstagedCount > 0 && this.folderStagedCount === 0) {
|
||||
return n__('%d unstaged change', '%d unstaged changes', this.folderUnstagedCount);
|
||||
} else if (this.folderUnstagedCount === 0 && this.folderStagedCount > 0) {
|
||||
return n__('%d staged change', '%d staged changes', this.folderStagedCount);
|
||||
}
|
||||
|
||||
return sprintf(__('%{unstaged} unstaged and %{staged} staged changes'), {
|
||||
unstaged: this.folderUnstagedCount,
|
||||
staged: this.folderStagedCount,
|
||||
});
|
||||
},
|
||||
isTree() {
|
||||
return this.file.type === 'tree';
|
||||
},
|
||||
|
@ -48,10 +83,19 @@ export default {
|
|||
'is-open': this.file.opened,
|
||||
};
|
||||
},
|
||||
showTreeChangesCount() {
|
||||
return this.isTree && this.changesCount > 0 && !this.file.opened;
|
||||
},
|
||||
showChangedFileIcon() {
|
||||
return this.file.changed || this.file.tempFile || this.file.staged;
|
||||
},
|
||||
},
|
||||
updated() {
|
||||
if (this.file.type === 'blob' && this.file.active) {
|
||||
this.$el.scrollIntoView();
|
||||
this.$el.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'nearest',
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -101,8 +145,23 @@ export default {
|
|||
<mr-file-icon
|
||||
v-if="file.mrChange"
|
||||
/>
|
||||
<span
|
||||
v-if="showTreeChangesCount"
|
||||
class="ide-tree-changes"
|
||||
>
|
||||
{{ changesCount }}
|
||||
<icon
|
||||
v-tooltip
|
||||
:title="folderChangesTooltip"
|
||||
data-container="body"
|
||||
data-placement="right"
|
||||
name="file-modified"
|
||||
:size="12"
|
||||
css-classes="prepend-left-5 multi-file-modified"
|
||||
/>
|
||||
</span>
|
||||
<changed-file-icon
|
||||
v-if="file.changed || file.tempFile || file.staged"
|
||||
v-else-if="showChangedFileIcon"
|
||||
:file="file"
|
||||
:show-tooltip="true"
|
||||
:show-staged-icon="true"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { __ } from '~/locale';
|
||||
import { getChangesCountForFiles, filePathMatches } from './utils';
|
||||
|
||||
export const activeFile = state => state.openFiles.find(file => file.active) || null;
|
||||
|
||||
|
@ -55,7 +56,23 @@ export const allBlobs = state =>
|
|||
}, [])
|
||||
.sort((a, b) => b.lastOpenedAt - a.lastOpenedAt);
|
||||
|
||||
export const getChangedFile = state => path => state.changedFiles.find(f => f.path === path);
|
||||
export const getStagedFile = state => path => state.stagedFiles.find(f => f.path === path);
|
||||
|
||||
export const getChangesInFolder = state => path => {
|
||||
const changedFilesCount = state.changedFiles.filter(f => filePathMatches(f, path)).length;
|
||||
const stagedFilesCount = state.stagedFiles.filter(
|
||||
f => filePathMatches(f, path) && !getChangedFile(state)(f.path),
|
||||
).length;
|
||||
|
||||
return changedFilesCount + stagedFilesCount;
|
||||
};
|
||||
|
||||
export const getUnstagedFilesCountForPath = state => path =>
|
||||
getChangesCountForFiles(state.changedFiles, path);
|
||||
|
||||
export const getStagedFilesCountForPath = state => path =>
|
||||
getChangesCountForFiles(state.stagedFiles, path);
|
||||
|
||||
// prevent babel-plugin-rewire from generating an invalid default during karma tests
|
||||
export default () => {};
|
||||
|
|
|
@ -33,7 +33,6 @@ export const dataStructure = () => ({
|
|||
raw: '',
|
||||
content: '',
|
||||
parentTreeUrl: '',
|
||||
parentPath: '',
|
||||
renderError: false,
|
||||
base64: false,
|
||||
editorRow: 1,
|
||||
|
@ -43,6 +42,7 @@ export const dataStructure = () => ({
|
|||
viewMode: 'edit',
|
||||
previewMode: null,
|
||||
size: 0,
|
||||
parentPath: null,
|
||||
lastOpenedAt: 0,
|
||||
});
|
||||
|
||||
|
@ -83,7 +83,6 @@ export const decorateData = entity => {
|
|||
opened,
|
||||
active,
|
||||
parentTreeUrl,
|
||||
parentPath,
|
||||
changed,
|
||||
renderError,
|
||||
content,
|
||||
|
@ -91,6 +90,7 @@ export const decorateData = entity => {
|
|||
previewMode,
|
||||
file_lock,
|
||||
html,
|
||||
parentPath,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -137,3 +137,9 @@ export const sortTree = sortedTree =>
|
|||
}),
|
||||
)
|
||||
.sort(sortTreesByTypeAndName);
|
||||
|
||||
export const filePathMatches = (f, path) =>
|
||||
f.path.replace(new RegExp(`${f.name}$`), '').indexOf(`${path}/`) === 0;
|
||||
|
||||
export const getChangesCountForFiles = (files, path) =>
|
||||
files.filter(f => filePathMatches(f, path)).length;
|
||||
|
|
|
@ -603,14 +603,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
.multi-file-additions,
|
||||
.multi-file-additions-solid {
|
||||
fill: $green-500;
|
||||
.multi-file-addition,
|
||||
.multi-file-addition-solid {
|
||||
color: $green-500;
|
||||
}
|
||||
|
||||
.multi-file-modified,
|
||||
.multi-file-modified-solid {
|
||||
fill: $orange-500;
|
||||
color: $orange-500;
|
||||
}
|
||||
|
||||
.multi-file-commit-list-collapsed {
|
||||
|
@ -991,6 +991,12 @@
|
|||
resize: none;
|
||||
}
|
||||
|
||||
.ide-tree-changes {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.ide-new-modal-label {
|
||||
line-height: 34px;
|
||||
}
|
||||
|
|
|
@ -48,6 +48,33 @@ describe('RepoFile', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('folder', () => {
|
||||
it('renders changes count inside folder', () => {
|
||||
const f = {
|
||||
...file('folder'),
|
||||
path: 'testing',
|
||||
type: 'tree',
|
||||
branchId: 'master',
|
||||
projectId: 'project',
|
||||
};
|
||||
|
||||
store.state.changedFiles.push({
|
||||
...file('fileName'),
|
||||
path: 'testing/fileName',
|
||||
});
|
||||
|
||||
createComponent({
|
||||
file: f,
|
||||
level: 0,
|
||||
});
|
||||
|
||||
const treeChangesEl = vm.$el.querySelector('.ide-tree-changes');
|
||||
|
||||
expect(treeChangesEl).not.toBeNull();
|
||||
expect(treeChangesEl.textContent).toContain('1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('locked file', () => {
|
||||
let f;
|
||||
|
||||
|
@ -72,8 +99,7 @@ describe('RepoFile', () => {
|
|||
|
||||
it('renders a tooltip', () => {
|
||||
expect(
|
||||
vm.$el.querySelector('.ide-file-name span:nth-child(2)').dataset
|
||||
.originalTitle,
|
||||
vm.$el.querySelector('.ide-file-name span:nth-child(2)').dataset.originalTitle,
|
||||
).toContain('Locked by testuser');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -84,4 +84,67 @@ describe('IDE store getters', () => {
|
|||
expect(getters.allBlobs(localState)[0].name).toBe('blob');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getChangesInFolder', () => {
|
||||
it('returns length of changed files for a path', () => {
|
||||
localState.changedFiles.push(
|
||||
{
|
||||
path: 'test/index',
|
||||
name: 'index',
|
||||
},
|
||||
{
|
||||
path: 'app/123',
|
||||
name: '123',
|
||||
},
|
||||
);
|
||||
|
||||
expect(getters.getChangesInFolder(localState)('test')).toBe(1);
|
||||
});
|
||||
|
||||
it('returns length of changed & staged files for a path', () => {
|
||||
localState.changedFiles.push(
|
||||
{
|
||||
path: 'test/index',
|
||||
name: 'index',
|
||||
},
|
||||
{
|
||||
path: 'testing/123',
|
||||
name: '123',
|
||||
},
|
||||
);
|
||||
|
||||
localState.stagedFiles.push(
|
||||
{
|
||||
path: 'test/123',
|
||||
name: '123',
|
||||
},
|
||||
{
|
||||
path: 'test/index',
|
||||
name: 'index',
|
||||
},
|
||||
{
|
||||
path: 'testing/12345',
|
||||
name: '12345',
|
||||
},
|
||||
);
|
||||
|
||||
expect(getters.getChangesInFolder(localState)('test')).toBe(2);
|
||||
});
|
||||
|
||||
it('returns length of changed & tempFiles files for a path', () => {
|
||||
localState.changedFiles.push(
|
||||
{
|
||||
path: 'test/index',
|
||||
name: 'index',
|
||||
},
|
||||
{
|
||||
path: 'test/newfile',
|
||||
name: 'newfile',
|
||||
tempFile: true,
|
||||
},
|
||||
);
|
||||
|
||||
expect(getters.getChangesInFolder(localState)('test')).toBe(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue