From 8bfb0b5088e1a8c9fe8108ef7e2a30f66d92a70d Mon Sep 17 00:00:00 2001 From: Raj Kamal Singh <1133322+rkssisodiya@users.noreply.github.com> Date: Thu, 21 Sep 2023 10:41:48 +0530 Subject: [PATCH 01/22] QueryBuilder filters for log pipelines (#3587) --- frontend/jest.config.ts | 2 +- frontend/public/locales/en/pipeline.json | 1 + .../AddNewPipeline/FormFields/FilterInput.tsx | 64 +++++++++++++++++++ .../FormFields/FilterSearch.tsx | 31 --------- .../PipelineFilterPreview/index.tsx | 24 +++++++ .../PipelineFilterPreview/styles.scss | 10 +++ .../TableComponents/index.tsx | 6 +- .../PipelinePage/PipelineListsView/config.ts | 5 +- .../container/PipelinePage/mocks/pipeline.ts | 30 ++++++++- .../tests/AddNewPipeline.test.tsx | 36 +++++++---- .../PipelinePage/tests/utils.test.ts | 4 +- .../filters/QueryBuilderSearch/index.tsx | 5 +- frontend/src/hooks/queryBuilder/useTag.ts | 42 +++++++----- frontend/src/types/api/pipeline/def.ts | 4 +- 14 files changed, 195 insertions(+), 69 deletions(-) create mode 100644 frontend/src/container/PipelinePage/PipelineListsView/AddNewPipeline/FormFields/FilterInput.tsx delete mode 100644 frontend/src/container/PipelinePage/PipelineListsView/AddNewPipeline/FormFields/FilterSearch.tsx create mode 100644 frontend/src/container/PipelinePage/PipelineListsView/TableComponents/PipelineFilterPreview/index.tsx create mode 100644 frontend/src/container/PipelinePage/PipelineListsView/TableComponents/PipelineFilterPreview/styles.scss diff --git a/frontend/jest.config.ts b/frontend/jest.config.ts index 00bd8505c8..b7cac3cc02 100644 --- a/frontend/jest.config.ts +++ b/frontend/jest.config.ts @@ -7,7 +7,7 @@ const config: Config.InitialOptions = { moduleFileExtensions: ['ts', 'tsx', 'js', 'json'], modulePathIgnorePatterns: ['dist'], moduleNameMapper: { - '\\.(css|less)$': '/__mocks__/cssMock.ts', + '\\.(css|less|scss)$': '/__mocks__/cssMock.ts', }, globals: { extensionsToTreatAsEsm: ['.ts'], diff --git a/frontend/public/locales/en/pipeline.json b/frontend/public/locales/en/pipeline.json index 515ce19003..77989fdfc5 100644 --- a/frontend/public/locales/en/pipeline.json +++ b/frontend/public/locales/en/pipeline.json @@ -25,6 +25,7 @@ "delete_processor_description": "Logs are processed sequentially in processors. Deleting a processor may change content of data processed by other processors", "search_pipeline_placeholder": "Filter Pipelines", "pipeline_name_placeholder": "Name", + "pipeline_filter_placeholder": "Filter for selecting logs to be processed by this pipeline. Example: service_name = billing", "pipeline_tags_placeholder": "Tags", "pipeline_description_placeholder": "Enter description for your pipeline", "processor_name_placeholder": "Name", diff --git a/frontend/src/container/PipelinePage/PipelineListsView/AddNewPipeline/FormFields/FilterInput.tsx b/frontend/src/container/PipelinePage/PipelineListsView/AddNewPipeline/FormFields/FilterInput.tsx new file mode 100644 index 0000000000..ef6b93f11c --- /dev/null +++ b/frontend/src/container/PipelinePage/PipelineListsView/AddNewPipeline/FormFields/FilterInput.tsx @@ -0,0 +1,64 @@ +import { Form } from 'antd'; +import { initialQueryBuilderFormValuesMap } from 'constants/queryBuilder'; +import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; +import isEqual from 'lodash-es/isEqual'; +import { useTranslation } from 'react-i18next'; +import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; + +import { ProcessorFormField } from '../../AddNewProcessor/config'; +import { formValidationRules } from '../../config'; +import { FormLabelStyle } from '../styles'; + +function TagFilterInput({ + value, + onChange, + placeholder, +}: TagFilterInputProps): JSX.Element { + const query = { ...initialQueryBuilderFormValuesMap.logs }; + if (value) { + query.filters = value; + } + + const onQueryChange = (newValue: TagFilter): void => { + // Avoid unnecessary onChange calls + if (!isEqual(newValue, query.filters)) { + onChange(newValue); + } + }; + + return ( + + ); +} + +interface TagFilterInputProps { + onChange: (filter: TagFilter) => void; + value: TagFilter; + placeholder: string; +} + +function FilterInput({ fieldData }: FilterInputProps): JSX.Element { + const { t } = useTranslation('pipeline'); + + return ( + {fieldData.fieldName}} + key={fieldData.id} + rules={formValidationRules} + name={fieldData.name} + > + {/* Antd form will supply value and onChange to here. + // @ts-ignore */} + + + ); +} +interface FilterInputProps { + fieldData: ProcessorFormField; +} +export default FilterInput; diff --git a/frontend/src/container/PipelinePage/PipelineListsView/AddNewPipeline/FormFields/FilterSearch.tsx b/frontend/src/container/PipelinePage/PipelineListsView/AddNewPipeline/FormFields/FilterSearch.tsx deleted file mode 100644 index 5b9863b8dd..0000000000 --- a/frontend/src/container/PipelinePage/PipelineListsView/AddNewPipeline/FormFields/FilterSearch.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { Form, Input } from 'antd'; -import { useTranslation } from 'react-i18next'; - -import { ProcessorFormField } from '../../AddNewProcessor/config'; -import { formValidationRules } from '../../config'; -import { FormLabelStyle } from '../styles'; - -function FilterSearch({ fieldData }: FilterSearchProps): JSX.Element { - const { t } = useTranslation('pipeline'); - - return ( - {fieldData.fieldName}} - key={fieldData.id} - rules={formValidationRules} - name={fieldData.name} - > - - - ); -} -interface FilterSearchProps { - fieldData: ProcessorFormField; -} -export default FilterSearch; diff --git a/frontend/src/container/PipelinePage/PipelineListsView/TableComponents/PipelineFilterPreview/index.tsx b/frontend/src/container/PipelinePage/PipelineListsView/TableComponents/PipelineFilterPreview/index.tsx new file mode 100644 index 0000000000..b33ed6c087 --- /dev/null +++ b/frontend/src/container/PipelinePage/PipelineListsView/TableComponents/PipelineFilterPreview/index.tsx @@ -0,0 +1,24 @@ +import './styles.scss'; + +import { queryFilterTags } from 'hooks/queryBuilder/useTag'; +import { PipelineData } from 'types/api/pipeline/def'; + +function PipelineFilterPreview({ + filter, +}: PipelineFilterPreviewProps): JSX.Element { + return ( +
+ {queryFilterTags(filter).map((tag) => ( +
+ {tag} +
+ ))} +
+ ); +} + +interface PipelineFilterPreviewProps { + filter: PipelineData['filter']; +} + +export default PipelineFilterPreview; diff --git a/frontend/src/container/PipelinePage/PipelineListsView/TableComponents/PipelineFilterPreview/styles.scss b/frontend/src/container/PipelinePage/PipelineListsView/TableComponents/PipelineFilterPreview/styles.scss new file mode 100644 index 0000000000..a5c5938fa4 --- /dev/null +++ b/frontend/src/container/PipelinePage/PipelineListsView/TableComponents/PipelineFilterPreview/styles.scss @@ -0,0 +1,10 @@ +.pipeline-filter-preview-condition { + padding: 0 0.2em; +} + +.pipeline-filter-preview-container { + display: flex; + flex-wrap: wrap; + gap: 0.4em; + font-size: 0.75rem; +} diff --git a/frontend/src/container/PipelinePage/PipelineListsView/TableComponents/index.tsx b/frontend/src/container/PipelinePage/PipelineListsView/TableComponents/index.tsx index 4d351c7a41..25ec206566 100644 --- a/frontend/src/container/PipelinePage/PipelineListsView/TableComponents/index.tsx +++ b/frontend/src/container/PipelinePage/PipelineListsView/TableComponents/index.tsx @@ -4,6 +4,7 @@ import { PipelineData, ProcessorData } from 'types/api/pipeline/def'; import { PipelineIndexIcon } from '../AddNewProcessor/styles'; import { ColumnDataStyle, ListDataStyle, ProcessorIndexIcon } from '../styles'; +import PipelineFilterPreview from './PipelineFilterPreview'; const componentMap: ComponentMap = { orderId: ({ record }) => {record}, @@ -14,6 +15,7 @@ const componentMap: ComponentMap = { ), id: ({ record }) => {record}, name: ({ record }) => {record}, + filter: ({ record }) => , }; function TableComponents({ @@ -31,7 +33,9 @@ type ComponentMap = { [key: string]: React.FC<{ record: Record }>; }; -export type Record = PipelineData['orderId'] & ProcessorData; +export type Record = PipelineData['orderId'] & + PipelineData['filter'] & + ProcessorData; interface TableComponentsProps { columnKey: string; diff --git a/frontend/src/container/PipelinePage/PipelineListsView/config.ts b/frontend/src/container/PipelinePage/PipelineListsView/config.ts index 44f6bcc0d7..baecbb8d14 100644 --- a/frontend/src/container/PipelinePage/PipelineListsView/config.ts +++ b/frontend/src/container/PipelinePage/PipelineListsView/config.ts @@ -8,15 +8,16 @@ import { import DeploymentStage from '../Layouts/ChangeHistory/DeploymentStage'; import DeploymentTime from '../Layouts/ChangeHistory/DeploymentTime'; import DescriptionTextArea from './AddNewPipeline/FormFields/DescriptionTextArea'; +import FilterInput from './AddNewPipeline/FormFields/FilterInput'; import NameInput from './AddNewPipeline/FormFields/NameInput'; export const pipelineFields = [ { id: 1, fieldName: 'Filter', - placeholder: 'search_pipeline_placeholder', + placeholder: 'pipeline_filter_placeholder', name: 'filter', - component: NameInput, + component: FilterInput, }, { id: 2, diff --git a/frontend/src/container/PipelinePage/mocks/pipeline.ts b/frontend/src/container/PipelinePage/mocks/pipeline.ts index d91665ce03..db309b0e50 100644 --- a/frontend/src/container/PipelinePage/mocks/pipeline.ts +++ b/frontend/src/container/PipelinePage/mocks/pipeline.ts @@ -1,7 +1,33 @@ import { Pipeline, PipelineData } from 'types/api/pipeline/def'; +import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; export const configurationVersion = '1.0'; +export function mockPipelineFilter( + key: string, + op: string, + value: string, +): TagFilter { + return { + op: 'AND', + items: [ + { + id: `${key}-${value}`, + key: { + key, + dataType: DataTypes.String, + type: '', + isColumn: false, + isJSON: false, + }, + op, + value, + }, + ], + }; +} + export const pipelineMockData: Array = [ { id: '4453c8b0-c0fd-42bf-bf09-7cc1b04ccdc9', @@ -10,7 +36,7 @@ export const pipelineMockData: Array = [ alias: 'apachecommonparser', description: 'This is a desc', enabled: false, - filter: 'attributes.source == nginx', + filter: mockPipelineFilter('source', '=', 'nginx'), config: [ { orderId: 1, @@ -43,7 +69,7 @@ export const pipelineMockData: Array = [ alias: 'movingpipelinenew', description: 'This is a desc of move', enabled: false, - filter: 'attributes.method == POST', + filter: mockPipelineFilter('method', '=', 'POST'), config: [ { orderId: 1, diff --git a/frontend/src/container/PipelinePage/tests/AddNewPipeline.test.tsx b/frontend/src/container/PipelinePage/tests/AddNewPipeline.test.tsx index c063c9d94d..8990ffa4e7 100644 --- a/frontend/src/container/PipelinePage/tests/AddNewPipeline.test.tsx +++ b/frontend/src/container/PipelinePage/tests/AddNewPipeline.test.tsx @@ -1,5 +1,6 @@ import { render } from '@testing-library/react'; import { I18nextProvider } from 'react-i18next'; +import { QueryClient, QueryClientProvider } from 'react-query'; import { Provider } from 'react-redux'; import { MemoryRouter } from 'react-router-dom'; import i18n from 'ReactI18'; @@ -27,25 +28,36 @@ beforeAll(() => { matchMedia(); }); +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + refetchOnWindowFocus: false, + }, + }, +}); + describe('PipelinePage container test', () => { it('should render AddNewPipeline section', () => { const setActionType = jest.fn(); const selectedPipelineData = pipelineMockData[0]; const isActionType = 'add-pipeline'; + const { asFragment } = render( - - - - - + + + + + + + , ); expect(asFragment()).toMatchSnapshot(); diff --git a/frontend/src/container/PipelinePage/tests/utils.test.ts b/frontend/src/container/PipelinePage/tests/utils.test.ts index f433c422b9..c21e8c5a4b 100644 --- a/frontend/src/container/PipelinePage/tests/utils.test.ts +++ b/frontend/src/container/PipelinePage/tests/utils.test.ts @@ -1,4 +1,4 @@ -import { pipelineMockData } from '../mocks/pipeline'; +import { mockPipelineFilter, pipelineMockData } from '../mocks/pipeline'; import { processorFields, processorTypes, @@ -68,7 +68,7 @@ describe('Utils testing of Pipeline Page', () => { ...pipelineMockData[findRecordIndex], name: 'updated name', description: 'changed description', - filter: 'value == test', + filter: mockPipelineFilter('value', '=', 'test'), tags: ['test'], }; const editedData = getEditedDataSource( diff --git a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx index 3b78eae7bd..4a83f1611f 100644 --- a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx +++ b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx @@ -41,6 +41,7 @@ function QueryBuilderSearch({ onChange, whereClauseConfig, className, + placeholder, }: QueryBuilderSearchProps): JSX.Element { const { updateTag, @@ -190,7 +191,7 @@ function QueryBuilderSearch({ filterOption={false} autoClearSearchValue={false} mode="multiple" - placeholder={PLACEHOLDER} + placeholder={placeholder} value={queryTags} searchValue={searchValue} className={className} @@ -218,11 +219,13 @@ interface QueryBuilderSearchProps { onChange: (value: TagFilter) => void; whereClauseConfig?: WhereClauseConfig; className?: string; + placeholder?: string; } QueryBuilderSearch.defaultProps = { whereClauseConfig: undefined, className: '', + placeholder: PLACEHOLDER, }; export interface CustomTagProps { diff --git a/frontend/src/hooks/queryBuilder/useTag.ts b/frontend/src/hooks/queryBuilder/useTag.ts index 04f2367971..268a01e0c6 100644 --- a/frontend/src/hooks/queryBuilder/useTag.ts +++ b/frontend/src/hooks/queryBuilder/useTag.ts @@ -7,10 +7,32 @@ import { import { unparse } from 'papaparse'; // eslint-disable-next-line import/no-extraneous-dependencies import { useCallback, useEffect, useMemo, useState } from 'react'; -import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; +import { + IBuilderQuery, + TagFilter, +} from 'types/api/queryBuilder/queryBuilderData'; import { WhereClauseConfig } from './useAutoComplete'; +/** + * Helper for formatting a TagFilter object into filter item strings + * @param {TagFilter} filters - query filter object to be converted + * @returns {string[]} An array of formatted conditions. Eg: `["service = web", "severity_text = INFO"]`) + */ +export function queryFilterTags(filter: TagFilter): string[] { + return (filter?.items || []).map((ele) => { + if (isInNInOperator(getOperatorFromValue(ele.op))) { + try { + const csvString = unparse([ele.value]); + return `${ele.key?.key} ${getOperatorFromValue(ele.op)} ${csvString}`; + } catch { + return `${ele.key?.key} ${getOperatorFromValue(ele.op)} ${ele.value}`; + } + } + return `${ele.key?.key} ${getOperatorFromValue(ele.op)} ${ele.value}`; + }); +} + type IUseTag = { handleAddTag: (value: string) => void; handleClearTag: (value: string) => void; @@ -33,21 +55,9 @@ export const useTag = ( setSearchKey: (value: string) => void, whereClauseConfig?: WhereClauseConfig, ): IUseTag => { - const initTagsData = useMemo( - () => - (query?.filters?.items || []).map((ele) => { - if (isInNInOperator(getOperatorFromValue(ele.op))) { - try { - const csvString = unparse([ele.value]); - return `${ele.key?.key} ${getOperatorFromValue(ele.op)} ${csvString}`; - } catch { - return `${ele.key?.key} ${getOperatorFromValue(ele.op)} ${ele.value}`; - } - } - return `${ele.key?.key} ${getOperatorFromValue(ele.op)} ${ele.value}`; - }), - [query.filters], - ); + const initTagsData = useMemo(() => queryFilterTags(query?.filters), [ + query?.filters, + ]); const [tags, setTags] = useState(initTagsData); diff --git a/frontend/src/types/api/pipeline/def.ts b/frontend/src/types/api/pipeline/def.ts index 5008cace6c..808aecf658 100644 --- a/frontend/src/types/api/pipeline/def.ts +++ b/frontend/src/types/api/pipeline/def.ts @@ -1,3 +1,5 @@ +import { TagFilter } from '../queryBuilder/queryBuilderData'; + export interface ProcessorData { type: string; id?: string; @@ -23,7 +25,7 @@ export interface PipelineData { description?: string; createdBy: string; enabled: boolean; - filter: string; + filter: TagFilter; id?: string; name: string; orderId: number; From 31b898b2c60d740e8ac39822cee35ed6249d9737 Mon Sep 17 00:00:00 2001 From: Palash Gupta Date: Thu, 21 Sep 2023 10:28:41 +0000 Subject: [PATCH 02/22] fix: active menu logic is updated (#3602) --- frontend/src/container/SideNav/menuItems.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/container/SideNav/menuItems.tsx b/frontend/src/container/SideNav/menuItems.tsx index f1305f3d19..4121c93ac0 100644 --- a/frontend/src/container/SideNav/menuItems.tsx +++ b/frontend/src/container/SideNav/menuItems.tsx @@ -71,7 +71,7 @@ const menuItems: SidebarMenu[] = [ export const NEW_ROUTES_MENU_ITEM_KEY_MAP = { [ROUTES.TRACES_EXPLORER]: ROUTES.TRACE, [ROUTES.TRACE_EXPLORER]: ROUTES.TRACE, - [ROUTES.LOGS_EXPLORER]: ROUTES.LOGS, + [ROUTES.LOGS_EXPLORER]: ROUTES.LOGS_EXPLORER, }; export default menuItems; From 5c437dd8f99a87661253c3a2d7366c888ab29567 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Thu, 21 Sep 2023 19:49:43 +0530 Subject: [PATCH 03/22] fix: the scroll issue and name issue in save view (#3604) --- .../src/components/ExplorerCard/ExplorerCard.tsx | 2 ++ .../components/ExplorerCard/MenuItemGenerator.tsx | 14 +++++++++++--- frontend/src/components/ExplorerCard/utils.ts | 7 +++++++ .../filters/AggregatorFilter/AggregatorFilter.tsx | 2 ++ .../filters/GroupByFilter/GroupByFilter.tsx | 2 ++ .../filters/HavingFilter/HavingFilter.tsx | 2 ++ .../filters/OrderByFilter/OrderByFilter.tsx | 2 ++ .../filters/QueryBuilderSearch/index.tsx | 2 ++ frontend/src/utils/selectPopupContainer.ts | 1 + 9 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 frontend/src/utils/selectPopupContainer.ts diff --git a/frontend/src/components/ExplorerCard/ExplorerCard.tsx b/frontend/src/components/ExplorerCard/ExplorerCard.tsx index 0944023b6c..6a3cd98e56 100644 --- a/frontend/src/components/ExplorerCard/ExplorerCard.tsx +++ b/frontend/src/components/ExplorerCard/ExplorerCard.tsx @@ -30,6 +30,7 @@ import { useNotifications } from 'hooks/useNotifications'; import { mapCompositeQueryFromQuery } from 'lib/newQueryBuilder/queryBuilderMappers/mapCompositeQueryFromQuery'; import { useState } from 'react'; import { useCopyToClipboard } from 'react-use'; +import { popupContainer } from 'utils/selectPopupContainer'; import { ExploreHeaderToolTip, SaveButtonText } from './constants'; import MenuItemGenerator from './MenuItemGenerator'; @@ -170,6 +171,7 @@ function ExplorerCard({ {viewsData?.data.data && viewsData?.data.data.length && ( {isFormatButtonVisible && ( - + )} diff --git a/frontend/src/container/LogLiveTail/index.tsx b/frontend/src/container/LogLiveTail/index.tsx index 08faeda94c..070423229f 100644 --- a/frontend/src/container/LogLiveTail/index.tsx +++ b/frontend/src/container/LogLiveTail/index.tsx @@ -30,6 +30,7 @@ import { TLogsLiveTailState } from 'types/api/logs/liveTail'; import { ILog } from 'types/api/logs/log'; import { GlobalReducer } from 'types/reducer/globalTime'; import { ILogsReducer } from 'types/reducer/logs'; +import { popupContainer } from 'utils/selectPopupContainer'; import { TIME_PICKER_OPTIONS } from './config'; import { StopContainer, TimePickerCard, TimePickerSelect } from './styles'; @@ -165,6 +166,7 @@ function LogLiveTail({ getLogsAggregate }: Props): JSX.Element { const OptionsPopOverContent = useMemo( () => ( { @@ -236,6 +238,7 @@ function LogLiveTail({ getLogsAggregate }: Props): JSX.Element { )} diff --git a/frontend/src/container/OnboardingContainer/APM/Java/Java.tsx b/frontend/src/container/OnboardingContainer/APM/Java/Java.tsx index 97fd2f907a..205d68d4c0 100644 --- a/frontend/src/container/OnboardingContainer/APM/Java/Java.tsx +++ b/frontend/src/container/OnboardingContainer/APM/Java/Java.tsx @@ -5,6 +5,7 @@ import { Form, Input, Select } from 'antd'; import Header from 'container/OnboardingContainer/common/Header/Header'; import { useEffect, useState } from 'react'; import { trackEvent } from 'utils/segmentAnalytics'; +import { popupContainer } from 'utils/selectPopupContainer'; import ConnectionStatus from '../common/ConnectionStatus/ConnectionStatus'; import JavaDocs from './md-docs/java.md'; @@ -66,6 +67,7 @@ export default function Java({
Select Framework
Select Framework {isFormatButtonVisible && ( - + )}