Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 25 additions & 14 deletions js_modules/ui-core/src/runs/RunsFilterInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@ import {TimeRangeState, useTimeRangeFilter} from '../ui/BaseFilters/useTimeRange
import {TruncatedTextWithFullTextOnHover} from '../ui/TruncatedTextWithFullTextOnHover';
import {useRepositoryOptions} from '../workspace/WorkspaceContext/util';

/**
* Split a tag string on the first `=` only, so tag values containing `=` are preserved.
* Returns [key, value] where value defaults to '' if no `=` is present.
*/
export function splitTagString(tagStr: string): [string, string] {
const eqIndex = tagStr.indexOf('=');
if (eqIndex === -1) {
return [tagStr, ''];
}
return [tagStr.slice(0, eqIndex), tagStr.slice(eqIndex + 1)];
}

export interface RunsFilterInputProps {
loading?: boolean;
tokens: RunFilterToken[];
Expand Down Expand Up @@ -157,13 +169,11 @@ export function runsFilterForSearchTokens(search: TokenizingFieldValue[]) {
} else if (item.token === 'snapshotId') {
obj.snapshotId = item.value;
} else if (item.token === 'tag') {
const [key, value = ''] = item.value.split('=');
const [key, value] = splitTagString(item.value);
if (obj.tags) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
obj.tags.push({key: key!, value});
obj.tags.push({key, value});
} else {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
obj.tags = [{key: key!, value}];
obj.tags = [{key, value}];
}
}
}
Expand Down Expand Up @@ -642,9 +652,8 @@ export const useRunsFilterInput = ({tokens, onChange, enabledFilters}: RunsFilte
return !tagsToExclude.includes(value.split('=')[0] as DagsterTag);
})
.map((token) => {
const [key, value] = token.value.split('=');
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return tagSuggestionValueObject(key!, value!).value;
const [key, value] = splitTagString(token.value);
return tagSuggestionValueObject(key, value).value;
});
}, [tokens]),

Expand Down Expand Up @@ -777,12 +786,14 @@ function tagToFilterValue(key: string, value: string) {
}

// Memoize this object because the static set filter component checks for object equality (set.has)
export const tagValueToFilterObject = memoize((value: string) => ({
key: value,
type: value.split('=')[0] as DagsterTag,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
value: value.split('=')[1]!,
}));
export const tagValueToFilterObject = memoize((value: string) => {
const [type, tagValue] = splitTagString(value);
return {
key: value,
type: type as DagsterTag,
value: tagValue,
};
});

export const tagSuggestionValueObject = memoize(
(key: string, value: string) => ({
Expand Down
87 changes: 87 additions & 0 deletions js_modules/ui-core/src/runs/__tests__/RunFilterInput.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import {
RUN_TAG_KEYS_QUERY,
RunFilterToken,
RunsFilterInputProps,
runsFilterForSearchTokens,
splitTagString,
tagSuggestionValueObject,
tagValueToFilterObject,
useRunsFilterInput,
Expand Down Expand Up @@ -115,6 +117,31 @@ describe('useTagDataFilterValues', () => {
});
});

describe('splitTagString', () => {
it('should split on the first = only', () => {
expect(splitTagString('foo=bar')).toEqual(['foo', 'bar']);
});

it('should preserve = signs in the value', () => {
expect(splitTagString('dagster/partition=foo=bar/baz=qux')).toEqual([
'dagster/partition',
'foo=bar/baz=qux',
]);
});

it('should return empty value when no = is present', () => {
expect(splitTagString('just-a-key')).toEqual(['just-a-key', '']);
});

it('should handle = as the last character', () => {
expect(splitTagString('key=')).toEqual(['key', '']);
});

it('should handle = as the first character', () => {
expect(splitTagString('=value')).toEqual(['', 'value']);
});
});

describe('tagValueToFilterObject', () => {
it('should return an object with the correct properties', () => {
const result = tagValueToFilterObject('tag1=value1');
Expand All @@ -124,6 +151,66 @@ describe('tagValueToFilterObject', () => {
value: 'value1',
});
});

it('should handle tag values containing = signs', () => {
const tagStr = 'dagster/partition=foo=bar/baz=qux';
const result = tagValueToFilterObject(tagStr);
expect(result).toEqual({
key: tagStr,
type: DagsterTag.Partition,
value: 'foo=bar/baz=qux',
});
});
});

describe('runsFilterForSearchTokens', () => {
it('should correctly parse tag values containing = signs', () => {
const tokens = [
{
token: 'tag' as const,
value: 'dagster/partition=foo=bar/baz=qux',
},
];
const result = runsFilterForSearchTokens(tokens);
expect(result.tags).toEqual([
{
key: DagsterTag.Partition,
value: 'foo=bar/baz=qux',
},
]);
});

it('should correctly parse multiple tags with = signs in their values', () => {
const tokens = [
{
token: 'tag' as const,
value: 'dagster/partition=foo=bar/baz=qux',
},
{
token: 'tag' as const,
value: 'alpha=bravo=charlie',
},
{
token: 'tag' as const,
value: 'simple_tag=simple_value',
},
];
const result = runsFilterForSearchTokens(tokens);
expect(result.tags).toEqual([
{
key: DagsterTag.Partition,
value: 'foo=bar/baz=qux',
},
{
key: 'alpha',
value: 'bravo=charlie',
},
{
key: 'simple_tag',
value: 'simple_value',
},
]);
});
});

describe('tagSuggestionValueObject', () => {
Expand Down