瀏覽代碼

Topic analysis visual improvements (#2691)

Co-authored-by: Oleg Shur <workshur@gmail.com>
Hrant Abrahamyan 2 年之前
父節點
當前提交
20543fc2f3

+ 13 - 6
kafka-ui-react-app/src/components/Topics/Topic/Statistics/Metrics.tsx

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useEffect, useState } from 'react';
 import {
   useAnalyzeTopic,
   useCancelTopicAnalysis,
@@ -15,6 +15,7 @@ import {
 } from 'components/common/PropertiesList/PropertiesList.styled';
 import BytesFormatted from 'components/common/BytesFormatted/BytesFormatted';
 import { useTimeFormat } from 'lib/hooks/useTimeFormat';
+import { calculateTimer } from 'lib/dateTimeHelpers';
 
 import * as S from './Statistics.styles';
 import Total from './Indicators/Total';
@@ -25,13 +26,14 @@ const Metrics: React.FC = () => {
   const formatTimestamp = useTimeFormat();
 
   const params = useAppParams<RouteParamsClusterTopic>();
-  const [isAnalyzing, setIsAnalyzing] = React.useState(true);
+
+  const [isAnalyzing, setIsAnalyzing] = useState(true);
   const analyzeTopic = useAnalyzeTopic(params);
   const cancelTopicAnalysis = useCancelTopicAnalysis(params);
 
   const { data } = useTopicAnalysis(params, isAnalyzing);
 
-  React.useEffect(() => {
+  useEffect(() => {
     if (data && !data.progress) {
       setIsAnalyzing(false);
     }
@@ -44,7 +46,10 @@ const Metrics: React.FC = () => {
   if (data.progress) {
     return (
       <S.ProgressContainer>
-        <ProgressBar completed={data.progress.completenessPercent || 0} />
+        <S.ProgressBarWrapper>
+          <ProgressBar completed={data.progress.completenessPercent || 0} />
+          <span> {Math.floor(data.progress.completenessPercent || 0)} %</span>
+        </S.ProgressBarWrapper>
         <Button
           onClick={async () => {
             await cancelTopicAnalysis.mutateAsync();
@@ -58,9 +63,12 @@ const Metrics: React.FC = () => {
         <List>
           <Label>Started at</Label>
           <span>{formatTimestamp(data.progress.startedAt, 'hh:mm:ss a')}</span>
+          <Label>Passed since start</Label>
+          <span>{calculateTimer(data.progress.startedAt as number)}</span>
           <Label>Scanned messages</Label>
+          <span>{data.progress.msgsScanned}</span>
+          <Label>Scanned size</Label>
           <span>
-            {data.progress.msgsScanned} /{' '}
             <BytesFormatted value={data.progress.bytesScanned} />
           </span>
         </List>
@@ -90,7 +98,6 @@ const Metrics: React.FC = () => {
           Restart Analysis
         </Button>
       </S.ActionsBar>
-
       <Informers.Wrapper>
         <Total {...totalStats} />
         {totalStats.keySize && (

+ 8 - 8
kafka-ui-react-app/src/components/Topics/Topic/Statistics/Statistics.styles.ts

@@ -1,7 +1,4 @@
-import {
-  Label,
-  List,
-} from 'components/common/PropertiesList/PropertiesList.styled';
+import { List } from 'components/common/PropertiesList/PropertiesList.styled';
 import styled from 'styled-components';
 
 export const ProgressContainer = styled.div`
@@ -16,10 +13,6 @@ export const ProgressContainer = styled.div`
 
   ${List} {
     opacity: 0.5;
-
-    ${Label} {
-      text-align: right;
-    }
   }
 `;
 
@@ -42,3 +35,10 @@ export const PartitionInfo = styled.div`
   grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
   column-gap: 24px;
 `;
+
+export const ProgressBarWrapper = styled.div`
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  width: 280px;
+`;

+ 5 - 0
kafka-ui-react-app/src/components/Topics/Topic/Statistics/__test__/Metrics.spec.tsx

@@ -34,6 +34,7 @@ describe('Metrics', () => {
 
   describe('when analysis is in progress', () => {
     const cancelMock = jest.fn();
+
     beforeEach(() => {
       (useCancelTopicAnalysis as jest.Mock).mockImplementation(() => ({
         mutateAsync: cancelMock,
@@ -62,6 +63,10 @@ describe('Metrics', () => {
       expect(progressbar).toBeInTheDocument();
       expect(progressbar).toHaveStyleRule('width', '0%');
     });
+
+    it('calculate Timer ', () => {
+      expect(screen.getByText('Passed since start')).toBeInTheDocument();
+    });
   });
 
   describe('when analysis is completed', () => {

+ 18 - 5
kafka-ui-react-app/src/components/Topics/Topic/Statistics/__test__/Statistics.spec.tsx

@@ -1,9 +1,10 @@
 import React from 'react';
-import { screen } from '@testing-library/react';
+import { screen, waitFor } from '@testing-library/react';
 import { render, WithRoute } from 'lib/testHelpers';
 import Statistics from 'components/Topics/Topic/Statistics/Statistics';
 import { clusterTopicStatisticsPath } from 'lib/paths';
-import { useTopicAnalysis } from 'lib/hooks/api/topics';
+import { useTopicAnalysis, useAnalyzeTopic } from 'lib/hooks/api/topics';
+import userEvent from '@testing-library/user-event';
 
 const clusterName = 'local';
 const topicName = 'topic';
@@ -11,6 +12,7 @@ const topicName = 'topic';
 jest.mock('lib/hooks/api/topics', () => ({
   ...jest.requireActual('lib/hooks/api/topics'),
   useTopicAnalysis: jest.fn(),
+  useAnalyzeTopic: jest.fn(),
 }));
 
 describe('Statistics', () => {
@@ -23,13 +25,24 @@ describe('Statistics', () => {
       { initialEntries: [path] }
     );
   };
-
-  it('renders Metricks component', () => {
+  const startMock = jest.fn();
+  it('renders Metricks component', async () => {
     (useTopicAnalysis as jest.Mock).mockImplementation(() => ({
       data: { result: 1 },
     }));
 
     renderComponent();
-    expect(screen.getByText('Restart Analysis')).toBeInTheDocument();
+    await expect(screen.getByText('Restart Analysis')).toBeInTheDocument();
+    expect(screen.queryByRole('progressbar')).not.toBeInTheDocument();
+  });
+  it('renders Start Analysis button', async () => {
+    (useAnalyzeTopic as jest.Mock).mockImplementation(() => ({
+      mutateAsync: startMock,
+    }));
+    renderComponent();
+    const btn = screen.getByRole('button', { name: 'Start Analysis' });
+    expect(btn).toBeInTheDocument();
+    await waitFor(() => userEvent.click(btn));
+    expect(startMock).toHaveBeenCalled();
   });
 });

+ 51 - 0
kafka-ui-react-app/src/lib/__test__/dateTimeHelpers.spec.ts

@@ -0,0 +1,51 @@
+import {
+  passedTime,
+  calculateTimer,
+  formatMilliseconds,
+} from 'lib/dateTimeHelpers';
+
+const startedAt = 1664891890889;
+
+describe('format Milliseconds', () => {
+  it('hours > 0', () => {
+    const result = formatMilliseconds(10000000);
+
+    expect(result).toEqual('2h 46m');
+  });
+  it('minutes > 0', () => {
+    const result = formatMilliseconds(1000000);
+
+    expect(result).toEqual('16m 40s');
+  });
+
+  it('seconds > 0', () => {
+    const result = formatMilliseconds(10000);
+
+    expect(result).toEqual('10s');
+  });
+
+  it('milliseconds > 0', () => {
+    const result = formatMilliseconds(100);
+
+    expect(result).toEqual('100ms' || '0ms');
+    expect(formatMilliseconds()).toEqual('0ms');
+  });
+});
+
+describe('calculate timer', () => {
+  it('time value < 10', () => {
+    expect(passedTime(5)).toBeTruthy();
+  });
+
+  it('time value > 9', () => {
+    expect(passedTime(10)).toBeTruthy();
+  });
+
+  it('run calculate time', () => {
+    expect(calculateTimer(startedAt));
+  });
+
+  it('return when startedAt > new Date()', () => {
+    expect(calculateTimer(1664891890889199)).toBe('00:00');
+  });
+});

+ 11 - 0
kafka-ui-react-app/src/lib/dateTimeHelpers.ts

@@ -32,3 +32,14 @@ export const formatMilliseconds = (input = 0) => {
 
   return `${milliseconds}ms`;
 };
+
+export const passedTime = (value: number) => (value < 10 ? `0${value}` : value);
+
+export const calculateTimer = (startedAt: number) => {
+  const now = new Date().getTime();
+  const newDate = now - startedAt;
+  const minutes = dayjs(newDate).minute();
+  const second = dayjs(newDate).second();
+
+  return newDate > 0 ? `${passedTime(minutes)}:${passedTime(second)}` : '00:00';
+};