Repository: provectus/kafka-ui
Issue: Broker configs: Display a tooltip about the source of the property
Labels: good first issue
scope/frontend
status/accepted
type/chore

<img width="257" alt="image" src="https://user-images.githubusercontent.com/1494347/193239459-4583ebbf-fde1-417c-b4e5-a0a9d2fb5cf8.png">
Add a tooltip icon with the following text on hover:

```
DYNAMIC_TOPIC_CONFIG: dynamic topic config that is configured for a specific topic
DYNAMIC_BROKER_LOGGER_CONFIG: dynamic broker logger config that is configured for a specific broker
DYNAMIC_BROKER_CONFIG: dynamic broker config that is configured for a specific broker
DYNAMIC_DEFAULT_BROKER_CONFIG: dynamic broker config that is configured as default for all brokers in the cluster
STATIC_BROKER_CONFIG: static broker config provided as broker properties at start up (e.g. server.properties file)
DEFAULT_CONFIG: built-in default configuration for configs that have a default value
UNKNOWN: source unknown e.g. in the ConfigEntry used for alter requests where source is not set
```










Issue comments:

Pull request review comments:
create separate object in root 
```
tooltip: {
  bg:
  text:
}
```

Patch that resolved the issue:
diff --git a/kafka-ui-react-app/package.json b/kafka-ui-react-app/package.json
index fa1842dedbd..b0779e54884 100644
--- a/kafka-ui-react-app/package.json
+++ b/kafka-ui-react-app/package.json
@@ -7,6 +7,7 @@
     "@babel/core": "^7.16.0",
     "@babel/plugin-syntax-flow": "^7.18.6",
     "@babel/plugin-transform-react-jsx": "^7.18.6",
+    "@floating-ui/react-dom-interactions": "^0.10.3",
     "@hookform/error-message": "^2.0.0",
     "@hookform/resolvers": "^2.7.1",
     "@microsoft/fetch-event-source": "^2.0.1",
diff --git a/kafka-ui-react-app/pnpm-lock.yaml b/kafka-ui-react-app/pnpm-lock.yaml
index 88fcfac42e1..dca028c2d9f 100644
--- a/kafka-ui-react-app/pnpm-lock.yaml
+++ b/kafka-ui-react-app/pnpm-lock.yaml
@@ -7,6 +7,7 @@ specifiers:
   '@babel/preset-env': ^7.18.2
   '@babel/preset-react': ^7.17.12
   '@babel/preset-typescript': ^7.17.12
+  '@floating-ui/react-dom-interactions': ^0.10.3
   '@hookform/error-message': ^2.0.0
   '@hookform/resolvers': ^2.7.1
   '@jest/types': ^29.0.3
@@ -94,6 +95,7 @@ dependencies:
   '@babel/core': 7.18.2
   '@babel/plugin-syntax-flow': 7.18.6_@babel+core@7.18.2
   '@babel/plugin-transform-react-jsx': 7.18.6_@babel+core@7.18.2
+  '@floating-ui/react-dom-interactions': 0.10.3_ohobp6rpsmerwlq5ipwfh5yigy
   '@hookform/error-message': 2.0.0_l2dcsysovzdujulgxvsen7vbsm
   '@hookform/resolvers': 2.8.9_react-hook-form@7.6.9
   '@microsoft/fetch-event-source': 2.0.1
@@ -2733,6 +2735,41 @@ packages:
       - supports-color
     dev: true
 
+  /@floating-ui/core/1.0.1:
+    resolution: {integrity: sha512-bO37brCPfteXQfFY0DyNDGB3+IMe4j150KFQcgJ5aBP295p9nBGeHEs/p0czrRbtlHq4Px/yoPXO/+dOCcF4uA==}
+    dev: false
+
+  /@floating-ui/dom/1.0.4:
+    resolution: {integrity: sha512-maYJRv+sAXTy4K9mzdv0JPyNW5YPVHrqtY90tEdI6XNpuLOP26Ci2pfwPsKBA/Wh4Z3FX5sUrtUFTdMYj9v+ug==}
+    dependencies:
+      '@floating-ui/core': 1.0.1
+    dev: false
+
+  /@floating-ui/react-dom-interactions/0.10.3_ohobp6rpsmerwlq5ipwfh5yigy:
+    resolution: {integrity: sha512-UEHqdnzyoiWNU5az/tAljr9iXFzN18DcvpMqW+/cXz4FEhDEB1ogLtWldOWCujLerPBnSRocADALafelOReMpw==}
+    peerDependencies:
+      react: '>=16.8.0'
+      react-dom: '>=16.8.0'
+    dependencies:
+      '@floating-ui/react-dom': 1.0.0_ef5jwxihqo6n7gxfmzogljlgcm
+      aria-hidden: 1.2.1_7cpxmzzodpxnolj5zcc5cr63ji
+      react: 18.1.0
+      react-dom: 18.1.0_react@18.1.0
+    transitivePeerDependencies:
+      - '@types/react'
+    dev: false
+
+  /@floating-ui/react-dom/1.0.0_ef5jwxihqo6n7gxfmzogljlgcm:
+    resolution: {integrity: sha512-uiOalFKPG937UCLm42RxjESTWUVpbbatvlphQAU6bsv+ence6IoVG8JOUZcy8eW81NkU+Idiwvx10WFLmR4MIg==}
+    peerDependencies:
+      react: '>=16.8.0'
+      react-dom: '>=16.8.0'
+    dependencies:
+      '@floating-ui/dom': 1.0.4
+      react: 18.1.0
+      react-dom: 18.1.0_react@18.1.0
+    dev: false
+
   /@hookform/error-message/2.0.0_l2dcsysovzdujulgxvsen7vbsm:
     resolution: {integrity: sha512-Y90nHzjgL2MP7GFy75kscdvxrCTjtyxGmOLLxX14nd08OXRIh9lMH/y9Kpdo0p1IPowJBiZMHyueg7p+yrqynQ==}
     peerDependencies:
@@ -3899,6 +3936,21 @@ packages:
     resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
     dev: true
 
+  /aria-hidden/1.2.1_7cpxmzzodpxnolj5zcc5cr63ji:
+    resolution: {integrity: sha512-PN344VAf9j1EAi+jyVHOJ8XidQdPVssGco39eNcsGdM4wcsILtxrKLkbuiMfLWYROK1FjRQasMWCBttrhjnr6A==}
+    engines: {node: '>=10'}
+    peerDependencies:
+      '@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0
+      react: ^16.9.0 || ^17.0.0 || ^18.0.0
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+    dependencies:
+      '@types/react': 18.0.9
+      react: 18.1.0
+      tslib: 2.4.0
+    dev: false
+
   /aria-query/4.2.2:
     resolution: {integrity: sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==}
     engines: {node: '>=6.0'}
@@ -8531,7 +8583,6 @@ packages:
 
   /tslib/2.4.0:
     resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==}
-    dev: true
 
   /tsutils/3.21.0_typescript@4.7.4:
     resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
diff --git a/kafka-ui-react-app/src/components/Brokers/Broker/Configs/Configs.styled.ts b/kafka-ui-react-app/src/components/Brokers/Broker/Configs/Configs.styled.ts
index 03030b12498..cced53373e8 100644
--- a/kafka-ui-react-app/src/components/Brokers/Broker/Configs/Configs.styled.ts
+++ b/kafka-ui-react-app/src/components/Brokers/Broker/Configs/Configs.styled.ts
@@ -24,3 +24,13 @@ export const SearchWrapper = styled.div`
   margin: 10px;
   width: 21%;
 `;
+
+export const Source = styled.div`
+  display: flex;
+  align-content: center;
+  svg {
+    margin-left: 10px;
+    vertical-align: middle;
+    cursor: pointer;
+  }
+`;
diff --git a/kafka-ui-react-app/src/components/Brokers/Broker/Configs/Configs.tsx b/kafka-ui-react-app/src/components/Brokers/Broker/Configs/Configs.tsx
index 31a42f60df4..8f909facac0 100644
--- a/kafka-ui-react-app/src/components/Brokers/Broker/Configs/Configs.tsx
+++ b/kafka-ui-react-app/src/components/Brokers/Broker/Configs/Configs.tsx
@@ -9,10 +9,20 @@ import {
 import Table from 'components/common/NewTable';
 import { BrokerConfig, ConfigSource } from 'generated-sources';
 import Search from 'components/common/Search/Search';
+import Tooltip from 'components/common/Tooltip/Tooltip';
+import InfoIcon from 'components/common/Icons/InfoIcon';
 
 import InputCell from './InputCell';
 import * as S from './Configs.styled';
 
+const tooltipContent = `DYNAMIC_TOPIC_CONFIG = dynamic topic config that is configured for a specific topic
+DYNAMIC_BROKER_LOGGER_CONFIG = dynamic broker logger config that is configured for a specific broker
+DYNAMIC_BROKER_CONFIG = dynamic broker config that is configured for a specific broker
+DYNAMIC_DEFAULT_BROKER_CONFIG = dynamic broker config that is configured as default for all brokers in the cluster
+STATIC_BROKER_CONFIG = static broker config provided as broker properties at start up (e.g. server.properties file)
+DEFAULT_CONFIG = built-in default configuration for configs that have a default value
+UNKNOWN = source unknown e.g. in the ConfigEntry used for alter requests where source is not set`;
+
 const Configs: React.FC = () => {
   const [keyword, setKeyword] = React.useState('');
   const { clusterName, brokerId } = useAppParams<ClusterBrokerParam>();
@@ -57,7 +67,19 @@ const Configs: React.FC = () => {
         cell: renderCell,
       },
       {
-        header: 'Source',
+        // eslint-disable-next-line react/no-unstable-nested-components
+        header: () => {
+          return (
+            <S.Source>
+              Source
+              <Tooltip
+                value={<InfoIcon />}
+                content={tooltipContent}
+                placement="top-end"
+              />
+            </S.Source>
+          );
+        },
         accessorKey: 'source',
       },
     ],
diff --git a/kafka-ui-react-app/src/components/common/Icons/InfoIcon.tsx b/kafka-ui-react-app/src/components/common/Icons/InfoIcon.tsx
new file mode 100644
index 00000000000..8e947c6ca97
--- /dev/null
+++ b/kafka-ui-react-app/src/components/common/Icons/InfoIcon.tsx
@@ -0,0 +1,53 @@
+import React from 'react';
+
+const InfoIcon: React.FC = () => {
+  return (
+    <svg
+      xmlns="http://www.w3.org/2000/svg"
+      viewBox="0 0 64 64"
+      width="12"
+      height="12"
+      aria-labelledby="title"
+      aria-describedby="desc"
+      role="img"
+    >
+      <desc>A line styled icon from Orion Icon Library.</desc>
+      <circle
+        data-name="layer2"
+        cx="32"
+        cy="32"
+        r="30"
+        fill="none"
+        stroke="#202020"
+        strokeMiterlimit="10"
+        strokeWidth="2"
+        strokeLinejoin="round"
+        strokeLinecap="round"
+      />
+      <path
+        data-name="layer1"
+        fill="none"
+        stroke="#202020"
+        strokeMiterlimit="10"
+        strokeWidth="2"
+        d="M28 26h4v22m-4 .008h8"
+        strokeLinejoin="round"
+        strokeLinecap="round"
+      />
+      <circle
+        data-name="layer1"
+        cx="31"
+        cy="19"
+        r="2"
+        fill="none"
+        stroke="#202020"
+        strokeMiterlimit="10"
+        strokeWidth="2"
+        strokeLinejoin="round"
+        strokeLinecap="round"
+      />
+    </svg>
+  );
+};
+
+export default InfoIcon;
diff --git a/kafka-ui-react-app/src/components/common/Tooltip/Tooltip.styled.ts b/kafka-ui-react-app/src/components/common/Tooltip/Tooltip.styled.ts
new file mode 100644
index 00000000000..ee352995bd4
--- /dev/null
+++ b/kafka-ui-react-app/src/components/common/Tooltip/Tooltip.styled.ts
@@ -0,0 +1,17 @@
+import styled from 'styled-components';
+
+export const MessageTooltip = styled.div`
+  max-width: 100%;
+  max-height: 100%;
+  background-color: ${({ theme }) => theme.tooltip.bg};
+  color: ${({ theme }) => theme.tooltip.text};
+  border-radius: 6px;
+  padding: 5px;
+  z-index: 1;
+  white-space: pre-wrap;
+`;
+
+export const Wrapper = styled.div`
+  display: flex;
+  align-items: center;
+`;
diff --git a/kafka-ui-react-app/src/components/common/Tooltip/Tooltip.tsx b/kafka-ui-react-app/src/components/common/Tooltip/Tooltip.tsx
new file mode 100644
index 00000000000..cd76f4cc4f4
--- /dev/null
+++ b/kafka-ui-react-app/src/components/common/Tooltip/Tooltip.tsx
@@ -0,0 +1,47 @@
+import React, { useState } from 'react';
+import {
+  useFloating,
+  useHover,
+  useInteractions,
+  Placement,
+} from '@floating-ui/react-dom-interactions';
+
+import * as S from './Tooltip.styled';
+
+export interface PropsTypes {
+  value: string | JSX.Element;
+  content: string;
+  placement?: Placement;
+}
+
+const Tooltip: React.FC<PropsTypes> = ({ value, content, placement }) => {
+  const [open, setOpen] = useState(false);
+  const { x, y, reference, floating, strategy, context } = useFloating({
+    open,
+    onOpenChange: setOpen,
+    placement,
+  });
+
+  useInteractions([useHover(context)]);
+
+  return (
+    <>
+      <S.Wrapper ref={reference}>{value}</S.Wrapper>
+      {open && (
+        <S.MessageTooltip
+          ref={floating}
+          style={{
+            position: strategy,
+            top: y ?? 0,
+            left: x ?? 0,
+            width: 'max-content',
+          }}
+        >
+          {content}
+        </S.MessageTooltip>
+      )}
+    </>
+  );
+};
+
+export default Tooltip;
diff --git a/kafka-ui-react-app/src/components/common/Tooltip/__tests__/Tooltip.spec.tsx b/kafka-ui-react-app/src/components/common/Tooltip/__tests__/Tooltip.spec.tsx
new file mode 100644
index 00000000000..df0cc470a1c
--- /dev/null
+++ b/kafka-ui-react-app/src/components/common/Tooltip/__tests__/Tooltip.spec.tsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import { render } from 'lib/testHelpers';
+import { screen } from '@testing-library/react';
+import Tooltip from 'components/common/Tooltip/Tooltip';
+import userEvent from '@testing-library/user-event';
+
+describe('Tooltip', () => {
+  const tooltipText = 'tooltip';
+  const tooltipContent = 'tooltip_Content';
+
+  const setUpComponent = () =>
+    render(<Tooltip value={tooltipText} content={tooltipContent} />);
+
+  it('should render the tooltip element with its value text', () => {
+    setUpComponent();
+    expect(screen.getByText(tooltipText)).toBeInTheDocument();
+  });
+
+  it('should render the tooltip with default closed', () => {
+    setUpComponent();
+    expect(screen.queryByText(tooltipContent)).not.toBeInTheDocument();
+  });
+
+  it('should render the tooltip with and open during hover', async () => {
+    setUpComponent();
+    await userEvent.hover(screen.getByText(tooltipText));
+    expect(screen.getByText(tooltipContent)).toBeInTheDocument();
+  });
+});
diff --git a/kafka-ui-react-app/src/theme/theme.ts b/kafka-ui-react-app/src/theme/theme.ts
index 5a2b806a01f..020d5819ee5 100644
--- a/kafka-ui-react-app/src/theme/theme.ts
+++ b/kafka-ui-react-app/src/theme/theme.ts
@@ -527,6 +527,10 @@ const theme = {
   configList: {
     color: Colors.neutral[30],
   },
+  tooltip: {
+    bg: Colors.neutral[70],
+    text: Colors.neutral[0],
+  },
   topicsList: {
     color: {
       normal: Colors.neutral[90],


Test patch: