diff --git a/pkg/ui/workspaces/cluster-ui/src/statementDetails/planView/planView.spec.tsx b/pkg/ui/workspaces/cluster-ui/src/statementDetails/planView/planView.spec.tsx index dad1c408fbd8..2d45431b0cab 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementDetails/planView/planView.spec.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/statementDetails/planView/planView.spec.tsx @@ -17,6 +17,8 @@ import { flattenTreeAttributes, flattenAttributes, standardizeKey, + planNodeToString, + planNodeAttrsToString, } from "./planView"; import IAttr = cockroach.sql.ExplainTreePlanNode.IAttr; @@ -260,4 +262,94 @@ describe("planView", () => { assert.equal(standardizeKey("(anti) hello world"), "helloWorld"); }); }); + + describe("planNodeAttrsToString", () => { + it("should convert an array of FlatPlanNodeAttribute[] into a string", () => { + const testNodeAttrs: FlatPlanNodeAttribute[] = [ + { + key: "Into", + values: ["users(id, city, name, address, credit_card)"], + warn: false, + }, + { + key: "Size", + values: ["5 columns, 5 rows"], + warn: false, + }, + ]; + + const expectedString = + "Into users(id, city, name, address, credit_card) Size 5 columns, 3 rows"; + + assert.deepEqual(planNodeAttrsToString(testNodeAttrs), expectedString); + }); + }); + + describe("planNodeToString", () => { + it("should recursively convert a FlatPlanNode into a string.", () => { + const testPlanNode: FlatPlanNode = { + name: "insert fast path", + attrs: [ + { + key: "Into", + values: ["users(id, city, name, address, credit_card)"], + warn: false, + }, + { + key: "Size", + values: ["5 columns, 3 rows"], + warn: false, + }, + ], + children: null, + }; + + const expectedString = + "insert fast path Into users(id, city, name, address, credit_card) Size 5 columns, 5 rows"; + + assert.deepEqual(planNodeToString(testPlanNode), expectedString); + }); + + it("should recursively convert a FlatPlanNode (with children) into a string.", () => { + const testPlanNode: FlatPlanNode = { + name: "render", + attrs: [], + children: [ + { + name: "group (scalar)", + attrs: [], + children: [ + { + name: "filter", + attrs: [ + { + key: "filter", + values: ["variable = _"], + warn: false, + }, + ], + children: [ + { + name: "virtual table", + attrs: [ + { + key: "table", + values: ["cluster_settings@primary"], + warn: false, + }, + ], + children: [], + }, + ], + }, + ], + }, + ], + }; + + const expectedString = + "render group (scalar) filter filter variable = _ virtual table table cluster_settings@primary"; + assert.deepEqual(planNodeToString(testPlanNode), expectedString); + }); + }); }); diff --git a/pkg/ui/workspaces/cluster-ui/src/statementDetails/planView/planView.tsx b/pkg/ui/workspaces/cluster-ui/src/statementDetails/planView/planView.tsx index 529ac44f286a..100a56c749c5 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementDetails/planView/planView.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/statementDetails/planView/planView.tsx @@ -56,6 +56,23 @@ function warnForAttribute(attr: IAttr): boolean { return false; } +// planNodeAttrsToString converts an array of FlatPlanNodeAttribute[] into a string. +export function planNodeAttrsToString(attrs: FlatPlanNodeAttribute[]): string { + return attrs.map(attr => `${attr.key} ${attr.values.join(" ")}`).join(" "); +} + +// planNodeAttributesToString recursively converts a FlatPlanNode into a string. +export function planNodeToString(plan: FlatPlanNode): string { + const str = `${plan.name} ${planNodeAttrsToString(plan.attrs)}`; + + if (plan.children.length > 0) { + return plan.children + .map(child => `${str} ${planNodeToString(child)}`) + .join(" "); + } + return str; +} + // flattenAttributes takes a list of attrs (IAttr[]) and collapses // all the values for the same key (FlatPlanNodeAttribute). For example, // if attrs was: diff --git a/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.tsx b/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.tsx index 67ee37c7058b..9d98d21b8eea 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.tsx @@ -76,6 +76,7 @@ import { } from "../timeScaleDropdown"; import { commonStyles } from "../common"; +import { flattenTreeAttributes, planNodeToString } from "../statementDetails"; const cx = classNames.bind(styles); const sortableTableCx = classNames.bind(sortableTableStyles); @@ -146,6 +147,23 @@ function statementsRequestFromProps( }); } +// filterBySearchQuery returns true if a search query matches the statement. +function filterBySearchQuery( + statement: AggregateStatistics, + search: string, +): boolean { + const label = statement.label.toLowerCase(); + const tree = flattenTreeAttributes( + statement.stats.sensitive_info && + statement.stats.sensitive_info.most_recent_plan_description, + ); + const plan = planNodeToString(tree).toLowerCase(); + const matchString = `${label}|${plan}`; + return search + .split(" ") + .every(val => matchString.includes(val.toLowerCase())); +} + export class StatementsPage extends React.Component< StatementsPageProps, StatementsPageState @@ -409,13 +427,7 @@ export class StatementsPage extends React.Component< ) .filter(statement => (filters.fullScan ? statement.fullScan : true)) .filter(statement => - search - ? search - .split(" ") - .every(val => - statement.label.toLowerCase().includes(val.toLowerCase()), - ) - : true, + search ? filterBySearchQuery(statement, search) : true, ) .filter( statement =>