mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-11 20:59:00 +08:00
Merge branch 'develop' into SIG-5729
This commit is contained in:
commit
c7bd7566c5
@ -33,7 +33,14 @@ export const Card = styled(CardComponent)<CardProps>`
|
||||
}
|
||||
|
||||
.ant-card-body {
|
||||
height: calc(100% - 30px);
|
||||
${({ $panelType }): StyledCSS =>
|
||||
$panelType === PANEL_TYPES.TABLE
|
||||
? css`
|
||||
height: 100%;
|
||||
`
|
||||
: css`
|
||||
height: calc(100% - 30px);
|
||||
`}
|
||||
padding: 0;
|
||||
}
|
||||
`;
|
||||
|
@ -63,6 +63,8 @@
|
||||
height: 40px;
|
||||
justify-content: end;
|
||||
padding: 0 8px;
|
||||
margin-top: 12px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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({
|
||||
<span>+ {omittedValues.length} </span>
|
||||
</Tooltip>
|
||||
)}
|
||||
allowClear
|
||||
>
|
||||
{enableSelectAll && (
|
||||
<Select.Option data-testid="option-ALL" value={ALL_SELECT_VALUE}>
|
||||
@ -500,11 +501,17 @@ function VariableItem({
|
||||
{...retProps(option as string)}
|
||||
onClick={(e): void => handleToggle(e as any, option as string)}
|
||||
>
|
||||
<Tooltip title={option.toString()} placement="bottomRight">
|
||||
<Typography.Text ellipsis className="option-text">
|
||||
{option.toString()}
|
||||
</Typography.Text>
|
||||
</Tooltip>
|
||||
<Typography.Text
|
||||
ellipsis={{
|
||||
tooltip: {
|
||||
placement: variableData.multiSelect ? 'top' : 'right',
|
||||
autoAdjustOverflow: true,
|
||||
},
|
||||
}}
|
||||
className="option-text"
|
||||
>
|
||||
{option.toString()}
|
||||
</Typography.Text>
|
||||
|
||||
{variableData.multiSelect &&
|
||||
optionState.tag === option.toString() &&
|
||||
|
@ -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<HavingForm>(
|
||||
initialHavingValues,
|
||||
);
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(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 (
|
||||
<Select
|
||||
getPopupContainer={popupContainer}
|
||||
autoClearSearchValue={false}
|
||||
mode="multiple"
|
||||
onSearch={handleSearch}
|
||||
searchValue={searchText}
|
||||
tagRender={tagRender}
|
||||
value={localValues}
|
||||
data-testid="havingSelect"
|
||||
disabled={isMetricsDataSource && !query.aggregateAttribute.key}
|
||||
style={{ width: '100%' }}
|
||||
notFoundContent={currentFormValue.value.length === 0 ? undefined : null}
|
||||
placeholder="GroupBy(operation) > 5"
|
||||
onDeselect={handleDeselect}
|
||||
onChange={handleChange}
|
||||
onSelect={handleSelect}
|
||||
>
|
||||
{options.map((opt) => (
|
||||
<Select.Option key={opt.value} value={opt.value} title="havingOption">
|
||||
{opt.label}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
<>
|
||||
<Select
|
||||
getPopupContainer={popupContainer}
|
||||
autoClearSearchValue={false}
|
||||
mode="multiple"
|
||||
onSearch={handleSearch}
|
||||
searchValue={searchText}
|
||||
tagRender={tagRender}
|
||||
value={localValues}
|
||||
data-testid="havingSelect"
|
||||
disabled={isMetricsDataSource && !query.aggregateAttribute.key}
|
||||
style={{ width: '100%' }}
|
||||
notFoundContent={currentFormValue.value.length === 0 ? undefined : null}
|
||||
placeholder="GroupBy(operation) > 5"
|
||||
onDeselect={handleDeselect}
|
||||
onChange={handleChange}
|
||||
onSelect={handleSelect}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
status={errorMessage ? 'error' : undefined}
|
||||
>
|
||||
{options.map((opt) => (
|
||||
<Select.Option key={opt.value} value={opt.value} title="havingOption">
|
||||
{opt.label}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
{errorMessage && (
|
||||
<div style={{ color: Color.BG_CHERRY_500 }}>{errorMessage}</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +52,8 @@
|
||||
height: 40px;
|
||||
justify-content: end;
|
||||
padding: 0 8px;
|
||||
margin-top: 12px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
|
2
go.mod
2
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
|
||||
|
2
go.sum
2
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=
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user