Commit c7d79faf authored by Filipa Lacerda's avatar Filipa Lacerda
Browse files

Merge branch 'diff-settings-dropdown' into 'master'

Added dropdown for diff settings

Closes #55491

See merge request gitlab-org/gitlab-ce!24460
......@@ -2,10 +2,10 @@
import { mapActions, mapGetters, mapState } from 'vuex';
import { GlTooltipDirective, GlLink, GlButton } from '@gitlab/ui';
import { __ } from '~/locale';
import { getParameterValues, mergeUrlParams } from '~/lib/utils/url_utility';
import { polyfillSticky } from '~/lib/utils/sticky';
import Icon from '~/vue_shared/components/icon.vue';
import CompareVersionsDropdown from './compare_versions_dropdown.vue';
import SettingsDropdown from './settings_dropdown.vue';
export default {
components: {
......@@ -13,6 +13,7 @@ export default {
Icon,
GlLink,
GlButton,
SettingsDropdown,
},
directives: {
GlTooltip: GlTooltipDirective,
......@@ -35,23 +36,10 @@ export default {
},
computed: {
...mapState('diffs', ['commit', 'showTreeList', 'startVersion', 'latestVersionPath']),
...mapGetters('diffs', ['isInlineView', 'isParallelView', 'hasCollapsedFile']),
...mapGetters('diffs', ['hasCollapsedFile']),
comparableDiffs() {
return this.mergeRequestDiffs.slice(1);
},
toggleWhitespaceText() {
if (this.isWhitespaceVisible()) {
return __('Hide whitespace changes');
}
return __('Show whitespace changes');
},
toggleWhitespacePath() {
if (this.isWhitespaceVisible()) {
return mergeUrlParams({ w: 1 }, window.location.href);
}
return mergeUrlParams({ w: 0 }, window.location.href);
},
showDropdowns() {
return !this.commit && this.mergeRequestDiffs.length;
},
......@@ -75,9 +63,6 @@ export default {
'expandAllFiles',
'toggleShowTreeList',
]),
isWhitespaceVisible() {
return getParameterValues('w')[0] !== '1';
},
},
};
</script>
......@@ -118,7 +103,7 @@ export default {
{{ __('Viewing commit') }}
<gl-link :href="commit.commit_url" class="monospace">{{ commit.short_id }}</gl-link>
</div>
<div class="inline-parallel-buttons d-none d-lg-flex ml-auto">
<div class="inline-parallel-buttons d-none d-md-flex ml-auto">
<gl-button
v-if="commit || startVersion"
:href="latestVersionPath"
......@@ -129,31 +114,7 @@ export default {
<a v-show="hasCollapsedFile" class="btn btn-default append-right-8" @click="expandAllFiles">
{{ __('Expand all') }}
</a>
<a :href="toggleWhitespacePath" class="btn btn-default qa-toggle-whitespace">
{{ toggleWhitespaceText }}
</a>
<div class="btn-group prepend-left-8">
<button
id="inline-diff-btn"
:class="{ active: isInlineView }"
type="button"
class="btn js-inline-diff-button"
data-view-type="inline"
@click="setInlineDiffViewType"
>
{{ __('Inline') }}
</button>
<button
id="parallel-diff-btn"
:class="{ active: isParallelView }"
type="button"
class="btn js-parallel-diff-button"
data-view-type="parallel"
@click="setParallelDiffViewType"
>
{{ __('Side-by-side') }}
</button>
</div>
<settings-dropdown />
</div>
</div>
</div>
......
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import { GlButton } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
export default {
components: {
GlButton,
Icon,
},
computed: {
...mapGetters('diffs', ['isInlineView', 'isParallelView']),
...mapState('diffs', ['renderTreeList', 'showWhitespace']),
},
methods: {
...mapActions('diffs', [
'setInlineDiffViewType',
'setParallelDiffViewType',
'setRenderTreeList',
'setShowWhitespace',
]),
},
};
</script>
<template>
<div class="dropdown">
<button
type="button"
class="btn btn-default js-show-diff-settings"
data-toggle="dropdown"
data-display="static"
>
<icon name="settings" /> <icon name="arrow-down" />
</button>
<div class="dropdown-menu dropdown-menu-right p-2 pt-3 pb-3">
<div>
<span class="bold d-block mb-1">{{ __('File browser') }}</span>
<div class="btn-group d-flex">
<gl-button
:class="{ active: !renderTreeList }"
class="w-100 js-list-view"
@click="setRenderTreeList(false)"
>
{{ __('List view') }}
</gl-button>
<gl-button
:class="{ active: renderTreeList }"
class="w-100 js-tree-view"
@click="setRenderTreeList(true)"
>
{{ __('Tree view') }}
</gl-button>
</div>
</div>
<div class="mt-2">
<span class="bold d-block mb-1">{{ __('Compare changes') }}</span>
<div class="btn-group d-flex js-diff-view-buttons">
<gl-button
id="inline-diff-btn"
:class="{ active: isInlineView }"
class="w-100 js-inline-diff-button"
data-view-type="inline"
@click="setInlineDiffViewType"
>
{{ __('Inline') }}
</gl-button>
<gl-button
id="parallel-diff-btn"
:class="{ active: isParallelView }"
class="w-100 js-parallel-diff-button"
data-view-type="parallel"
@click="setParallelDiffViewType"
>
{{ __('Side-by-side') }}
</gl-button>
</div>
</div>
<div class="mt-2">
<label class="mb-0">
<input
id="show-whitespace"
type="checkbox"
:checked="showWhitespace"
@change="setShowWhitespace({ showWhitespace: $event.target.checked, pushState: true })"
/>
{{ __('Show whitespace changes') }}
</label>
</div>
</div>
</div>
</template>
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import { GlTooltipDirective } from '@gitlab/ui';
import { parseBoolean } from '~/lib/utils/common_utils';
import Icon from '~/vue_shared/components/icon.vue';
import FileRow from '~/vue_shared/components/file_row.vue';
import FileRowStats from './file_row_stats.vue';
const treeListStorageKey = 'mr_diff_tree_list';
export default {
directives: {
GlTooltip: GlTooltipDirective,
......@@ -17,17 +14,12 @@ export default {
FileRow,
},
data() {
const treeListStored = localStorage.getItem(treeListStorageKey);
const renderTreeList = treeListStored !== null ? parseBoolean(treeListStored) : true;
return {
search: '',
renderTreeList,
focusSearch: false,
};
},
computed: {
...mapState('diffs', ['tree', 'addedLines', 'removedLines']),
...mapState('diffs', ['tree', 'addedLines', 'removedLines', 'renderTreeList']),
...mapGetters('diffs', ['allBlobs', 'diffFilesLength']),
filteredTreeList() {
const search = this.search.toLowerCase().trim();
......@@ -52,19 +44,6 @@ export default {
...mapActions('diffs', ['toggleTreeOpen', 'scrollToFile']),
clearSearch() {
this.search = '';
this.toggleFocusSearch(false);
},
toggleRenderTreeList(toggle) {
this.renderTreeList = toggle;
localStorage.setItem(treeListStorageKey, this.renderTreeList);
},
toggleFocusSearch(toggle) {
this.focusSearch = toggle;
},
blurSearch() {
if (this.search.trim() === '') {
this.toggleFocusSearch(false);
}
},
},
FileRowStats,
......@@ -81,8 +60,6 @@ export default {
:placeholder="s__('MergeRequest|Filter files')"
type="search"
class="form-control"
@focus="toggleFocusSearch(true)"
@blur="blurSearch"
/>
<button
v-show="search"
......@@ -94,34 +71,6 @@ export default {
<icon name="close" />
</button>
</div>
<div v-show="!focusSearch" class="btn-group prepend-left-8 tree-list-view-toggle">
<button
v-gl-tooltip.hover
:aria-label="__('List view')"
:title="__('List view')"
:class="{
active: !renderTreeList,
}"
class="btn btn-default pt-0 pb-0 d-flex align-items-center"
type="button"
@click="toggleRenderTreeList(false)"
>
<icon name="hamburger" />
</button>
<button
v-gl-tooltip.hover
:aria-label="__('Tree view')"
:title="__('Tree view')"
:class="{
active: renderTreeList,
}"
class="btn btn-default pt-0 pb-0 d-flex align-items-center"
type="button"
@click="toggleRenderTreeList(true)"
>
<icon name="file-tree" />
</button>
</div>
</div>
<div :class="{ 'pt-0 tree-list-blobs': !renderTreeList }" class="tree-list-scroll">
<template v-if="filteredTreeList.length">
......
......@@ -34,3 +34,5 @@ export const MAX_LINES_TO_BE_RENDERED = 2000;
export const MR_TREE_SHOW_KEY = 'mr_tree_show';
export const TREE_TYPE = 'tree';
export const TREE_LIST_STORAGE_KEY = 'mr_diff_tree_list';
export const WHITESPACE_STORAGE_KEY = 'mr_show_whitespace';
import Vue from 'vue';
import { mapState } from 'vuex';
import { mapActions, mapState } from 'vuex';
import { parseBoolean } from '~/lib/utils/common_utils';
import { getParameterValues } from '~/lib/utils/url_utility';
import diffsApp from './components/app.vue';
import { TREE_LIST_STORAGE_KEY } from './constants';
export default function initDiffsApp(store) {
return new Vue({
......@@ -26,6 +29,16 @@ export default function initDiffsApp(store) {
activeTab: state => state.page.activeTab,
}),
},
created() {
const treeListStored = localStorage.getItem(TREE_LIST_STORAGE_KEY);
const renderTreeList = treeListStored !== null ? parseBoolean(treeListStored) : true;
this.setRenderTreeList(renderTreeList);
this.setShowWhitespace({ showWhitespace: getParameterValues('w')[0] !== '1' });
},
methods: {
...mapActions('diffs', ['setRenderTreeList', 'setShowWhitespace']),
},
render(createElement) {
return createElement('diffs-app', {
props: {
......
......@@ -14,6 +14,8 @@ import {
INLINE_DIFF_VIEW_TYPE,
DIFF_VIEW_COOKIE_NAME,
MR_TREE_SHOW_KEY,
TREE_LIST_STORAGE_KEY,
WHITESPACE_STORAGE_KEY,
} from '../constants';
export const setBaseConfig = ({ commit }, options) => {
......@@ -33,7 +35,7 @@ export const fetchDiffFiles = ({ state, commit }) => {
});
return axios
.get(state.endpoint)
.get(state.endpoint, { params: { w: state.showWhitespace ? null : '1' } })
.then(res => {
commit(types.SET_LOADING, false);
commit(types.SET_MERGE_REQUEST_DIFFS, res.data.merge_request_diffs || []);
......@@ -278,5 +280,21 @@ export const closeDiffFileCommentForm = ({ commit }, fileHash) => {
commit(types.CLOSE_DIFF_FILE_COMMENT_FORM, fileHash);
};
export const setRenderTreeList = ({ commit }, renderTreeList) => {
commit(types.SET_RENDER_TREE_LIST, renderTreeList);
localStorage.setItem(TREE_LIST_STORAGE_KEY, renderTreeList);
};
export const setShowWhitespace = ({ commit }, { showWhitespace, pushState = false }) => {
commit(types.SET_SHOW_WHITESPACE, showWhitespace);
localStorage.setItem(WHITESPACE_STORAGE_KEY, showWhitespace);
if (pushState) {
historyPushState(showWhitespace ? '?w=0' : '?w=1');
}
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
......@@ -27,4 +27,6 @@ export default () => ({
projectPath: '',
commentForms: [],
highlightedRow: null,
renderTreeList: true,
showWhitespace: true,
});
......@@ -20,3 +20,5 @@ export const CLOSE_DIFF_FILE_COMMENT_FORM = 'CLOSE_DIFF_FILE_COMMENT_FORM';
export const SET_HIGHLIGHTED_ROW = 'SET_HIGHLIGHTED_ROW';
export const SET_TREE_DATA = 'SET_TREE_DATA';
export const SET_RENDER_TREE_LIST = 'SET_RENDER_TREE_LIST';
export const SET_SHOW_WHITESPACE = 'SET_SHOW_WHITESPACE';
......@@ -238,4 +238,10 @@ export default {
state.treeEntries = treeEntries;
state.tree = tree;
},
[types.SET_RENDER_TREE_LIST](state, renderTreeList) {
state.renderTreeList = renderTreeList;
},
[types.SET_SHOW_WHITESPACE](state, showWhitespace) {
state.showWhitespace = showWhitespace;
},
};
......@@ -428,7 +428,7 @@ export default class MergeRequestTabs {
}
diffViewType() {
return $('.inline-parallel-buttons button.active').data('viewType');
return $('.js-diff-view-buttons button.active').data('viewType');
}
isDiffAction(action) {
......
......@@ -1095,12 +1095,6 @@
}
}
.tree-list-view-toggle {
svg {
top: 0;
}
}
.image-diff-overlay,
.image-diff-overlay-add-comment {
top: 0;
......
......@@ -2018,6 +2018,9 @@ msgstr ""
msgid "Compare Revisions"
msgstr ""
msgid "Compare changes"
msgstr ""
msgid "Compare changes with the last commit"
msgstr ""
......@@ -3142,6 +3145,9 @@ msgstr ""
msgid "File added"
msgstr ""
msgid "File browser"
msgstr ""
msgid "File deleted"
msgstr ""
......@@ -3594,9 +3600,6 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
msgid "Hide whitespace changes"
msgstr ""
msgid "History"
msgstr ""
......
......@@ -91,6 +91,7 @@
# Check the same comments in the side-by-side view.
execute_script("window.scrollTo(0,0);")
find('.js-show-diff-settings').click
click_button 'Side-by-side'
wait_for_requests
......
......@@ -126,6 +126,7 @@
describe 'side-by-side view' do
before do
page.within('.merge-request-tabs') { click_link 'Changes' }
find('.js-show-diff-settings').click
page.find('#parallel-diff-btn').click
end
......
......@@ -9,17 +9,23 @@
project.add_maintainer(user)
sign_in(user)
visit diffs_project_merge_request_path(project, merge_request)
find('.js-show-diff-settings').click
end
it 'has a button to toggle whitespace changes' do
expect(page).to have_content 'Hide whitespace changes'
expect(page).to have_content 'Show whitespace changes'
end
describe 'clicking "Hide whitespace changes" button' do
it 'toggles the "Hide whitespace changes" button' do
click_link 'Hide whitespace changes'
find('#show-whitespace').click
visit diffs_project_merge_request_path(project, merge_request)
find('.js-show-diff-settings').click
expect(page).to have_content 'Show whitespace changes'
expect(find('#show-whitespace')).to be_checked
end
end
end
......@@ -23,6 +23,8 @@
end
it 'shows diffs' do
find('.js-show-diff-settings').click
expect(page).to have_css('.tab-content #diffs.active')
expect(page).to have_css('#parallel-diff-btn', count: 1)
expect(page).to have_css('#inline-diff-btn', count: 1)
......@@ -38,6 +40,8 @@
context 'when in the side-by-side view' do
before do
find('.js-show-diff-settings').click
click_button 'Side-by-side'
wait_for_requests
......
......@@ -51,15 +51,6 @@ describe('CompareVersions', () => {
});
});
it('should render whitespace toggle button with correct attributes', () => {
const whitespaceBtn = vm.$el.querySelector('.qa-toggle-whitespace');
const href = vm.toggleWhitespacePath;
expect(whitespaceBtn).not.toBeNull();
expect(whitespaceBtn.getAttribute('href')).toEqual(href);
expect(whitespaceBtn.innerHTML).toContain('Hide whitespace changes');
});
it('should render view types buttons with correct values', () => {
const inlineBtn = vm.$el.querySelector('#inline-diff-btn');
const parallelBtn = vm.$el.querySelector('#parallel-diff-btn');
......@@ -106,30 +97,6 @@ describe('CompareVersions', () => {
});
});
describe('isWhitespaceVisible', () => {
const originalHref = window.location.href;
afterEach(() => {
window.history.replaceState({}, null, originalHref);
});
it('should return "true" when no "w" flag is present in the URL (default)', () => {
expect(vm.isWhitespaceVisible()).toBe(true);
});
it('should return "false" when the flag is set to "1" in the URL', () => {
window.history.replaceState({}, null, '?w=1');
expect(vm.isWhitespaceVisible()).toBe(false);
});
it('should return "true" when the flag is set to "0" in the URL', () => {
window.history.replaceState({}, null, '?w=0');
expect(vm.isWhitespaceVisible()).toBe(true);
});
});
describe('commit', () => {
beforeEach(done => {
vm.$store.state.diffs.commit = getDiffWithCommit().commit;
......
import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import diffModule from '~/diffs/store/modules';
import SettingsDropdown from '~/diffs/components/settings_dropdown.vue';
import { PARALLEL_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE } from '~/diffs/constants';
describe('Diff settiings dropdown component', () => {
let vm;
let actions;
function createComponent(extendStore = () => {}) {
const localVue = createLocalVue();
localVue.use(Vuex);
const store = new Vuex.Store({
modules: {
diffs: {
namespaced: true,
actions,
state: diffModule().state,
getters: diffModule().getters,
},
},
});
extendStore(store);
vm = mount(SettingsDropdown, {
localVue,
store,
});
}
beforeEach(() => {
actions = {
setInlineDiffViewType: jasmine.createSpy('setInlineDiffViewType'),
setParallelDiffViewType: jasmine.createSpy('setParallelDiffViewType'),
setRenderTreeList: jasmine.createSpy('setRenderTreeList'),
setShowWhitespace: jasmine.createSpy('setShowWhitespace'),
};
});
afterEach(() => {