Commit ee294618 authored by Matthias Käppler's avatar Matthias Käppler

Merge branch 'blob-show-spec' into 'master'

Blob refactor: Fix/update blob show specs

See merge request gitlab-org/gitlab!78541
parents 84ff263c 9e1369c1
...@@ -42,6 +42,11 @@ export default { ...@@ -42,6 +42,11 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
showPath: {
type: Boolean,
required: false,
default: true,
},
}, },
data() { data() {
return { return {
...@@ -55,6 +60,9 @@ export default { ...@@ -55,6 +60,9 @@ export default {
showDefaultActions() { showDefaultActions() {
return !this.hideDefaultActions; return !this.hideDefaultActions;
}, },
isEmpty() {
return this.blob.rawSize === 0;
},
}, },
watch: { watch: {
viewer(newVal, oldVal) { viewer(newVal, oldVal) {
...@@ -74,7 +82,7 @@ export default { ...@@ -74,7 +82,7 @@ export default {
<div class="js-file-title file-title-flex-parent"> <div class="js-file-title file-title-flex-parent">
<div class="gl-display-flex"> <div class="gl-display-flex">
<table-of-contents class="gl-pr-2" /> <table-of-contents class="gl-pr-2" />
<blob-filepath :blob="blob"> <blob-filepath :blob="blob" :show-path="showPath">
<template #filepath-prepend> <template #filepath-prepend>
<slot name="prepend"></slot> <slot name="prepend"></slot>
</template> </template>
...@@ -88,12 +96,13 @@ export default { ...@@ -88,12 +96,13 @@ export default {
<default-actions <default-actions
v-if="showDefaultActions" v-if="showDefaultActions"
:raw-path="blob.rawPath" :raw-path="blob.externalStorageUrl || blob.rawPath"
:active-viewer="viewer" :active-viewer="viewer"
:has-render-error="hasRenderError" :has-render-error="hasRenderError"
:is-binary="isBinary" :is-binary="isBinary"
:environment-name="blob.environmentFormattedExternalUrl" :environment-name="blob.environmentFormattedExternalUrl"
:environment-path="blob.environmentExternalUrlForRouteMap" :environment-path="blob.environmentExternalUrlForRouteMap"
:is-empty="isEmpty"
@copy="proxyCopyRequest" @copy="proxyCopyRequest"
/> />
</div> </div>
......
...@@ -48,6 +48,11 @@ export default { ...@@ -48,6 +48,11 @@ export default {
required: false, required: false,
default: null, default: null,
}, },
isEmpty: {
type: Boolean,
required: false,
default: false,
},
}, },
computed: { computed: {
downloadUrl() { downloadUrl() {
...@@ -87,6 +92,7 @@ export default { ...@@ -87,6 +92,7 @@ export default {
icon="copy-to-clipboard" icon="copy-to-clipboard"
category="primary" category="primary"
variant="default" variant="default"
class="js-copy-blob-source-btn"
/> />
<gl-button <gl-button
v-if="!isBinary" v-if="!isBinary"
...@@ -100,6 +106,7 @@ export default { ...@@ -100,6 +106,7 @@ export default {
variant="default" variant="default"
/> />
<gl-button <gl-button
v-if="!isEmpty"
v-gl-tooltip.hover v-gl-tooltip.hover
:aria-label="$options.BTN_DOWNLOAD_TITLE" :aria-label="$options.BTN_DOWNLOAD_TITLE"
:title="$options.BTN_DOWNLOAD_TITLE" :title="$options.BTN_DOWNLOAD_TITLE"
......
...@@ -15,6 +15,11 @@ export default { ...@@ -15,6 +15,11 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
showPath: {
type: Boolean,
required: false,
default: true,
},
}, },
computed: { computed: {
blobSize() { blobSize() {
...@@ -26,6 +31,13 @@ export default { ...@@ -26,6 +31,13 @@ export default {
showLfsBadge() { showLfsBadge() {
return this.blob.storedExternally && this.blob.externalStorage === 'lfs'; return this.blob.storedExternally && this.blob.externalStorage === 'lfs';
}, },
fileName() {
if (this.showPath) {
return this.blob.path;
}
return this.blob.name;
},
}, },
}; };
</script> </script>
...@@ -33,12 +45,12 @@ export default { ...@@ -33,12 +45,12 @@ export default {
<div class="file-header-content d-flex align-items-center lh-100"> <div class="file-header-content d-flex align-items-center lh-100">
<slot name="filepath-prepend"></slot> <slot name="filepath-prepend"></slot>
<template v-if="blob.path"> <template v-if="fileName">
<file-icon :file-name="blob.path" :size="16" aria-hidden="true" css-classes="mr-2" /> <file-icon :file-name="fileName" :size="16" aria-hidden="true" css-classes="mr-2" />
<strong <strong
class="file-title-name mr-1 js-blob-header-filepath" class="file-title-name mr-1 js-blob-header-filepath"
data-qa-selector="file_title_content" data-qa-selector="file_title_content"
>{{ blob.path }}</strong >{{ fileName }}</strong
> >
</template> </template>
......
...@@ -153,7 +153,7 @@ export default { ...@@ -153,7 +153,7 @@ export default {
}, },
blobViewer() { blobViewer() {
const { fileType } = this.viewer; const { fileType } = this.viewer;
return loadViewer(fileType); return loadViewer(fileType, this.isUsingLfs);
}, },
viewerProps() { viewerProps() {
const { fileType } = this.viewer; const { fileType } = this.viewer;
...@@ -185,6 +185,9 @@ export default { ...@@ -185,6 +185,9 @@ export default {
? this.blobInfo.ideForkAndEditPath ? this.blobInfo.ideForkAndEditPath
: this.blobInfo.forkAndEditPath; : this.blobInfo.forkAndEditPath;
}, },
isUsingLfs() {
return this.blobInfo.storedExternally && this.blobInfo.externalStorage === 'lfs';
},
}, },
methods: { methods: {
loadLegacyViewer(type) { loadLegacyViewer(type) {
...@@ -245,10 +248,11 @@ export default { ...@@ -245,10 +248,11 @@ export default {
<div v-if="blobInfo && !isLoading" class="file-holder"> <div v-if="blobInfo && !isLoading" class="file-holder">
<blob-header <blob-header
:blob="blobInfo" :blob="blobInfo"
:hide-viewer-switcher="!hasRichViewer || isBinaryFileType" :hide-viewer-switcher="!hasRichViewer || isBinaryFileType || isUsingLfs"
:is-binary="isBinaryFileType" :is-binary="isBinaryFileType"
:active-viewer-type="viewer.type" :active-viewer-type="viewer.type"
:has-render-error="hasRenderError" :has-render-error="hasRenderError"
:show-path="false"
@viewer-changed="switchViewer" @viewer-changed="switchViewer"
> >
<template #actions> <template #actions>
......
export const loadViewer = (type) => { const viewers = {
switch (type) { download: () => import('./download_viewer.vue'),
case 'empty': image: () => import('./image_viewer.vue'),
return () => import(/* webpackChunkName: 'blob_empty_viewer' */ './empty_viewer.vue'); video: () => import('./video_viewer.vue'),
case 'text': empty: () => import('./empty_viewer.vue'),
return gon.features.highlightJs text: () => import('~/vue_shared/components/source_viewer.vue'),
? () => pdf: () => import('./pdf_viewer.vue'),
import( lfs: () => import('./lfs_viewer.vue'),
/* webpackChunkName: 'blob_text_viewer' */ '~/vue_shared/components/source_viewer.vue' };
)
: null; export const loadViewer = (type, isUsingLfs) => {
case 'download': let viewer = viewers[type];
return () => import(/* webpackChunkName: 'blob_download_viewer' */ './download_viewer.vue');
case 'image': if (!viewer && isUsingLfs) {
return () => import(/* webpackChunkName: 'blob_image_viewer' */ './image_viewer.vue'); viewer = viewers.lfs;
case 'video':
return () => import(/* webpackChunkName: 'blob_video_viewer' */ './video_viewer.vue');
case 'pdf':
return () => import(/* webpackChunkName: 'blob_pdf_viewer' */ './pdf_viewer.vue');
default:
return null;
} }
return viewer;
}; };
export const viewerProps = (type, blob) => { export const viewerProps = (type, blob) => {
return { const props = {
text: { text: {
content: blob.rawTextBlob, content: blob.rawTextBlob,
autoDetect: true, // We'll eventually disable autoDetect and pass the language explicitly to reduce the footprint (https://gitlab.com/gitlab-org/gitlab/-/issues/348145) autoDetect: true, // We'll eventually disable autoDetect and pass the language explicitly to reduce the footprint (https://gitlab.com/gitlab-org/gitlab/-/issues/348145)
...@@ -44,5 +40,11 @@ export const viewerProps = (type, blob) => { ...@@ -44,5 +40,11 @@ export const viewerProps = (type, blob) => {
url: blob.rawPath, url: blob.rawPath,
fileSize: blob.rawSize, fileSize: blob.rawSize,
}, },
}[type]; lfs: {
fileName: blob.name,
filePath: blob.rawPath,
},
};
return props[type] || props[blob.externalStorage];
}; };
<script>
import { GlLink, GlSprintf } from '@gitlab/ui';
import { __ } from '~/locale';
export default {
i18n: {
lfsText: __(
'This content could not be displayed because it is stored in LFS. You can %{linkStart}download it%{linkEnd} instead.',
),
},
components: {
GlLink,
GlSprintf,
},
props: {
fileName: {
type: String,
required: true,
},
filePath: {
type: String,
required: true,
},
},
};
</script>
<template>
<div class="gl-text-center gl-py-13 gl-bg-gray-50" data-type="lfs">
<gl-sprintf :message="$options.i18n.lfsText">
<template #link="{ content }">
<gl-link :href="filePath" :download="fileName" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</div>
</template>
...@@ -32,6 +32,7 @@ query getBlobInfo($projectPath: ID!, $filePath: String!, $ref: String!) { ...@@ -32,6 +32,7 @@ query getBlobInfo($projectPath: ID!, $filePath: String!, $ref: String!) {
archived archived
storedExternally storedExternally
externalStorage externalStorage
externalStorageUrl
rawPath rawPath
replacePath replacePath
pipelineEditorPath pipelineEditorPath
......
...@@ -115,7 +115,12 @@ export default { ...@@ -115,7 +115,12 @@ export default {
}; };
</script> </script>
<template> <template>
<div class="file-content code js-syntax-highlight" :class="$options.userColorScheme"> <div
class="file-content code js-syntax-highlight blob-content"
:class="$options.userColorScheme"
data-type="simple"
data-qa-selector="blob_viewer_file_content"
>
<line-numbers :lines="lineNumbers" /> <line-numbers :lines="lineNumbers" />
<pre class="code"><code v-safe-html="highlightedContent"></code> <pre class="code"><code v-safe-html="highlightedContent"></code>
</pre> </pre>
......
...@@ -125,7 +125,7 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated ...@@ -125,7 +125,7 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
def external_storage_url def external_storage_url
return unless static_objects_external_storage_enabled? return unless static_objects_external_storage_enabled?
external_storage_url_or_path(url_helpers.project_raw_url(project, ref_qualified_path)) external_storage_url_or_path(url_helpers.project_raw_url(project, ref_qualified_path), project)
end end
private private
......
...@@ -36578,6 +36578,9 @@ msgstr "" ...@@ -36578,6 +36578,9 @@ msgstr ""
msgid "This content could not be displayed because %{reason}. You can %{options} instead." msgid "This content could not be displayed because %{reason}. You can %{options} instead."
msgstr "" msgstr ""
msgid "This content could not be displayed because it is stored in LFS. You can %{linkStart}download it%{linkEnd} instead."
msgstr ""
msgid "This credential has expired" msgid "This credential has expired"
msgstr "" msgstr ""
......
...@@ -22,6 +22,10 @@ module QA ...@@ -22,6 +22,10 @@ module QA
element :copy_contents_button element :copy_contents_button
end end
base.view 'app/assets/javascripts/vue_shared/components/source_viewer.vue' do
element :blob_viewer_file_content
end
base.view 'app/views/projects/blob/_header_content.html.haml' do base.view 'app/views/projects/blob/_header_content.html.haml' do
element :file_name_content element :file_name_content
end end
......
...@@ -13,6 +13,7 @@ exports[`Blob Header Default Actions rendering matches the snapshot 1`] = ` ...@@ -13,6 +13,7 @@ exports[`Blob Header Default Actions rendering matches the snapshot 1`] = `
<blob-filepath-stub <blob-filepath-stub
blob="[object Object]" blob="[object Object]"
showpath="true"
/> />
</div> </div>
......
...@@ -236,7 +236,7 @@ describe('Blob content viewer component', () => { ...@@ -236,7 +236,7 @@ describe('Blob content viewer component', () => {
await waitForPromises(); await waitForPromises();
expect(loadViewer).toHaveBeenCalledWith(viewer); expect(loadViewer).toHaveBeenCalledWith(viewer, false);
expect(wrapper.findComponent(loadViewerReturnValue).exists()).toBe(true); expect(wrapper.findComponent(loadViewerReturnValue).exists()).toBe(true);
}, },
); );
......
import { GlLink, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import LfsViewer from '~/repository/components/blob_viewers/lfs_viewer.vue';
describe('LFS Viewer', () => {
let wrapper;
const DEFAULT_PROPS = {
fileName: 'file_name.js',
filePath: '/some/file/path',
};
const createComponent = () => {
wrapper = shallowMount(LfsViewer, {
propsData: { ...DEFAULT_PROPS },
stubs: { GlSprintf },
});
};
const findLink = () => wrapper.findComponent(GlLink);
beforeEach(() => createComponent());
afterEach(() => wrapper.destroy());
it('renders the correct text', () => {
expect(wrapper.text()).toBe(
'This content could not be displayed because it is stored in LFS. You can download it instead.',
);
});
it('renders download link', () => {
const { filePath, fileName } = DEFAULT_PROPS;
expect(findLink().attributes()).toMatchObject({
target: '_blank',
href: filePath,
download: fileName,
});
});
});
...@@ -17,6 +17,7 @@ export const simpleViewerMock = { ...@@ -17,6 +17,7 @@ export const simpleViewerMock = {
canCurrentUserPushToBranch: true, canCurrentUserPushToBranch: true,
archived: false, archived: false,
storedExternally: false, storedExternally: false,
externalStorageUrl: '',
externalStorage: 'lfs', externalStorage: 'lfs',
rawPath: 'some_file.js', rawPath: 'some_file.js',
replacePath: 'some_file.js/replace', replacePath: 'some_file.js/replace',
......
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