From 336bbd9de67d5fbb5c6f8f093dbe3551491d2541 Mon Sep 17 00:00:00 2001
From: Jacques Erasmus <jerasmus@gitlab.com>
Date: Mon, 29 Mar 2021 09:40:23 +0000
Subject: [PATCH] Add blob content viewer

Added blob content viewer to the app
---
 .../components/blob_content_viewer.vue        | 93 +++++++++++++++++++
 .../queries/blob_info.query.graphql           | 30 ++++++
 config/known_invalid_graphql_queries.yml      |  1 +
 locale/gitlab.pot                             |  3 +
 .../components/blob_content_viewer_spec.js    | 85 +++++++++++++++++
 5 files changed, 212 insertions(+)
 create mode 100644 app/assets/javascripts/repository/components/blob_content_viewer.vue
 create mode 100644 app/assets/javascripts/repository/queries/blob_info.query.graphql
 create mode 100644 spec/frontend/repository/components/blob_content_viewer_spec.js

diff --git a/app/assets/javascripts/repository/components/blob_content_viewer.vue b/app/assets/javascripts/repository/components/blob_content_viewer.vue
new file mode 100644
index 000000000000..a77c1a417878
--- /dev/null
+++ b/app/assets/javascripts/repository/components/blob_content_viewer.vue
@@ -0,0 +1,93 @@
+<script>
+import { GlLoadingIcon } from '@gitlab/ui';
+import { uniqueId } from 'lodash';
+import BlobContent from '~/blob/components/blob_content.vue';
+import BlobHeader from '~/blob/components/blob_header.vue';
+import createFlash from '~/flash';
+import { __ } from '~/locale';
+import blobInfoQuery from '../queries/blob_info.query.graphql';
+import projectPathQuery from '../queries/project_path.query.graphql';
+
+export default {
+  components: {
+    BlobHeader,
+    BlobContent,
+    GlLoadingIcon,
+  },
+  apollo: {
+    projectPath: {
+      query: projectPathQuery,
+    },
+    blobInfo: {
+      query: blobInfoQuery,
+      variables() {
+        return {
+          projectPath: this.projectPath,
+          filePath: this.path,
+        };
+      },
+      error() {
+        createFlash({ message: __('An error occurred while loading the file. Please try again.') });
+      },
+    },
+  },
+  provide() {
+    return {
+      blobHash: uniqueId(),
+    };
+  },
+  data() {
+    return {
+      projectPath: '',
+      blobInfo: {
+        name: '',
+        size: '',
+        rawBlob: '',
+        type: '',
+        fileType: '',
+        tooLarge: false,
+        path: '',
+        editBlobPath: '',
+        ideEditPath: '',
+        storedExternally: false,
+        rawPath: '',
+        externalStorageUrl: '',
+        replacePath: '',
+        deletePath: '',
+        canLock: false,
+        isLocked: false,
+        lockLink: '',
+        canModifyBlob: true,
+        forkPath: '',
+        simpleViewer: '',
+        richViewer: '',
+      },
+    };
+  },
+  computed: {
+    isLoading() {
+      return this.$apollo.queries.blobInfo.loading;
+    },
+    viewer() {
+      const { fileType, tooLarge, type } = this.blobInfo;
+
+      return { fileType, tooLarge, type };
+    },
+  },
+};
+</script>
+
+<template>
+  <div>
+    <gl-loading-icon v-if="isLoading" />
+    <div v-if="blobInfo && !isLoading">
+      <blob-header :blob="blobInfo" />
+      <blob-content
+        :blob="blobInfo"
+        :content="blobInfo.rawBlob"
+        :active-viewer="viewer"
+        :loading="false"
+      />
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/repository/queries/blob_info.query.graphql b/app/assets/javascripts/repository/queries/blob_info.query.graphql
new file mode 100644
index 000000000000..e0bbf12f3ebc
--- /dev/null
+++ b/app/assets/javascripts/repository/queries/blob_info.query.graphql
@@ -0,0 +1,30 @@
+query getBlobInfo($projectPath: ID!, $filePath: String!) {
+  project(fullPath: $projectPath) {
+    id
+    repository {
+      blobs(path: $filePath) {
+        name
+        size
+        rawBlob
+        type
+        fileType
+        tooLarge
+        path
+        editBlobPath
+        ideEditPath
+        storedExternally
+        rawPath
+        externalStorageUrl
+        replacePath
+        deletePath
+        canLock
+        isLocked
+        lockLink
+        canModifyBlob
+        forkPath
+        simpleViewer
+        richViewer
+      }
+    }
+  }
+}
diff --git a/config/known_invalid_graphql_queries.yml b/config/known_invalid_graphql_queries.yml
index 2989b3a42626..26188d6068f0 100644
--- a/config/known_invalid_graphql_queries.yml
+++ b/config/known_invalid_graphql_queries.yml
@@ -4,3 +4,4 @@ filenames:
   - ee/app/assets/javascripts/security_configuration/api_fuzzing/graphql/api_fuzzing_ci_configuration.query.graphql
   - ee/app/assets/javascripts/security_configuration/api_fuzzing/graphql/create_api_fuzzing_configuration.mutation.graphql
   - ee/app/assets/javascripts/security_configuration/dast_profiles/graphql/dast_failed_site_validations.query.graphql
+  - app/assets/javascripts/repository/queries/blob_info.query.graphql
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 6d474eae8916..4db67aa1dc7f 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -3533,6 +3533,9 @@ msgstr ""
 msgid "An error occurred while loading the file. Please try again later."
 msgstr ""
 
+msgid "An error occurred while loading the file. Please try again."
+msgstr ""
+
 msgid "An error occurred while loading the merge request changes."
 msgstr ""
 
diff --git a/spec/frontend/repository/components/blob_content_viewer_spec.js b/spec/frontend/repository/components/blob_content_viewer_spec.js
new file mode 100644
index 000000000000..a0d00ff9a7d3
--- /dev/null
+++ b/spec/frontend/repository/components/blob_content_viewer_spec.js
@@ -0,0 +1,85 @@
+import { GlLoadingIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import BlobContent from '~/blob/components/blob_content.vue';
+import BlobHeader from '~/blob/components/blob_header.vue';
+import BlobContentViewer from '~/repository/components/blob_content_viewer.vue';
+
+let wrapper;
+const mockData = {
+  name: 'some_file.js',
+  size: 123,
+  rawBlob: 'raw content',
+  type: 'text',
+  fileType: 'text',
+  tooLarge: false,
+  path: 'some_file.js',
+  editBlobPath: 'some_file.js/edit',
+  ideEditPath: 'some_file.js/ide/edit',
+  storedExternally: false,
+  rawPath: 'some_file.js',
+  externalStorageUrl: 'some_file.js',
+  replacePath: 'some_file.js/replace',
+  deletePath: 'some_file.js/delete',
+  canLock: true,
+  isLocked: false,
+  lockLink: 'some_file.js/lock',
+  canModifyBlob: true,
+  forkPath: 'some_file.js/fork',
+  simpleViewer: {},
+  richViewer: {},
+};
+
+function factory(path, loading = false) {
+  wrapper = shallowMount(BlobContentViewer, {
+    propsData: {
+      path,
+    },
+    mocks: {
+      $apollo: {
+        queries: {
+          blobInfo: {
+            loading,
+          },
+        },
+      },
+    },
+  });
+
+  wrapper.setData({ blobInfo: mockData });
+}
+
+describe('Blob content viewer component', () => {
+  const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
+  const findBlobHeader = () => wrapper.find(BlobHeader);
+  const findBlobContent = () => wrapper.find(BlobContent);
+
+  afterEach(() => {
+    wrapper.destroy();
+  });
+
+  beforeEach(() => {
+    factory('some_file.js');
+  });
+
+  it('renders a GlLoadingIcon component', () => {
+    factory('some_file.js', true);
+
+    expect(findLoadingIcon().exists()).toBe(true);
+  });
+
+  it('renders a BlobHeader component', () => {
+    expect(findBlobHeader().exists()).toBe(true);
+  });
+
+  it('renders a BlobContent component', () => {
+    expect(findBlobContent().exists()).toBe(true);
+
+    expect(findBlobContent().props('loading')).toEqual(false);
+    expect(findBlobContent().props('content')).toEqual('raw content');
+    expect(findBlobContent().props('activeViewer')).toEqual({
+      fileType: 'text',
+      tooLarge: false,
+      type: 'text',
+    });
+  });
+});
-- 
GitLab