Skip to content

Commit

Permalink
Implement AntlrRunSelectionVisitor on frontend (#26243)
Browse files Browse the repository at this point in the history
## Summary & Motivation
We now want to implement the visitor methods for `RunSelectionVisitor`. This requires having access to the metadata for each run to get the status. This is not available on type `GraphQueryItem`, so a new type `RunGraphQueryItem` was added to include the metadata.

## How I Tested These Changes
`AntlrRunSelection.test.ts`
  • Loading branch information
briantu authored Dec 4, 2024
1 parent 0cf935f commit 40796bd
Show file tree
Hide file tree
Showing 8 changed files with 421 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {AssetSelectionParser} from './generated/AssetSelectionParser';
import {featureEnabled} from '../app/Flags';
import {filterByQuery} from '../app/GraphQueryImpl';

class AntlrInputErrorListener implements ANTLRErrorListener<any> {
export class AntlrInputErrorListener implements ANTLRErrorListener<any> {
syntaxError(
recognizer: Recognizer<any, any>,
offendingSymbol: any,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {GraphTraverser} from '../app/GraphQueryImpl';
import {AssetGraphQueryItem} from '../asset-graph/useAssetGraphData';
import {buildRepoPathForHuman} from '../workspace/buildRepoAddress';

function getTraversalDepth(ctx: TraversalContext): number {
export function getTraversalDepth(ctx: TraversalContext): number {
if (ctx.STAR()) {
return Number.MAX_SAFE_INTEGER;
}
Expand All @@ -39,7 +39,7 @@ function getTraversalDepth(ctx: TraversalContext): number {
throw new Error('Invalid traversal');
}

function getFunctionName(ctx: FunctionNameContext): string {
export function getFunctionName(ctx: FunctionNameContext): string {
if (ctx.SINKS()) {
return 'sinks';
}
Expand All @@ -49,7 +49,7 @@ function getFunctionName(ctx: FunctionNameContext): string {
throw new Error('Invalid function name');
}

function getValue(ctx: ValueContext): string {
export function getValue(ctx: ValueContext): string {
if (ctx.QUOTED_STRING()) {
return ctx.text.slice(1, -1);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ describe('parseAssetSelectionQuery', () => {
});

it('should parse key_substring query', () => {
assertQueryResult('key:A', ['A']);
assertQueryResult('key_substring:A', ['A']);
assertQueryResult('key_substring:B', ['B', 'B2']);
});

it('should parse and query', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,30 @@ describe('toGraphQueryItems', () => {
inputs: [],
name: 'a',
outputs: [{dependedBy: [{solid: {name: 'b[1]'}}, {solid: {name: 'b[?]'}}]}],
metadata: {
attempts: [],
markers: [],
state: 'succeeded',
transitions: [],
},
},
{
inputs: [{dependsOn: [{solid: {name: 'a'}}]}],
name: 'b[1]',
outputs: [],
metadata: {
attempts: [],
markers: [],
state: 'succeeded',
transitions: [],
},
},
{
inputs: [{dependsOn: [{solid: {name: 'a'}}]}],
name: 'b[?]',
outputs: [],
metadata: undefined,
},
{inputs: [{dependsOn: [{solid: {name: 'a'}}]}], name: 'b[1]', outputs: []},
{inputs: [{dependsOn: [{solid: {name: 'a'}}]}], name: 'b[?]', outputs: []},
]);
});

Expand Down Expand Up @@ -129,6 +150,12 @@ describe('toGraphQueryItems', () => {
],
},
],
metadata: {
attempts: [],
markers: [],
state: 'succeeded',
transitions: [],
},
},
{
inputs: [
Expand All @@ -154,6 +181,12 @@ describe('toGraphQueryItems', () => {
],
},
],
metadata: {
attempts: [],
markers: [],
state: 'succeeded',
transitions: [],
},
},
{
inputs: [
Expand All @@ -169,6 +202,12 @@ describe('toGraphQueryItems', () => {
],
name: 'b[2]',
outputs: [],
metadata: {
attempts: [],
markers: [],
state: 'succeeded',
transitions: [],
},
},
{
inputs: [
Expand All @@ -194,6 +233,7 @@ describe('toGraphQueryItems', () => {
],
},
],
metadata: undefined,
},
{
inputs: [
Expand All @@ -209,6 +249,12 @@ describe('toGraphQueryItems', () => {
],
name: 'c[1]',
outputs: [],
metadata: {
attempts: [],
markers: [],
state: 'succeeded',
transitions: [],
},
},
{
inputs: [
Expand All @@ -224,6 +270,7 @@ describe('toGraphQueryItems', () => {
],
name: 'c[?]',
outputs: [],
metadata: undefined,
},
]);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import {GraphQueryItem} from '../app/GraphQueryImpl';
import {StepKind} from '../graphql/types';
import {IStepMetadata, IStepState} from '../runs/RunMetadataProvider';

export type RunGraphQueryItem = GraphQueryItem & {
metadata: IStepMetadata | undefined;
};

/**
* Converts a Run execution plan into a tree of `GraphQueryItem` items that
* can be used as the input to the "solid query" filtering algorithm or rendered
Expand All @@ -18,7 +22,7 @@ import {IStepMetadata, IStepState} from '../runs/RunMetadataProvider';
export const toGraphQueryItems = (
plan: ExecutionPlanToGraphFragment,
runtimeStepMetadata: {[key: string]: IStepMetadata},
) => {
): RunGraphQueryItem[] => {
// Step 1: Find unresolved steps in the initial plan and build a mapping
// of their unresolved names to their resolved step keys, eg:
// "multiply_input[*]" => ["multiply_input[1]", "multiply_input[2]"]
Expand Down Expand Up @@ -47,14 +51,15 @@ export const toGraphQueryItems = (
}

// Step 2: Create a graph node for each resolved step without any inputs or outputs.
const nodeTable: {[key: string]: GraphQueryItem} = {};
const nodeTable: {[key: string]: RunGraphQueryItem} = {};
for (const step of plan.steps) {
const stepRuntimeKeys = keyExpansionMap[step.key] || [step.key];
for (const key of stepRuntimeKeys) {
nodeTable[key] = {
name: key,
inputs: [],
outputs: [],
metadata: runtimeStepMetadata[key],
};
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {CharStreams, CommonTokenStream} from 'antlr4ts';

import {AntlrRunSelectionVisitor} from './AntlrRunSelectionVisitor';
import {AntlrInputErrorListener} from '../asset-selection/AntlrAssetSelection';
import {RunGraphQueryItem} from '../gantt/toGraphQueryItems';
import {RunSelectionLexer} from './generated/RunSelectionLexer';
import {RunSelectionParser} from './generated/RunSelectionParser';

type RunSelectionQueryResult = {
all: RunGraphQueryItem[];
focus: RunGraphQueryItem[];
};

export const parseRunSelectionQuery = (
all_runs: RunGraphQueryItem[],
query: string,
): RunSelectionQueryResult | Error => {
try {
const lexer = new RunSelectionLexer(CharStreams.fromString(query));
lexer.removeErrorListeners();
lexer.addErrorListener(new AntlrInputErrorListener());

const tokenStream = new CommonTokenStream(lexer);

const parser = new RunSelectionParser(tokenStream);
parser.removeErrorListeners();
parser.addErrorListener(new AntlrInputErrorListener());

const tree = parser.start();

const visitor = new AntlrRunSelectionVisitor(all_runs);
const all_selection = visitor.visit(tree);
const focus_selection = visitor.focus_runs;

return {
all: Array.from(all_selection),
focus: Array.from(focus_selection),
};
} catch (e) {
return e as Error;
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import {AbstractParseTreeVisitor} from 'antlr4ts/tree/AbstractParseTreeVisitor';

import {GraphTraverser} from '../app/GraphQueryImpl';
import {RunGraphQueryItem} from '../gantt/toGraphQueryItems';
import {
AllExpressionContext,
AndExpressionContext,
AttributeExpressionContext,
DownTraversalExpressionContext,
FunctionCallExpressionContext,
NameExprContext,
NameSubstringExprContext,
NotExpressionContext,
OrExpressionContext,
ParenthesizedExpressionContext,
StartContext,
StatusAttributeExprContext,
TraversalAllowedExpressionContext,
UpAndDownTraversalExpressionContext,
UpTraversalExpressionContext,
} from './generated/RunSelectionParser';
import {RunSelectionVisitor} from './generated/RunSelectionVisitor';
import {
getFunctionName,
getTraversalDepth,
getValue,
} from '../asset-selection/AntlrAssetSelectionVisitor';

export class AntlrRunSelectionVisitor
extends AbstractParseTreeVisitor<Set<RunGraphQueryItem>>
implements RunSelectionVisitor<Set<RunGraphQueryItem>>
{
all_runs: Set<RunGraphQueryItem>;
focus_runs: Set<RunGraphQueryItem>;
traverser: GraphTraverser<RunGraphQueryItem>;

protected defaultResult() {
return new Set<RunGraphQueryItem>();
}

constructor(all_runs: RunGraphQueryItem[]) {
super();
this.all_runs = new Set(all_runs);
this.focus_runs = new Set();
this.traverser = new GraphTraverser(all_runs);
}

visitStart(ctx: StartContext) {
return this.visit(ctx.expr());
}

visitTraversalAllowedExpression(ctx: TraversalAllowedExpressionContext) {
return this.visit(ctx.traversalAllowedExpr());
}

visitUpAndDownTraversalExpression(ctx: UpAndDownTraversalExpressionContext) {
const selection = this.visit(ctx.traversalAllowedExpr());
const up_depth: number = getTraversalDepth(ctx.traversal(0));
const down_depth: number = getTraversalDepth(ctx.traversal(1));
const selection_copy = new Set(selection);
for (const item of selection_copy) {
this.traverser.fetchUpstream(item, up_depth).forEach((i) => selection.add(i));
this.traverser.fetchDownstream(item, down_depth).forEach((i) => selection.add(i));
}
return selection;
}

visitUpTraversalExpression(ctx: UpTraversalExpressionContext) {
const selection = this.visit(ctx.traversalAllowedExpr());
const traversal_depth: number = getTraversalDepth(ctx.traversal());
const selection_copy = new Set(selection);
for (const item of selection_copy) {
this.traverser.fetchUpstream(item, traversal_depth).forEach((i) => selection.add(i));
}
return selection;
}

visitDownTraversalExpression(ctx: DownTraversalExpressionContext) {
const selection = this.visit(ctx.traversalAllowedExpr());
const traversal_depth: number = getTraversalDepth(ctx.traversal());
const selection_copy = new Set(selection);
for (const item of selection_copy) {
this.traverser.fetchDownstream(item, traversal_depth).forEach((i) => selection.add(i));
}
return selection;
}

visitNotExpression(ctx: NotExpressionContext) {
const selection = this.visit(ctx.expr());
return new Set([...this.all_runs].filter((i) => !selection.has(i)));
}

visitAndExpression(ctx: AndExpressionContext) {
const left = this.visit(ctx.expr(0));
const right = this.visit(ctx.expr(1));
return new Set([...left].filter((i) => right.has(i)));
}

visitOrExpression(ctx: OrExpressionContext) {
const left = this.visit(ctx.expr(0));
const right = this.visit(ctx.expr(1));
return new Set([...left, ...right]);
}

visitAllExpression(_ctx: AllExpressionContext) {
return this.all_runs;
}

visitAttributeExpression(ctx: AttributeExpressionContext) {
return this.visit(ctx.attributeExpr());
}

visitFunctionCallExpression(ctx: FunctionCallExpressionContext) {
const function_name: string = getFunctionName(ctx.functionName());
const selection = this.visit(ctx.expr());
if (function_name === 'sinks') {
const sinks = new Set<RunGraphQueryItem>();
for (const item of selection) {
const downstream = this.traverser
.fetchDownstream(item, Number.MAX_VALUE)
.filter((i) => selection.has(i));
if (downstream.length === 0 || (downstream.length === 1 && downstream[0] === item)) {
sinks.add(item);
}
}
return sinks;
}
if (function_name === 'roots') {
const roots = new Set<RunGraphQueryItem>();
for (const item of selection) {
const upstream = this.traverser
.fetchUpstream(item, Number.MAX_VALUE)
.filter((i) => selection.has(i));
if (upstream.length === 0 || (upstream.length === 1 && upstream[0] === item)) {
roots.add(item);
}
}
return roots;
}
throw new Error(`Unknown function: ${function_name}`);
}

visitParenthesizedExpression(ctx: ParenthesizedExpressionContext) {
return this.visit(ctx.expr());
}

visitNameExpr(ctx: NameExprContext) {
const value: string = getValue(ctx.value());
const selection = [...this.all_runs].filter((i) => i.name === value);
selection.forEach((i) => this.focus_runs.add(i));
return new Set(selection);
}

visitNameSubstringExpr(ctx: NameSubstringExprContext) {
const value: string = getValue(ctx.value());
const selection = [...this.all_runs].filter((i) => i.name.includes(value));
selection.forEach((i) => this.focus_runs.add(i));
return new Set(selection);
}

visitStatusAttributeExpr(ctx: StatusAttributeExprContext) {
const state: string = getValue(ctx.value()).toLowerCase();
return new Set([...this.all_runs].filter((i) => i.metadata?.state === state));
}
}
Loading

1 comment on commit 40796bd

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deploy preview for dagit-core-storybook ready!

✅ Preview
https://dagit-core-storybook-mhn3f0ugo-elementl.vercel.app

Built with commit 40796bd.
This pull request is being automatically deployed with vercel-action

Please sign in to comment.