Commit a743d97b authored by Samantha Ming's avatar Samantha Ming

Use BlobHeader vue component for repo files

Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/323210
parent ad3db46a
...@@ -19,7 +19,7 @@ const apolloProvider = new VueApollo({ ...@@ -19,7 +19,7 @@ const apolloProvider = new VueApollo({
const viewBlobEl = document.querySelector('#js-view-blob-app'); const viewBlobEl = document.querySelector('#js-view-blob-app');
if (viewBlobEl) { if (viewBlobEl) {
const { blobPath, projectPath } = viewBlobEl.dataset; const { blobPath, projectPath, hasRichViewer } = viewBlobEl.dataset;
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Vue({ new Vue({
...@@ -28,6 +28,7 @@ if (viewBlobEl) { ...@@ -28,6 +28,7 @@ if (viewBlobEl) {
render(createElement) { render(createElement) {
return createElement(BlobContentViewer, { return createElement(BlobContentViewer, {
props: { props: {
hasRichViewer: JSON.parse(hasRichViewer),
path: blobPath, path: blobPath,
projectPath, projectPath,
}, },
......
...@@ -3,6 +3,7 @@ import { GlLoadingIcon } from '@gitlab/ui'; ...@@ -3,6 +3,7 @@ import { GlLoadingIcon } from '@gitlab/ui';
import { uniqueId } from 'lodash'; import { uniqueId } from 'lodash';
import BlobContent from '~/blob/components/blob_content.vue'; import BlobContent from '~/blob/components/blob_content.vue';
import BlobHeader from '~/blob/components/blob_header.vue'; import BlobHeader from '~/blob/components/blob_header.vue';
import { SIMPLE_BLOB_VIEWER, RICH_BLOB_VIEWER } from '~/blob/components/constants';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { __ } from '~/locale'; import { __ } from '~/locale';
import blobInfoQuery from '../queries/blob_info.query.graphql'; import blobInfoQuery from '../queries/blob_info.query.graphql';
...@@ -41,9 +42,15 @@ export default { ...@@ -41,9 +42,15 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
hasRichViewer: {
type: Boolean,
required: true,
},
}, },
data() { data() {
return { return {
activeViewerType:
this.hasRichViewer && !window.location.hash ? RICH_BLOB_VIEWER : SIMPLE_BLOB_VIEWER,
project: { project: {
repository: { repository: {
blobs: { blobs: {
...@@ -87,10 +94,16 @@ export default { ...@@ -87,10 +94,16 @@ export default {
return nodes[0] || {}; return nodes[0] || {};
}, },
viewer() { viewer() {
const viewer = this.blobInfo.richViewer || this.blobInfo.simpleViewer; const { richViewer, simpleViewer } = this.blobInfo;
const { fileType, tooLarge, type } = viewer; return this.activeViewerType === RICH_BLOB_VIEWER ? richViewer : simpleViewer;
},
return { fileType, tooLarge, type }; hasRenderError() {
return Boolean(this.viewer.renderError);
},
},
methods: {
switchViewer(newViewer) {
this.activeViewerType = newViewer || SIMPLE_BLOB_VIEWER;
}, },
}, },
}; };
...@@ -99,8 +112,14 @@ export default { ...@@ -99,8 +112,14 @@ export default {
<template> <template>
<div> <div>
<gl-loading-icon v-if="isLoading" /> <gl-loading-icon v-if="isLoading" />
<div v-if="blobInfo && !isLoading"> <div v-if="blobInfo && !isLoading" class="file-holder">
<blob-header :blob="blobInfo" /> <blob-header
:blob="blobInfo"
:hide-viewer-switcher="!hasRichViewer"
:active-viewer-type="viewer.type"
:has-render-error="hasRenderError"
@viewer-changed="switchViewer"
/>
<blob-content <blob-content
:blob="blobInfo" :blob="blobInfo"
:content="blobInfo.rawTextBlob" :content="blobInfo.rawTextBlob"
......
...@@ -6,6 +6,7 @@ query getBlobInfo($projectPath: ID!, $filePath: String!) { ...@@ -6,6 +6,7 @@ query getBlobInfo($projectPath: ID!, $filePath: String!) {
nodes { nodes {
webPath webPath
name name
size
rawSize rawSize
rawTextBlob rawTextBlob
fileType fileType
...@@ -18,11 +19,13 @@ query getBlobInfo($projectPath: ID!, $filePath: String!) { ...@@ -18,11 +19,13 @@ query getBlobInfo($projectPath: ID!, $filePath: String!) {
fileType fileType
tooLarge tooLarge
type type
renderError
} }
richViewer { richViewer {
fileType fileType
tooLarge tooLarge
type type
renderError
} }
} }
} }
......
...@@ -37,6 +37,10 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -37,6 +37,10 @@ class Projects::BlobController < Projects::ApplicationController
feature_category :source_code_management feature_category :source_code_management
before_action do
push_frontend_feature_flag(:refactor_blob_viewer, @project, default_enabled: :yaml)
end
def new def new
commit unless @repository.empty? commit unless @repository.empty?
end end
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
- if @code_navigation_path - if @code_navigation_path
#js-code-navigation{ data: { code_navigation_path: @code_navigation_path, blob_path: blob.path, definition_path_prefix: project_blob_path(@project, @ref) } } #js-code-navigation{ data: { code_navigation_path: @code_navigation_path, blob_path: blob.path, definition_path_prefix: project_blob_path(@project, @ref) } }
- if Feature.enabled?(:refactor_blob_viewer, @project, default_enabled: :yaml) - if Feature.enabled?(:refactor_blob_viewer, @project, default_enabled: :yaml)
#js-view-blob-app{ data: { blob_path: blob.path, project_path: @project.full_path } } #js-view-blob-app{ data: { has_rich_viewer: blob.rich_viewer.present?.to_json, blob_path: blob.path, project_path: @project.full_path } }
.gl-spinner-container .gl-spinner-container
= loading_icon(size: 'md') = loading_icon(size: 'md')
- else - else
......
...@@ -5,9 +5,10 @@ import BlobHeader from '~/blob/components/blob_header.vue'; ...@@ -5,9 +5,10 @@ import BlobHeader from '~/blob/components/blob_header.vue';
import BlobContentViewer from '~/repository/components/blob_content_viewer.vue'; import BlobContentViewer from '~/repository/components/blob_content_viewer.vue';
let wrapper; let wrapper;
const mockData = { const simpleMockData = {
name: 'some_file.js', name: 'some_file.js',
size: 123, size: 123,
rawSize: 123,
rawTextBlob: 'raw content', rawTextBlob: 'raw content',
type: 'text', type: 'text',
fileType: 'text', fileType: 'text',
...@@ -29,15 +30,27 @@ const mockData = { ...@@ -29,15 +30,27 @@ const mockData = {
fileType: 'text', fileType: 'text',
tooLarge: false, tooLarge: false,
type: 'simple', type: 'simple',
renderError: null,
}, },
richViewer: null, richViewer: null,
}; };
const richMockData = {
...simpleMockData,
richViewer: {
fileType: 'markup',
tooLarge: false,
type: 'rich',
renderError: null,
},
};
function factory(path, loading = false) { function factory({ props = {}, mockData = {} } = {}, loading = false) {
wrapper = shallowMount(BlobContentViewer, { wrapper = shallowMount(BlobContentViewer, {
propsData: { propsData: {
path, path: 'some_file.js',
projectPath: 'some/path', projectPath: 'some/path',
hasRichViewer: false,
...props,
}, },
mocks: { mocks: {
$apollo: { $apollo: {
...@@ -58,27 +71,29 @@ describe('Blob content viewer component', () => { ...@@ -58,27 +71,29 @@ describe('Blob content viewer component', () => {
const findBlobHeader = () => wrapper.find(BlobHeader); const findBlobHeader = () => wrapper.find(BlobHeader);
const findBlobContent = () => wrapper.find(BlobContent); const findBlobContent = () => wrapper.find(BlobContent);
afterEach(() => { beforeEach(() => {
wrapper.destroy(); factory({ mockData: simpleMockData });
}); });
beforeEach(() => { afterEach(() => {
factory('some_file.js'); wrapper.destroy();
}); });
it('renders a GlLoadingIcon component', () => { it('renders a GlLoadingIcon component', () => {
factory('some_file.js', true); factory({ mockData: simpleMockData }, true);
expect(findLoadingIcon().exists()).toBe(true); expect(findLoadingIcon().exists()).toBe(true);
}); });
describe('simple viewer', () => {
it('renders a BlobHeader component', () => { it('renders a BlobHeader component', () => {
expect(findBlobHeader().exists()).toBe(true); expect(findBlobHeader().props('activeViewerType')).toEqual('simple');
expect(findBlobHeader().props('hasRenderError')).toEqual(false);
expect(findBlobHeader().props('hideViewerSwitcher')).toEqual(true);
expect(findBlobHeader().props('blob')).toEqual(simpleMockData);
}); });
it('renders a BlobContent component', () => { it('renders a BlobContent component', () => {
expect(findBlobContent().exists()).toBe(true);
expect(findBlobContent().props('loading')).toEqual(false); expect(findBlobContent().props('loading')).toEqual(false);
expect(findBlobContent().props('content')).toEqual('raw content'); expect(findBlobContent().props('content')).toEqual('raw content');
expect(findBlobContent().props('isRawContent')).toBe(true); expect(findBlobContent().props('isRawContent')).toBe(true);
...@@ -86,6 +101,52 @@ describe('Blob content viewer component', () => { ...@@ -86,6 +101,52 @@ describe('Blob content viewer component', () => {
fileType: 'text', fileType: 'text',
tooLarge: false, tooLarge: false,
type: 'simple', type: 'simple',
renderError: null,
});
});
});
describe('rich viewer', () => {
beforeEach(() => {
factory({ props: { hasRichViewer: true }, mockData: richMockData });
});
it('renders a BlobHeader component', () => {
expect(findBlobHeader().props('activeViewerType')).toEqual('rich');
expect(findBlobHeader().props('hasRenderError')).toEqual(false);
expect(findBlobHeader().props('hideViewerSwitcher')).toEqual(false);
expect(findBlobHeader().props('blob')).toEqual(richMockData);
});
it('renders a BlobContent component', () => {
expect(findBlobContent().props('loading')).toEqual(false);
expect(findBlobContent().props('content')).toEqual('raw content');
expect(findBlobContent().props('isRawContent')).toBe(true);
expect(findBlobContent().props('activeViewer')).toEqual({
fileType: 'markup',
tooLarge: false,
type: 'rich',
renderError: null,
});
});
it('updates viewer type when viewer changed is clicked', async () => {
expect(findBlobContent().props('activeViewer')).toEqual(
expect.objectContaining({
type: 'rich',
}),
);
expect(findBlobHeader().props('activeViewerType')).toEqual('rich');
findBlobHeader().vm.$emit('viewer-changed', 'simple');
await wrapper.vm.$nextTick();
expect(findBlobHeader().props('activeViewerType')).toEqual('simple');
expect(findBlobContent().props('activeViewer')).toEqual(
expect.objectContaining({
type: 'simple',
}),
);
}); });
}); });
}); });
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