From 6cec4e047746b959dd5abb483bd6c9c48b04b229 Mon Sep 17 00:00:00 2001 From: gtr Date: Tue, 20 Dec 2022 14:18:18 -0500 Subject: [PATCH] ui: add missing filters for workload insights and active execution pages Fixes #87741, #87783. Previously, the workload insights page was missing a filter for insights type for both the statement and transaction tabs. Furthermore, the active execution pages for statement and transactions was also missing a status filter. This commit adds the ability to filter by insights type for the workload insights page and the ability to filter by execution status for the active execution pages. Release note (ui change): added an insights type filter for the workload insights page, added a execution status filter for the active execution pages. --- .../cluster-ui/src/insights/types.ts | 26 +++++-- .../cluster-ui/src/insights/utils.ts | 25 +++++++ .../statementInsightsView.tsx | 13 +++- .../transactionInsightsView.tsx | 13 +++- .../workloadInsightsPageConnected.tsx | 3 + .../src/queryFilter/filter.spec.tsx | 16 +++++ .../cluster-ui/src/queryFilter/filter.tsx | 71 +++++++++++++++++++ .../cluster-ui/src/queryFilter/utils.ts | 3 + .../src/recentExecutions/execTableCommon.tsx | 3 +- .../recentStatementUtils.spec.ts | 9 +-- .../recentExecutions/recentStatementUtils.ts | 22 +++++- .../recentStatementsSection.tsx | 2 +- .../recentTransactionsSection.tsx | 6 +- .../cluster-ui/src/recentExecutions/types.ts | 6 +- .../selectors/recentExecutions.selectors.ts | 14 +++- .../recentExecutionsCommon.selectors.ts | 17 +++-- .../recentStatementsPage.selectors.ts | 2 + .../statementsPage/recentStatementsView.tsx | 11 ++- .../statementInsights.selectors.ts | 9 +++ .../localStorage/localStorage.reducer.ts | 2 + .../recentTransactionsPage.selectors.tsx | 2 + .../recentTransactionsView.tsx | 11 ++- .../selectors/recentExecutionsSelectors.ts | 9 +++ .../src/views/insights/insightsSelectors.ts | 12 +++- .../views/insights/workloadInsightsPage.tsx | 3 + .../statements/recentStatementsSelectors.tsx | 12 +++- .../recentTransactionsSelectors.tsx | 12 +++- 27 files changed, 299 insertions(+), 35 deletions(-) diff --git a/pkg/ui/workspaces/cluster-ui/src/insights/types.ts b/pkg/ui/workspaces/cluster-ui/src/insights/types.ts index fc860e48b28d..e99e4e64a3b4 100644 --- a/pkg/ui/workspaces/cluster-ui/src/insights/types.ts +++ b/pkg/ui/workspaces/cluster-ui/src/insights/types.ts @@ -200,7 +200,7 @@ export const highContentionInsight = ( const description = `This ${execType} waited on other ${execType}s to execute for ${waitDuration}.`; return { name: InsightNameEnum.highContention, - label: "High Contention", + label: InsightEnumToLabel.get(InsightNameEnum.highContention), description: description, tooltipDescription: description + ` Click the ${execType} execution ID to see more details.`, @@ -219,7 +219,7 @@ export const slowExecutionInsight = ( const description = `This ${execType} took longer than ${threshold} to execute.`; return { name: InsightNameEnum.slowExecution, - label: "Slow Execution", + label: InsightEnumToLabel.get(InsightNameEnum.slowExecution), description: description, tooltipDescription: description + ` Click the ${execType} execution ID to see more details.`, @@ -233,7 +233,7 @@ export const planRegressionInsight = (execType: InsightExecEnum): Insight => { `search conditions, or a change in the database schema.`; return { name: InsightNameEnum.planRegression, - label: "Plan Regression", + label: InsightEnumToLabel.get(InsightNameEnum.planRegression), description: description, tooltipDescription: description + ` Click the ${execType} execution ID to see more details.`, @@ -246,7 +246,7 @@ export const suboptimalPlanInsight = (execType: InsightExecEnum): Insight => { `due to outdated statistics or missing indexes.`; return { name: InsightNameEnum.suboptimalPlan, - label: "Suboptimal Plan", + label: InsightEnumToLabel.get(InsightNameEnum.suboptimalPlan), description: description, tooltipDescription: description + ` Click the ${execType} execution ID to see more details.`, @@ -259,7 +259,7 @@ export const highRetryCountInsight = (execType: InsightExecEnum): Insight => { `'sql.insights.high_retry_count.threshold' cluster setting.`; return { name: InsightNameEnum.highRetryCount, - label: "High Retry Count", + label: InsightEnumToLabel.get(InsightNameEnum.highRetryCount), description: description, tooltipDescription: description + ` Click the ${execType} execution ID to see more details.`, @@ -272,7 +272,7 @@ export const failedExecutionInsight = (execType: InsightExecEnum): Insight => { `saturation, or syntax errors.`; return { name: InsightNameEnum.failedExecution, - label: "Failed Execution", + label: InsightEnumToLabel.get(InsightNameEnum.failedExecution), description: description, tooltipDescription: description + ` Click the ${execType} execution ID to see more details.`, @@ -310,7 +310,19 @@ export const InsightExecOptions = new Map([ [InsightExecEnum.STATEMENT.toString(), "Statement Executions"], ]); -export type WorkloadInsightEventFilters = Pick; +export const InsightEnumToLabel = new Map([ + [InsightNameEnum.highContention.toString(), "High Contention"], + [InsightNameEnum.slowExecution.toString(), "Slow Execution"], + [InsightNameEnum.planRegression.toString(), "Plan Regression"], + [InsightNameEnum.suboptimalPlan.toString(), "Suboptimal Plan"], + [InsightNameEnum.highRetryCount.toString(), "High Retry Count"], + [InsightNameEnum.failedExecution.toString(), "Failed Execution"], +]); + +export type WorkloadInsightEventFilters = Pick< + Filters, + "app" | "workloadInsightType" +>; export type SchemaInsightEventFilters = Pick< Filters, diff --git a/pkg/ui/workspaces/cluster-ui/src/insights/utils.ts b/pkg/ui/workspaces/cluster-ui/src/insights/utils.ts index 52627d177516..33ef41d0121f 100644 --- a/pkg/ui/workspaces/cluster-ui/src/insights/utils.ts +++ b/pkg/ui/workspaces/cluster-ui/src/insights/utils.ts @@ -63,6 +63,18 @@ export const filterTransactionInsights = ( } else { filteredTransactions = filteredTransactions.filter(txn => !isInternal(txn)); } + if (filters.workloadInsightType) { + const workloadInsightTypes = filters.workloadInsightType + .toString() + .split(","); + filteredTransactions = filteredTransactions.filter( + transactionInsight => + workloadInsightTypes.length === 0 || + workloadInsightTypes.includes( + transactionInsight.insights.map(insight => insight.label).toString(), + ), + ); + } if (search) { search = search.toLowerCase(); @@ -205,6 +217,19 @@ export const filterStatementInsights = ( stmt => !isInternal(stmt.application), ); } + if (filters.workloadInsightType && filters.workloadInsightType.length > 0) { + const workloadInsightTypes = filters.workloadInsightType + .toString() + .split(","); + + filteredStatements = filteredStatements.filter(statementInsight => + statementInsight.insights.some(stmtInsight => + workloadInsightTypes.some( + workloadType => workloadType === stmtInsight.label, + ), + ), + ); + } if (search) { search = search.toLowerCase(); filteredStatements = filteredStatements.filter( diff --git a/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsights/statementInsights/statementInsightsView.tsx b/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsights/statementInsights/statementInsightsView.tsx index c444e2f4736a..2675ba1c69d4 100644 --- a/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsights/statementInsights/statementInsightsView.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsights/statementInsights/statementInsightsView.tsx @@ -20,6 +20,7 @@ import { PageConfig, PageConfigItem } from "src/pageConfig/pageConfig"; import { Search } from "src/search/search"; import { calculateActiveFilters, + defaultFilters, Filter, getFullFiltersAsStringRecord, } from "src/queryFilter/filter"; @@ -62,6 +63,7 @@ const sortableTableCx = classNames.bind(sortableTableStyles); export type StatementInsightsViewStateProps = { statements: FlattenedStmtInsights; statementsError: Error | null; + insightTypes: string[]; filters: WorkloadInsightEventFilters; sortSetting: SortSetting; selectedColumnNames: string[]; @@ -91,6 +93,7 @@ export const StatementInsightsView: React.FC = ( sortSetting, statements, statementsError, + insightTypes, filters, timeScale, isLoading, @@ -210,7 +213,8 @@ export const StatementInsightsView: React.FC = ( const clearFilters = () => onSubmitFilters({ - app: "", + app: defaultFilters.app, + workloadInsightType: defaultFilters.workloadInsightType, }); const apps = getAppsFromStatementInsights( @@ -253,6 +257,8 @@ export const StatementInsightsView: React.FC = ( onSubmitFilters={onSubmitFilters} appNames={apps} filters={filters} + workloadInsightTypes={insightTypes.sort()} + showWorkloadInsightTypes={true} /> @@ -294,7 +300,10 @@ export const StatementInsightsView: React.FC = ( renderNoResult={ 0 && filteredStatements?.length === 0 + (search?.length > 0 && + filteredStatements?.length === 0) || + (countActiveFilters > 0 && + filteredStatements?.length === 0) } /> } diff --git a/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsights/transactionInsights/transactionInsightsView.tsx b/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsights/transactionInsights/transactionInsightsView.tsx index 14b047277c44..01e4214f75dd 100644 --- a/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsights/transactionInsights/transactionInsightsView.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsights/transactionInsights/transactionInsightsView.tsx @@ -20,6 +20,7 @@ import { PageConfig, PageConfigItem } from "src/pageConfig/pageConfig"; import { Search } from "src/search/search"; import { calculateActiveFilters, + defaultFilters, Filter, getFullFiltersAsStringRecord, } from "src/queryFilter/filter"; @@ -56,6 +57,7 @@ const sortableTableCx = classNames.bind(sortableTableStyles); export type TransactionInsightsViewStateProps = { transactions: MergedTxnInsightEvent[]; transactionsError: Error | null; + insightTypes: string[]; filters: WorkloadInsightEventFilters; sortSetting: SortSetting; isLoading?: boolean; @@ -83,6 +85,7 @@ export const TransactionInsightsView: React.FC = ( sortSetting, transactions, transactionsError, + insightTypes, filters, timeScale, isLoading, @@ -194,7 +197,8 @@ export const TransactionInsightsView: React.FC = ( const clearFilters = () => onSubmitFilters({ - app: "", + app: defaultFilters.app, + workloadInsightType: defaultFilters.workloadInsightType, }); const transactionInsights = transactions; @@ -228,6 +232,8 @@ export const TransactionInsightsView: React.FC = ( onSubmitFilters={onSubmitFilters} appNames={apps} filters={filters} + workloadInsightTypes={insightTypes.sort()} + showWorkloadInsightTypes={true} /> @@ -265,7 +271,10 @@ export const TransactionInsightsView: React.FC = ( renderNoResult={ 0 && filteredTransactions?.length === 0 + (search?.length > 0 && + filteredTransactions?.length === 0) || + (countActiveFilters > 0 && + filteredTransactions?.length === 0) } /> } diff --git a/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsights/workloadInsightsPageConnected.tsx b/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsights/workloadInsightsPageConnected.tsx index 9adc51e9b01b..47bf11daf57c 100644 --- a/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsights/workloadInsightsPageConnected.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsights/workloadInsightsPageConnected.tsx @@ -32,6 +32,7 @@ import { selectExecutionInsights, selectExecutionInsightsError, selectExecutionInsightsLoading, + selectInsightTypes, } from "src/store/insights/statementInsights"; import { actions as transactionInsights, @@ -52,6 +53,7 @@ const transactionMapStateToProps = ( ): TransactionInsightsViewStateProps => ({ transactions: selectTransactionInsights(state), transactionsError: selectTransactionInsightsError(state), + insightTypes: selectInsightTypes(), filters: selectFilters(state), sortSetting: selectSortSetting(state), timeScale: selectTimeScale(state), @@ -64,6 +66,7 @@ const statementMapStateToProps = ( ): StatementInsightsViewStateProps => ({ statements: selectExecutionInsights(state), statementsError: selectExecutionInsightsError(state), + insightTypes: selectInsightTypes(), filters: selectFilters(state), sortSetting: selectSortSetting(state), selectedColumnNames: selectColumns(state), diff --git a/pkg/ui/workspaces/cluster-ui/src/queryFilter/filter.spec.tsx b/pkg/ui/workspaces/cluster-ui/src/queryFilter/filter.spec.tsx index 3bab1b4b9173..b0bc8814f000 100644 --- a/pkg/ui/workspaces/cluster-ui/src/queryFilter/filter.spec.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/queryFilter/filter.spec.tsx @@ -17,6 +17,7 @@ describe("Test filter functions", (): void => { app: "", timeNumber: "0", timeUnit: "seconds", + executionStatus: "", fullScan: false, sqlType: "", database: "", @@ -25,6 +26,7 @@ describe("Test filter functions", (): void => { sessionStatus: "", nodes: "", username: "", + workloadInsightType: "", }; const resultFilters = getFiltersFromQueryString(""); expect(resultFilters).toEqual(expectedFilters); @@ -36,6 +38,7 @@ describe("Test filter functions", (): void => { app: "$ internal", timeNumber: "1", timeUnit: "milliseconds", + executionStatus: "", fullScan: true, sqlType: "DML", database: "movr", @@ -44,6 +47,7 @@ describe("Test filter functions", (): void => { sessionStatus: "idle", nodes: "n1,n2", username: "root", + workloadInsightType: "", }; const resultFilters = getFiltersFromQueryString( "app=%24+internal&timeNumber=1&timeUnit=milliseconds&fullScan=true&sqlType=DML&database=movr&sessionStatus=idle&username=root®ions=us-central&nodes=n1,n2&schemaInsightType=Drop+Unused+Index", @@ -56,6 +60,7 @@ describe("Test filter functions", (): void => { app: "", timeNumber: "0", timeUnit: "seconds", + executionStatus: "", fullScan: true, sqlType: "", database: "", @@ -64,6 +69,7 @@ describe("Test filter functions", (): void => { sessionStatus: "", nodes: "", username: "", + workloadInsightType: "", }; const resultFilters = getFiltersFromQueryString("fullScan=true"); expect(resultFilters).toEqual(expectedFilters); @@ -74,6 +80,7 @@ describe("Test filter functions", (): void => { app: "", timeNumber: "0", timeUnit: "seconds", + executionStatus: "", fullScan: false, sqlType: "", database: "", @@ -82,6 +89,7 @@ describe("Test filter functions", (): void => { sessionStatus: "", nodes: "", username: "", + workloadInsightType: "", }; const resultFilters = getFiltersFromQueryString("fullScan=false"); expect(resultFilters).toEqual(expectedFilters); @@ -92,6 +100,7 @@ describe("Test filter functions", (): void => { app: "", timeNumber: "0", timeUnit: "seconds", + executionStatus: "", fullScan: false, sqlType: "", database: "", @@ -100,6 +109,7 @@ describe("Test filter functions", (): void => { sessionStatus: "open", nodes: "", username: "", + workloadInsightType: "", }; const resultFilters = getFiltersFromQueryString("sessionStatus=open"); expect(resultFilters).toEqual(expectedFilters); @@ -110,6 +120,7 @@ describe("Test filter functions", (): void => { app: "", timeNumber: "0", timeUnit: "seconds", + executionStatus: "", fullScan: false, sqlType: "", database: "", @@ -118,6 +129,7 @@ describe("Test filter functions", (): void => { sessionStatus: "idle", nodes: "", username: "", + workloadInsightType: "", }; const resultFilters = getFiltersFromQueryString("sessionStatus=idle"); expect(resultFilters).toEqual(expectedFilters); @@ -128,6 +140,7 @@ describe("Test filter functions", (): void => { app: "", timeNumber: "0", timeUnit: "seconds", + executionStatus: "", fullScan: false, sqlType: "", database: "", @@ -136,6 +149,7 @@ describe("Test filter functions", (): void => { sessionStatus: "closed", nodes: "", username: "", + workloadInsightType: "", }; const resultFilters = getFiltersFromQueryString("sessionStatus=closed"); expect(resultFilters).toEqual(expectedFilters); @@ -146,6 +160,7 @@ describe("Test filter functions", (): void => { app: "", timeNumber: "0", timeUnit: "seconds", + executionStatus: "", fullScan: false, sqlType: "", database: "", @@ -154,6 +169,7 @@ describe("Test filter functions", (): void => { sessionStatus: "", nodes: "", username: "", + workloadInsightType: "", }; const resultFilters = getFiltersFromQueryString( "schemaInsightType=Drop+Unused+Index", diff --git a/pkg/ui/workspaces/cluster-ui/src/queryFilter/filter.tsx b/pkg/ui/workspaces/cluster-ui/src/queryFilter/filter.tsx index 0b8bed3d2b81..532667689066 100644 --- a/pkg/ui/workspaces/cluster-ui/src/queryFilter/filter.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/queryFilter/filter.tsx @@ -39,7 +39,9 @@ interface QueryFilter { dbNames?: string[]; usernames?: string[]; sessionStatuses?: string[]; + executionStatuses?: string[]; schemaInsightTypes?: string[]; + workloadInsightTypes?: string[]; regions?: string[]; nodes?: string[]; hideAppNames?: boolean; @@ -47,7 +49,9 @@ interface QueryFilter { showDB?: boolean; showUsername?: boolean; showSessionStatus?: boolean; + showExecutionStatus?: boolean; showSchemaInsightTypes?: boolean; + showWorkloadInsightTypes?: boolean; showSqlType?: boolean; showScan?: boolean; showRegions?: boolean; @@ -70,7 +74,9 @@ export interface Filters extends Record { nodes?: string; username?: string; sessionStatus?: string; + executionStatus?: string; schemaInsightType?: string; + workloadInsightType?: string; } const timeUnit = [ @@ -91,6 +97,8 @@ export const defaultFilters: Required = { username: "", sessionStatus: "", schemaInsightType: "", + workloadInsightType: "", + executionStatus: "", }; // getFullFiltersObject returns Filters with every field defined as @@ -253,6 +261,9 @@ export const inactiveFiltersState: Required> = { regions: "", sessionStatus: "", nodes: "", + workloadInsightType: "", + schemaInsightType: "", + executionStatus: "", }; export const calculateActiveFilters = (filters: Filters): number => { @@ -385,7 +396,9 @@ export class Filter extends React.Component { dbNames, usernames, sessionStatuses, + executionStatuses, schemaInsightTypes, + workloadInsightTypes, regions, nodes, activeFilters, @@ -399,7 +412,9 @@ export class Filter extends React.Component { hideTimeLabel, showUsername, showSessionStatus, + showExecutionStatus, showSchemaInsightTypes, + showWorkloadInsightTypes, } = this.props; const dropdownArea = hide ? hidden : dropdown; const customStyles = { @@ -530,6 +545,32 @@ export class Filter extends React.Component { ); + const executionStatusOptions = showExecutionStatus + ? executionStatuses.map(executionStatus => ({ + label: executionStatus, + value: executionStatus, + isSelected: this.isOptionSelected( + executionStatus, + filters.executionStatus, + ), + })) + : []; + const executionStatusValue = executionStatusOptions.filter(option => + filters.executionStatus.split(",").includes(option.label), + ); + const executionStatusFilter = ( +
+
Execution Status
+ +
+ ); + const schemaInsightTypeOptions = showSchemaInsightTypes ? schemaInsightTypes.map(schemaInsight => ({ label: schemaInsight, @@ -556,6 +597,34 @@ export class Filter extends React.Component { ); + const workloadInsightTypeOptions = showWorkloadInsightTypes + ? workloadInsightTypes.map(workloadInsight => ({ + label: workloadInsight, + value: workloadInsight, + isSelected: this.isOptionSelected( + workloadInsight, + filters.workloadInsightType, + ), + })) + : []; + const workloadInsightTypeValue = workloadInsightTypeOptions.filter( + option => { + return filters.workloadInsightType.split(",").includes(option.label); + }, + ); + const workloadInsightTypeFilter = ( +
+
Workload Insight Type
+ +
+ ); + const regionsOptions = showRegions ? regions.map(region => ({ label: region, @@ -671,7 +740,9 @@ export class Filter extends React.Component { {showDB ? dbFilter : ""} {showUsername ? usernameFilter : ""} {showSessionStatus ? sessionStatusFilter : ""} + {showExecutionStatus ? executionStatusFilter : ""} {showSchemaInsightTypes ? schemaInsightTypeFilter : ""} + {showWorkloadInsightTypes ? workloadInsightTypeFilter : ""} {showSqlType ? sqlTypeFilter : ""} {showRegions ? regionsFilter : ""} {showNodes ? nodesFilter : ""} diff --git a/pkg/ui/workspaces/cluster-ui/src/queryFilter/utils.ts b/pkg/ui/workspaces/cluster-ui/src/queryFilter/utils.ts index d5e605963042..3775166c5524 100644 --- a/pkg/ui/workspaces/cluster-ui/src/queryFilter/utils.ts +++ b/pkg/ui/workspaces/cluster-ui/src/queryFilter/utils.ts @@ -47,6 +47,7 @@ export function getRecentStatementFiltersFromURL( const appFilters = { app: filters.app, + executionStatus: filters.executionStatus, }; // If every entry is null, there were no active stmt filters. Return null. @@ -63,6 +64,7 @@ export function getRecentTransactionFiltersFromURL( const appFilters = { app: filters.app, + executionStatus: filters.executionStatus, }; // If every entry is null, there were no active stmt filters. Return null. @@ -79,6 +81,7 @@ export function getWorkloadInsightEventFiltersFromURL( const appFilters = { app: filters.app, + workloadInsightType: filters.workloadInsightType, }; // If every entry is null, there were no active filters. Return null. diff --git a/pkg/ui/workspaces/cluster-ui/src/recentExecutions/execTableCommon.tsx b/pkg/ui/workspaces/cluster-ui/src/recentExecutions/execTableCommon.tsx index 8789ceae29dd..260c1a109812 100644 --- a/pkg/ui/workspaces/cluster-ui/src/recentExecutions/execTableCommon.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/recentExecutions/execTableCommon.tsx @@ -113,7 +113,8 @@ export const executionsTableTitles: ExecutionsTableTitleType = { {`The status of the ${execType}'s execution. If "Preparing", the ${execType} is being parsed and planned. If "Executing", the ${execType} is currently being - executed.`} + executed. If "Waiting", the ${execType} is currently + experiencing contention.`}

} > diff --git a/pkg/ui/workspaces/cluster-ui/src/recentExecutions/recentStatementUtils.spec.ts b/pkg/ui/workspaces/cluster-ui/src/recentExecutions/recentStatementUtils.spec.ts index 9c8025b5bf6f..97377cf932c2 100644 --- a/pkg/ui/workspaces/cluster-ui/src/recentExecutions/recentStatementUtils.spec.ts +++ b/pkg/ui/workspaces/cluster-ui/src/recentExecutions/recentStatementUtils.spec.ts @@ -16,6 +16,7 @@ import { SessionStatusType, RecentStatementFilters, RecentTransactionFilters, + ExecutionStatus, } from "./types"; import * as protos from "@cockroachlabs/crdb-protobuf-client"; import moment from "moment"; @@ -50,7 +51,7 @@ const defaultActiveStatement: RecentStatement = { transactionID: "transactionID", sessionID: "sessionID", query: defaultActiveQuery.sql, - status: "Executing", + status: ExecutionStatus.Executing, start: MOCK_START_TIME, elapsedTime: moment.duration(60), application: "test", @@ -87,7 +88,7 @@ function makeActiveTxn( lastAutoRetryReason: null, priority: "Normal", statementCount: 5, - status: "Executing", + status: ExecutionStatus.Executing, ...props, }; } @@ -231,7 +232,7 @@ describe("test activeStatementUtils", () => { fail(`stmt user should be foo or bar, got ${stmt.user}`); } // expect(stmt.transactionID).toBe(defaultActiveStatement.transactionID); - expect(stmt.status).toBe("Executing"); + expect(stmt.status).toBe(ExecutionStatus.Executing); expect(stmt.start.unix()).toBe( TimestampToMoment(defaultActiveQuery.start).unix(), ); @@ -302,7 +303,7 @@ describe("test activeStatementUtils", () => { expect(txn.application).toBe( sessionsResponse.sessions[i].application_name, ); - expect(txn.status).toBe("Executing"); + expect(txn.status).toBe(ExecutionStatus.Executing); expect(txn.query).toBeTruthy(); expect(txn.start.unix()).toBe( TimestampToMoment(defaultActiveQuery.start).unix(), diff --git a/pkg/ui/workspaces/cluster-ui/src/recentExecutions/recentStatementUtils.ts b/pkg/ui/workspaces/cluster-ui/src/recentExecutions/recentStatementUtils.ts index c48cd6bbf350..c88d6a0eac04 100644 --- a/pkg/ui/workspaces/cluster-ui/src/recentExecutions/recentStatementUtils.ts +++ b/pkg/ui/workspaces/cluster-ui/src/recentExecutions/recentStatementUtils.ts @@ -65,6 +65,16 @@ export function filterRecentStatements( ); } + if (filters.executionStatus) { + filteredStatements = filteredStatements.filter( + (statement: RecentStatement) => { + const executionStatuses = filters.executionStatus.toString().split(","); + + return executionStatuses.includes(statement.status); + }, + ); + } + if (search) { const searchCaseInsensitive = search.toLowerCase(); filteredStatements = filteredStatements.filter(stmt => @@ -113,8 +123,8 @@ export function getRecentExecutionsFromSessions( query: query.sql?.length > 0 ? query.sql : query.sql_no_constants, status: query.phase === ActiveStatementPhase.EXECUTING - ? "Executing" - : "Preparing", + ? ExecutionStatus.Executing + : ExecutionStatus.Preparing, start: TimestampToMoment(query.start), elapsedTime: DurationToMomentDuration(query.elapsed_time), application: session.application_name, @@ -206,6 +216,14 @@ export function filterRecentTransactions( filteredTxns = filteredTxns.filter(txn => !isInternal(txn)); } + if (filters.executionStatus) { + filteredTxns = filteredTxns.filter((txn: RecentTransaction) => { + const executionStatuses = filters.executionStatus.toString().split(","); + + return executionStatuses.includes(txn.status); + }); + } + if (search) { const searchCaseInsensitive = search.toLowerCase(); filteredTxns = filteredTxns.filter(txn => diff --git a/pkg/ui/workspaces/cluster-ui/src/recentExecutions/recentStatementsSection.tsx b/pkg/ui/workspaces/cluster-ui/src/recentExecutions/recentStatementsSection.tsx index b291f044af91..534ac1e5bba6 100644 --- a/pkg/ui/workspaces/cluster-ui/src/recentExecutions/recentStatementsSection.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/recentExecutions/recentStatementsSection.tsx @@ -101,7 +101,7 @@ export const RecentStatementsSection: React.FC< onChangeSortSetting={onChangeSortSetting} renderNoResult={ 0 && statements.length > 0} + isEmptySearchResults={(search?.length > 0 || activeFilters > 0) && statements.length === 0} statementView={StatementViewType.ACTIVE} /> } diff --git a/pkg/ui/workspaces/cluster-ui/src/recentExecutions/recentTransactionsSection.tsx b/pkg/ui/workspaces/cluster-ui/src/recentExecutions/recentTransactionsSection.tsx index 715ae90910f6..4a42abae8a5d 100644 --- a/pkg/ui/workspaces/cluster-ui/src/recentExecutions/recentTransactionsSection.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/recentExecutions/recentTransactionsSection.tsx @@ -11,8 +11,8 @@ import React, { useMemo } from "react"; import classNames from "classnames/bind"; import { - RecentStatementFilters, RecentTransaction, + RecentTransactionFilters, } from "src/recentExecutions/types"; import ColumnsSelector, { SelectOption, @@ -36,7 +36,7 @@ import { SortedTable } from "src/sortedtable"; const sortableTableCx = classNames.bind(sortableTableStyles); type RecentTransactionsSectionProps = { - filters: RecentStatementFilters; + filters: RecentTransactionFilters; isTenant?: boolean; pagination: ISortedTablePagination; search: string; @@ -100,7 +100,7 @@ export const RecentTransactionsSection: React.FC< onChangeSortSetting={onChangeSortSetting} renderNoResult={ 0 && transactions.length > 0} + isEmptySearchResults={(search?.length > 0 || activeFilters > 0) && transactions.length === 0} transactionView={TransactionViewType.ACTIVE} /> } diff --git a/pkg/ui/workspaces/cluster-ui/src/recentExecutions/types.ts b/pkg/ui/workspaces/cluster-ui/src/recentExecutions/types.ts index 799f2f1315c4..931a23c8ac84 100644 --- a/pkg/ui/workspaces/cluster-ui/src/recentExecutions/types.ts +++ b/pkg/ui/workspaces/cluster-ui/src/recentExecutions/types.ts @@ -16,7 +16,11 @@ export type SessionsResponse = protos.cockroach.server.serverpb.ListSessionsResponse; export type ActiveStatementResponse = protos.cockroach.server.serverpb.ActiveQuery; -export type ExecutionStatus = "Waiting" | "Executing" | "Preparing"; +export enum ExecutionStatus { + Waiting = "Waiting", + Executing = "Executing", + Preparing = "Preparing", +} export type ExecutionType = "statement" | "transaction"; export const ActiveStatementPhase = diff --git a/pkg/ui/workspaces/cluster-ui/src/selectors/recentExecutions.selectors.ts b/pkg/ui/workspaces/cluster-ui/src/selectors/recentExecutions.selectors.ts index 20e8f9e7cf21..db7b7da641ab 100644 --- a/pkg/ui/workspaces/cluster-ui/src/selectors/recentExecutions.selectors.ts +++ b/pkg/ui/workspaces/cluster-ui/src/selectors/recentExecutions.selectors.ts @@ -9,7 +9,11 @@ // licenses/APL.txt. import { createSelector } from "reselect"; -import { RecentExecutions } from "src/recentExecutions/types"; +import { + RecentExecutions, + RecentTransaction, + ExecutionStatus, +} from "src/recentExecutions/types"; import { AppState } from "src/store"; import { selectRecentExecutionsCombiner } from "src/selectors/recentExecutionsCommon.selectors"; import { selectExecutionID } from "src/selectors/common"; @@ -39,6 +43,14 @@ export const selectRecentStatements = createSelector( (executions: RecentExecutions) => executions.statements, ); +export const selectExecutionStatus = () => { + const execStatuses: string[] = []; + for (const execStatus in ExecutionStatus) { + execStatuses.push(execStatus); + } + return execStatuses; +}; + export const selecteRecentStatement = createSelector( selectRecentStatements, selectExecutionID, diff --git a/pkg/ui/workspaces/cluster-ui/src/selectors/recentExecutionsCommon.selectors.ts b/pkg/ui/workspaces/cluster-ui/src/selectors/recentExecutionsCommon.selectors.ts index 631c9b5ecae6..72ff0e46e7b4 100644 --- a/pkg/ui/workspaces/cluster-ui/src/selectors/recentExecutionsCommon.selectors.ts +++ b/pkg/ui/workspaces/cluster-ui/src/selectors/recentExecutionsCommon.selectors.ts @@ -8,8 +8,11 @@ // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. -import { RouteComponentProps } from "react-router"; -import { RecentExecutions, SessionsResponse } from "src/recentExecutions/types"; +import { + ExecutionStatus, + RecentExecutions, + SessionsResponse, +} from "src/recentExecutions/types"; import { ClusterLocksResponse } from "src/api"; import { getRecentExecutionsFromSessions, @@ -32,12 +35,18 @@ export const selectRecentExecutionsCombiner = ( return { statements: execs.statements.map(s => ({ ...s, - status: waitTimeByTxnID[s.transactionID] != null ? "Waiting" : s.status, + status: + waitTimeByTxnID[s.transactionID] != null + ? ExecutionStatus.Waiting + : s.status, timeSpentWaiting: waitTimeByTxnID[s.transactionID], })), transactions: execs.transactions.map(t => ({ ...t, - status: waitTimeByTxnID[t.transactionID] != null ? "Waiting" : t.status, + status: + waitTimeByTxnID[t.transactionID] != null + ? ExecutionStatus.Waiting + : t.status, timeSpentWaiting: waitTimeByTxnID[t.transactionID], })), }; diff --git a/pkg/ui/workspaces/cluster-ui/src/statementsPage/recentStatementsPage.selectors.ts b/pkg/ui/workspaces/cluster-ui/src/statementsPage/recentStatementsPage.selectors.ts index fd7393cf7dfc..e3198d9642d4 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementsPage/recentStatementsPage.selectors.ts +++ b/pkg/ui/workspaces/cluster-ui/src/statementsPage/recentStatementsPage.selectors.ts @@ -20,6 +20,7 @@ import { import { selectRecentStatements, selectAppName, + selectExecutionStatus, } from "src/selectors/recentExecutions.selectors"; import { actions as localStorageActions } from "src/store/localStorage"; import { actions as sessionsActions } from "src/store/sessions"; @@ -54,6 +55,7 @@ export const mapStateToRecentStatementsPageProps = ( selectedColumns: selectColumns(state), sortSetting: selectSortSetting(state), filters: selectFilters(state), + executionStatus: selectExecutionStatus(), internalAppNamePrefix: selectAppName(state), isTenant: selectIsTenant(state), }); diff --git a/pkg/ui/workspaces/cluster-ui/src/statementsPage/recentStatementsView.tsx b/pkg/ui/workspaces/cluster-ui/src/statementsPage/recentStatementsView.tsx index 385716db859c..c52dcfbe78b2 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementsPage/recentStatementsView.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/statementsPage/recentStatementsView.tsx @@ -28,6 +28,7 @@ import { } from "../recentExecutions/recentStatementUtils"; import { calculateActiveFilters, + defaultFilters, getFullFiltersAsStringRecord, } from "../queryFilter/filter"; import { RecentStatementsSection } from "../recentExecutions/recentStatementsSection"; @@ -53,6 +54,7 @@ export type RecentStatementsViewStateProps = { sortSetting: SortSetting; sessionsError: Error | null; filters: RecentStatementFilters; + executionStatus: string[]; internalAppNamePrefix: string; isTenant?: boolean; }; @@ -70,6 +72,7 @@ export const RecentStatementsView: React.FC = ({ statements, sessionsError, filters, + executionStatus, internalAppNamePrefix, isTenant, }: RecentStatementsViewProps) => { @@ -154,7 +157,11 @@ export const RecentStatementsView: React.FC = ({ }; const clearSearch = () => onSubmitSearch(""); - const clearFilters = () => onSubmitFilters({ app: inactiveFiltersState.app }); + const clearFilters = () => + onSubmitFilters({ + app: defaultFilters.app, + executionStatus: defaultFilters.executionStatus, + }); const apps = getAppsFromRecentExecutions(statements, internalAppNamePrefix); const countActiveFilters = calculateActiveFilters(filters); @@ -180,6 +187,8 @@ export const RecentStatementsView: React.FC = ({ diff --git a/pkg/ui/workspaces/cluster-ui/src/store/insights/statementInsights/statementInsights.selectors.ts b/pkg/ui/workspaces/cluster-ui/src/store/insights/statementInsights/statementInsights.selectors.ts index dbaae343c2fc..b2bba4137b80 100644 --- a/pkg/ui/workspaces/cluster-ui/src/store/insights/statementInsights/statementInsights.selectors.ts +++ b/pkg/ui/workspaces/cluster-ui/src/store/insights/statementInsights/statementInsights.selectors.ts @@ -17,6 +17,7 @@ import { selectStatementInsightDetailsCombiner, } from "src/selectors/insightsCommon.selectors"; import { selectID } from "src/selectors/common"; +import { InsightEnumToLabel } from "src/insights"; export const selectExecutionInsights = createSelector( (state: AppState) => state.adminUI.executionInsights?.data, @@ -32,6 +33,14 @@ export const selectStatementInsightDetails = createSelector( selectStatementInsightDetailsCombiner, ); +export const selectInsightTypes = () => { + const insights: string[] = []; + InsightEnumToLabel.forEach(insight => { + insights.push(insight); + }); + return insights; +}; + export const selectColumns = createSelector( localStorageSelector, localStorage => diff --git a/pkg/ui/workspaces/cluster-ui/src/store/localStorage/localStorage.reducer.ts b/pkg/ui/workspaces/cluster-ui/src/store/localStorage/localStorage.reducer.ts index 194336e5a7c6..e91f13dbc49f 100644 --- a/pkg/ui/workspaces/cluster-ui/src/store/localStorage/localStorage.reducer.ts +++ b/pkg/ui/workspaces/cluster-ui/src/store/localStorage/localStorage.reducer.ts @@ -80,10 +80,12 @@ const defaultSortSettingSchemaInsights: SortSetting = { const defaultFiltersActiveExecutions = { app: "", + executionStatus: "", }; const defaultFiltersInsights = { app: "", + workloadInsightType: "", }; const defaultFiltersSchemaInsights = { diff --git a/pkg/ui/workspaces/cluster-ui/src/transactionsPage/recentTransactionsPage.selectors.tsx b/pkg/ui/workspaces/cluster-ui/src/transactionsPage/recentTransactionsPage.selectors.tsx index 7c70af49cc8d..ddd8cc718a83 100644 --- a/pkg/ui/workspaces/cluster-ui/src/transactionsPage/recentTransactionsPage.selectors.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/transactionsPage/recentTransactionsPage.selectors.tsx @@ -20,6 +20,7 @@ import { import { selectAppName, selectRecentTransactions, + selectExecutionStatus, } from "src/selectors/recentExecutions.selectors"; import { actions as localStorageActions } from "src/store/localStorage"; import { actions as sessionsActions } from "src/store/sessions"; @@ -54,6 +55,7 @@ export const mapStateToRecentTransactionsPageProps = ( selectedColumns: selectColumns(state), sortSetting: selectSortSetting(state), filters: selectFilters(state), + executionStatus: selectExecutionStatus(), internalAppNamePrefix: selectAppName(state), isTenant: selectIsTenant(state), }); diff --git a/pkg/ui/workspaces/cluster-ui/src/transactionsPage/recentTransactionsView.tsx b/pkg/ui/workspaces/cluster-ui/src/transactionsPage/recentTransactionsView.tsx index fad329cc748b..de0a9b4b526b 100644 --- a/pkg/ui/workspaces/cluster-ui/src/transactionsPage/recentTransactionsView.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/transactionsPage/recentTransactionsView.tsx @@ -52,6 +52,7 @@ export type RecentTransactionsViewStateProps = { transactions: RecentTransaction[]; sessionsError: Error | null; filters: RecentTransactionFilters; + executionStatus: string[]; sortSetting: SortSetting; internalAppNamePrefix: string; isTenant?: boolean; @@ -73,12 +74,14 @@ export const RecentTransactionsView: React.FC = ({ transactions, sessionsError, filters, + executionStatus, internalAppNamePrefix, }: RecentTransactionsViewProps) => { const [pagination, setPagination] = useState({ current: 1, pageSize: 20, }); + const history = useHistory(); const [search, setSearch] = useState( queryByName(history.location, RECENT_TXN_SEARCH_PARAM), @@ -155,7 +158,11 @@ export const RecentTransactionsView: React.FC = ({ }; const clearSearch = () => onSubmitSearch(""); - const clearFilters = () => onSubmitFilters({ app: inactiveFiltersState.app }); + const clearFilters = () => + onSubmitFilters({ + app: inactiveFiltersState.app, + executionStatus: inactiveFiltersState.executionStatus, + }); const apps = getAppsFromRecentExecutions(transactions, internalAppNamePrefix); const countActiveFilters = calculateActiveFilters(filters); @@ -181,6 +188,8 @@ export const RecentTransactionsView: React.FC = ({ diff --git a/pkg/ui/workspaces/db-console/src/selectors/recentExecutionsSelectors.ts b/pkg/ui/workspaces/db-console/src/selectors/recentExecutionsSelectors.ts index 713d3cd8c595..2b9ee48615f6 100644 --- a/pkg/ui/workspaces/db-console/src/selectors/recentExecutionsSelectors.ts +++ b/pkg/ui/workspaces/db-console/src/selectors/recentExecutionsSelectors.ts @@ -15,6 +15,7 @@ import { getRecentTransaction, getContentionDetailsFromLocksAndTxns, selectExecutionID, + ExecutionStatus, } from "@cockroachlabs/cluster-ui"; import { createSelector } from "reselect"; import { CachedDataReducerState } from "src/redux/apiReducers"; @@ -37,6 +38,14 @@ export const selectRecentStatements = createSelector( (executions: RecentExecutions) => executions.statements, ); +export const selectExecutionStatus = () => { + const execStatuses: string[] = []; + for (const execStatus in ExecutionStatus) { + execStatuses.push(execStatus); + } + return execStatuses; +}; + export const selectRecentStatement = createSelector( selectRecentStatements, selectExecutionID, diff --git a/pkg/ui/workspaces/db-console/src/views/insights/insightsSelectors.ts b/pkg/ui/workspaces/db-console/src/views/insights/insightsSelectors.ts index 976ae8c78687..8af84e880ff8 100644 --- a/pkg/ui/workspaces/db-console/src/views/insights/insightsSelectors.ts +++ b/pkg/ui/workspaces/db-console/src/views/insights/insightsSelectors.ts @@ -23,13 +23,15 @@ import { selectTxnInsightsCombiner, TxnContentionInsightDetails, selectTxnInsightDetailsCombiner, + InsightEnumToLabel, } from "@cockroachlabs/cluster-ui"; export const filtersLocalSetting = new LocalSetting< AdminUIState, WorkloadInsightEventFilters >("filters/InsightsPage", (state: AdminUIState) => state.localSettings, { - app: "", + app: defaultFilters.app, + workloadInsightType: defaultFilters.workloadInsightType, }); export const sortSettingLocalSetting = new LocalSetting< @@ -106,6 +108,14 @@ export const selectExecutionInsights = createSelector((state: AdminUIState) => { } else return null; }, selectFlattenedStmtInsightsCombiner); +export const selectInsightTypes = () => { + const insights: string[] = []; + InsightEnumToLabel.forEach(insight => { + insights.push(insight); + }); + return insights; +}; + export const selectStatementInsightDetails = createSelector( selectExecutionInsights, selectID, diff --git a/pkg/ui/workspaces/db-console/src/views/insights/workloadInsightsPage.tsx b/pkg/ui/workspaces/db-console/src/views/insights/workloadInsightsPage.tsx index 3a0e3b132026..cfa9750f06b1 100644 --- a/pkg/ui/workspaces/db-console/src/views/insights/workloadInsightsPage.tsx +++ b/pkg/ui/workspaces/db-console/src/views/insights/workloadInsightsPage.tsx @@ -32,6 +32,7 @@ import { selectTransactionInsights, selectExecutionInsightsLoading, selectTransactionInsightsLoading, + selectInsightTypes, } from "src/views/insights/insightsSelectors"; import { bindActionCreators } from "redux"; import { LocalSetting } from "src/redux/localsettings"; @@ -53,6 +54,7 @@ const transactionMapStateToProps = ( ): TransactionInsightsViewStateProps => ({ transactions: selectTransactionInsights(state), transactionsError: state.cachedData?.transactionInsights?.lastError, + insightTypes: selectInsightTypes(), filters: filtersLocalSetting.selector(state), sortSetting: sortSettingLocalSetting.selector(state), timeScale: selectTimeScale(state), @@ -66,6 +68,7 @@ const statementMapStateToProps = ( statements: selectExecutionInsights(state), statementsError: state.cachedData?.executionInsights?.lastError, filters: filtersLocalSetting.selector(state), + insightTypes: selectInsightTypes(), sortSetting: sortSettingLocalSetting.selector(state), selectedColumnNames: insightStatementColumnsLocalSetting.selectorToArray(state), diff --git a/pkg/ui/workspaces/db-console/src/views/statements/recentStatementsSelectors.tsx b/pkg/ui/workspaces/db-console/src/views/statements/recentStatementsSelectors.tsx index dc19a9cc51a0..468e2cf53c27 100644 --- a/pkg/ui/workspaces/db-console/src/views/statements/recentStatementsSelectors.tsx +++ b/pkg/ui/workspaces/db-console/src/views/statements/recentStatementsSelectors.tsx @@ -13,7 +13,11 @@ import { defaultFilters, SortSetting, } from "@cockroachlabs/cluster-ui"; -import { selectRecentStatements, selectAppName } from "src/selectors"; +import { + selectRecentStatements, + selectAppName, + selectExecutionStatus, +} from "src/selectors"; import { refreshLiveWorkload } from "src/redux/apiReducers"; import { LocalSetting } from "src/redux/localsettings"; import { AdminUIState } from "src/redux/state"; @@ -27,7 +31,10 @@ const selectedColumnsLocalSetting = new LocalSetting< null, ); -const defaultActiveFilters = { app: defaultFilters.app }; +const defaultActiveFilters = { + app: defaultFilters.app, + executionStatus: defaultFilters.executionStatus, +}; const filtersLocalSetting = new LocalSetting< AdminUIState, @@ -49,6 +56,7 @@ export const mapStateToRecentStatementViewProps = (state: AdminUIState) => ({ selectedColumns: selectedColumnsLocalSetting.selectorToArray(state), sortSetting: sortSettingLocalSetting.selector(state), statements: selectRecentStatements(state), + executionStatus: selectExecutionStatus(), sessionsError: state.cachedData?.sessions.lastError, internalAppNamePrefix: selectAppName(state), }); diff --git a/pkg/ui/workspaces/db-console/src/views/transactions/recentTransactionsSelectors.tsx b/pkg/ui/workspaces/db-console/src/views/transactions/recentTransactionsSelectors.tsx index 65706c651d54..5fa7ea3a5c50 100644 --- a/pkg/ui/workspaces/db-console/src/views/transactions/recentTransactionsSelectors.tsx +++ b/pkg/ui/workspaces/db-console/src/views/transactions/recentTransactionsSelectors.tsx @@ -14,7 +14,11 @@ import { defaultFilters, SortSetting, } from "@cockroachlabs/cluster-ui"; -import { selectAppName, selectRecentTransactions } from "src/selectors"; +import { + selectAppName, + selectRecentTransactions, + selectExecutionStatus, +} from "src/selectors"; import { refreshLiveWorkload } from "src/redux/apiReducers"; import { LocalSetting } from "src/redux/localsettings"; import { AdminUIState } from "src/redux/state"; @@ -28,7 +32,10 @@ const transactionsColumnsLocalSetting = new LocalSetting< null, ); -const defaultActiveTxnFilters = { app: defaultFilters.app }; +const defaultActiveTxnFilters = { + app: defaultFilters.app, + executionStatus: defaultFilters.executionStatus, +}; const filtersLocalSetting = new LocalSetting< AdminUIState, @@ -50,6 +57,7 @@ export const mapStateToRecentTransactionsPageProps = (state: AdminUIState) => ({ transactions: selectRecentTransactions(state), sessionsError: state.cachedData?.sessions.lastError, filters: filtersLocalSetting.selector(state), + executionStatus: selectExecutionStatus(), sortSetting: sortSettingLocalSetting.selector(state), internalAppNamePrefix: selectAppName(state), });