diff --git a/pkg/ui/workspaces/cluster-ui/src/insights/types.ts b/pkg/ui/workspaces/cluster-ui/src/insights/types.ts index fc860e48b28d..72ff8786d7c0 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,18 @@ 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.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..bdf21c33d3bf 100644 --- a/pkg/ui/workspaces/cluster-ui/src/insights/utils.ts +++ b/pkg/ui/workspaces/cluster-ui/src/insights/utils.ts @@ -63,6 +63,19 @@ export const filterTransactionInsights = ( } else { filteredTransactions = filteredTransactions.filter(txn => !isInternal(txn)); } + if (filters.workloadInsightType && filters.workloadInsightType.length > 0) { + const workloadInsightTypes = filters.workloadInsightType + .toString() + .split(","); + + filteredTransactions = filteredTransactions.filter(transaction => + workloadInsightTypes.some(workloadType => + transaction.insights.some( + txnInsight => workloadType === txnInsight.label, + ), + ), + ); + } if (search) { search = search.toLowerCase(); @@ -205,6 +218,19 @@ export const filterStatementInsights = ( stmt => !isInternal(stmt.application), ); } + if (filters.workloadInsightType && filters.workloadInsightType.length > 0) { + const workloadInsightTypes = filters.workloadInsightType + .toString() + .split(","); + + filteredStatements = filteredStatements.filter(statement => + workloadInsightTypes.some(workloadType => + statement.insights.some( + stmtInsight => 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..d5d6938cbfc8 100644 --- a/pkg/ui/workspaces/cluster-ui/src/recentExecutions/recentStatementsSection.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/recentExecutions/recentStatementsSection.tsx @@ -101,7 +101,10 @@ 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..8c17547df108 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,10 @@ 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), });