Commit bf8f32da authored by Natalia Tepluhina's avatar Natalia Tepluhina Committed by Phil Hughes

Replaced part of diff file properties with diff viewer

- replaced file.too_large
- replaced file.text
- replaced file.collapsed
parent 8f209ed5
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
import EmptyFileViewer from '~/vue_shared/components/diff_viewer/viewers/empty_file.vue';
import NotDiffableViewer from '~/vue_shared/components/diff_viewer/viewers/not_diffable.vue';
import NoPreviewViewer from '~/vue_shared/components/diff_viewer/viewers/no_preview.vue';
import InlineDiffView from './inline_diff_view.vue';
import ParallelDiffView from './parallel_diff_view.vue';
import NoteForm from '../../notes/components/note_form.vue';
......@@ -9,6 +10,7 @@ import ImageDiffOverlay from './image_diff_overlay.vue';
import DiffDiscussions from './diff_discussions.vue';
import { IMAGE_DIFF_POSITION_TYPE } from '../constants';
import { getDiffMode } from '../store/utils';
import { diffViewerModes } from '~/ide/constants';
export default {
components: {
......@@ -18,7 +20,8 @@ export default {
NoteForm,
DiffDiscussions,
ImageDiffOverlay,
EmptyFileViewer,
NotDiffableViewer,
NoPreviewViewer,
},
props: {
diffFile: {
......@@ -42,11 +45,17 @@ export default {
diffMode() {
return getDiffMode(this.diffFile);
},
diffViewerMode() {
return this.diffFile.viewer.name;
},
isTextFile() {
return this.diffFile.viewer.name === 'text';
return this.diffViewerMode === diffViewerModes.text;
},
noPreview() {
return this.diffViewerMode === diffViewerModes.no_preview;
},
errorMessage() {
return this.diffFile.viewer.error;
notDiffable() {
return this.diffViewerMode === diffViewerModes.not_diffable;
},
diffFileCommentForm() {
return this.getCommentFormForDiffFile(this.diffFile.file_hash);
......@@ -78,11 +87,10 @@ export default {
<template>
<div class="diff-content">
<div v-if="!errorMessage" class="diff-viewer">
<div class="diff-viewer">
<template v-if="isTextFile">
<empty-file-viewer v-if="diffFile.empty" />
<inline-diff-view
v-else-if="isInlineView"
v-if="isInlineView"
:diff-file="diffFile"
:diff-lines="diffFile.highlighted_diff_lines || []"
:help-page-path="helpPagePath"
......@@ -94,9 +102,12 @@ export default {
:help-page-path="helpPagePath"
/>
</template>
<not-diffable-viewer v-else-if="notDiffable" />
<no-preview-viewer v-else-if="noPreview" />
<diff-viewer
v-else
:diff-mode="diffMode"
:diff-viewer-mode="diffViewerMode"
:new-path="diffFile.new_path"
:new-sha="diffFile.diff_refs.head_sha"
:old-path="diffFile.old_path"
......@@ -132,8 +143,5 @@ export default {
</div>
</diff-viewer>
</div>
<div v-else class="diff-viewer">
<div class="nothing-here-block" v-html="errorMessage"></div>
</div>
</div>
</template>
......@@ -7,6 +7,7 @@ import { GlLoadingIcon } from '@gitlab/ui';
import eventHub from '../../notes/event_hub';
import DiffFileHeader from './diff_file_header.vue';
import DiffContent from './diff_content.vue';
import { diffViewerErrors } from '~/ide/constants';
export default {
components: {
......@@ -33,15 +34,14 @@ export default {
return {
isLoadingCollapsedDiff: false,
forkMessageVisible: false,
isCollapsed: this.file.viewer.collapsed || false,
renderIt: this.file.renderIt,
};
},
computed: {
...mapState('diffs', ['currentDiffFileId']),
...mapGetters(['isNotesFetched']),
...mapGetters('diffs', ['getDiffFileDiscussions']),
isCollapsed() {
return this.file.collapsed || false;
},
viewBlobLink() {
return sprintf(
__('You can %{linkStart}view the blob%{linkEnd} instead.'),
......@@ -52,19 +52,8 @@ export default {
false,
);
},
showExpandMessage() {
return (
this.isCollapsed ||
(!this.file.highlighted_diff_lines &&
!this.isLoadingCollapsedDiff &&
!this.file.too_large &&
this.file.text &&
!this.file.renamed_file &&
!this.file.mode_changed)
);
},
showLoadingIcon() {
return this.isLoadingCollapsedDiff || (!this.file.renderIt && !this.isCollapsed);
return this.isLoadingCollapsedDiff || (!this.renderIt && !this.isCollapsed);
},
hasDiffLines() {
return (
......@@ -73,9 +62,15 @@ export default {
this.file.parallel_diff_lines.length > 0
);
},
isFileTooLarge() {
return this.file.viewer.error === diffViewerErrors.too_large;
},
errorMessage() {
return this.file.viewer.error_message;
},
},
watch: {
'file.collapsed': function fileCollapsedWatch(newVal, oldVal) {
isCollapsed: function fileCollapsedWatch(newVal, oldVal) {
if (!newVal && oldVal && !this.hasDiffLines) {
this.handleLoadCollapsedDiff();
}
......@@ -90,8 +85,8 @@ export default {
if (!this.hasDiffLines) {
this.handleLoadCollapsedDiff();
} else {
this.file.collapsed = !this.file.collapsed;
this.file.renderIt = true;
this.isCollapsed = !this.isCollapsed;
this.renderIt = true;
}
},
handleLoadCollapsedDiff() {
......@@ -100,8 +95,8 @@ export default {
this.loadCollapsedDiff(this.file)
.then(() => {
this.isLoadingCollapsedDiff = false;
this.file.collapsed = false;
this.file.renderIt = true;
this.isCollapsed = false;
this.renderIt = true;
})
.then(() => {
requestIdleCallback(
......@@ -164,21 +159,25 @@ export default {
Cancel
</button>
</div>
<diff-content
v-if="!isCollapsed && file.renderIt"
:class="{ hidden: isCollapsed || file.too_large }"
:diff-file="file"
:help-page-path="helpPagePath"
/>
<gl-loading-icon v-if="showLoadingIcon" class="diff-content loading" />
<div v-else-if="showExpandMessage" class="nothing-here-block diff-collapsed">
{{ __('This diff is collapsed.') }}
<a class="click-to-expand js-click-to-expand" href="#" @click.prevent="handleToggle">{{
__('Click to expand it.')
}}</a>
</div>
<div v-if="file.too_large" class="nothing-here-block diff-collapsed js-too-large-diff">
<template v-else>
<div v-if="errorMessage" class="diff-viewer">
<div class="nothing-here-block" v-html="errorMessage"></div>
</div>
<div v-else-if="isCollapsed" class="nothing-here-block diff-collapsed">
{{ __('This diff is collapsed.') }}
<a class="click-to-expand js-click-to-expand" href="#" @click.prevent="handleToggle">{{
__('Click to expand it.')
}}</a>
</div>
<diff-content
v-else
:class="{ hidden: isCollapsed || isFileTooLarge }"
:diff-file="file"
:help-page-path="helpPagePath"
/>
</template>
<div v-if="isFileTooLarge" class="nothing-here-block diff-collapsed js-too-large-diff">
{{ __('This source diff could not be displayed because it is too large.') }}
<span v-html="viewBlobLink"></span>
</div>
......
......@@ -8,6 +8,7 @@ import FileIcon from '~/vue_shared/components/file_icon.vue';
import { GlTooltipDirective } from '@gitlab/ui';
import { truncateSha } from '~/lib/utils/text_utility';
import { __, s__, sprintf } from '~/locale';
import { diffViewerModes } from '~/ide/constants';
import EditButton from './edit_button.vue';
import DiffStats from './diff_stats.vue';
......@@ -118,6 +119,12 @@ export default {
gfmCopyText() {
return `\`${this.diffFile.file_path}\``;
},
isFileRenamed() {
return this.diffFile.viewer.name === diffViewerModes.renamed;
},
isModeChanged() {
return this.diffFile.viewer.name === diffViewerModes.mode_changed;
},
},
mounted() {
polyfillSticky(this.$refs.header);
......@@ -165,7 +172,7 @@ export default {
aria-hidden="true"
css-classes="js-file-icon append-right-5"
/>
<span v-if="diffFile.renamed_file">
<span v-if="isFileRenamed">
<strong
v-gl-tooltip
:title="diffFile.old_path"
......@@ -193,7 +200,7 @@ export default {
css-class="btn-default btn-transparent btn-clipboard"
/>
<small v-if="diffFile.mode_changed" ref="fileMode">
<small v-if="isModeChanged" ref="fileMode">
{{ diffFile.a_mode }}{{ diffFile.b_mode }}
</small>
......
......@@ -17,6 +17,7 @@ import {
TREE_LIST_STORAGE_KEY,
WHITESPACE_STORAGE_KEY,
} from '../constants';
import { diffViewerModes } from '~/ide/constants';
export const setBaseConfig = ({ commit }, options) => {
const { endpoint, projectPath } = options;
......@@ -91,7 +92,7 @@ export const renderFileForDiscussionId = ({ commit, rootState, state }, discussi
commit(types.RENDER_FILE, file);
}
if (file.collapsed) {
if (file.viewer.collapsed) {
eventHub.$emit(`loadCollapsedDiff/${file.file_hash}`);
scrollToElement(document.getElementById(file.file_hash));
} else {
......@@ -105,7 +106,8 @@ export const startRenderDiffsQueue = ({ state, commit }) => {
const checkItem = () =>
new Promise(resolve => {
const nextFile = state.diffFiles.find(
file => !file.renderIt && (!file.collapsed || !file.text),
file =>
!file.renderIt && (!file.viewer.collapsed || !file.viewer.name === diffViewerModes.text),
);
if (nextFile) {
......
......@@ -4,7 +4,8 @@ export const isParallelView = state => state.diffViewType === PARALLEL_DIFF_VIEW
export const isInlineView = state => state.diffViewType === INLINE_DIFF_VIEW_TYPE;
export const hasCollapsedFile = state => state.diffFiles.some(file => file.collapsed);
export const hasCollapsedFile = state =>
state.diffFiles.some(file => file.viewer && file.viewer.collapsed);
export const commitId = state => (state.commit && state.commit.id ? state.commit.id : null);
......
import _ from 'underscore';
import { diffModes } from '~/ide/constants';
import { truncatePathMiddleToLength } from '~/lib/utils/text_utility';
import { diffModes, diffViewerModes } from '~/ide/constants';
import {
LINE_POSITION_LEFT,
LINE_POSITION_RIGHT,
......@@ -248,7 +248,8 @@ export function prepareDiffData(diffData) {
Object.assign(file, {
renderIt: showingLines < LINES_TO_BE_RENDERED_DIRECTLY,
collapsed: file.text && showingLines > MAX_LINES_TO_BE_RENDERED,
collapsed:
file.viewer.name === diffViewerModes.text && showingLines > MAX_LINES_TO_BE_RENDERED,
discussions: [],
});
}
......@@ -404,7 +405,9 @@ export const getDiffMode = diffFile => {
const diffModeKey = Object.keys(diffModes).find(key => diffFile[`${key}_file`]);
return (
diffModes[diffModeKey] ||
(diffFile.mode_changed && diffModes.mode_changed) ||
(diffFile.viewer &&
diffFile.viewer.name === diffViewerModes.mode_changed &&
diffViewerModes.mode_changed) ||
diffModes.replaced
);
};
......@@ -24,6 +24,22 @@ export const diffModes = {
mode_changed: 'mode_changed',
};
export const diffViewerModes = Object.freeze({
not_diffable: 'not_diffable',
no_preview: 'no_preview',
added: 'added',
deleted: 'deleted',
renamed: 'renamed',
mode_changed: 'mode_changed',
text: 'text',
image: 'image',
});
export const diffViewerErrors = Object.freeze({
too_large: 'too_large',
stored_externally: 'server_side_but_stored_externally',
});
export const rightSidebarViews = {
pipelines: { name: 'pipelines-list', keepAlive: true },
jobsDetail: { name: 'jobs-detail', keepAlive: false },
......
......@@ -5,6 +5,7 @@ import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
import ImageDiffOverlay from '~/diffs/components/image_diff_overlay.vue';
import { GlSkeletonLoading } from '@gitlab/ui';
import { getDiffMode } from '~/diffs/store/utils';
import { diffViewerModes } from '~/ide/constants';
export default {
components: {
......@@ -31,6 +32,12 @@ export default {
diffMode() {
return getDiffMode(this.discussion.diff_file);
},
diffViewerMode() {
return this.discussion.diff_file.viewer.name;
},
isTextFile() {
return this.diffViewerMode === diffViewerModes.text;
},
hasTruncatedDiffLines() {
return (
this.discussion.truncated_diff_lines && this.discussion.truncated_diff_lines.length !== 0
......@@ -58,18 +65,14 @@ export default {
</script>
<template>
<div :class="{ 'text-file': discussion.diff_file.text }" class="diff-file file-holder">
<div :class="{ 'text-file': isTextFile }" class="diff-file file-holder">
<diff-file-header
:discussion-path="discussion.discussion_path"
:diff-file="discussion.diff_file"
:can-current-user-fork="false"
:expanded="!discussion.diff_file.collapsed"
:expanded="!discussion.diff_file.viewer.collapsed"
/>
<div
v-if="discussion.diff_file.text"
:class="$options.userColorSchemeClass"
class="diff-content code"
>
<div v-if="isTextFile" :class="$options.userColorSchemeClass" class="diff-content code">
<table>
<template v-if="hasTruncatedDiffLines">
<tr
......@@ -109,6 +112,7 @@ export default {
<div v-else>
<diff-viewer
:diff-mode="diffMode"
:diff-viewer-mode="diffViewerMode"
:new-path="discussion.diff_file.new_path"
:new-sha="discussion.diff_file.diff_refs.head_sha"
:old-path="discussion.diff_file.old_path"
......
<script>
import { diffModes } from '~/ide/constants';
import { viewerInformationForPath } from '../content_viewer/lib/viewer_utils';
import { diffViewerModes, diffModes } from '~/ide/constants';
import ImageDiffViewer from './viewers/image_diff_viewer.vue';
import DownloadDiffViewer from './viewers/download_diff_viewer.vue';
import RenamedFile from './viewers/renamed.vue';
......@@ -12,6 +11,10 @@ export default {
type: String,
required: true,
},
diffViewerMode: {
type: String,
required: true,
},
newPath: {
type: String,
required: true,
......@@ -46,7 +49,7 @@ export default {
},
computed: {
viewer() {
if (this.diffMode === diffModes.renamed) {
if (this.diffViewerMode === diffViewerModes.renamed) {
return RenamedFile;
} else if (this.diffMode === diffModes.mode_changed) {
return ModeChanged;
......@@ -54,11 +57,8 @@ export default {
if (!this.newPath) return null;
const previewInfo = viewerInformationForPath(this.newPath);
if (!previewInfo) return DownloadDiffViewer;
switch (previewInfo.id) {
case 'image':
switch (this.diffViewerMode) {
case diffViewerModes.image:
return ImageDiffViewer;
default:
return DownloadDiffViewer;
......
<template>
<div class="nothing-here-block">
{{ __('No preview for this file type') }}
</div>
</template>
<template>
<div class="nothing-here-block">
{{ __('This diff was suppressed by a .gitattributes entry.') }}
</div>
</template>
......@@ -72,17 +72,20 @@ class DiffFileBaseEntity < Grape::Entity
expose :old_path
expose :new_path
expose :new_file?, as: :new_file
expose :collapsed?, as: :collapsed
expose :text?, as: :text
expose :renamed_file?, as: :renamed_file
expose :deleted_file?, as: :deleted_file
expose :diff_refs
expose :stored_externally?, as: :stored_externally
expose :external_storage
expose :renamed_file?, as: :renamed_file
expose :deleted_file?, as: :deleted_file
expose :mode_changed?, as: :mode_changed
expose :a_mode
expose :b_mode
expose :viewer, using: DiffViewerEntity
private
def memoized_submodule_links(diff_file)
......
......@@ -4,12 +4,10 @@ class DiffFileEntity < DiffFileBaseEntity
include CommitsHelper
include IconsHelper
expose :too_large?, as: :too_large
expose :empty?, as: :empty
expose :added_lines
expose :removed_lines
expose :load_collapsed_diff_url, if: -> (diff_file, options) { diff_file.text? && options[:merge_request] } do |diff_file|
expose :load_collapsed_diff_url, if: -> (diff_file, options) { diff_file.viewer.collapsed? && options[:merge_request] } do |diff_file|
merge_request = options[:merge_request]
project = merge_request.target_project
......@@ -36,10 +34,6 @@ class DiffFileEntity < DiffFileBaseEntity
project_blob_path(project, tree_join(diff_file.content_sha, diff_file.new_path))
end
expose :viewer, using: DiffViewerEntity do |diff_file|
diff_file.rich_viewer || diff_file.simple_viewer
end
expose :replaced_view_path, if: -> (_, options) { options[:merge_request] } do |diff_file|
image_diff = diff_file.rich_viewer && diff_file.rich_viewer.partial_name == 'image'
image_replaced = diff_file.old_content_sha && diff_file.old_content_sha != diff_file.content_sha
......
# frozen_string_literal: true
class DiffViewerEntity < Grape::Entity
# Partial name refers directly to a Rails feature, let's avoid
# using this on the frontend.
expose :partial_name, as: :name
expose :error do |diff_viewer|
diff_viewer.render_error_message
end
expose :render_error, as: :error
expose :render_error_message, as: :error_message
expose :collapsed?, as: :collapsed
end
......@@ -293,6 +293,10 @@ module Gitlab
end
end
def viewer
rich_viewer || simple_viewer
end
def simple_viewer
@simple_viewer ||= simple_viewer_class.new(self)
end
......
......@@ -222,6 +222,11 @@ describe 'Merge request > User creates image diff notes', :js do
end
def create_image_diff_note
expand_text = 'Click to expand it.'
page.all('a', text: expand_text).each do |element|
element.click
end
find('.js-add-image-diff-note-button', match: :first).click
find('.diff-content .note-textarea').native.send_keys('image diff test comment')
click_button 'Comment'
......
......@@ -14,6 +14,17 @@
"string",
"null"
]
},
"error_message": {
"type": [
"string",
"null"
]
},
"collapsed": {
"type": [
"boolean"
]
}
},
"additionalProperties": false
......
......@@ -6,6 +6,7 @@ import { GREEN_BOX_IMAGE_URL, RED_BOX_IMAGE_URL } from 'spec/test_constants';
import '~/behaviors/markdown/render_gfm';
import diffFileMockData from '../mock_data/diff_file';
import discussionsMockData from '../mock_data/diff_discussions';
import { diffViewerModes } from '~/ide/constants';
describe('DiffContent', () => {
const Component = Vue.extend(DiffContentComponent);
......@@ -52,26 +53,39 @@ describe('DiffContent', () => {
describe('empty files', () => {
beforeEach(() => {
vm.diffFile.empty = true;
vm.diffFile.highlighted_diff_lines = [];
vm.diffFile.parallel_diff_lines = [];
});
it('should render a message', done => {
it('should render a no preview message if viewer returns no preview', done => {
vm.diffFile.viewer.name = diffViewerModes.no_preview;
vm.$nextTick(() => {
const block = vm.$el.querySelector('.diff-viewer .nothing-here-block');
expect(block).not.toBe(null);
expect(block.textContent.trim()).toContain('Empty file');
expect(block.textContent.trim()).toContain('No preview for this file type');
done();
});
});
it('should render a not diffable message if viewer returns not diffable', done => {
vm.diffFile.viewer.name = diffViewerModes.not_diffable;
vm.$nextTick(() => {
const block = vm.$el.querySelector('.diff-viewer .nothing-here-block');
expect(block).not.toBe(null);
expect(block.textContent.trim()).toContain(
'This diff was suppressed by a .gitattributes entry',
);
done();
});
});
it('should not render multiple messages', done => {
vm.diffFile.mode_changed = true;
vm.diffFile.b_mode = '100755';
vm.diffFile.viewer.name = 'mode_changed';
vm.diffFile.viewer.name = diffViewerModes.mode_changed;
vm.$nextTick(() => {
expect(vm.$el.querySelectorAll('.nothing-here-block').length).toBe(1);
......@@ -81,6 +95,7 @@ describe('DiffContent', () => {
});
it('should not render diff table', done => {
vm.diffFile.viewer.name = diffViewerModes.no_preview;
vm.$nextTick(() => {
expect(vm.$el.querySelector('table')).toBe(null);
......@@ -157,6 +172,7 @@ describe('DiffContent', () => {
vm.diffFile.new_sha = 'DEF';
vm.diffFile.old_path = 'test.abc';
vm.diffFile.old_sha = 'ABC';
vm.diffFile.viewer.name = diffViewerModes.added;
vm.$nextTick(() => {
expect(el.querySelectorAll('.js-diff-inline-view').length).toEqual(0);
......
......@@ -4,15 +4,15 @@ import diffsModule from '~/diffs/store/modules';
import notesModule from '~/notes/stores/modules';
import DiffFileHeader from '~/diffs/components/diff_file_header.vue';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import diffDiscussionsMockData from '../mock_data/diff_discussions';
import { diffViewerModes } from '~/ide/constants';
Vue.use(Vuex);
const discussionFixture = 'merge_requests/diff_discussion.json';
describe('diff_file_header', () => {
let vm;
let props;
const diffDiscussionMock = getJSONFixture(discussionFixture)[0];
const diffDiscussionMock = diffDiscussionsMockData;
const Component = Vue.extend(DiffFileHeader);
const store = new Vuex.Store({
......@@ -303,13 +303,13 @@ describe('diff_file_header', () => {
});
it('displays old and new path if the file was renamed', () => {
props.diffFile.renamed_file = true;
props.diffFile.viewer.name = diffViewerModes.renamed;
vm = mountComponentWithStore(Component, { props, store });
expect(filePaths()).toHaveLength(2);
expect(filePaths()[0]).toHaveText(props.diffFile.old_path);
expect(filePaths()[1]).toHaveText(props.diffFile.new_path);
expect(filePaths()[0]).toHaveText(props.diffFile.old_path_html);
expect(filePaths()[1]).toHaveText(props.diffFile.new_path_html);
});
});
......@@ -319,14 +319,12 @@ describe('diff_file_header', () => {
const button = vm.$el.querySelector('.btn-clipboard');
expect(button).not.toBe(null);
expect(button.dataset.clipboardText).toBe(
'{"text":"files/ruby/popen.rb","gfm":"`files/ruby/popen.rb`"}',
);
expect(button.dataset.clipboardText).toBe('{"text":"CHANGELOG.rb","gfm":"`CHANGELOG.rb`"}');
});
describe('file mode', () => {
it('it displays old and new file mode if it changed', () => {
props.diffFile.mode_changed = true;
props.diffFile.viewer.name = diffViewerModes.mode_changed;
vm = mountComponentWithStore(Component, { props, store });
......@@ -338,7 +336,7 @@ describe('diff_file_header', () => {
});
it('does not display the file mode if it has not changed', () => {
props.diffFile.mode_changed = false;
props.diffFile.viewer.name = diffViewerModes.text;
vm = mountComponentWithStore(Component, { props, store });
......
import Vue from 'vue';
import DiffFileComponent from '~/diffs/components/diff_file.vue';
import { diffViewerModes, diffViewerErrors } from '~/ide/constants';
import store from '~/mr_notes/stores';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import diffFileMockData from '../mock_data/diff_file';
......@@ -27,8 +28,8 @@ describe('DiffFile', () => {
expect(el.querySelector('.file-title-name').innerText.indexOf(file_path)).toBeGreaterThan(-1);
expect(el.querySelector('.js-syntax-highlight')).toBeDefined();
expect(vm.file.renderIt).toEqual(false);
vm.file.renderIt = true;
expect(vm.renderIt).toEqual(false);
vm.renderIt = true;
vm.$nextTick(() => {
expect(el.querySelectorAll('.line_content').length).toBeGreaterThan(5);
......@@ -38,9 +39,9 @@ describe('DiffFile', () => {
describe('collapsed', () => {
it('should not have file content', done => {
expect(vm.$el.querySelectorAll('.diff-content').length).toEqual(1);
expect(vm.file.collapsed).toEqual(false);
vm.file<