From 58d6487f779b3990954180103a1ae52c1f7f23d7 Mon Sep 17 00:00:00 2001 From: SagarRajput-7 <162284829+SagarRajput-7@users.noreply.github.com> Date: Wed, 25 Sep 2024 11:32:06 +0530 Subject: [PATCH 1/5] fix: fixed extra space at bottom for list and table panel (#6049) --- frontend/src/container/GridCardLayout/styles.ts | 9 ++++++++- .../LogsPanelTable/LogsPanelComponent.styles.scss | 2 ++ .../TracesTableComponent.styles.scss | 2 ++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/frontend/src/container/GridCardLayout/styles.ts b/frontend/src/container/GridCardLayout/styles.ts index e3f24308de..df2004da52 100644 --- a/frontend/src/container/GridCardLayout/styles.ts +++ b/frontend/src/container/GridCardLayout/styles.ts @@ -33,7 +33,14 @@ export const Card = styled(CardComponent)` } .ant-card-body { - height: calc(100% - 30px); + ${({ $panelType }): StyledCSS => + $panelType === PANEL_TYPES.TABLE + ? css` + height: 100%; + ` + : css` + height: calc(100% - 30px); + `} padding: 0; } `; diff --git a/frontend/src/container/LogsPanelTable/LogsPanelComponent.styles.scss b/frontend/src/container/LogsPanelTable/LogsPanelComponent.styles.scss index 6317ea2134..b355c90551 100644 --- a/frontend/src/container/LogsPanelTable/LogsPanelComponent.styles.scss +++ b/frontend/src/container/LogsPanelTable/LogsPanelComponent.styles.scss @@ -63,6 +63,8 @@ height: 40px; justify-content: end; padding: 0 8px; + margin-top: 12px; + margin-bottom: 2px; } } diff --git a/frontend/src/container/TracesTableComponent/TracesTableComponent.styles.scss b/frontend/src/container/TracesTableComponent/TracesTableComponent.styles.scss index 74e80f8764..c59bf3c5ad 100644 --- a/frontend/src/container/TracesTableComponent/TracesTableComponent.styles.scss +++ b/frontend/src/container/TracesTableComponent/TracesTableComponent.styles.scss @@ -52,6 +52,8 @@ height: 40px; justify-content: end; padding: 0 8px; + margin-top: 12px; + margin-bottom: 2px; } } From 35f8e133a9e539d66203d7afa06192875ee7a64d Mon Sep 17 00:00:00 2001 From: SagarRajput-7 <162284829+SagarRajput-7@users.noreply.github.com> Date: Wed, 25 Sep 2024 11:32:19 +0530 Subject: [PATCH 2/5] fix: dashboard variable - ux and usability fixes (#6038) * fix: dashboard variable - ux and usability fixes * fix: separarted all option, fixed tooltip handling, added clear option etc --- .../DashboardVariableSelection.styles.scss | 22 ++++++++++------ .../VariableItem.tsx | 25 ++++++++++++------- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/frontend/src/container/NewDashboard/DashboardVariablesSelection/DashboardVariableSelection.styles.scss b/frontend/src/container/NewDashboard/DashboardVariablesSelection/DashboardVariableSelection.styles.scss index f7fcb83a53..6df3e79906 100644 --- a/frontend/src/container/NewDashboard/DashboardVariablesSelection/DashboardVariableSelection.styles.scss +++ b/frontend/src/container/NewDashboard/DashboardVariablesSelection/DashboardVariableSelection.styles.scss @@ -43,6 +43,15 @@ .ant-select-item { display: flex; align-items: center; + gap: 8px; + } + + .rc-virtual-list-holder { + [data-testid='option-ALL'] { + border-bottom: 1px solid var(--bg-slate-400); + padding-bottom: 12px; + margin-bottom: 8px; + } } .all-label { @@ -56,28 +65,25 @@ } .dropdown-value { - display: flex; - justify-content: space-between; - align-items: center; + display: grid; + grid-template-columns: 1fr max-content; .option-text { - max-width: 180px; padding: 0 8px; } .toggle-tag-label { padding-left: 8px; right: 40px; - font-weight: normal; - position: absolute; + font-weight: 500; } } } } .dropdown-styles { - min-width: 300px; - max-width: 350px; + min-width: 400px; + max-width: 500px; } .lightMode { diff --git a/frontend/src/container/NewDashboard/DashboardVariablesSelection/VariableItem.tsx b/frontend/src/container/NewDashboard/DashboardVariablesSelection/VariableItem.tsx index e14162d0ce..a0a444a715 100644 --- a/frontend/src/container/NewDashboard/DashboardVariablesSelection/VariableItem.tsx +++ b/frontend/src/container/NewDashboard/DashboardVariablesSelection/VariableItem.tsx @@ -62,14 +62,14 @@ interface VariableItemProps { const getSelectValue = ( selectedValue: IDashboardVariable['selectedValue'], variableData: IDashboardVariable, -): string | string[] => { +): string | string[] | undefined => { if (Array.isArray(selectedValue)) { if (!variableData.multiSelect && selectedValue.length === 1) { - return selectedValue[0]?.toString() || ''; + return selectedValue[0]?.toString(); } return selectedValue.map((item) => item.toString()); } - return selectedValue?.toString() || ''; + return selectedValue?.toString(); }; // eslint-disable-next-line sonarjs/cognitive-complexity @@ -300,7 +300,7 @@ function VariableItem({ e.stopPropagation(); e.preventDefault(); const isChecked = - variableData.allSelected || selectValue.includes(ALL_SELECT_VALUE); + variableData.allSelected || selectValue?.includes(ALL_SELECT_VALUE); if (isChecked) { handleChange([]); @@ -462,6 +462,7 @@ function VariableItem({ + {omittedValues.length} )} + allowClear > {enableSelectAll && ( @@ -500,11 +501,17 @@ function VariableItem({ {...retProps(option as string)} onClick={(e): void => handleToggle(e as any, option as string)} > - - - {option.toString()} - - + + {option.toString()} + {variableData.multiSelect && optionState.tag === option.toString() && From 55f653d92eefe5101f64e198c6b0d8c970541e40 Mon Sep 17 00:00:00 2001 From: Vikrant Gupta Date: Wed, 25 Sep 2024 20:31:06 +0530 Subject: [PATCH 3/5] fix: added support for `body contains X` tag on pressing enter after selecting attribute key (#6059) * fix: added empty operator in the top to support body contains * fix: address review comments --- .../QueryBuilderSearchV2.tsx | 104 +++++++++++++++--- 1 file changed, 87 insertions(+), 17 deletions(-) diff --git a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearchV2/QueryBuilderSearchV2.tsx b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearchV2/QueryBuilderSearchV2.tsx index 3d3fca4654..0925c10d97 100644 --- a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearchV2/QueryBuilderSearchV2.tsx +++ b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearchV2/QueryBuilderSearchV2.tsx @@ -286,16 +286,62 @@ function QueryBuilderSearchV2( parsedValue = value; } if (currentState === DropdownState.ATTRIBUTE_KEY) { - setCurrentFilterItem((prev) => ({ - ...prev, - key: parsedValue as BaseAutocompleteData, - op: '', - value: '', - })); - setCurrentState(DropdownState.OPERATOR); - setSearchValue((parsedValue as BaseAutocompleteData)?.key); + // Case - convert abc def ghi type attribute keys directly to body contains abc def ghi + if ( + isObject(parsedValue) && + parsedValue?.key && + parsedValue?.key?.split(' ').length > 1 + ) { + setTags((prev) => [ + ...prev, + { + key: { + key: 'body', + dataType: DataTypes.String, + type: '', + isColumn: true, + isJSON: false, + // eslint-disable-next-line sonarjs/no-duplicate-string + id: 'body--string----true', + }, + op: OPERATORS.CONTAINS, + value: (parsedValue as BaseAutocompleteData)?.key, + }, + ]); + setCurrentFilterItem(undefined); + setSearchValue(''); + setCurrentState(DropdownState.ATTRIBUTE_KEY); + } else { + setCurrentFilterItem((prev) => ({ + ...prev, + key: parsedValue as BaseAutocompleteData, + op: '', + value: '', + })); + setCurrentState(DropdownState.OPERATOR); + setSearchValue((parsedValue as BaseAutocompleteData)?.key); + } } else if (currentState === DropdownState.OPERATOR) { - if (value === OPERATORS.EXISTS || value === OPERATORS.NOT_EXISTS) { + if (isEmpty(value) && currentFilterItem?.key?.key) { + setTags((prev) => [ + ...prev, + { + key: { + key: 'body', + dataType: DataTypes.String, + type: '', + isColumn: true, + isJSON: false, + id: 'body--string----true', + }, + op: OPERATORS.CONTAINS, + value: currentFilterItem?.key?.key, + }, + ]); + setCurrentFilterItem(undefined); + setSearchValue(''); + setCurrentState(DropdownState.ATTRIBUTE_KEY); + } else if (value === OPERATORS.EXISTS || value === OPERATORS.NOT_EXISTS) { setTags((prev) => [ ...prev, { @@ -399,6 +445,7 @@ function QueryBuilderSearchV2( whereClauseConfig?.customKey === 'body' && whereClauseConfig?.customOp === OPERATORS.CONTAINS ) { + // eslint-disable-next-line sonarjs/no-identical-functions setTags((prev) => [ ...prev, { @@ -519,19 +566,20 @@ function QueryBuilderSearchV2( setCurrentState(DropdownState.OPERATOR); } } - if (suggestionsData?.payload?.attributes?.length === 0) { + // again let's not auto select anything for the user + if (tagOperator) { setCurrentFilterItem({ key: { - key: tagKey.split(' ')[0], + key: tagKey, dataType: DataTypes.EMPTY, type: '', isColumn: false, isJSON: false, }, - op: '', + op: tagOperator, value: '', }); - setCurrentState(DropdownState.OPERATOR); + setCurrentState(DropdownState.ATTRIBUTE_VALUE); } } else if ( // Case 2 - if key is defined but the search text doesn't match with the set key, @@ -607,13 +655,32 @@ function QueryBuilderSearchV2( // the useEffect takes care of setting the dropdown values correctly on change of the current state useEffect(() => { if (currentState === DropdownState.ATTRIBUTE_KEY) { + const { tagKey } = getTagToken(searchValue); if (isLogsExplorerPage) { - setDropdownOptions( - suggestionsData?.payload?.attributes?.map((key) => ({ + // add the user typed option in the dropdown to select that and move ahead irrespective of the matches and all + setDropdownOptions([ + ...(!isEmpty(tagKey) && + !suggestionsData?.payload?.attributes?.some((val) => + isEqual(val.key, tagKey), + ) + ? [ + { + label: tagKey, + value: { + key: tagKey, + dataType: DataTypes.EMPTY, + type: '', + isColumn: false, + isJSON: false, + }, + }, + ] + : []), + ...(suggestionsData?.payload?.attributes?.map((key) => ({ label: key.key, value: key, - })) || [], - ); + })) || []), + ]); } else { setDropdownOptions( data?.payload?.attributeKeys?.map((key) => ({ @@ -643,12 +710,14 @@ function QueryBuilderSearchV2( op.label.startsWith(partialOperator.toLocaleUpperCase()), ); } + operatorOptions = [{ label: '', value: '' }, ...operatorOptions]; setDropdownOptions(operatorOptions); } else if (strippedKey.endsWith('[*]') && strippedKey.startsWith('body.')) { operatorOptions = [OPERATORS.HAS, OPERATORS.NHAS].map((operator) => ({ label: operator, value: operator, })); + operatorOptions = [{ label: '', value: '' }, ...operatorOptions]; setDropdownOptions(operatorOptions); } else { operatorOptions = QUERY_BUILDER_OPERATORS_BY_TYPES.universal.map( @@ -663,6 +732,7 @@ function QueryBuilderSearchV2( op.label.startsWith(partialOperator.toLocaleUpperCase()), ); } + operatorOptions = [{ label: '', value: '' }, ...operatorOptions]; setDropdownOptions(operatorOptions); } } From fc8391c5aabc75146ad145065920cd3e65f72d8d Mon Sep 17 00:00:00 2001 From: Raj Kamal Singh <1133322+raj-k-singh@users.noreply.github.com> Date: Thu, 26 Sep 2024 15:26:14 +0530 Subject: [PATCH 4/5] Feat: logs filter suggestions higher rank for special resource attribs like service.name and env etc (#6060) * chore: upgrade signoz-otel-collector dependency to v0.102.10 * feat: first stab at ranking resource attribs higher * chore: add test todo for validating resource attribs get ranked higher in logs filter suggestions * chore: add test validating higher ranking for special resource attribs * chore: some cleanup * chore: some more cleanup --- go.mod | 2 +- go.sum | 2 + .../clickhouseReader/filter_suggestions.go | 78 ++++++++++++++----- .../integration/filter_suggestions_test.go | 56 +++++++++++++ 4 files changed, 117 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index 9d61916d42..6f523045d1 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/ClickHouse/clickhouse-go/v2 v2.23.2 github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd - github.com/SigNoz/signoz-otel-collector v0.102.2 + github.com/SigNoz/signoz-otel-collector v0.102.10 github.com/SigNoz/zap_otlp/zap_otlp_encoder v0.0.0-20230822164844-1b861a431974 github.com/SigNoz/zap_otlp/zap_otlp_sync v0.0.0-20230822164844-1b861a431974 github.com/antonmedv/expr v1.15.3 diff --git a/go.sum b/go.sum index a442200b0e..c557bc6f87 100644 --- a/go.sum +++ b/go.sum @@ -66,6 +66,8 @@ github.com/SigNoz/prometheus v1.11.1 h1:roM8ugYf4UxaeKKujEeBvoX7ybq3IrS+TB26KiRt github.com/SigNoz/prometheus v1.11.1/go.mod h1:uv4mQwZQtx7y4GQ6EdHOi8Wsk07uHNn2XHd1zM85m6I= github.com/SigNoz/signoz-otel-collector v0.102.2 h1:SmjsBZjMjTVVpuOlfJXlsDJQbdefQP/9Wz3CyzSuZuU= github.com/SigNoz/signoz-otel-collector v0.102.2/go.mod h1:ISAXYhZenojCWg6CdDJtPMpfS6Zwc08+uoxH25tc6Y0= +github.com/SigNoz/signoz-otel-collector v0.102.10 h1:1zjU31OcRZL6fS0IIag8LA8bdhP4S28dzovDwuOg7Lg= +github.com/SigNoz/signoz-otel-collector v0.102.10/go.mod h1:APoBVD4aRu9vIny1vdzZSi2wPY3elyjHA/I/rh1hKfs= github.com/SigNoz/zap_otlp v0.1.0 h1:T7rRcFN87GavY8lDGZj0Z3Xv6OhJA6Pj3I9dNPmqvRc= github.com/SigNoz/zap_otlp v0.1.0/go.mod h1:lcHvbDbRgvDnPxo9lDlaL1JK2PyOyouP/C3ynnYIvyo= github.com/SigNoz/zap_otlp/zap_otlp_encoder v0.0.0-20230822164844-1b861a431974 h1:PKVgdf83Yw+lZJbFtNGBgqXiXNf3+kOXW2qZ7Ms7OaY= diff --git a/pkg/query-service/app/clickhouseReader/filter_suggestions.go b/pkg/query-service/app/clickhouseReader/filter_suggestions.go index 1b80ccbef4..fe78fc5555 100644 --- a/pkg/query-service/app/clickhouseReader/filter_suggestions.go +++ b/pkg/query-service/app/clickhouseReader/filter_suggestions.go @@ -8,6 +8,7 @@ import ( "slices" "strings" + "github.com/SigNoz/signoz-otel-collector/exporter/clickhouselogsexporter/logsv2" "go.signoz.io/signoz/pkg/query-service/model" v3 "go.signoz.io/signoz/pkg/query-service/model/v3" "go.uber.org/zap" @@ -36,26 +37,7 @@ func (r *ClickHouseReader) GetQBFilterSuggestionsForLogs( suggestions.AttributeKeys = attribKeysResp.AttributeKeys // Rank suggested attributes - slices.SortFunc(suggestions.AttributeKeys, func(a v3.AttributeKey, b v3.AttributeKey) int { - - // Higher score => higher rank - attribKeyScore := func(a v3.AttributeKey) int { - - // Scoring criteria is expected to get more sophisticated in follow up changes - if a.Type == v3.AttributeKeyTypeResource { - return 2 - } - - if a.Type == v3.AttributeKeyTypeTag { - return 1 - } - - return 0 - } - - // To sort in descending order of score the return value must be negative when a > b - return attribKeyScore(b) - attribKeyScore(a) - }) + attribRanker.sort(suggestions.AttributeKeys) // Put together suggested example queries. @@ -268,3 +250,59 @@ func (r *ClickHouseReader) getValuesForLogAttributes( return result, nil } + +var attribRanker = newRankingStrategy() + +func newRankingStrategy() attribRankingStrategy { + // Some special resource attributes should get ranked above all others. + interestingResourceAttrsInDescRank := []string{ + "service", "service.name", "env", "k8s.namespace.name", + } + + // Synonyms of interesting attributes should come next + resourceHierarchy := logsv2.ResourceHierarchy() + for _, attr := range []string{ + "service.name", + "deployment.environment", + "k8s.namespace.name", + "k8s.pod.name", + "k8s.container.name", + "k8s.node.name", + } { + interestingResourceAttrsInDescRank = append( + interestingResourceAttrsInDescRank, resourceHierarchy.Synonyms(attr)..., + ) + } + + interestingResourceAttrsInAscRank := interestingResourceAttrsInDescRank[:] + slices.Reverse(interestingResourceAttrsInAscRank) + + return attribRankingStrategy{ + interestingResourceAttrsInAscRank: interestingResourceAttrsInAscRank, + } +} + +type attribRankingStrategy struct { + interestingResourceAttrsInAscRank []string +} + +// The higher the score, the higher the rank +func (s *attribRankingStrategy) score(attrib v3.AttributeKey) int { + if attrib.Type == v3.AttributeKeyTypeResource { + // 3 + (-1) if attrib.Key is not an interesting resource attribute + return 3 + slices.Index(s.interestingResourceAttrsInAscRank, attrib.Key) + } + + if attrib.Type == v3.AttributeKeyTypeTag { + return 1 + } + + return 0 +} + +func (s *attribRankingStrategy) sort(attribKeys []v3.AttributeKey) { + slices.SortFunc(attribKeys, func(a v3.AttributeKey, b v3.AttributeKey) int { + // To sort in descending order of score the return value must be negative when a > b + return s.score(b) - s.score(a) + }) +} diff --git a/pkg/query-service/tests/integration/filter_suggestions_test.go b/pkg/query-service/tests/integration/filter_suggestions_test.go index a1f56115c5..6c8224be50 100644 --- a/pkg/query-service/tests/integration/filter_suggestions_test.go +++ b/pkg/query-service/tests/integration/filter_suggestions_test.go @@ -138,6 +138,62 @@ func TestLogsFilterSuggestionsWithExistingFilter(t *testing.T) { } } +func TestResourceAttribsRankedHigherInLogsFilterSuggestions(t *testing.T) { + require := require.New(t) + + tagKeys := []v3.AttributeKey{} + for _, k := range []string{"user_id", "user_email"} { + tagKeys = append(tagKeys, v3.AttributeKey{ + Key: k, + Type: v3.AttributeKeyTypeTag, + DataType: v3.AttributeKeyDataTypeString, + IsColumn: false, + }) + } + + specialResourceAttrKeys := []v3.AttributeKey{} + for _, k := range []string{"service", "env"} { + specialResourceAttrKeys = append(specialResourceAttrKeys, v3.AttributeKey{ + Key: k, + Type: v3.AttributeKeyTypeResource, + DataType: v3.AttributeKeyDataTypeString, + IsColumn: false, + }) + } + + otherResourceAttrKeys := []v3.AttributeKey{} + for _, k := range []string{"container_name", "container_id"} { + otherResourceAttrKeys = append(otherResourceAttrKeys, v3.AttributeKey{ + Key: k, + Type: v3.AttributeKeyTypeResource, + DataType: v3.AttributeKeyDataTypeString, + IsColumn: false, + }) + } + + tb := NewFilterSuggestionsTestBed(t) + + mockAttrKeysInDB := append(tagKeys, otherResourceAttrKeys...) + mockAttrKeysInDB = append(mockAttrKeysInDB, specialResourceAttrKeys...) + + tb.mockAttribKeysQueryResponse(mockAttrKeysInDB) + + expectedTopSuggestions := append(specialResourceAttrKeys, otherResourceAttrKeys...) + expectedTopSuggestions = append(expectedTopSuggestions, tagKeys...) + + tb.mockAttribValuesQueryResponse( + expectedTopSuggestions[:2], [][]string{{"test"}, {"test"}}, + ) + + suggestionsQueryParams := map[string]string{"examplesLimit": "2"} + suggestionsResp := tb.GetQBFilterSuggestionsForLogs(suggestionsQueryParams) + + require.Equal( + expectedTopSuggestions, + suggestionsResp.AttributeKeys[:len(expectedTopSuggestions)], + ) +} + // Mocks response for CH queries made by reader.GetLogAttributeKeys func (tb *FilterSuggestionsTestBed) mockAttribKeysQueryResponse( attribsToReturn []v3.AttributeKey, From 6e3141a4ce28ffb5d4e2c763f2dad9aa3935e546 Mon Sep 17 00:00:00 2001 From: rahulkeswani101 Date: Thu, 26 Sep 2024 16:12:49 +0530 Subject: [PATCH 5/5] feat: added blur event to having input in query section (#5684) * feat: added blur event to having input in query section * feat: added a error message for incomplete having clause and improved handleBlur * feat: added focus event to remove error message --------- Co-authored-by: Srikanth Chekuri --- .../filters/HavingFilter/HavingFilter.tsx | 79 +++++++++++++------ 1 file changed, 56 insertions(+), 23 deletions(-) diff --git a/frontend/src/container/QueryBuilder/filters/HavingFilter/HavingFilter.tsx b/frontend/src/container/QueryBuilder/filters/HavingFilter/HavingFilter.tsx index 7d11d018cc..3eab3e50ee 100644 --- a/frontend/src/container/QueryBuilder/filters/HavingFilter/HavingFilter.tsx +++ b/frontend/src/container/QueryBuilder/filters/HavingFilter/HavingFilter.tsx @@ -1,3 +1,4 @@ +import { Color } from '@signozhq/design-tokens'; import { Select } from 'antd'; import { ENTITY_VERSION_V4 } from 'constants/app'; // ** Constants @@ -34,6 +35,7 @@ export function HavingFilter({ const [currentFormValue, setCurrentFormValue] = useState( initialHavingValues, ); + const [errorMessage, setErrorMessage] = useState(null); const { isMulti } = useTagValidation( currentFormValue.op, @@ -198,6 +200,29 @@ export function HavingFilter({ resetChanges(); }; + const handleFocus = useCallback(() => { + setErrorMessage(null); + }, []); + + const handleBlur = useCallback((): void => { + if (searchText) { + const { columnName, op, value } = getHavingObject(searchText); + const isCompleteHavingClause = + columnName && op && value.every((v) => v !== ''); + + if (isCompleteHavingClause && isValidHavingValue(searchText)) { + setLocalValues((prev) => { + const updatedValues = [...prev, searchText]; + onChange(updatedValues.map(transformFromStringToHaving)); + return updatedValues; + }); + setSearchText(''); + } else { + setErrorMessage('Invalid HAVING clause'); + } + } + }, [searchText, onChange]); + useEffect(() => { parseSearchText(searchText); }, [searchText, parseSearchText]); @@ -209,28 +234,36 @@ export function HavingFilter({ const isMetricsDataSource = query.dataSource === DataSource.METRICS; return ( - + <> + + {errorMessage && ( +
{errorMessage}
+ )} + ); }