Repository: provectus/kafka-ui
Issue: Implement a tooltip with smart filters documentation
Labels: type/enhancement
good first issue
scope/frontend
status/accepted

filter examples, etc.

Issue comments:
![image](https://user-images.githubusercontent.com/1494347/168826481-93afd5f2-4764-43f6-8d1b-d816b914b701.png)


5. multiline filters are also allowed:
```
def name = value.name  
def age = value.age
name == 'iliax' && age == 30
```


Pull request review comments:
why are we rendering AddFilter in InfoModal spec  instead of infoModal ?
Because I need to check the button works in AddFilter modal
fixed
you can write onClick={toggle}
this should have a more descriptive name
fixed
fixed

Patch that resolved the issue:
diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/AddFilter.tsx b/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/AddFilter.tsx
index 995e3d7666f..d877654e591 100644
--- a/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/AddFilter.tsx
+++ b/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/AddFilter.tsx
@@ -4,8 +4,11 @@ import { MessageFilters } from 'components/Topics/Topic/Details/Messages/Filters
 import { FilterEdit } from 'components/Topics/Topic/Details/Messages/Filters/FilterModal';
 import SavedFilters from 'components/Topics/Topic/Details/Messages/Filters/SavedFilters';
 import SavedIcon from 'components/common/Icons/SavedIcon';
+import QuestionIcon from 'components/common/Icons/QuestionIcon';
+import useModal from 'lib/hooks/useModal';
 
 import AddEditFilterContainer from './AddEditFilterContainer';
+import InfoModal from './InfoModal';
 
 export interface FilterModalProps {
   toggleIsOpen(): void;
@@ -32,6 +35,7 @@ const AddFilter: React.FC<FilterModalProps> = ({
 }) => {
   const [savedFilterState, setSavedFilterState] =
     React.useState<boolean>(false);
+  const { isOpen, toggle } = useModal();
 
   const onSubmit = React.useCallback(
     async (values: AddMessageFilters) => {
@@ -52,7 +56,19 @@ const AddFilter: React.FC<FilterModalProps> = ({
   );
   return (
     <>
-      <S.FilterTitle>Add filter</S.FilterTitle>
+      <S.FilterTitle>
+        Add filter
+        <div>
+          <S.QuestionIconContainer
+            type="button"
+            aria-label="info"
+            onClick={toggle}
+          >
+            <QuestionIcon />
+          </S.QuestionIconContainer>
+          {isOpen && <InfoModal toggleIsOpen={toggle} />}
+        </div>
+      </S.FilterTitle>
       {savedFilterState ? (
         <SavedFilters
           deleteFilter={deleteFilter}
diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/Filters.styled.ts b/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/Filters.styled.ts
index 9977612a954..7bb48b37686 100644
--- a/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/Filters.styled.ts
+++ b/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/Filters.styled.ts
@@ -102,6 +102,29 @@ export const ClearAll = styled.span`
   cursor: pointer;
 `;
 
+export const ButtonContainer = styled.div`
+  width: 100%;
+  display: flex;
+  justify-content: center;
+  margin-top: 20px;
+`;
+
+export const ListItem = styled.li`
+  font-size: 12px;
+  font-weight: 400;
+  margin-left: 20px;
+  line-height: 1.5;
+  color: ${({ theme }) => theme.table.td.color.normal};
+`;
+
+export const InfoParagraph = styled.p`
+  font-size: 12px;
+  font-weight: 400;
+  line-height: 1.5;
+  margin-bottom: 10px;
+  color: ${({ theme }) => theme.table.td.color.normal};
+`;
+
 export const MessageFilterModal = styled.div`
   height: auto;
   width: 560px;
@@ -115,11 +138,34 @@ export const MessageFilterModal = styled.div`
   z-index: 1;
 `;
 
+export const InfoModal = styled.div`
+  height: auto;
+  width: 560px;
+  border-radius: 8px;
+  background: ${({ theme }) => theme.modal.backgroundColor};
+  position: absolute;
+  left: 25%;
+  border: 1px solid ${({ theme }) => theme.breadcrumb};
+  box-shadow: ${({ theme }) => theme.modal.shadow};
+  padding: 32px;
+  z-index: 1;
+`;
+
+export const QuestionIconContainer = styled.button`
+  cursor: pointer;
+  padding: 0;
+  background: none;
+  border: none;
+`;
+
 export const FilterTitle = styled.h3`
   line-height: 32px;
   font-size: 20px;
   margin-bottom: 40px;
   position: relative;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
   &:after {
     content: '';
     width: calc(100% + 32px);
diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/InfoModal.tsx b/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/InfoModal.tsx
new file mode 100644
index 00000000000..d3036cd1372
--- /dev/null
+++ b/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/InfoModal.tsx
@@ -0,0 +1,60 @@
+import React from 'react';
+import * as S from 'components/Topics/Topic/Details/Messages/Filters/Filters.styled';
+import { Button } from 'components/common/Button/Button';
+
+export interface InfoModalProps {
+  toggleIsOpen(): void;
+}
+
+const InfoModal: React.FC<InfoModalProps> = ({ toggleIsOpen }) => {
+  return (
+    <S.InfoModal>
+      <S.InfoParagraph>
+        <b>Variables bound to groovy context:</b> partition, timestampMs,
+        keyAsText, valueAsText, header, key (json if possible), value (json if
+        possible).
+      </S.InfoParagraph>
+      <S.InfoParagraph>
+        <b>JSON parsing logic:</b>
+      </S.InfoParagraph>
+      <S.InfoParagraph>
+        Key and Value (if they can be parsed to JSON) they are bound as JSON
+        objects, otherwise bound as nulls.
+      </S.InfoParagraph>
+      <S.InfoParagraph>
+        <b>Sample filters:</b>
+      </S.InfoParagraph>
+      <ol aria-label="info-list">
+        <S.ListItem>
+          `keyAsText != null && keyAsText ~&quot;([Gg])roovy&quot;` - regex for
+          key as a string
+        </S.ListItem>
+        <S.ListItem>
+          `value.name == &quot;iS.ListItemax&quot; && value.age &gt; 30` - in
+          case value is json
+        </S.ListItem>
+        <S.ListItem>
+          `value == null && valueAsText != null` - search for values that are
+          not nulls and are not json
+        </S.ListItem>
+        <S.ListItem>
+          `headers.sentBy == &quot;some system&quot; &&
+          headers[&quot;sentAt&quot;] == &quot;2020-01-01&quot;`
+        </S.ListItem>
+        <S.ListItem>multiS.ListItemne filters are also allowed:</S.ListItem>
+      </ol>
+      <S.ButtonContainer>
+        <Button
+          buttonSize="M"
+          buttonType="secondary"
+          type="button"
+          onClick={toggleIsOpen}
+        >
+          Ok
+        </Button>
+      </S.ButtonContainer>
+    </S.InfoModal>
+  );
+};
+
+export default InfoModal;
diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/__tests__/AddFilter.spec.tsx b/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/__tests__/AddFilter.spec.tsx
index 6e659de446b..4569c81e649 100644
--- a/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/__tests__/AddFilter.spec.tsx
+++ b/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/__tests__/AddFilter.spec.tsx
@@ -36,6 +36,18 @@ describe('AddFilter component', () => {
     expect(screen.getAllByRole('savedFilter')).toHaveLength(2);
   });
 
+  it('info button to be in the document', () => {
+    renderComponent();
+    expect(screen.getByRole('button', { name: 'info' })).toBeInTheDocument();
+  });
+
+  it('renders InfoModal', () => {
+    renderComponent();
+    userEvent.click(screen.getByRole('button', { name: 'info' }));
+    expect(screen.getByRole('button', { name: 'Ok' })).toBeInTheDocument();
+    expect(screen.getByRole('list', { name: 'info-list' })).toBeInTheDocument();
+  });
+
   it('should test click on return to custom filter redirects to Add filters', async () => {
     renderComponent();
     userEvent.click(screen.getByRole('savedFilterText'));
@@ -159,7 +171,7 @@ describe('AddFilter component', () => {
       it('OnSubmit condition with checkbox on functionality', async () => {
         await act(() => {
           userEvent.click(screen.getByRole('checkbox'));
-          userEvent.click(screen.getAllByRole('button')[1]);
+          userEvent.click(screen.getAllByRole('button')[2]);
         });
 
         expect(activeFilterHandlerMock).not.toHaveBeenCalled();
diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/__tests__/InfoModal.spec.tsx b/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/__tests__/InfoModal.spec.tsx
new file mode 100644
index 00000000000..1721c489151
--- /dev/null
+++ b/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/__tests__/InfoModal.spec.tsx
@@ -0,0 +1,14 @@
+import userEvent from '@testing-library/user-event';
+import { screen } from '@testing-library/react';
+import { render } from 'lib/testHelpers';
+import React from 'react';
+import InfoModal from 'components/Topics/Topic/Details/Messages/Filters/InfoModal';
+
+describe('InfoModal component', () => {
+  it('closes InfoModal', () => {
+    const toggleInfoModal = jest.fn();
+    render(<InfoModal toggleIsOpen={toggleInfoModal} />);
+    userEvent.click(screen.getByRole('button', { name: 'Ok' }));
+    expect(toggleInfoModal).toHaveBeenCalledTimes(1);
+  });
+});
diff --git a/kafka-ui-react-app/src/components/common/Icons/QuestionIcon.tsx b/kafka-ui-react-app/src/components/common/Icons/QuestionIcon.tsx
new file mode 100644
index 00000000000..11490b100df
--- /dev/null
+++ b/kafka-ui-react-app/src/components/common/Icons/QuestionIcon.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import { useTheme } from 'styled-components';
+
+const QuestionIcon: React.FC = () => {
+  const theme = useTheme();
+  return (
+    <svg
+      width="19"
+      height="19"
+      viewBox="0 0 19 19"
+      fill="none"
+      xmlns="http://www.w3.org/2000/svg"
+    >
+      <circle cx="9.5" cy="9.5" r="8.5" stroke="#5C5CF6" strokeWidth="2" />
+      <path
+        d="M8.31818 11.2727V11.0682C8.31818 10.5994 8.35511 10.2259 8.42898 9.94744C8.50284 9.66903 8.61222 9.44602 8.7571 9.27841C8.90199 9.10795 9.07955 8.95455 9.28977 8.81818C9.47159 8.69886 9.63352 8.58381 9.77557 8.47301C9.92045 8.36222 10.0341 8.24432 10.1165 8.11932C10.2017 7.99432 10.2443 7.85227 10.2443 7.69318C10.2443 7.55114 10.2102 7.42614 10.142 7.31818C10.0739 7.21023 9.98153 7.12642 9.86506 7.06676C9.74858 7.0071 9.61932 6.97727 9.47727 6.97727C9.32386 6.97727 9.18182 7.01278 9.05114 7.08381C8.9233 7.15483 8.8196 7.25284 8.74006 7.37784C8.66335 7.50284 8.625 7.64773 8.625 7.8125H6.44318C6.44886 7.1875 6.59091 6.6804 6.86932 6.29119C7.14773 5.89915 7.51705 5.61222 7.97727 5.4304C8.4375 5.24574 8.94318 5.15341 9.49432 5.15341C10.1023 5.15341 10.6449 5.2429 11.1222 5.42188C11.5994 5.59801 11.9759 5.86506 12.2514 6.22301C12.527 6.57812 12.6648 7.02273 12.6648 7.55682C12.6648 7.90057 12.6051 8.20312 12.4858 8.46449C12.3693 8.72301 12.206 8.9517 11.9957 9.15057C11.7884 9.34659 11.5455 9.52557 11.267 9.6875C11.0625 9.80682 10.8906 9.9304 10.7514 10.0582C10.6122 10.1832 10.5071 10.3267 10.4361 10.4886C10.3651 10.6477 10.3295 10.8409 10.3295 11.0682V11.2727H8.31818ZM9.35795 14.1364C9.02841 14.1364 8.74574 14.0213 8.50994 13.7912C8.27699 13.5582 8.16193 13.2756 8.16477 12.9432C8.16193 12.6193 8.27699 12.3423 8.50994 12.1122C8.74574 11.8821 9.02841 11.767 9.35795 11.767C9.67045 11.767 9.94602 11.8821 10.1847 12.1122C10.4261 12.3423 10.5483 12.6193 10.5511 12.9432C10.5483 13.1648 10.4901 13.3665 10.3764 13.5483C10.2656 13.7273 10.1207 13.8707 9.94176 13.9787C9.76278 14.0838 9.56818 14.1364 9.35795 14.1364Z"
+        fill={theme.icons.savedIcon}
+      />
+    </svg>
+  );
+};
+
+export default QuestionIcon;


Test patch: