Skip to content

Commit 6d1775f

Browse files
authored
fix: parsing of string map values (#582)
* fix: fix parsing of string map values * fix: linting * test: add them * style: prettier * fix: linting * refactor: move util method to class that uses it
1 parent afad04d commit 6d1775f

File tree

3 files changed

+119
-3
lines changed

3 files changed

+119
-3
lines changed

projects/components/src/filtering/filter/builder/types/string-map-filter-builder.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ export class StringMapFilterBuilder extends AbstractFilterBuilder<string | [stri
3838
return attribute.displayName;
3939
}
4040

41-
return `${attribute.displayName}${MAP_LHS_DELIMITER}${Array.isArray(value) ? value[0] : value}`;
41+
const displayValue: string = Array.isArray(value) ? (value.length > 0 ? value[0] : '') : value;
42+
43+
return `${attribute.displayName}${MAP_LHS_DELIMITER}${displayValue}`;
4244
}
4345

4446
private buildUserFilterStringRhs(operator?: FilterOperator, value?: string | string[]): string {
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { FilterAttribute, FilterAttributeType } from '@hypertrace/components';
2+
import { getTestFilterAttribute } from '@hypertrace/test-utils';
3+
import { ParsedFilter } from '../parsed-filter';
4+
import { ContainsFilterParser } from './contains-filter-parser';
5+
6+
describe('Filter Parser', () => {
7+
test('correctly parses CONTAINS_KEY with dot', () => {
8+
const attribute: FilterAttribute = getTestFilterAttribute(FilterAttributeType.StringMap);
9+
const filterString = 'String Map Attribute CONTAINS_KEY http.url';
10+
11+
const containsFilterParser: ContainsFilterParser = new ContainsFilterParser();
12+
const parsedFilter: ParsedFilter<unknown> | undefined = containsFilterParser.parseFilterString(
13+
attribute,
14+
filterString
15+
);
16+
17+
expect(parsedFilter).toEqual({
18+
field: 'stringMapAttribute',
19+
operator: 'CONTAINS_KEY',
20+
value: ['http.url']
21+
});
22+
});
23+
24+
test('correctly parses CONTAINS_KEY with colon', () => {
25+
const attribute: FilterAttribute = getTestFilterAttribute(FilterAttributeType.StringMap);
26+
const filterString = 'String Map Attribute CONTAINS_KEY http:url';
27+
28+
const containsFilterParser: ContainsFilterParser = new ContainsFilterParser();
29+
const parsedFilter: ParsedFilter<unknown> | undefined = containsFilterParser.parseFilterString(
30+
attribute,
31+
filterString
32+
);
33+
34+
expect(parsedFilter).toEqual({
35+
field: 'stringMapAttribute',
36+
operator: 'CONTAINS_KEY',
37+
value: ['http:url']
38+
});
39+
});
40+
41+
test('correctly parses CONTAINS_KEY_VALUE for tag with dot and URL', () => {
42+
const attribute: FilterAttribute = getTestFilterAttribute(FilterAttributeType.StringMap);
43+
const filterString =
44+
'String Map Attribute.http.url CONTAINS_KEY_VALUE http://dataservice:9394/userreview?productId=f2620500-8b55-4fab-b7a2-fe8af6f5ae24';
45+
46+
const containsFilterParser: ContainsFilterParser = new ContainsFilterParser();
47+
const parsedFilter: ParsedFilter<unknown> | undefined = containsFilterParser.parseFilterString(
48+
attribute,
49+
filterString
50+
);
51+
52+
expect(parsedFilter).toEqual({
53+
field: 'stringMapAttribute',
54+
operator: 'CONTAINS_KEY_VALUE',
55+
value: ['http.url', 'http://dataservice:9394/userreview?productId=f2620500-8b55-4fab-b7a2-fe8af6f5ae24']
56+
});
57+
});
58+
59+
test('correctly parses CONTAINS_KEY_VALUE for tag with colon and URL', () => {
60+
const attribute: FilterAttribute = getTestFilterAttribute(FilterAttributeType.StringMap);
61+
const filterString =
62+
'String Map Attribute.http:url CONTAINS_KEY_VALUE http://dataservice:9394/userreview?productId=f2620500-8b55-4fab-b7a2-fe8af6f5ae24';
63+
64+
const containsFilterParser: ContainsFilterParser = new ContainsFilterParser();
65+
const parsedFilter: ParsedFilter<unknown> | undefined = containsFilterParser.parseFilterString(
66+
attribute,
67+
filterString
68+
);
69+
70+
expect(parsedFilter).toEqual({
71+
field: 'stringMapAttribute',
72+
operator: 'CONTAINS_KEY_VALUE',
73+
value: ['http:url', 'http://dataservice:9394/userreview?productId=f2620500-8b55-4fab-b7a2-fe8af6f5ae24']
74+
});
75+
});
76+
77+
test('correctly parses CONTAINS_KEY_VALUE for tag with colon and empty string', () => {
78+
const attribute: FilterAttribute = getTestFilterAttribute(FilterAttributeType.StringMap);
79+
const filterString = 'String Map Attribute.http:url CONTAINS_KEY_VALUE ""';
80+
const containsFilterParser: ContainsFilterParser = new ContainsFilterParser();
81+
const parsedFilter: ParsedFilter<unknown> | undefined = containsFilterParser.parseFilterString(
82+
attribute,
83+
filterString
84+
);
85+
86+
expect(parsedFilter).toEqual({
87+
field: 'stringMapAttribute',
88+
operator: 'CONTAINS_KEY_VALUE',
89+
value: ['http:url', '""']
90+
});
91+
});
92+
});

projects/components/src/filtering/filter/parser/types/contains-filter-parser.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { assertUnreachable } from '@hypertrace/common';
1+
import { assertUnreachable, isNonEmptyString } from '@hypertrace/common';
22
import { FilterAttribute } from '../../filter-attribute';
33
import { FilterAttributeType } from '../../filter-attribute-type';
44
import { MAP_LHS_DELIMITER, MAP_RHS_DELIMITER } from '../../filter-delimiters';
@@ -46,7 +46,23 @@ export class ContainsFilterParser extends AbstractFilterParser<PossibleValuesTyp
4646
splitFilter: SplitFilter<FilterOperator>
4747
): string[] | undefined {
4848
if (splitFilter.lhs === attribute.displayName) {
49-
return splitFilter.rhs.split(MAP_RHS_DELIMITER);
49+
switch (splitFilter.operator) {
50+
case FilterOperator.ContainsKey:
51+
return [splitFilter.rhs];
52+
case FilterOperator.ContainsKeyValue:
53+
return splitFirstOccurrenceOmitEmpty(splitFilter.rhs, MAP_RHS_DELIMITER);
54+
case FilterOperator.Equals:
55+
case FilterOperator.NotEquals:
56+
case FilterOperator.LessThan:
57+
case FilterOperator.LessThanOrEqualTo:
58+
case FilterOperator.GreaterThan:
59+
case FilterOperator.GreaterThanOrEqualTo:
60+
case FilterOperator.Like:
61+
case FilterOperator.In:
62+
return undefined;
63+
default:
64+
assertUnreachable(splitFilter.operator);
65+
}
5066
}
5167

5268
const splitLhs = this.splitLhs(attribute, splitFilter);
@@ -76,3 +92,9 @@ interface SplitLhs {
7692
displayName: string;
7793
key?: string;
7894
}
95+
96+
const splitFirstOccurrenceOmitEmpty = (str: string, delimiter: string): string[] => {
97+
const firstIndex = str.indexOf(delimiter);
98+
99+
return [str.substr(0, firstIndex), str.substr(firstIndex + 1)].filter(isNonEmptyString);
100+
};

0 commit comments

Comments
 (0)