Repository: provectus/kafka-ui
Issue: Display warning for users who use outdated version of app
Labels: type/enhancement

### Is your proposal related to a problem?

<!--
  Provide a clear and concise description of what the problem is.
  For example, "I'm always frustrated when..."
-->

Some users use outdated version of the app

### Describe the solution you'd like

<!--
  Provide a clear and concise description of what you want to happen.
-->

Show to user info about latest release

Issue comments:

Pull request review comments:
```suggestion
  const arr = c.spilt('-')[0].split('.');
```
Is it a good place to use useMemo?
let's wrap all this function to try .. catch
We should be ready to case when customer runs our App internally without internet. 

Patch that resolved the issue:
diff --git a/kafka-ui-react-app/src/components/Version/Version.tsx b/kafka-ui-react-app/src/components/Version/Version.tsx
index dfdb94fd74c..d833d16b1a1 100644
--- a/kafka-ui-react-app/src/components/Version/Version.tsx
+++ b/kafka-ui-react-app/src/components/Version/Version.tsx
@@ -1,5 +1,8 @@
-import React from 'react';
+import React, { useEffect, useState } from 'react';
 import { gitCommitPath } from 'lib/paths';
+import { GIT_REPO_LATEST_RELEASE_LINK } from 'lib/constants';
+
+import compareVersions from './compareVersions';
 
 export interface VesionProps {
   tag?: string;
@@ -7,14 +10,40 @@ export interface VesionProps {
 }
 
 const Version: React.FC<VesionProps> = ({ tag, commit }) => {
+  const [latestVersionInfo, setLatestVersionInfo] = useState({
+    outdated: false,
+    latestTag: '',
+  });
+  useEffect(() => {
+    if (tag) {
+      fetch(GIT_REPO_LATEST_RELEASE_LINK)
+        .then((response) => response.json())
+        .then((data) => {
+          setLatestVersionInfo({
+            outdated: compareVersions(tag, data.tag_name) === -1,
+            latestTag: data.tag_name,
+          });
+        });
+    }
+  }, [tag]);
   if (!tag) {
     return null;
   }
 
+  const { outdated, latestTag } = latestVersionInfo;
+
   return (
     <div className="is-size-7 has-text-grey">
       <span className="has-text-grey-light mr-1">Version:</span>
       <span className="mr-1">{tag}</span>
+      {outdated && (
+        <span
+          className="icon has-text-warning"
+          title={`Your app version is outdated. Current latest version is ${latestTag}`}
+        >
+          <i className="fas fa-exclamation-triangle" />
+        </span>
+      )}
       {commit && (
         <>
           <span>&#40;</span>
diff --git a/kafka-ui-react-app/src/components/Version/__tests__/compareVersions.spec.ts b/kafka-ui-react-app/src/components/Version/__tests__/compareVersions.spec.ts
new file mode 100644
index 00000000000..807d247e77a
--- /dev/null
+++ b/kafka-ui-react-app/src/components/Version/__tests__/compareVersions.spec.ts
@@ -0,0 +1,69 @@
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-nocheck
+
+import compareVersions from 'components/Version/compareVersions';
+
+describe('compareVersions function', () => {
+  it('single-segment versions', () => {
+    expect(compareVersions('10', '9')).toEqual(1);
+    expect(compareVersions('10', '10')).toEqual(0);
+    expect(compareVersions('9', '10')).toEqual(-1);
+  });
+
+  it('two-segment versions', () => {
+    expect(compareVersions('10.8', '10.4')).toEqual(1);
+    expect(compareVersions('10.1', '10.1')).toEqual(0);
+    expect(compareVersions('10.1', '10.2')).toEqual(-1);
+  });
+
+  it('three-segment versions', () => {
+    expect(compareVersions('10.1.8', '10.0.4')).toEqual(1);
+    expect(compareVersions('10.0.1', '10.0.1')).toEqual(0);
+    expect(compareVersions('10.1.1', '10.2.2')).toEqual(-1);
+  });
+
+  it('four-segment versions', () => {
+    expect(compareVersions('1.0.0.0', '1')).toEqual(0);
+    expect(compareVersions('1.0.0.0', '1.0')).toEqual(0);
+    expect(compareVersions('1.0.0.0', '1.0.0')).toEqual(0);
+    expect(compareVersions('1.0.0.0', '1.0.0.0')).toEqual(0);
+    expect(compareVersions('1.2.3.4', '1.2.3.4')).toEqual(0);
+    expect(compareVersions('1.2.3.4', '1.2.3.04')).toEqual(0);
+    expect(compareVersions('v1.2.3.4', '01.2.3.4')).toEqual(0);
+
+    expect(compareVersions('1.2.3.4', '1.2.3.5')).toEqual(-1);
+    expect(compareVersions('1.2.3.5', '1.2.3.4')).toEqual(1);
+    expect(compareVersions('1.0.0.0-alpha', '1.0.0-alpha')).toEqual(0);
+    expect(compareVersions('1.0.0.0-alpha', '1.0.0.0-beta')).toEqual(0);
+  });
+
+  it('different number of digits in same group', () => {
+    expect(compareVersions('11.0.10', '11.0.2')).toEqual(1);
+    expect(compareVersions('11.0.2', '11.0.10')).toEqual(-1);
+  });
+
+  it('different number of digits in different groups', () => {
+    expect(compareVersions('11.1.10', '11.0')).toEqual(1);
+  });
+
+  it('different number of digits', () => {
+    expect(compareVersions('1.1.1', '1')).toEqual(1);
+    expect(compareVersions('1.0.0', '1')).toEqual(0);
+    expect(compareVersions('1.0', '1.4.1')).toEqual(-1);
+  });
+
+  it('ignore non-numeric characters', () => {
+    expect(compareVersions('1.0.0-alpha.1', '1.0.0-alpha')).toEqual(0);
+    expect(compareVersions('1.0.0-rc', '1.0.0')).toEqual(0);
+    expect(compareVersions('1.0.0-alpha', '1')).toEqual(0);
+    expect(compareVersions('v1.0.0', '1.0.0')).toEqual(0);
+  });
+
+  it('returns valid result (negative test cases)', () => {
+    expect(compareVersions(123, 'v0.0.0')).toEqual(0);
+    expect(compareVersions(undefined, 'v0.0.0')).toEqual(0);
+    expect(compareVersions('v0.0.0', 123)).toEqual(0);
+    expect(compareVersions('v0.0.0', undefined)).toEqual(0);
+    expect(compareVersions(undefined, undefined)).toEqual(0);
+  });
+});
diff --git a/kafka-ui-react-app/src/components/Version/compareVersions.ts b/kafka-ui-react-app/src/components/Version/compareVersions.ts
new file mode 100644
index 00000000000..72a8db926f7
--- /dev/null
+++ b/kafka-ui-react-app/src/components/Version/compareVersions.ts
@@ -0,0 +1,25 @@
+const split = (v: string): string[] => {
+  const c = v.replace(/^v/, '').replace(/\+.*$/, '');
+  return c.split('-')[0].split('.');
+};
+
+const compareVersions = (v1: string, v2: string): number => {
+  try {
+    const s1 = split(v1);
+    const s2 = split(v2);
+
+    for (let i = 0; i < Math.max(s1.length, s2.length); i += 1) {
+      const n1 = parseInt(s1[i] || '0', 10);
+      const n2 = parseInt(s2[i] || '0', 10);
+
+      if (n1 > n2) return 1;
+      if (n2 > n1) return -1;
+    }
+
+    return 0;
+  } catch (_) {
+    return 0;
+  }
+};
+
+export default compareVersions;
diff --git a/kafka-ui-react-app/src/lib/constants.ts b/kafka-ui-react-app/src/lib/constants.ts
index 7ee0e6de035..f93abfa0fdd 100644
--- a/kafka-ui-react-app/src/lib/constants.ts
+++ b/kafka-ui-react-app/src/lib/constants.ts
@@ -51,5 +51,7 @@ export const BYTES_IN_GB = 1_073_741_824;
 export const PER_PAGE = 25;
 
 export const GIT_REPO_LINK = 'https://github.com/provectus/kafka-ui';
+export const GIT_REPO_LATEST_RELEASE_LINK =
+  'https://api.github.com/repos/provectus/kafka-ui/releases/latest';
 export const GIT_TAG = process.env.REACT_APP_TAG;
 export const GIT_COMMIT = process.env.REACT_APP_COMMIT;


Test patch: