Repository: provectus/kafka-ui
Issue: Cannot view protobuf schema in Schema Registry
Labels: type/bug
scope/frontend
status/confirmed

**Describe the bug**
(A clear and concise description of what the bug is.)

Kafka UI is able to list the name of all schema in Schema Registry, but when I want to view a **Protobuf** schema, the app crashed with the following error. I believed this is because the app tried to parse the schema as JSON and failed.

```
SyntaxError: Unexpected token s in JSON at position 0
    at JSON.parse (<anonymous>)
    at Wa (LatestVersionItem.tsx:38)
    at ia (react-dom.production.min.js:157)
    at Vs (react-dom.production.min.js:267)
    at Eu (react-dom.production.min.js:250)
    at Cu (react-dom.production.min.js:250)
    at Su (react-dom.production.min.js:250)
    at mu (react-dom.production.min.js:243)
    at react-dom.production.min.js:123
    at t.unstable_runWithPriority (scheduler.production.min.js:18)
SyntaxError: Unexpected token s in JSON at position 0
    at JSON.parse (<anonymous>)
    at za (SchemaVersion.tsx:20)
    at ia (react-dom.production.min.js:157)
    at Vs (react-dom.production.min.js:267)
    at Eu (react-dom.production.min.js:250)
    at Cu (react-dom.production.min.js:250)
    at Su (react-dom.production.min.js:250)
    at mu (react-dom.production.min.js:243)
    at react-dom.production.min.js:123
    at t.unstable_runWithPriority (scheduler.production.min.js:18)
```

**Set up**
(How do you run the app?)

1. Setup Kafka UI with Schema Registry
2. Add a proto schema to schema registry (via HTTP, etc...)

**Steps to Reproduce**
Steps to reproduce the behavior:

1. Click on Schema Registry in the side bar
2. Click on a protobuf schema.

**Expected behavior**

The app doesn't crash.

**Screenshots**

![image](https://user-images.githubusercontent.com/42168740/129302960-495ab6ee-5fe0-4db4-8588-00e2657295dc.png)


**Additional context**
(Add any other context about the problem here)

Issue comments:
If this hasn't already been fixed yet, I will create a PR.
@tuananhlai, thanks for creating this issue. This one is not fixed. If you will create a PR it would be very helpful for us!
Hey @tuananhlai, 
this has been fixed (kudos to @IndeedSi) in latest commit. 
You can pull `master` labeled image to try it out. Let us know if there are any other problems.

Pull request review comments:
When two subjects use the same schema id, this will conflict in existing code. So I fixed this key duplicate problem

Patch that resolved the issue:
diff --git a/kafka-ui-react-app/src/components/Schemas/Details/LatestVersionItem.tsx b/kafka-ui-react-app/src/components/Schemas/Details/LatestVersionItem.tsx
index ecc666abd06..cef2e415e18 100644
--- a/kafka-ui-react-app/src/components/Schemas/Details/LatestVersionItem.tsx
+++ b/kafka-ui-react-app/src/components/Schemas/Details/LatestVersionItem.tsx
@@ -35,7 +35,11 @@ const LatestVersionItem: React.FC<LatestVersionProps> = ({
         <JSONEditor
           isFixedHeight
           name="schema"
-          value={JSON.stringify(JSON.parse(schema), null, '\t')}
+          value={
+            schema.trim().startsWith('{')
+              ? JSON.stringify(JSON.parse(schema), null, '\t')
+              : schema
+          }
           setOptions={{
             showLineNumbers: false,
             maxLines: 40,
diff --git a/kafka-ui-react-app/src/components/Schemas/Details/SchemaVersion.tsx b/kafka-ui-react-app/src/components/Schemas/Details/SchemaVersion.tsx
index f230cad602c..d6840b1e0e4 100644
--- a/kafka-ui-react-app/src/components/Schemas/Details/SchemaVersion.tsx
+++ b/kafka-ui-react-app/src/components/Schemas/Details/SchemaVersion.tsx
@@ -32,7 +32,11 @@ const SchemaVersion: React.FC<SchemaVersionProps> = ({
           <JSONEditor
             isFixedHeight
             name="schema"
-            value={JSON.stringify(JSON.parse(schema), null, '\t')}
+            value={
+              schema.trim().startsWith('{')
+                ? JSON.stringify(JSON.parse(schema), null, '\t')
+                : schema
+            }
             setOptions={{
               showLineNumbers: false,
               maxLines: 40,
diff --git a/kafka-ui-react-app/src/components/Schemas/Details/__test__/Details.spec.tsx b/kafka-ui-react-app/src/components/Schemas/Details/__test__/Details.spec.tsx
index bf67d54262e..1134b40c73c 100644
--- a/kafka-ui-react-app/src/components/Schemas/Details/__test__/Details.spec.tsx
+++ b/kafka-ui-react-app/src/components/Schemas/Details/__test__/Details.spec.tsx
@@ -7,7 +7,7 @@ import ClusterContext from 'components/contexts/ClusterContext';
 import DetailsContainer from 'components/Schemas/Details/DetailsContainer';
 import Details, { DetailsProps } from 'components/Schemas/Details/Details';
 
-import { schema, versions } from './fixtures';
+import { jsonSchema, versions } from './fixtures';
 
 const clusterName = 'testCluster';
 const fetchSchemaVersionsMock = jest.fn();
@@ -37,8 +37,8 @@ describe('Details', () => {
   describe('View', () => {
     const setupWrapper = (props: Partial<DetailsProps> = {}) => (
       <Details
-        subject={schema.subject}
-        schema={schema}
+        subject={jsonSchema.subject}
+        schema={jsonSchema}
         clusterName={clusterName}
         fetchSchemaVersions={fetchSchemaVersionsMock}
         deleteSchema={jest.fn()}
@@ -66,7 +66,7 @@ describe('Details', () => {
 
         expect(fetchSchemaVersionsMock).toHaveBeenCalledWith(
           clusterName,
-          schema.subject
+          jsonSchema.subject
         );
       });
 
@@ -114,7 +114,7 @@ describe('Details', () => {
           expect(wrapper.exists('LatestVersionItem')).toBeTruthy();
           expect(wrapper.exists('button')).toBeTruthy();
           expect(wrapper.exists('thead')).toBeTruthy();
-          expect(wrapper.find('SchemaVersion').length).toEqual(2);
+          expect(wrapper.find('SchemaVersion').length).toEqual(3);
         });
 
         it('matches snapshot', () => {
diff --git a/kafka-ui-react-app/src/components/Schemas/Details/__test__/LatestVersionItem.spec.tsx b/kafka-ui-react-app/src/components/Schemas/Details/__test__/LatestVersionItem.spec.tsx
index 0e0aff8cea6..47b6e38b491 100644
--- a/kafka-ui-react-app/src/components/Schemas/Details/__test__/LatestVersionItem.spec.tsx
+++ b/kafka-ui-react-app/src/components/Schemas/Details/__test__/LatestVersionItem.spec.tsx
@@ -2,18 +2,28 @@ import React from 'react';
 import { mount, shallow } from 'enzyme';
 import LatestVersionItem from 'components/Schemas/Details/LatestVersionItem';
 
-import { schema } from './fixtures';
+import { jsonSchema, protoSchema } from './fixtures';
 
 describe('LatestVersionItem', () => {
-  it('renders latest version of schema', () => {
-    const wrapper = mount(<LatestVersionItem schema={schema} />);
+  it('renders latest version of json schema', () => {
+    const wrapper = mount(<LatestVersionItem schema={jsonSchema} />);
 
     expect(wrapper.find('table').length).toEqual(1);
     expect(wrapper.find('td').at(1).text()).toEqual('1');
     expect(wrapper.exists('JSONEditor')).toBeTruthy();
   });
 
+  it('renders latest version of proto schema', () => {
+    const wrapper = mount(<LatestVersionItem schema={protoSchema} />);
+
+    expect(wrapper.find('table').length).toEqual(1);
+    expect(wrapper.find('td').at(1).text()).toEqual('2');
+    expect(wrapper.exists('JSONEditor')).toBeTruthy();
+  });
+
   it('matches snapshot', () => {
-    expect(shallow(<LatestVersionItem schema={schema} />)).toMatchSnapshot();
+    expect(
+      shallow(<LatestVersionItem schema={jsonSchema} />)
+    ).toMatchSnapshot();
   });
 });
diff --git a/kafka-ui-react-app/src/components/Schemas/Details/__test__/__snapshots__/Details.spec.tsx.snap b/kafka-ui-react-app/src/components/Schemas/Details/__test__/__snapshots__/Details.spec.tsx.snap
index 5ca52fab2a9..61c5096f65a 100644
--- a/kafka-ui-react-app/src/components/Schemas/Details/__test__/__snapshots__/Details.spec.tsx.snap
+++ b/kafka-ui-react-app/src/components/Schemas/Details/__test__/__snapshots__/Details.spec.tsx.snap
@@ -327,6 +327,26 @@ exports[`Details View when page with schema versions loaded when schema has vers
             }
           }
         />
+        <SchemaVersion
+          key="3"
+          version={
+            Object {
+              "compatibilityLevel": "BACKWARD",
+              "id": 3,
+              "schema": "syntax = \\"proto3\\";
+package com.indeed;
+
+message MyRecord {
+  int32 id = 1;
+  string name = 2;
+}
+",
+              "schemaType": "PROTOBUF",
+              "subject": "test",
+              "version": "3",
+            }
+          }
+        />
       </tbody>
     </table>
   </div>
diff --git a/kafka-ui-react-app/src/components/Schemas/Details/__test__/fixtures.ts b/kafka-ui-react-app/src/components/Schemas/Details/__test__/fixtures.ts
index d53190c9f63..bc0b1f2bd2e 100644
--- a/kafka-ui-react-app/src/components/Schemas/Details/__test__/fixtures.ts
+++ b/kafka-ui-react-app/src/components/Schemas/Details/__test__/fixtures.ts
@@ -1,6 +1,6 @@
 import { SchemaSubject, SchemaType } from 'generated-sources';
 
-export const schema: SchemaSubject = {
+export const jsonSchema: SchemaSubject = {
   subject: 'test',
   version: '1',
   id: 1,
@@ -10,6 +10,16 @@ export const schema: SchemaSubject = {
   schemaType: SchemaType.JSON,
 };
 
+export const protoSchema: SchemaSubject = {
+  subject: 'test_proto',
+  version: '1',
+  id: 2,
+  schema:
+    'syntax = "proto3";\npackage com.indeed;\n\nmessage MyRecord {\n  int32 id = 1;\n  string name = 2;\n}\n',
+  compatibilityLevel: 'BACKWARD',
+  schemaType: SchemaType.PROTOBUF,
+};
+
 export const versions: SchemaSubject[] = [
   {
     subject: 'test',
@@ -29,4 +39,13 @@ export const versions: SchemaSubject[] = [
     compatibilityLevel: 'BACKWARD',
     schemaType: SchemaType.JSON,
   },
+  {
+    subject: 'test',
+    version: '3',
+    id: 3,
+    schema:
+      'syntax = "proto3";\npackage com.indeed;\n\nmessage MyRecord {\n  int32 id = 1;\n  string name = 2;\n}\n',
+    compatibilityLevel: 'BACKWARD',
+    schemaType: SchemaType.PROTOBUF,
+  },
 ];
diff --git a/kafka-ui-react-app/src/components/Schemas/List/List.tsx b/kafka-ui-react-app/src/components/Schemas/List/List.tsx
index 8b5082077b9..ab2128c8b50 100644
--- a/kafka-ui-react-app/src/components/Schemas/List/List.tsx
+++ b/kafka-ui-react-app/src/components/Schemas/List/List.tsx
@@ -88,7 +88,10 @@ const List: React.FC<ListProps> = ({
                 </tr>
               )}
               {schemas.map((subject) => (
-                <ListItem key={subject.id} subject={subject} />
+                <ListItem
+                  key={[subject.id, subject.subject].join('-')}
+                  subject={subject}
+                />
               ))}
             </tbody>
           </table>


Test patch: