浏览代码

#641 Warning that app version is outdated (#642)

* Added warning that app version is outdated

* added small fixes and tests to compareVersions
Ilnur Yakupov 4 年之前
父节点
当前提交
e5f1e47c99

+ 30 - 1
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>

+ 69 - 0
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);
+  });
+});

+ 25 - 0
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;

+ 2 - 0
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;