Skip to content

Commit 4c53cb4

Browse files
committed
[Asset Selection] Make submitting an invalid query with only a single value convert to a key substring filter. (#28435)
## Summary & Motivation A lot of users are using their muscle memory and doing: 1. Type partial asset key 2. Hit enter 3. Error -> No results 4. Give up. Lets just do the right thing for these users by automatically converting their invalid query into a valid one! ## How I Tested these changes https://github.com/user-attachments/assets/f8533af6-81db-4b38-8e2e-ed2a6e18640e
1 parent fe8949c commit 4c53cb4

File tree

3 files changed

+77
-1
lines changed

3 files changed

+77
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import {isUnmatchedValueQuery} from '../isUnmatchedValueQuery';
2+
3+
describe('isUnmatchedValueQuery', () => {
4+
it('should return true for unmatched value queries', () => {
5+
expect(isUnmatchedValueQuery('key:"value"')).toBe(false);
6+
expect(isUnmatchedValueQuery('one two')).toBe(false);
7+
expect(isUnmatchedValueQuery('one')).toBe(true);
8+
});
9+
});

js_modules/dagster-ui/packages/ui-core/src/asset-selection/input/AssetSelectionInput.oss.tsx

+15-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {useCallback} from 'react';
12
import {useAssetSelectionAutoCompleteProvider as defaultUseAssetSelectionAutoCompleteProvider} from 'shared/asset-selection/input/useAssetSelectionAutoCompleteProvider.oss';
23

34
import {assetSelectionSyntaxSupportedAttributes, unsupportedAttributeMessages} from './util';
@@ -8,6 +9,8 @@ import {SelectionAutoCompleteInput} from '../../selection/SelectionInput';
89
import {createSelectionLinter} from '../../selection/createSelectionLinter';
910
import {AssetSelectionLexer} from '../generated/AssetSelectionLexer';
1011
import {AssetSelectionParser} from '../generated/AssetSelectionParser';
12+
import {isUnmatchedValueQuery} from '../isUnmatchedValueQuery';
13+
import {parseAssetSelectionQuery} from '../parseAssetSelectionQuery';
1114

1215
export interface AssetSelectionInputProps {
1316
assets: AssetGraphQueryItem[];
@@ -30,7 +33,7 @@ const defaultLinter = createSelectionLinter({
3033

3134
export const AssetSelectionInput = ({
3235
value,
33-
onChange,
36+
onChange: _onChange,
3437
assets,
3538
linter = defaultLinter,
3639
useAssetSelectionAutoComplete = defaultUseAssetSelectionAutoCompleteProvider,
@@ -39,6 +42,17 @@ export const AssetSelectionInput = ({
3942
}: AssetSelectionInputProps) => {
4043
const {useAutoComplete} = useAssetSelectionAutoComplete(assets);
4144

45+
const onChange = useCallback(
46+
(value: string) => {
47+
if (parseAssetSelectionQuery([], value) instanceof Error && isUnmatchedValueQuery(value)) {
48+
_onChange(`key:"*${value}*"`);
49+
} else {
50+
_onChange(value);
51+
}
52+
},
53+
[_onChange],
54+
);
55+
4256
return (
4357
<SelectionAutoCompleteInput
4458
id="asset-selection-input"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import {CharStreams, CommonTokenStream} from 'antlr4ts';
2+
import {AbstractParseTreeVisitor} from 'antlr4ts/tree/AbstractParseTreeVisitor';
3+
4+
import {AntlrInputErrorListener} from './parseAssetSelectionQuery';
5+
import {SelectionAutoCompleteLexer} from '../selection/generated/SelectionAutoCompleteLexer';
6+
import {
7+
SelectionAutoCompleteParser,
8+
UnmatchedValueContext,
9+
} from '../selection/generated/SelectionAutoCompleteParser';
10+
import {SelectionAutoCompleteVisitor} from '../selection/generated/SelectionAutoCompleteVisitor';
11+
12+
class UnmatchedValueQueryVisitor
13+
extends AbstractParseTreeVisitor<void>
14+
implements SelectionAutoCompleteVisitor<void>
15+
{
16+
public isUnmatchedValue: boolean;
17+
18+
defaultResult() {}
19+
20+
constructor(private query: string) {
21+
super();
22+
this.isUnmatchedValue = false;
23+
}
24+
25+
visitUnmatchedValue(ctx: UnmatchedValueContext) {
26+
if (ctx.start.startIndex === 0 && ctx.stop?.stopIndex === this.query.length - 1) {
27+
// If a single attribute expression is the entire query, it's not a complex
28+
this.isUnmatchedValue = true;
29+
}
30+
}
31+
}
32+
33+
export const isUnmatchedValueQuery = (query: string) => {
34+
try {
35+
const lexer = new SelectionAutoCompleteLexer(CharStreams.fromString(query));
36+
lexer.removeErrorListeners();
37+
lexer.addErrorListener(new AntlrInputErrorListener());
38+
39+
const tokenStream = new CommonTokenStream(lexer);
40+
41+
const parser = new SelectionAutoCompleteParser(tokenStream);
42+
parser.removeErrorListeners();
43+
parser.addErrorListener(new AntlrInputErrorListener());
44+
45+
const tree = parser.start();
46+
47+
const visitor = new UnmatchedValueQueryVisitor(query);
48+
visitor.visit(tree);
49+
return visitor.isUnmatchedValue;
50+
} catch {
51+
return false;
52+
}
53+
};

0 commit comments

Comments
 (0)