Преглед изворни кода

Merge remote-tracking branch 'origin/master' into issue/3422

michalcesek пре 2 година
родитељ
комит
e87e45d03b
21 измењених фајлова са 163 додато и 286 уклоњено
  1. 2 2
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/serdes/ConsumerRecordDeserializer.java
  2. 0 2
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/serdes/SerdesInitializer.java
  3. 1 0
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/KafkaConnectService.java
  4. 3 4
      kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/analyze/TopicAnalysisStats.java
  5. 2 0
      kafka-ui-react-app/src/components/Topics/Topic/Messages/Message.tsx
  6. 6 7
      kafka-ui-react-app/src/components/Topics/Topic/Messages/MessageContent/MessageContent.tsx
  7. 7 66
      kafka-ui-react-app/src/components/Topics/Topic/Messages/MessageContent/__tests__/MessageContent.spec.tsx
  8. 20 6
      kafka-ui-react-app/src/components/Topics/Topic/SendMessage/SendMessage.styled.tsx
  9. 64 42
      kafka-ui-react-app/src/components/Topics/Topic/SendMessage/SendMessage.tsx
  10. 1 1
      kafka-ui-react-app/src/components/Topics/Topic/SendMessage/__test__/SendMessage.spec.tsx
  11. 1 1
      kafka-ui-react-app/src/components/Topics/Topic/Topic.tsx
  12. 8 22
      kafka-ui-react-app/src/components/Version/Version.tsx
  13. 19 66
      kafka-ui-react-app/src/components/Version/__tests__/Version.spec.tsx
  14. 1 0
      kafka-ui-react-app/src/components/common/Icons/WarningIcon.tsx
  15. 1 1
      kafka-ui-react-app/src/components/common/SlidingSidebar/SlidingSidebar.styled.ts
  16. 0 12
      kafka-ui-react-app/src/lib/fixtures/actuatorInfo.ts
  17. 14 1
      kafka-ui-react-app/src/lib/fixtures/latestVersion.ts
  18. 0 17
      kafka-ui-react-app/src/lib/hooks/api/__tests__/actuatorInfo.spec.ts
  19. 5 7
      kafka-ui-react-app/src/lib/hooks/api/__tests__/latestVersion.spec.ts
  20. 0 19
      kafka-ui-react-app/src/lib/hooks/api/actuatorInfo.ts
  21. 8 10
      kafka-ui-react-app/src/lib/hooks/api/latestVersion.ts

+ 2 - 2
kafka-ui-api/src/main/java/com/provectus/kafka/ui/serdes/ConsumerRecordDeserializer.java

@@ -123,11 +123,11 @@ public class ConsumerRecordDeserializer {
   }
   }
 
 
   private static Long getKeySize(ConsumerRecord<Bytes, Bytes> consumerRecord) {
   private static Long getKeySize(ConsumerRecord<Bytes, Bytes> consumerRecord) {
-    return consumerRecord.key() != null ? (long) consumerRecord.key().get().length : null;
+    return consumerRecord.key() != null ? (long) consumerRecord.serializedKeySize() : null;
   }
   }
 
 
   private static Long getValueSize(ConsumerRecord<Bytes, Bytes> consumerRecord) {
   private static Long getValueSize(ConsumerRecord<Bytes, Bytes> consumerRecord) {
-    return consumerRecord.value() != null ? (long) consumerRecord.value().get().length : null;
+    return consumerRecord.value() != null ? (long) consumerRecord.serializedValueSize() : null;
   }
   }
 
 
   private static int headerSize(Header header) {
   private static int headerSize(Header header) {

+ 0 - 2
kafka-ui-api/src/main/java/com/provectus/kafka/ui/serdes/SerdesInitializer.java

@@ -122,8 +122,6 @@ public class SerdesInitializer {
         registeredSerdes,
         registeredSerdes,
         Optional.ofNullable(clusterProperties.getDefaultKeySerde())
         Optional.ofNullable(clusterProperties.getDefaultKeySerde())
             .map(name -> Preconditions.checkNotNull(registeredSerdes.get(name), "Default key serde not found"))
             .map(name -> Preconditions.checkNotNull(registeredSerdes.get(name), "Default key serde not found"))
-            .or(() -> Optional.ofNullable(registeredSerdes.get(SchemaRegistrySerde.name())))
-            .or(() -> Optional.ofNullable(registeredSerdes.get(ProtobufFileSerde.name())))
             .orElse(null),
             .orElse(null),
         Optional.ofNullable(clusterProperties.getDefaultValueSerde())
         Optional.ofNullable(clusterProperties.getDefaultValueSerde())
             .map(name -> Preconditions.checkNotNull(registeredSerdes.get(name), "Default value serde not found"))
             .map(name -> Preconditions.checkNotNull(registeredSerdes.get(name), "Default value serde not found"))

+ 1 - 0
kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/KafkaConnectService.java

@@ -109,6 +109,7 @@ public class KafkaConnectService {
   private Stream<String> getStringsForSearch(FullConnectorInfoDTO fullConnectorInfo) {
   private Stream<String> getStringsForSearch(FullConnectorInfoDTO fullConnectorInfo) {
     return Stream.of(
     return Stream.of(
         fullConnectorInfo.getName(),
         fullConnectorInfo.getName(),
+        fullConnectorInfo.getConnect(),
         fullConnectorInfo.getStatus().getState().getValue(),
         fullConnectorInfo.getStatus().getState().getValue(),
         fullConnectorInfo.getType().getValue());
         fullConnectorInfo.getType().getValue());
   }
   }

+ 3 - 4
kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/analyze/TopicAnalysisStats.java

@@ -43,8 +43,7 @@ class TopicAnalysisStats {
     Long max;
     Long max;
     final UpdateDoublesSketch sizeSketch = DoublesSketch.builder().build();
     final UpdateDoublesSketch sizeSketch = DoublesSketch.builder().build();
 
 
-    void apply(byte[] bytes) {
-      int len = bytes.length;
+    void apply(int len) {
       sum += len;
       sum += len;
       min = minNullable(min, len);
       min = minNullable(min, len);
       max = maxNullable(max, len);
       max = maxNullable(max, len);
@@ -98,7 +97,7 @@ class TopicAnalysisStats {
 
 
     if (rec.key() != null) {
     if (rec.key() != null) {
       byte[] keyBytes = rec.key().get();
       byte[] keyBytes = rec.key().get();
-      keysSize.apply(keyBytes);
+      keysSize.apply(rec.serializedKeySize());
       uniqKeys.update(keyBytes);
       uniqKeys.update(keyBytes);
     } else {
     } else {
       nullKeys++;
       nullKeys++;
@@ -106,7 +105,7 @@ class TopicAnalysisStats {
 
 
     if (rec.value() != null) {
     if (rec.value() != null) {
       byte[] valueBytes = rec.value().get();
       byte[] valueBytes = rec.value().get();
-      valuesSize.apply(valueBytes);
+      valuesSize.apply(rec.serializedValueSize());
       uniqValues.update(valueBytes);
       uniqValues.update(valueBytes);
     } else {
     } else {
       nullValues++;
       nullValues++;

+ 2 - 0
kafka-ui-react-app/src/components/Topics/Topic/Messages/Message.tsx

@@ -142,6 +142,8 @@ const Message: React.FC<Props> = ({
           timestampType={timestampType}
           timestampType={timestampType}
           keySize={keySize}
           keySize={keySize}
           contentSize={valueSize}
           contentSize={valueSize}
+          keySerde={keySerde}
+          valueSerde={valueSerde}
         />
         />
       )}
       )}
     </>
     </>

+ 6 - 7
kafka-ui-react-app/src/components/Topics/Topic/Messages/MessageContent/MessageContent.tsx

@@ -3,7 +3,6 @@ import EditorViewer from 'components/common/EditorViewer/EditorViewer';
 import BytesFormatted from 'components/common/BytesFormatted/BytesFormatted';
 import BytesFormatted from 'components/common/BytesFormatted/BytesFormatted';
 import { SchemaType, TopicMessageTimestampTypeEnum } from 'generated-sources';
 import { SchemaType, TopicMessageTimestampTypeEnum } from 'generated-sources';
 import { formatTimestamp } from 'lib/dateTimeHelpers';
 import { formatTimestamp } from 'lib/dateTimeHelpers';
-import { useSearchParams } from 'react-router-dom';
 
 
 import * as S from './MessageContent.styled';
 import * as S from './MessageContent.styled';
 
 
@@ -17,6 +16,8 @@ export interface MessageContentProps {
   timestampType?: TopicMessageTimestampTypeEnum;
   timestampType?: TopicMessageTimestampTypeEnum;
   keySize?: number;
   keySize?: number;
   contentSize?: number;
   contentSize?: number;
+  keySerde?: string;
+  valueSerde?: string;
 }
 }
 
 
 const MessageContent: React.FC<MessageContentProps> = ({
 const MessageContent: React.FC<MessageContentProps> = ({
@@ -27,12 +28,10 @@ const MessageContent: React.FC<MessageContentProps> = ({
   timestampType,
   timestampType,
   keySize,
   keySize,
   contentSize,
   contentSize,
+  keySerde,
+  valueSerde,
 }) => {
 }) => {
   const [activeTab, setActiveTab] = React.useState<Tab>('content');
   const [activeTab, setActiveTab] = React.useState<Tab>('content');
-  const [searchParams] = useSearchParams();
-  const keyFormat = searchParams.get('keySerde') || '';
-  const valueFormat = searchParams.get('valueSerde') || '';
-
   const activeTabContent = () => {
   const activeTabContent = () => {
     switch (activeTab) {
     switch (activeTab) {
       case 'content':
       case 'content':
@@ -110,7 +109,7 @@ const MessageContent: React.FC<MessageContentProps> = ({
             <S.Metadata>
             <S.Metadata>
               <S.MetadataLabel>Key Serde</S.MetadataLabel>
               <S.MetadataLabel>Key Serde</S.MetadataLabel>
               <span>
               <span>
-                <S.MetadataValue>{keyFormat}</S.MetadataValue>
+                <S.MetadataValue>{keySerde}</S.MetadataValue>
                 <S.MetadataMeta>
                 <S.MetadataMeta>
                   Size: <BytesFormatted value={keySize} />
                   Size: <BytesFormatted value={keySize} />
                 </S.MetadataMeta>
                 </S.MetadataMeta>
@@ -120,7 +119,7 @@ const MessageContent: React.FC<MessageContentProps> = ({
             <S.Metadata>
             <S.Metadata>
               <S.MetadataLabel>Value Serde</S.MetadataLabel>
               <S.MetadataLabel>Value Serde</S.MetadataLabel>
               <span>
               <span>
-                <S.MetadataValue>{valueFormat}</S.MetadataValue>
+                <S.MetadataValue>{valueSerde}</S.MetadataValue>
                 <S.MetadataMeta>
                 <S.MetadataMeta>
                   Size: <BytesFormatted value={contentSize} />
                   Size: <BytesFormatted value={contentSize} />
                 </S.MetadataMeta>
                 </S.MetadataMeta>

+ 7 - 66
kafka-ui-react-app/src/components/Topics/Topic/Messages/MessageContent/__tests__/MessageContent.spec.tsx

@@ -20,6 +20,8 @@ const setupWrapper = (props?: Partial<MessageContentProps>) => {
           headers={{ header: 'test' }}
           headers={{ header: 'test' }}
           timestamp={new Date(0)}
           timestamp={new Date(0)}
           timestampType={TopicMessageTimestampTypeEnum.CREATE_TIME}
           timestampType={TopicMessageTimestampTypeEnum.CREATE_TIME}
+          keySerde="SchemaRegistry"
+          valueSerde="Avro"
           {...props}
           {...props}
         />
         />
       </tbody>
       </tbody>
@@ -27,42 +29,20 @@ const setupWrapper = (props?: Partial<MessageContentProps>) => {
   );
   );
 };
 };
 
 
-const proto =
-  'syntax = "proto3";\npackage com.provectus;\n\nmessage TestProtoRecord {\n  string f1 = 1;\n  int32 f2 = 2;\n}\n';
-
 global.TextEncoder = TextEncoder;
 global.TextEncoder = TextEncoder;
 
 
-const searchParamsContentAVRO = new URLSearchParams({
-  keySerde: 'SchemaRegistry',
-  valueSerde: 'AVRO',
-  limit: '100',
-});
-
-const searchParamsContentJSON = new URLSearchParams({
-  keySerde: 'SchemaRegistry',
-  valueSerde: 'JSON',
-  limit: '100',
-});
-
-const searchParamsContentPROTOBUF = new URLSearchParams({
-  keySerde: 'SchemaRegistry',
-  valueSerde: 'PROTOBUF',
-  limit: '100',
-});
 describe('MessageContent screen', () => {
 describe('MessageContent screen', () => {
   beforeEach(() => {
   beforeEach(() => {
-    render(setupWrapper(), {
-      initialEntries: [`/messages?${searchParamsContentAVRO}`],
-    });
+    render(setupWrapper());
   });
   });
 
 
-  describe('renders', () => {
-    it('key format in document', () => {
+  describe('Checking keySerde and valueSerde', () => {
+    it('keySerde in document', () => {
       expect(screen.getByText('SchemaRegistry')).toBeInTheDocument();
       expect(screen.getByText('SchemaRegistry')).toBeInTheDocument();
     });
     });
 
 
-    it('content format in document', () => {
-      expect(screen.getByText('AVRO')).toBeInTheDocument();
+    it('valueSerde in document', () => {
+      expect(screen.getByText('Avro')).toBeInTheDocument();
     });
     });
   });
   });
 
 
@@ -98,42 +78,3 @@ describe('MessageContent screen', () => {
     });
     });
   });
   });
 });
 });
-
-describe('checking content type depend on message type', () => {
-  it('renders component with message having JSON type', () => {
-    render(
-      setupWrapper({
-        messageContent: '{"data": "test"}',
-      }),
-      { initialEntries: [`/messages?${searchParamsContentJSON}`] }
-    );
-    expect(screen.getByText('JSON')).toBeInTheDocument();
-  });
-  it('renders component with message having AVRO type', () => {
-    render(
-      setupWrapper({
-        messageContent: '{"data": "test"}',
-      }),
-      { initialEntries: [`/messages?${searchParamsContentAVRO}`] }
-    );
-    expect(screen.getByText('AVRO')).toBeInTheDocument();
-  });
-  it('renders component with message having PROTOBUF type', () => {
-    render(
-      setupWrapper({
-        messageContent: proto,
-      }),
-      { initialEntries: [`/messages?${searchParamsContentPROTOBUF}`] }
-    );
-    expect(screen.getByText('PROTOBUF')).toBeInTheDocument();
-  });
-  it('renders component with message having no type which is equal to having PROTOBUF type', () => {
-    render(
-      setupWrapper({
-        messageContent: '',
-      }),
-      { initialEntries: [`/messages?${searchParamsContentPROTOBUF}`] }
-    );
-    expect(screen.getByText('PROTOBUF')).toBeInTheDocument();
-  });
-});

+ 20 - 6
kafka-ui-react-app/src/components/Topics/Topic/SendMessage/SendMessage.styled.tsx

@@ -8,15 +8,29 @@ export const Wrapper = styled.div`
 export const Columns = styled.div`
 export const Columns = styled.div`
   margin: -0.75rem;
   margin: -0.75rem;
   margin-bottom: 0.75rem;
   margin-bottom: 0.75rem;
+  display: flex;
+  flex-direction: column;
+  padding: 0.75rem;
+  gap: 8px;
 
 
   @media screen and (min-width: 769px) {
   @media screen and (min-width: 769px) {
     display: flex;
     display: flex;
   }
   }
 `;
 `;
-
-export const Column = styled.div`
-  flex-basis: 0;
-  flex-grow: 1;
-  flex-shrink: 1;
-  padding: 0.75rem;
+export const Flex = styled.div`
+  display: flex;
+  flex-direction: row;
+  gap: 8px;
+  @media screen and (max-width: 1200px) {
+    flex-direction: column;
+  }
+`;
+export const FlexItem = styled.div`
+  width: 18rem;
+  @media screen and (max-width: 1450px) {
+    width: 50%;
+  }
+  @media screen and (max-width: 1200px) {
+    width: 100%;
+  }
 `;
 `;

+ 64 - 42
kafka-ui-react-app/src/components/Topics/Topic/SendMessage/SendMessage.tsx

@@ -4,6 +4,7 @@ import { RouteParamsClusterTopic } from 'lib/paths';
 import { Button } from 'components/common/Button/Button';
 import { Button } from 'components/common/Button/Button';
 import Editor from 'components/common/Editor/Editor';
 import Editor from 'components/common/Editor/Editor';
 import Select, { SelectOption } from 'components/common/Select/Select';
 import Select, { SelectOption } from 'components/common/Select/Select';
+import Switch from 'components/common/Switch/Switch';
 import useAppParams from 'lib/hooks/useAppParams';
 import useAppParams from 'lib/hooks/useAppParams';
 import { showAlert } from 'lib/errorHandling';
 import { showAlert } from 'lib/errorHandling';
 import { useSendMessage, useTopicDetails } from 'lib/hooks/api/topics';
 import { useSendMessage, useTopicDetails } from 'lib/hooks/api/topics';
@@ -26,9 +27,12 @@ interface FormType {
   partition: number;
   partition: number;
   keySerde: string;
   keySerde: string;
   valueSerde: string;
   valueSerde: string;
+  keepContents: boolean;
 }
 }
 
 
-const SendMessage: React.FC<{ onSubmit: () => void }> = ({ onSubmit }) => {
+const SendMessage: React.FC<{ closeSidebar: () => void }> = ({
+  closeSidebar,
+}) => {
   const { clusterName, topicName } = useAppParams<RouteParamsClusterTopic>();
   const { clusterName, topicName } = useAppParams<RouteParamsClusterTopic>();
   const { data: topic } = useTopicDetails({ clusterName, topicName });
   const { data: topic } = useTopicDetails({ clusterName, topicName });
   const { data: serdes = {} } = useSerdes({
   const { data: serdes = {} } = useSerdes({
@@ -47,11 +51,13 @@ const SendMessage: React.FC<{ onSubmit: () => void }> = ({ onSubmit }) => {
     handleSubmit,
     handleSubmit,
     formState: { isSubmitting },
     formState: { isSubmitting },
     control,
     control,
+    setValue,
   } = useForm<FormType>({
   } = useForm<FormType>({
     mode: 'onChange',
     mode: 'onChange',
     defaultValues: {
     defaultValues: {
       ...defaultValues,
       ...defaultValues,
       partition: Number(partitionOptions[0].value),
       partition: Number(partitionOptions[0].value),
+      keepContents: false,
     },
     },
   });
   });
 
 
@@ -62,6 +68,7 @@ const SendMessage: React.FC<{ onSubmit: () => void }> = ({ onSubmit }) => {
     content,
     content,
     headers,
     headers,
     partition,
     partition,
+    keepContents,
   }: FormType) => {
   }: FormType) => {
     let errors: string[] = [];
     let errors: string[] = [];
 
 
@@ -110,7 +117,11 @@ const SendMessage: React.FC<{ onSubmit: () => void }> = ({ onSubmit }) => {
         keySerde,
         keySerde,
         valueSerde,
         valueSerde,
       });
       });
-      onSubmit();
+      if (!keepContents) {
+        setValue('key', '');
+        setValue('content', '');
+        closeSidebar();
+      }
     } catch (e) {
     } catch (e) {
       // do nothing
       // do nothing
     }
     }
@@ -120,7 +131,7 @@ const SendMessage: React.FC<{ onSubmit: () => void }> = ({ onSubmit }) => {
     <S.Wrapper>
     <S.Wrapper>
       <form onSubmit={handleSubmit(submit)}>
       <form onSubmit={handleSubmit(submit)}>
         <S.Columns>
         <S.Columns>
-          <S.Column>
+          <S.FlexItem>
             <InputLabel>Partition</InputLabel>
             <InputLabel>Partition</InputLabel>
             <Controller
             <Controller
               control={control}
               control={control}
@@ -137,47 +148,58 @@ const SendMessage: React.FC<{ onSubmit: () => void }> = ({ onSubmit }) => {
                 />
                 />
               )}
               )}
             />
             />
-          </S.Column>
-          <S.Column>
-            <InputLabel>Key Serde</InputLabel>
+          </S.FlexItem>
+          <S.Flex>
+            <S.FlexItem>
+              <InputLabel>Key Serde</InputLabel>
+              <Controller
+                control={control}
+                name="keySerde"
+                render={({ field: { name, onChange, value } }) => (
+                  <Select
+                    id="selectKeySerdeOptions"
+                    aria-labelledby="selectKeySerdeOptions"
+                    name={name}
+                    onChange={onChange}
+                    minWidth="100%"
+                    options={getSerdeOptions(serdes.key || [])}
+                    value={value}
+                  />
+                )}
+              />
+            </S.FlexItem>
+            <S.FlexItem>
+              <InputLabel>Value Serde</InputLabel>
+              <Controller
+                control={control}
+                name="valueSerde"
+                render={({ field: { name, onChange, value } }) => (
+                  <Select
+                    id="selectValueSerdeOptions"
+                    aria-labelledby="selectValueSerdeOptions"
+                    name={name}
+                    onChange={onChange}
+                    minWidth="100%"
+                    options={getSerdeOptions(serdes.value || [])}
+                    value={value}
+                  />
+                )}
+              />
+            </S.FlexItem>
+          </S.Flex>
+          <div>
             <Controller
             <Controller
               control={control}
               control={control}
-              name="keySerde"
+              name="keepContents"
               render={({ field: { name, onChange, value } }) => (
               render={({ field: { name, onChange, value } }) => (
-                <Select
-                  id="selectKeySerdeOptions"
-                  aria-labelledby="selectKeySerdeOptions"
-                  name={name}
-                  onChange={onChange}
-                  minWidth="100%"
-                  options={getSerdeOptions(serdes.key || [])}
-                  value={value}
-                />
+                <Switch name={name} onChange={onChange} checked={value} />
               )}
               )}
             />
             />
-          </S.Column>
-          <S.Column>
-            <InputLabel>Value Serde</InputLabel>
-            <Controller
-              control={control}
-              name="valueSerde"
-              render={({ field: { name, onChange, value } }) => (
-                <Select
-                  id="selectValueSerdeOptions"
-                  aria-labelledby="selectValueSerdeOptions"
-                  name={name}
-                  onChange={onChange}
-                  minWidth="100%"
-                  options={getSerdeOptions(serdes.value || [])}
-                  value={value}
-                />
-              )}
-            />
-          </S.Column>
+            <InputLabel>Keep contents</InputLabel>
+          </div>
         </S.Columns>
         </S.Columns>
-
         <S.Columns>
         <S.Columns>
-          <S.Column>
+          <div>
             <InputLabel>Key</InputLabel>
             <InputLabel>Key</InputLabel>
             <Controller
             <Controller
               control={control}
               control={control}
@@ -191,8 +213,8 @@ const SendMessage: React.FC<{ onSubmit: () => void }> = ({ onSubmit }) => {
                 />
                 />
               )}
               )}
             />
             />
-          </S.Column>
-          <S.Column>
+          </div>
+          <div>
             <InputLabel>Value</InputLabel>
             <InputLabel>Value</InputLabel>
             <Controller
             <Controller
               control={control}
               control={control}
@@ -206,10 +228,10 @@ const SendMessage: React.FC<{ onSubmit: () => void }> = ({ onSubmit }) => {
                 />
                 />
               )}
               )}
             />
             />
-          </S.Column>
+          </div>
         </S.Columns>
         </S.Columns>
         <S.Columns>
         <S.Columns>
-          <S.Column>
+          <div>
             <InputLabel>Headers</InputLabel>
             <InputLabel>Headers</InputLabel>
             <Controller
             <Controller
               control={control}
               control={control}
@@ -224,7 +246,7 @@ const SendMessage: React.FC<{ onSubmit: () => void }> = ({ onSubmit }) => {
                 />
                 />
               )}
               )}
             />
             />
-          </S.Column>
+          </div>
         </S.Columns>
         </S.Columns>
         <Button
         <Button
           buttonSize="M"
           buttonSize="M"

+ 1 - 1
kafka-ui-react-app/src/components/Topics/Topic/SendMessage/__test__/SendMessage.spec.tsx

@@ -49,7 +49,7 @@ const renderComponent = async () => {
   const path = clusterTopicPath(clusterName, topicName);
   const path = clusterTopicPath(clusterName, topicName);
   await render(
   await render(
     <WithRoute path={clusterTopicPath()}>
     <WithRoute path={clusterTopicPath()}>
-      <SendMessage onSubmit={mockOnSubmit} />
+      <SendMessage closeSidebar={mockOnSubmit} />
     </WithRoute>,
     </WithRoute>,
     { initialEntries: [path] }
     { initialEntries: [path] }
   );
   );

+ 1 - 1
kafka-ui-react-app/src/components/Topics/Topic/Topic.tsx

@@ -236,7 +236,7 @@ const Topic: React.FC = () => {
         title="Produce Message"
         title="Produce Message"
       >
       >
         <Suspense fallback={<PageLoader />}>
         <Suspense fallback={<PageLoader />}>
-          <SendMessage onSubmit={closeSidebar} />
+          <SendMessage closeSidebar={closeSidebar} />
         </Suspense>
         </Suspense>
       </SlidingSidebar>
       </SlidingSidebar>
     </>
     </>

+ 8 - 22
kafka-ui-react-app/src/components/Version/Version.tsx

@@ -1,52 +1,38 @@
 import React from 'react';
 import React from 'react';
 import WarningIcon from 'components/common/Icons/WarningIcon';
 import WarningIcon from 'components/common/Icons/WarningIcon';
 import { gitCommitPath } from 'lib/paths';
 import { gitCommitPath } from 'lib/paths';
-import { useActuatorInfo } from 'lib/hooks/api/actuatorInfo';
-import { BUILD_VERSION_PATTERN } from 'lib/constants';
 import { useLatestVersion } from 'lib/hooks/api/latestVersion';
 import { useLatestVersion } from 'lib/hooks/api/latestVersion';
 import { formatTimestamp } from 'lib/dateTimeHelpers';
 import { formatTimestamp } from 'lib/dateTimeHelpers';
 
 
 import * as S from './Version.styled';
 import * as S from './Version.styled';
-import compareVersions from './compareVersions';
 
 
 const Version: React.FC = () => {
 const Version: React.FC = () => {
-  const { data: actuatorInfo = {} } = useActuatorInfo();
   const { data: latestVersionInfo = {} } = useLatestVersion();
   const { data: latestVersionInfo = {} } = useLatestVersion();
-
-  const tag = actuatorInfo?.build?.version;
-  const commit = actuatorInfo?.git?.commit.id;
-  const { tag_name: latestTag } = latestVersionInfo;
-
-  const outdated = compareVersions(tag, latestTag);
-
-  const currentVersion = tag?.match(BUILD_VERSION_PATTERN)
-    ? tag
-    : formatTimestamp(actuatorInfo?.build?.time);
-
-  if (!tag) return null;
+  const { buildTime, commitId, isLatestRelease } = latestVersionInfo.build;
+  const { versionTag } = latestVersionInfo?.latestRelease || '';
 
 
   return (
   return (
     <S.Wrapper>
     <S.Wrapper>
-      {!!outdated && (
+      {!isLatestRelease && (
         <S.OutdatedWarning
         <S.OutdatedWarning
-          title={`Your app version is outdated. Current latest version is ${latestTag}`}
+          title={`Your app version is outdated. Current latest version is ${versionTag}`}
         >
         >
           <WarningIcon />
           <WarningIcon />
         </S.OutdatedWarning>
         </S.OutdatedWarning>
       )}
       )}
 
 
-      {commit && (
+      {commitId && (
         <div>
         <div>
           <S.CurrentCommitLink
           <S.CurrentCommitLink
             title="Current commit"
             title="Current commit"
             target="__blank"
             target="__blank"
-            href={gitCommitPath(commit)}
+            href={gitCommitPath(commitId)}
           >
           >
-            {commit}
+            {commitId}
           </S.CurrentCommitLink>
           </S.CurrentCommitLink>
         </div>
         </div>
       )}
       )}
-      <S.CurrentVersion>{currentVersion}</S.CurrentVersion>
+      <S.CurrentVersion>{formatTimestamp(buildTime)}</S.CurrentVersion>
     </S.Wrapper>
     </S.Wrapper>
   );
   );
 };
 };

+ 19 - 66
kafka-ui-react-app/src/components/Version/__tests__/Version.spec.tsx

@@ -2,87 +2,40 @@ import React from 'react';
 import { screen } from '@testing-library/dom';
 import { screen } from '@testing-library/dom';
 import Version from 'components/Version/Version';
 import Version from 'components/Version/Version';
 import { render } from 'lib/testHelpers';
 import { render } from 'lib/testHelpers';
-import { formatTimestamp } from 'lib/dateTimeHelpers';
-import { useActuatorInfo } from 'lib/hooks/api/actuatorInfo';
 import { useLatestVersion } from 'lib/hooks/api/latestVersion';
 import { useLatestVersion } from 'lib/hooks/api/latestVersion';
-import { actuatorInfoPayload } from 'lib/fixtures/actuatorInfo';
-import { latestVersionPayload } from 'lib/fixtures/latestVersion';
+import {
+  deprecatedVersionPayload,
+  latestVersionPayload,
+} from 'lib/fixtures/latestVersion';
 
 
-jest.mock('lib/hooks/api/actuatorInfo', () => ({
-  useActuatorInfo: jest.fn(),
-}));
 jest.mock('lib/hooks/api/latestVersion', () => ({
 jest.mock('lib/hooks/api/latestVersion', () => ({
   useLatestVersion: jest.fn(),
   useLatestVersion: jest.fn(),
 }));
 }));
-
 describe('Version Component', () => {
 describe('Version Component', () => {
-  const versionTag = 'v0.5.0';
-  const snapshotTag = 'test-SNAPSHOT';
-  const commitTag = 'befd3b328e2c9c7df57b0c5746561b2f7fee8813';
-
-  const actuatorVersionPayload = actuatorInfoPayload(versionTag);
-  const formattedTimestamp = formatTimestamp(actuatorVersionPayload.build.time);
-
-  beforeEach(() => {
-    (useActuatorInfo as jest.Mock).mockImplementation(() => ({
-      data: actuatorVersionPayload,
-    }));
-    (useLatestVersion as jest.Mock).mockImplementation(() => ({
-      data: latestVersionPayload,
-    }));
-  });
-
-  describe('tag does not exist', () => {
-    it('does not render component', async () => {
-      (useActuatorInfo as jest.Mock).mockImplementation(() => ({
-        data: null,
-      }));
-      const { container } = render(<Version />);
-      expect(container.firstChild).toBeEmptyDOMElement();
-    });
-  });
+  const commitId = '96a577a';
 
 
-  describe('renders current version', () => {
-    it('renders release build version as current version', async () => {
-      render(<Version />);
-      expect(screen.getByText(versionTag)).toBeInTheDocument();
-    });
-    it('renders formatted timestamp as current version when version is commit', async () => {
-      (useActuatorInfo as jest.Mock).mockImplementation(() => ({
-        data: actuatorInfoPayload(commitTag),
+  describe('render latest version', () => {
+    beforeEach(() => {
+      (useLatestVersion as jest.Mock).mockImplementation(() => ({
+        data: latestVersionPayload,
       }));
       }));
-      render(<Version />);
-      expect(screen.getByText(formattedTimestamp)).toBeInTheDocument();
     });
     });
-    it('renders formatted timestamp as current version when version contains -SNAPSHOT', async () => {
-      (useActuatorInfo as jest.Mock).mockImplementation(() => ({
-        data: actuatorInfoPayload(snapshotTag),
-      }));
+    it('renders latest release version as current version', async () => {
       render(<Version />);
       render(<Version />);
-      expect(screen.getByText(formattedTimestamp)).toBeInTheDocument();
+      expect(screen.getByText(commitId)).toBeInTheDocument();
     });
     });
-  });
 
 
-  describe('outdated build version', () => {
-    it('renders warning message', async () => {
-      (useActuatorInfo as jest.Mock).mockImplementation(() => ({
-        data: actuatorInfoPayload('v0.3.0'),
-      }));
+    it('should not show warning icon if it is last release', async () => {
       render(<Version />);
       render(<Version />);
-      expect(
-        screen.getByTitle(
-          `Your app version is outdated. Current latest version is ${latestVersionPayload.tag_name}`
-        )
-      ).toBeInTheDocument();
+      expect(screen.queryByRole('img')).not.toBeInTheDocument();
     });
     });
   });
   });
 
 
-  describe('current commit id with link', () => {
-    it('renders', async () => {
-      render(<Version />);
-      expect(
-        screen.getByText(actuatorVersionPayload.git.commit.id)
-      ).toBeInTheDocument();
-    });
+  it('show warning icon if it is not last release', async () => {
+    (useLatestVersion as jest.Mock).mockImplementation(() => ({
+      data: deprecatedVersionPayload,
+    }));
+    render(<Version />);
+    expect(screen.getByRole('img')).toBeInTheDocument();
   });
   });
 });
 });

+ 1 - 0
kafka-ui-react-app/src/components/common/Icons/WarningIcon.tsx

@@ -13,6 +13,7 @@ const WarningIcon: React.FC = () => {
   return (
   return (
     <WarningIconContainer>
     <WarningIconContainer>
       <svg
       <svg
+        role="img"
         width="14"
         width="14"
         height="13"
         height="13"
         viewBox="0 0 14 13"
         viewBox="0 0 14 13"

+ 1 - 1
kafka-ui-react-app/src/components/common/SlidingSidebar/SlidingSidebar.styled.ts

@@ -6,7 +6,7 @@ export const Wrapper = styled.div<{ $open?: boolean }>(
   position: fixed;
   position: fixed;
   top: ${theme.layout.navBarHeight};
   top: ${theme.layout.navBarHeight};
   bottom: 0;
   bottom: 0;
-  width: 60vw;
+  width: 37vw;
   right: calc(${$open ? '0px' : theme.layout.rightSidebarWidth} * -1);
   right: calc(${$open ? '0px' : theme.layout.rightSidebarWidth} * -1);
   box-shadow: -1px 0px 10px 0px rgba(0, 0, 0, 0.2);
   box-shadow: -1px 0px 10px 0px rgba(0, 0, 0, 0.2);
   transition: right 0.3s linear;
   transition: right 0.3s linear;

+ 0 - 12
kafka-ui-react-app/src/lib/fixtures/actuatorInfo.ts

@@ -1,12 +0,0 @@
-export const actuatorInfoPayload = (
-  version = 'befd3b328e2c9c7df57b0c5746561b2f7fee8813'
-) => ({
-  git: { commit: { id: 'befd3b3' } },
-  build: {
-    artifact: 'kafka-ui-api',
-    name: 'kafka-ui-api',
-    time: '2022-09-15T09:52:21.753Z',
-    version,
-    group: 'com.provectus',
-  },
-});

+ 14 - 1
kafka-ui-react-app/src/lib/fixtures/latestVersion.ts

@@ -1,3 +1,16 @@
+export const deprecatedVersionPayload = {
+  build: {
+    buildTime: '2023-04-14T09:47:35.463Z',
+    commitId: '96a577a',
+    isLatestRelease: false,
+    version: '96a577a98c6069376c5d22ed49cffd3739f1bbdc',
+  },
+};
 export const latestVersionPayload = {
 export const latestVersionPayload = {
-  tag_name: 'v0.4.0',
+  build: {
+    buildTime: '2023-04-14T09:47:35.463Z',
+    commitId: '96a577a',
+    isLatestRelease: true,
+    version: '96a577a98c6069376c5d22ed49cffd3739f1bbdc',
+  },
 };
 };

+ 0 - 17
kafka-ui-react-app/src/lib/hooks/api/__tests__/actuatorInfo.spec.ts

@@ -1,17 +0,0 @@
-import fetchMock from 'fetch-mock';
-import * as hooks from 'lib/hooks/api/actuatorInfo';
-import { expectQueryWorks, renderQueryHook } from 'lib/testHelpers';
-import { actuatorInfoPayload } from 'lib/fixtures/actuatorInfo';
-
-const actuatorInfoPath = '/actuator/info';
-
-describe('Actuator info hooks', () => {
-  beforeEach(() => fetchMock.restore());
-  describe('useActuatorInfo', () => {
-    it('returns the correct data', async () => {
-      const mock = fetchMock.getOnce(actuatorInfoPath, actuatorInfoPayload());
-      const { result } = renderQueryHook(() => hooks.useActuatorInfo());
-      await expectQueryWorks(mock, result);
-    });
-  });
-});

+ 5 - 7
kafka-ui-react-app/src/lib/hooks/api/__tests__/latestVersion.spec.ts

@@ -1,18 +1,16 @@
 import fetchMock from 'fetch-mock';
 import fetchMock from 'fetch-mock';
 import { expectQueryWorks, renderQueryHook } from 'lib/testHelpers';
 import { expectQueryWorks, renderQueryHook } from 'lib/testHelpers';
-import * as hooks from 'lib/hooks/api/latestVersion';
-import { GIT_REPO_LATEST_RELEASE_LINK } from 'lib/constants';
 import { latestVersionPayload } from 'lib/fixtures/latestVersion';
 import { latestVersionPayload } from 'lib/fixtures/latestVersion';
+import { useLatestVersion } from 'lib/hooks/api/latestVersion';
+
+const latestVersionPath = '/api/info';
 
 
 describe('Latest version hooks', () => {
 describe('Latest version hooks', () => {
   beforeEach(() => fetchMock.restore());
   beforeEach(() => fetchMock.restore());
   describe('useLatestVersion', () => {
   describe('useLatestVersion', () => {
     it('returns the correct data', async () => {
     it('returns the correct data', async () => {
-      const mock = fetchMock.getOnce(
-        GIT_REPO_LATEST_RELEASE_LINK,
-        latestVersionPayload
-      );
-      const { result } = renderQueryHook(() => hooks.useLatestVersion());
+      const mock = fetchMock.getOnce(latestVersionPath, latestVersionPayload);
+      const { result } = renderQueryHook(() => useLatestVersion());
       await expectQueryWorks(mock, result);
       await expectQueryWorks(mock, result);
     });
     });
   });
   });

+ 0 - 19
kafka-ui-react-app/src/lib/hooks/api/actuatorInfo.ts

@@ -1,19 +0,0 @@
-import { useQuery } from '@tanstack/react-query';
-import { BASE_PARAMS, QUERY_REFETCH_OFF_OPTIONS } from 'lib/constants';
-
-const fetchActuatorInfo = async () => {
-  const data = await fetch(
-    `${BASE_PARAMS.basePath}/actuator/info`,
-    BASE_PARAMS
-  ).then((res) => res.json());
-
-  return data;
-};
-
-export function useActuatorInfo() {
-  return useQuery(
-    ['actuatorInfo'],
-    fetchActuatorInfo,
-    QUERY_REFETCH_OFF_OPTIONS
-  );
-}

+ 8 - 10
kafka-ui-react-app/src/lib/hooks/api/latestVersion.ts

@@ -1,21 +1,19 @@
 import { useQuery } from '@tanstack/react-query';
 import { useQuery } from '@tanstack/react-query';
-import {
-  QUERY_REFETCH_OFF_OPTIONS,
-  GIT_REPO_LATEST_RELEASE_LINK,
-} from 'lib/constants';
+import { BASE_PARAMS, QUERY_REFETCH_OFF_OPTIONS } from 'lib/constants';
 
 
-const fetchLatestVersion = async () => {
-  const data = await fetch(GIT_REPO_LATEST_RELEASE_LINK).then((res) =>
-    res.json()
-  );
+const fetchLatestVersionInfo = async () => {
+  const data = await fetch(
+    `${BASE_PARAMS.basePath}/api/info`,
+    BASE_PARAMS
+  ).then((res) => res.json());
 
 
   return data;
   return data;
 };
 };
 
 
 export function useLatestVersion() {
 export function useLatestVersion() {
   return useQuery(
   return useQuery(
-    ['latestVersion'],
-    fetchLatestVersion,
+    ['versionInfo'],
+    fetchLatestVersionInfo,
     QUERY_REFETCH_OFF_OPTIONS
     QUERY_REFETCH_OFF_OPTIONS
   );
   );
 }
 }