Commit 34c70444 authored by Ezekiel Kigbo's avatar Ezekiel Kigbo

Merge branch '333893-add-support-for-legacy-viewer-blob-refactor' into 'master'

Add support for legacy blob viewers

See merge request gitlab-org/gitlab!64839
parents e2905b3a 440dd4f4
......@@ -27,6 +27,11 @@ export default {
default: false,
required: false,
},
richViewer: {
type: String,
default: '',
required: false,
},
loading: {
type: Boolean,
default: true,
......@@ -71,6 +76,7 @@ export default {
v-else
ref="contentViewer"
:content="content"
:rich-viewer="richViewer"
:is-raw-content="isRawContent"
:file-name="blob.name"
:type="activeViewer.fileType"
......
......@@ -5,6 +5,7 @@ import BlobContent from '~/blob/components/blob_content.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 axios from '~/lib/utils/axios_utils';
import { isLoggedIn } from '~/lib/utils/common_utils';
import { __ } from '~/locale';
import blobInfoQuery from '../queries/blob_info.query.graphql';
......@@ -29,12 +30,15 @@ export default {
};
},
result() {
if (this.hasRichViewer) {
this.loadLegacyViewer();
}
this.switchViewer(
this.hasRichViewer && !window.location.hash ? RICH_BLOB_VIEWER : SIMPLE_BLOB_VIEWER,
);
},
error() {
createFlash({ message: __('An error occurred while loading the file. Please try again.') });
this.displayError();
},
},
},
......@@ -55,6 +59,9 @@ export default {
},
data() {
return {
legacyRichViewer: null,
isBinary: false,
isLoadingLegacyViewer: false,
activeViewerType: SIMPLE_BLOB_VIEWER,
project: {
repository: {
......@@ -94,7 +101,7 @@ export default {
return isLoggedIn();
},
isLoading() {
return this.$apollo.queries.project.loading;
return this.$apollo.queries.project.loading || this.isLoadingLegacyViewer;
},
blobInfo() {
const nodes = this.project?.repository?.blobs?.nodes;
......@@ -113,6 +120,20 @@ export default {
},
},
methods: {
loadLegacyViewer() {
this.isLoadingLegacyViewer = true;
axios
.get(`${this.blobInfo.webPath}?format=json&viewer=rich`)
.then(({ data: { html, binary } }) => {
this.legacyRichViewer = html;
this.isBinary = binary;
this.isLoadingLegacyViewer = false;
})
.catch(() => this.displayError());
},
displayError() {
createFlash({ message: __('An error occurred while loading the file. Please try again.') });
},
switchViewer(newViewer) {
this.activeViewerType = newViewer || SIMPLE_BLOB_VIEWER;
},
......@@ -126,13 +147,17 @@ export default {
<div v-if="blobInfo && !isLoading" class="file-holder">
<blob-header
:blob="blobInfo"
:hide-viewer-switcher="!hasRichViewer"
:hide-viewer-switcher="!hasRichViewer || isBinary"
:active-viewer-type="viewer.type"
:has-render-error="hasRenderError"
@viewer-changed="switchViewer"
>
<template #actions>
<blob-edit :edit-path="blobInfo.editBlobPath" :web-ide-path="blobInfo.ideEditPath" />
<blob-edit
v-if="!isBinary"
:edit-path="blobInfo.editBlobPath"
:web-ide-path="blobInfo.ideEditPath"
/>
<blob-button-group
v-if="isLoggedIn"
:path="path"
......@@ -143,6 +168,7 @@ export default {
</template>
</blob-header>
<blob-content
:rich-viewer="legacyRichViewer"
:blob="blobInfo"
:content="blobInfo.rawTextBlob"
:is-raw-content="true"
......
......@@ -5,7 +5,13 @@ export default {
props: {
content: {
type: String,
required: true,
required: false,
default: null,
},
richViewer: {
type: String,
default: '',
required: false,
},
type: {
type: String,
......
......@@ -18,5 +18,5 @@ export default {
};
</script>
<template>
<markdown-field-view ref="content" v-safe-html="content" />
<markdown-field-view ref="content" v-safe-html="richViewer || content" />
</template>
import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount, mount } from '@vue/test-utils';
import { shallowMount, mount, createLocalVue } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import BlobContent from '~/blob/components/blob_content.vue';
import BlobHeader from '~/blob/components/blob_header.vue';
import BlobButtonGroup from '~/repository/components/blob_button_group.vue';
import BlobContentViewer from '~/repository/components/blob_content_viewer.vue';
import BlobEdit from '~/repository/components/blob_edit.vue';
import blobInfoQuery from '~/repository/queries/blob_info.query.graphql';
let wrapper;
const simpleMockData = {
......@@ -17,6 +23,7 @@ const simpleMockData = {
fileType: 'text',
tooLarge: false,
path: 'some_file.js',
webPath: 'some_file.js',
editBlobPath: 'some_file.js/edit',
ideEditPath: 'some_file.js/ide/edit',
storedExternally: false,
......@@ -47,6 +54,28 @@ const richMockData = {
},
};
const localVue = createLocalVue();
const mockAxios = new MockAdapter(axios);
const createComponentWithApollo = (mockData) => {
localVue.use(VueApollo);
const mockResolver = jest
.fn()
.mockResolvedValue({ data: { project: { repository: { blobs: { nodes: [mockData] } } } } });
const fakeApollo = createMockApollo([[blobInfoQuery, mockResolver]]);
wrapper = shallowMount(BlobContentViewer, {
localVue,
apolloProvider: fakeApollo,
propsData: {
path: 'some_file.js',
projectPath: 'some/path',
},
});
};
const createFactory = (mountFn) => (
{ props = {}, mockData = {}, stubs = {} } = {},
loading = false,
......@@ -163,6 +192,22 @@ describe('Blob content viewer component', () => {
});
});
describe('legacy viewers', () => {
it('does not load a legacy viewer when a rich viewer is not available', async () => {
createComponentWithApollo(simpleMockData);
await waitForPromises();
expect(mockAxios.history.get).toHaveLength(0);
});
it('loads a legacy viewer when a rich viewer is available', async () => {
createComponentWithApollo(richMockData);
await waitForPromises();
expect(mockAxios.history.get).toHaveLength(1);
});
});
describe('BlobHeader action slot', () => {
const { ideEditPath, editBlobPath } = simpleMockData;
......@@ -200,6 +245,20 @@ describe('Blob content viewer component', () => {
});
});
it('does not render BlobHeaderEdit button when viewing a binary file', async () => {
fullFactory({
mockData: { blobInfo: richMockData, isBinary: true },
stubs: {
BlobContent: true,
BlobReplace: true,
},
});
await nextTick();
expect(findBlobEdit().exists()).toBe(false);
});
describe('BlobButtonGroup', () => {
const { name, path } = simpleMockData;
......
......@@ -10,9 +10,10 @@ describe('Blob Rich Viewer component', () => {
const content = '<h1 id="markdown">Foo Bar</h1>';
const defaultType = 'markdown';
function createComponent(type = defaultType) {
function createComponent(type = defaultType, richViewer) {
wrapper = shallowMount(RichViewer, {
propsData: {
richViewer,
content,
type,
},
......@@ -31,6 +32,12 @@ describe('Blob Rich Viewer component', () => {
expect(wrapper.html()).toContain(content);
});
it('renders the richViewer if one is present', () => {
const richViewer = '<div class="js-pdf-viewer"></div>';
createComponent('pdf', richViewer);
expect(wrapper.html()).toContain(richViewer);
});
it('queries for advanced viewer', () => {
expect(handleBlobRichViewer).toHaveBeenCalledWith(expect.anything(), defaultType);
});
......
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