diff --git a/pkg/ui/workspaces/cluster-ui/src/columnsSelector/columnsSelector.tsx b/pkg/ui/workspaces/cluster-ui/src/columnsSelector/columnsSelector.tsx index c0a2b4be13e4..fb9c7885b3c6 100644 --- a/pkg/ui/workspaces/cluster-ui/src/columnsSelector/columnsSelector.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/columnsSelector/columnsSelector.tsx @@ -84,7 +84,7 @@ const customStyles = { option: (provided: any, state: any) => ({ ...provided, backgroundColor: "white", - color: "#394455", + color: "#475872", cursor: "pointer", padding: "4px 10px", }), diff --git a/pkg/ui/workspaces/cluster-ui/src/sortedtable/sortedtable.tsx b/pkg/ui/workspaces/cluster-ui/src/sortedtable/sortedtable.tsx index b12d2507b979..9ec4330a4947 100644 --- a/pkg/ui/workspaces/cluster-ui/src/sortedtable/sortedtable.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/sortedtable/sortedtable.tsx @@ -285,7 +285,7 @@ export class SortedTable extends React.Component< return this.props.expandableConfig.expansionKey(this.getItemAt(rowIndex)); } - onChangeExpansion = (rowIndex: number, expanded: boolean) => { + onChangeExpansion = (rowIndex: number, expanded: boolean): void => { const key = this.getKeyAt(rowIndex); const expandedRows = this.state.expandedRows; if (expanded) { @@ -308,7 +308,7 @@ export class SortedTable extends React.Component< return this.props.expandableConfig.expandedContent(item); }; - paginatedData = (sortData?: T[]) => { + paginatedData = (sortData?: T[]): T[] => { const { pagination, data } = this.props; if (!pagination) { return sortData || data; diff --git a/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.tsx b/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.tsx index 97a744a22f2a..81bbadd86834 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.tsx @@ -60,7 +60,7 @@ type IStatementDiagnosticsReport = cockroach.server.serverpb.IStatementDiagnosti import sortableTableStyles from "src/sortedtable/sortedtable.module.scss"; import ColumnsSelector from "../columnsSelector/columnsSelector"; import { SelectOption } from "../multiSelectCheckbox/multiSelectCheckbox"; -import { UIConfigState } from "../store/uiConfig"; +import { UIConfigState } from "../store"; import { StatementsRequest } from "src/api/statementsApi"; import Long from "long"; @@ -195,7 +195,7 @@ export class StatementsPage extends React.Component< history.replace(history.location); }; - changeSortSetting = (ss: SortSetting) => { + changeSortSetting = (ss: SortSetting): void => { this.setState({ sortSetting: ss, }); @@ -223,7 +223,7 @@ export class StatementsPage extends React.Component< ); }; - selectApp = (value: string) => { + selectApp = (value: string): void => { if (value == "All") value = ""; const { history, onFilterChange } = this.props; history.location.pathname = `/statements/${encodeURIComponent(value)}`; @@ -234,7 +234,7 @@ export class StatementsPage extends React.Component< } }; - resetPagination = () => { + resetPagination = (): void => { this.setState(prevState => { return { pagination: { @@ -245,12 +245,12 @@ export class StatementsPage extends React.Component< }); }; - refreshStatements = () => { + refreshStatements = (): void => { const req = statementsRequestFromProps(this.props); this.props.refreshStatements(req); }; - componentDidMount() { + componentDidMount(): void { this.refreshStatements(); if (!this.props.isTenant) { this.props.refreshStatementDiagnosticsRequests(); @@ -260,7 +260,7 @@ export class StatementsPage extends React.Component< componentDidUpdate = ( __: StatementsPageProps, prevState: StatementsPageState, - ) => { + ): void => { if (this.state.search && this.state.search !== prevState.search) { this.props.onSearchComplete(this.filteredStatementsData()); } @@ -270,17 +270,17 @@ export class StatementsPage extends React.Component< } }; - componentWillUnmount() { + componentWillUnmount(): void { this.props.dismissAlertMessage(); } - onChangePage = (current: number) => { + onChangePage = (current: number): void => { const { pagination } = this.state; this.setState({ pagination: { ...pagination, current } }); this.props.onPageChanged(current); }; - onSubmitSearchField = (search: string) => { + onSubmitSearchField = (search: string): void => { this.setState({ search }); this.resetPagination(); this.syncHistory({ @@ -288,7 +288,7 @@ export class StatementsPage extends React.Component< }); }; - onSubmitFilters = (filters: Filters) => { + onSubmitFilters = (filters: Filters): void => { this.setState({ filters: { ...this.state.filters, @@ -310,14 +310,14 @@ export class StatementsPage extends React.Component< this.selectApp(filters.app); }; - onClearSearchField = () => { + onClearSearchField = (): void => { this.setState({ search: "" }); this.syncHistory({ q: undefined, }); }; - onClearFilters = () => { + onClearFilters = (): void => { this.setState({ filters: { ...defaultFilters, @@ -337,7 +337,7 @@ export class StatementsPage extends React.Component< this.selectApp(""); }; - filteredStatementsData = () => { + filteredStatementsData = (): AggregateStatistics[] => { const { search, filters } = this.state; const { statements, nodeRegions, isTenant } = this.props; const timeValue = getTimeValueInSeconds(filters); @@ -452,7 +452,9 @@ export class StatementsPage extends React.Component< : unique(nodes.map(node => nodeRegions[node.toString()])).sort(); populateRegionNodeForStatements(statements, nodeRegions, isTenant); - // Creates a list of all possible columns + // Creates a list of all possible columns, + // hiding nodeRegions if is not multi-region and + // hiding columns that won't be displayed for tenants. const columns = makeStatementsColumns( statements, selectedApp, @@ -464,13 +466,9 @@ export class StatementsPage extends React.Component< this.activateDiagnosticsRef, onDiagnosticsReportDownload, onStatementClick, - ).filter(c => !(isTenant && c.hideIfTenant)); - - // If it's multi-region, we want to show the Regions/Nodes column by default - // and hide otherwise. - if (regions.length > 1) { - columns.filter(c => c.name === "regionNodes")[0].showByDefault = true; - } + ) + .filter(c => !(c.name === "regionNodes" && regions.length < 2)) + .filter(c => !(isTenant && c.hideIfTenant)); const isColumnSelected = (c: ColumnDescriptor) => { return ( diff --git a/pkg/ui/workspaces/cluster-ui/src/statementsTable/statementsTable.tsx b/pkg/ui/workspaces/cluster-ui/src/statementsTable/statementsTable.tsx index 449436d804ac..aa31450813f2 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementsTable/statementsTable.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/statementsTable/statementsTable.tsx @@ -86,7 +86,7 @@ function makeCommonColumns( ); const retryBar = retryBarChart(statements, defaultBarChartOptions); - const columns: ColumnDescriptor[] = [ + return [ { name: "executionCount", title: statisticsTableTitles.executionCount(statType), @@ -173,11 +173,9 @@ function makeCommonColumns( return longListWithTooltip(stmt.regionNodes.sort().join(", "), 50); }, sort: (stmt: AggregateStatistics) => stmt.regionNodes.sort().join(", "), - showByDefault: false, hideIfTenant: true, }, ]; - return columns; } export interface AggregateStatistics { @@ -309,12 +307,14 @@ export function makeNodesColumns( * node it was executed on. * @param nodeRegions: object with keys being the node id and the value * which region it belongs to. + * @param isTenant: boolean indicating if the cluster is tenant, since + * node information doesn't need to be populated on this case. */ export function populateRegionNodeForStatements( statements: AggregateStatistics[], nodeRegions: { [p: string]: string }, isTenant: boolean, -) { +): void { statements.forEach(stmt => { if (isTenant) { stmt.regionNodes = []; diff --git a/pkg/ui/workspaces/cluster-ui/src/statsTableUtil/statsTableUtil.tsx b/pkg/ui/workspaces/cluster-ui/src/statsTableUtil/statsTableUtil.tsx index f31d8553f1c1..ffdc1800514d 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statsTableUtil/statsTableUtil.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/statsTableUtil/statsTableUtil.tsx @@ -27,19 +27,21 @@ export type NodeNames = { [nodeId: string]: string }; // Single place for column names. Used in table columns and in columns selector. export const statisticsColumnLabels = { - statements: "Statements", - database: "Database", - executionCount: "Execution Count", - rowsRead: "Rows Read", bytesRead: "Bytes Read", - time: "Time", contention: "Contention", + database: "Database", + diagnostics: "Diagnostics", + executionCount: "Execution Count", maxMemUsage: "Max Memory", networkBytes: "Network", + regionNodes: "Regions/Nodes", retries: "Retries", + rowsRead: "Rows Read", + statements: "Statements", + statementsCount: "Statements", + time: "Time", + transactions: "Transactions", workloadPct: "% of All Runtime", - regionNodes: "Regions/Nodes", - diagnostics: "Diagnostics", }; export const contentModifiers = { @@ -114,6 +116,25 @@ export const statisticsTableTitles: StatisticTableTitleType = { ); }, + transactions: (statType: StatisticType) => { + return ( + +

+ {`A transaction fingerprint represents one or more SQL transactions by replacing the literal values (e.g., numbers and strings) with + underscores (_). To view additional details of a SQL transaction fingerprint, click the fingerprint to + open the Transaction Details page.`} +

+ + } + > + {getLabel("transactions")} +
+ ); + }, executionCount: (statType: StatisticType) => { let contentModifier = ""; let fingerprintModifier = ""; @@ -564,4 +585,21 @@ export const statisticsTableTitles: StatisticTableTitleType = { ); }, + statementsCount: (statType: StatisticType) => { + return ( + +

+ {`The number of statements being executed on this transaction fingerprint`} +

+ + } + > + {getLabel("statementsCount")} +
+ ); + }, }; 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 cf93ae57ba07..27cde4cb3f9d 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 @@ -20,6 +20,7 @@ type StatementsDateRangeState = { export type LocalStorageState = { "adminUi/showDiagnosticsModal": boolean; "showColumns/StatementsPage": string; + "showColumns/TransactionPage": string; "dateRange/StatementsPage": StatementsDateRangeState; }; @@ -43,6 +44,8 @@ const initialState: LocalStorageState = { false, "showColumns/StatementsPage": JSON.parse(localStorage.getItem("showColumns/StatementsPage")) || null, + "showColumns/TransactionPage": + JSON.parse(localStorage.getItem("showColumns/TransactionPage")) || null, "dateRange/StatementsPage": JSON.parse(localStorage.getItem("dateRange/StatementsPage")) || defaultDateRange, diff --git a/pkg/ui/workspaces/cluster-ui/src/transactionsPage/transactionsPage.selectors.ts b/pkg/ui/workspaces/cluster-ui/src/transactionsPage/transactionsPage.selectors.ts index c58f4d1c478c..f435a375d01b 100644 --- a/pkg/ui/workspaces/cluster-ui/src/transactionsPage/transactionsPage.selectors.ts +++ b/pkg/ui/workspaces/cluster-ui/src/transactionsPage/transactionsPage.selectors.ts @@ -10,7 +10,10 @@ import { createSelector } from "reselect"; -import { adminUISelector } from "../statementsPage/statementsPage.selectors"; +import { + adminUISelector, + localStorageSelector, +} from "../statementsPage/statementsPage.selectors"; export const selectTransactionsSlice = createSelector( adminUISelector, @@ -26,3 +29,12 @@ export const selectTransactionsLastError = createSelector( selectTransactionsSlice, state => state.lastError, ); + +export const selectTxnColumns = createSelector( + localStorageSelector, + // return array of columns if user have customized it or `null` otherwise + localStorage => + localStorage["showColumns/TransactionPage"] + ? localStorage["showColumns/TransactionPage"].split(",") + : null, +); diff --git a/pkg/ui/workspaces/cluster-ui/src/transactionsPage/transactionsPage.tsx b/pkg/ui/workspaces/cluster-ui/src/transactionsPage/transactionsPage.tsx index 6b8d81e74170..0c88922b157a 100644 --- a/pkg/ui/workspaces/cluster-ui/src/transactionsPage/transactionsPage.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/transactionsPage/transactionsPage.tsx @@ -14,10 +14,18 @@ import classNames from "classnames/bind"; import styles from "../statementsPage/statementsPage.module.scss"; import moment, { Moment } from "moment"; import { RouteComponentProps } from "react-router-dom"; -import { TransactionInfo, TransactionsTable } from "../transactionsTable"; +import { + makeTransactionsColumns, + TransactionInfo, + TransactionsTable, +} from "../transactionsTable"; import { DateRange } from "src/dateRange"; import { TransactionDetails } from "../transactionDetails"; -import { ISortedTablePagination, SortSetting } from "../sortedtable"; +import { + ColumnDescriptor, + ISortedTablePagination, + SortSetting, +} from "../sortedtable"; import { Pagination } from "../pagination"; import { TableStatistics } from "../tableStatistics"; import { @@ -48,8 +56,14 @@ import { defaultFilters, getFiltersFromQueryString, } from "../queryFilter"; -import { UIConfigState } from "../store/uiConfig"; +import { UIConfigState } from "../store"; import { StatementsRequest } from "src/api/statementsApi"; +import ColumnsSelector from "../columnsSelector/columnsSelector"; +import { SelectOption } from "../multiSelectCheckbox/multiSelectCheckbox"; +import { + getLabel, + StatisticTableColumnKeys, +} from "../statsTableUtil/statsTableUtil"; type IStatementsResponse = protos.cockroach.server.serverpb.IStatementsResponse; type TransactionStats = protos.cockroach.sql.ITransactionStatistics; @@ -72,12 +86,14 @@ export interface TransactionsPageStateProps { error?: Error | null; pageSize?: number; isTenant?: UIConfigState["isTenant"]; + columns: string[]; } export interface TransactionsPageDispatchProps { refreshData: (req?: StatementsRequest) => void; resetSQLStats: () => void; onDateRangeChange?: (start: Moment, end: Moment) => void; + onColumnsChange?: (selectedColumns: string[]) => void; } export type TransactionsPageProps = TransactionsPageStateProps & @@ -123,15 +139,15 @@ export class TransactionsPage extends React.Component< transactionStats: null, }; - refreshData = () => { + refreshData = (): void => { const req = statementsRequestFromProps(this.props); this.props.refreshData(req); }; - componentDidMount() { + componentDidMount(): void { this.refreshData(); } - componentDidUpdate() { + componentDidUpdate(): void { this.refreshData(); } @@ -151,7 +167,7 @@ export class TransactionsPage extends React.Component< history.replace(history.location); }; - onChangeSortSetting = (ss: SortSetting) => { + onChangeSortSetting = (ss: SortSetting): void => { this.setState({ sortSetting: ss, }); @@ -161,12 +177,12 @@ export class TransactionsPage extends React.Component< }); }; - onChangePage = (current: number) => { + onChangePage = (current: number): void => { const { pagination } = this.state; this.setState({ pagination: { ...pagination, current } }); }; - resetPagination = () => { + resetPagination = (): void => { this.setState((prevState: TState) => { return { pagination: { @@ -177,14 +193,14 @@ export class TransactionsPage extends React.Component< }); }; - onClearSearchField = () => { + onClearSearchField = (): void => { this.setState({ search: "" }); this.syncHistory({ q: undefined, }); }; - onSubmitSearchField = (search: string) => { + onSubmitSearchField = (search: string): void => { this.setState({ search }); this.resetPagination(); this.syncHistory({ @@ -192,7 +208,7 @@ export class TransactionsPage extends React.Component< }); }; - onSubmitFilters = (filters: Filters) => { + onSubmitFilters = (filters: Filters): void => { this.setState({ filters: { ...this.state.filters, @@ -209,7 +225,7 @@ export class TransactionsPage extends React.Component< }); }; - onClearFilters = () => { + onClearFilters = (): void => { this.setState({ filters: { ...defaultFilters, @@ -228,11 +244,11 @@ export class TransactionsPage extends React.Component< handleDetails = ( statementFingerprintIds: Long[] | null, transactionStats: TransactionStats, - ) => { + ): void => { this.setState({ statementFingerprintIds, transactionStats }); }; - lastReset = () => { + lastReset = (): Date => { return new Date(Number(this.props.data?.last_reset.seconds) * 1000); }; @@ -258,7 +274,14 @@ export class TransactionsPage extends React.Component< loading={!this.props?.data} error={this.props?.error} render={() => { - const { data, resetSQLStats, nodeRegions, isTenant } = this.props; + const { + data, + resetSQLStats, + nodeRegions, + isTenant, + onColumnsChange, + columns: userSelectedColumnsToShow, + } = this.props; const { pagination, search, filters } = this.state; const { statements, internal_app_name_prefix } = data; const appNames = getTrxAppFilterOptions( @@ -304,6 +327,50 @@ export class TransactionsPage extends React.Component< const { current, pageSize } = pagination; const hasData = data.transactions?.length > 0; const isUsedFilter = search?.length > 0; + + // Creates a list of all possible columns, + // hiding nodeRegions if is not multi-region and + // hiding columns that won't be displayed for tenants. + const columns = makeTransactionsColumns( + transactionsToDisplay, + statements, + isTenant, + this.handleDetails, + search, + ) + .filter(c => !(c.name === "regionNodes" && regions.length < 2)) + .filter(c => !(isTenant && c.hideIfTenant)); + + const isColumnSelected = (c: ColumnDescriptor) => { + return ( + ((userSelectedColumnsToShow === null || + userSelectedColumnsToShow === undefined) && + c.showByDefault !== false) || // show column if list of visible was never defined and can be show by default. + (userSelectedColumnsToShow !== null && + userSelectedColumnsToShow.includes(c.name)) || // show column if user changed its visibility. + c.alwaysShow === true // show column if alwaysShow option is set explicitly. + ); + }; + + // Iterate over all available columns and create list of SelectOptions with initial selection + // values based on stored user selections in local storage and default column configs. + // Columns that are set to alwaysShow are filtered from the list. + const tableColumns = columns + .filter(c => !c.alwaysShow) + .map( + (c): SelectOption => ({ + label: getLabel( + c.name as StatisticTableColumnKeys, + "transaction", + ), + value: c.name, + isSelected: isColumnSelected(c), + }), + ); + + // List of all columns that will be displayed based on the column selection. + const displayColumns = columns.filter(c => isColumnSelected(c)); + return ( <> @@ -348,6 +415,10 @@ export class TransactionsPage extends React.Component< )}
+ ({ refreshData: (req?: StatementsRequest) => @@ -56,6 +59,18 @@ export const TransactionsPageConnected = withRouter( }), ); }, + // We use `null` when the value was never set and it will show all columns. + // If the user modifies the selection and no columns are selected, + // the function will save the value as a blank space, otherwise + // it gets saved as `null`. + onColumnsChange: (selectedColumns: string[]) => + dispatch( + localStorageActions.update({ + key: "showColumns/TransactionPage", + value: + selectedColumns.length === 0 ? " " : selectedColumns.join(","), + }), + ), }), )(TransactionsPage), ); diff --git a/pkg/ui/workspaces/cluster-ui/src/transactionsPage/utils.ts b/pkg/ui/workspaces/cluster-ui/src/transactionsPage/utils.ts index 54a2128ac7b7..3f8a6fe1f3a1 100644 --- a/pkg/ui/workspaces/cluster-ui/src/transactionsPage/utils.ts +++ b/pkg/ui/workspaces/cluster-ui/src/transactionsPage/utils.ts @@ -21,10 +21,8 @@ import _ from "lodash"; import { addExecStats, aggregateNumericStats, - containAny, FixLong, longToInt, - unique, } from "../util"; type Statement = protos.cockroach.server.serverpb.StatementsResponse.ICollectedStatementStatistics; diff --git a/pkg/ui/workspaces/cluster-ui/src/transactionsTable/transactionsTable.tsx b/pkg/ui/workspaces/cluster-ui/src/transactionsTable/transactionsTable.tsx index 3587979d5b37..221127ad6446 100644 --- a/pkg/ui/workspaces/cluster-ui/src/transactionsTable/transactionsTable.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/transactionsTable/transactionsTable.tsx @@ -14,6 +14,7 @@ import { SortedTable, ISortedTablePagination, longListWithTooltip, + ColumnDescriptor, } from "../sortedtable"; import { transactionsCountBarChart, @@ -47,16 +48,9 @@ interface TransactionsTable { transactions: TransactionInfo[]; sortSetting: SortSetting; onChangeSortSetting: (ss: SortSetting) => void; - handleDetails: ( - statementFingerprintIds: Long[] | null, - transactionStats: TransactionStats, - ) => void; pagination: ISortedTablePagination; - statements: Statement[]; - nodeRegions: { [key: string]: string }; - isTenant: boolean; - search?: string; renderNoResult?: React.ReactNode; + columns: ColumnDescriptor[]; } export interface TransactionInfo extends Transaction { @@ -67,7 +61,16 @@ const { latencyClasses } = tableClasses; const cx = classNames.bind(statsTablePageStyles); -export const TransactionsTable: React.FC = props => { +export function makeTransactionsColumns( + transactions: TransactionInfo[], + statements: Statement[], + isTenant: boolean, + handleDetails: ( + statementFingerprintIds: Long[] | null, + transactionStats: TransactionStats, + ) => void, + search?: string, +): ColumnDescriptor[] { const defaultBarChartOptions = { classes: { root: cx("statements-table__col--bar-chart"), @@ -81,7 +84,6 @@ export const TransactionsTable: React.FC = props => { }, }; - const { transactions, handleDetails, statements, search, isTenant } = props; const countBar = transactionsCountBarChart(transactions); const rowsReadBar = transactionsRowsReadBarChart( transactions, @@ -108,10 +110,12 @@ export const TransactionsTable: React.FC = props => { sampledExecStatsBarChartOptions, ); const retryBar = transactionsRetryBarChart(transactions); - const columns = [ + + const statType = "transaction"; + return [ { name: "transactions", - title: <>Transactions, + title: statisticsTableTitles.transactions(statType), cell: (item: TransactionInfo) => textCell({ transactionText: statementFingerprintIdsToText( @@ -130,56 +134,57 @@ export const TransactionsTable: React.FC = props => { statements, ), ), + alwaysShow: true, }, { - name: "execution count", - title: statisticsTableTitles.executionCount("transaction"), + name: "executionCount", + title: statisticsTableTitles.executionCount(statType), cell: countBar, sort: (item: TransactionInfo) => FixLong(Number(item.stats_data.stats.count)), }, { - name: "rows read", - title: statisticsTableTitles.rowsRead("transaction"), + name: "rowsRead", + title: statisticsTableTitles.rowsRead(statType), cell: rowsReadBar, className: cx("statements-table__col-rows-read"), sort: (item: TransactionInfo) => FixLong(Number(item.stats_data.stats.rows_read.mean)), }, { - name: "bytes read", - title: statisticsTableTitles.bytesRead("transaction"), + name: "bytesRead", + title: statisticsTableTitles.bytesRead(statType), cell: bytesReadBar, className: cx("statements-table__col-bytes-read"), sort: (item: TransactionInfo) => FixLong(Number(item.stats_data.stats.bytes_read.mean)), }, { - name: "latency", - title: statisticsTableTitles.time("transaction"), + name: "time", + title: statisticsTableTitles.time(statType), cell: latencyBar, className: latencyClasses.column, sort: (item: TransactionInfo) => item.stats_data.stats.service_lat.mean, }, { name: "contention", - title: statisticsTableTitles.contention("transaction"), + title: statisticsTableTitles.contention(statType), cell: contentionBar, className: cx("statements-table__col-contention"), sort: (item: TransactionInfo) => FixLong(Number(item.stats_data.stats.exec_stats.contention_time?.mean)), }, { - name: "max memory", - title: statisticsTableTitles.maxMemUsage("transaction"), + name: "maxMemUsage", + title: statisticsTableTitles.maxMemUsage(statType), cell: maxMemUsageBar, className: cx("statements-table__col-max-mem-usage"), sort: (item: TransactionInfo) => FixLong(Number(item.stats_data.stats.exec_stats.max_mem_usage?.mean)), }, { - name: "network", - title: statisticsTableTitles.networkBytes("transaction"), + name: "networkBytes", + title: statisticsTableTitles.networkBytes(statType), cell: networkBytesBar, className: cx("statements-table__col-network-bytes"), sort: (item: TransactionInfo) => @@ -187,14 +192,14 @@ export const TransactionsTable: React.FC = props => { }, { name: "retries", - title: statisticsTableTitles.retries("transaction"), + title: statisticsTableTitles.retries(statType), cell: retryBar, sort: (item: TransactionInfo) => longToInt(Number(item.stats_data.stats.max_retries)), }, { name: "regionNodes", - title: statisticsTableTitles.regionNodes("transaction"), + title: statisticsTableTitles.regionNodes(statType), className: cx("statements-table__col-regions"), cell: (item: TransactionInfo) => { return longListWithTooltip(item.regionNodes.sort().join(", "), 50); @@ -203,14 +208,18 @@ export const TransactionsTable: React.FC = props => { hideIfTenant: true, }, { - name: "statements", - title: <>Statements, + name: "statementsCount", + title: statisticsTableTitles.statementsCount(statType), cell: (item: TransactionInfo) => item.stats_data.statement_fingerprint_ids.length, sort: (item: TransactionInfo) => item.stats_data.statement_fingerprint_ids.length, }, ].filter(c => !(isTenant && c.hideIfTenant)); +} + +export const TransactionsTable: React.FC = props => { + const { transactions, columns } = props; return ( state.localSettings, + null, +); + const TransactionsPageConnected = withRouter( connect( (state: AdminUIState) => ({ @@ -69,11 +76,20 @@ const TransactionsPageConnected = withRouter( lastReset: selectLastReset(state), error: selectLastError(state), nodeRegions: nodeRegionsByIDSelector(state), + columns: transactionColumnsLocalSetting.selectorToArray(state), }), { refreshData: refreshStatements, resetSQLStats: resetSQLStatsAction, onDateRangeChange: setCombinedStatementsDateRangeAction, + // We use `null` when the value was never set and it will show all columns. + // If the user modifies the selection and no columns are selected, + // the function will save the value as a blank space, otherwise + // it gets saved as `null`. + onColumnsChange: (value: string[]) => + transactionColumnsLocalSetting.set( + value.length === 0 ? " " : value.join(","), + ), }, )(TransactionsPage), );