diff --git a/pkg/ui/workspaces/cluster-ui/src/statementDetails/statementDetails.tsx b/pkg/ui/workspaces/cluster-ui/src/statementDetails/statementDetails.tsx index d231ed4ecd80..35cb7139c02c 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementDetails/statementDetails.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/statementDetails/statementDetails.tsx @@ -635,20 +635,18 @@ export class StatementDetails extends React.Component< {!isTenant && ( - <> - ( - nodes.map(n => ), - ", ", - )} - /> - (regions, ", ")} - /> - + ( + nodes.map(n => ), + ", ", + )} + /> )} + (regions, ", ")} + /> { isLoading: isLoading, statementsError: lastError, timeScale: selectTimeScale(state), + // TODO(todd): Remove this unused property! nodeNames: selectIsTenant(state) ? {} : nodeDisplayNameByIDSelector(state), - nodeRegions: selectIsTenant(state) ? {} : nodeRegionsByIDSelector(state), + nodeRegions: nodeRegionsByIDSelector(state), diagnosticsReports: selectIsTenant(state) || selectHasViewActivityRedactedRole(state) ? [] diff --git a/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.tsx b/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.tsx index 106dfaab1f48..33cce77060ad 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.tsx @@ -525,10 +525,7 @@ export class StatementsPage extends React.Component< .filter( // The statement must contain at least one value from the selected regions // list if the list is not empty. - // If the cluster is a tenant cluster we don't care - // about regions. statement => - isTenant || regions.length == 0 || (statement.stats.nodes && containAny( @@ -578,7 +575,7 @@ export class StatementsPage extends React.Component< const isEmptySearchResults = statements?.length > 0 && search?.length > 0; // If the cluster is a tenant cluster we don't show info // about nodes/regions. - populateRegionNodeForStatements(statements, nodeRegions, isTenant); + populateRegionNodeForStatements(statements, nodeRegions); // Creates a list of all possible columns, // hiding nodeRegions if is not multi-region and @@ -595,6 +592,7 @@ export class StatementsPage extends React.Component< onSelectDiagnosticsReportDropdownOption, onStatementClick, ) + .filter(c => !(c.name === "regions" && regions.length < 2)) .filter(c => !(c.name === "regionNodes" && regions.length < 2)) .filter(c => !(isTenant && c.hideIfTenant)); @@ -673,14 +671,14 @@ export class StatementsPage extends React.Component< nodeRegions, } = this.props; - const nodes = isTenant - ? [] - : Object.keys(nodeRegions) - .map(n => Number(n)) - .sort(); - const regions = isTenant - ? [] - : unique(nodes.map(node => nodeRegions[node.toString()])).sort(); + const nodes = Object.keys(nodeRegions) + .map(n => Number(n)) + .sort(); + + const regions = unique( + nodes.map(node => nodeRegions[node.toString()]), + ).sort(); + const { filters, activeFilters } = this.state; const longLoadingMessage = isNil(this.props.statements) && @@ -716,7 +714,7 @@ export class StatementsPage extends React.Component< showSqlType={true} showScan={true} showRegions={regions.length > 1} - showNodes={nodes.length > 1} + showNodes={!isTenant && nodes.length > 1} /> diff --git a/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPageConnected.tsx b/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPageConnected.tsx index 45c5a377f70b..23fc03ecfc96 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPageConnected.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPageConnected.tsx @@ -88,9 +88,7 @@ export const ConnectedStatementsPage = withRouter( isTenant: selectIsTenant(state), hasViewActivityRedactedRole: selectHasViewActivityRedactedRole(state), lastReset: selectLastReset(state), - nodeRegions: selectIsTenant(state) - ? {} - : nodeRegionsByIDSelector(state), + nodeRegions: nodeRegionsByIDSelector(state), search: selectSearch(state), sortSetting: selectSortSetting(state), statements: selectStatements(state, props), diff --git a/pkg/ui/workspaces/cluster-ui/src/statementsTable/statementsTable.tsx b/pkg/ui/workspaces/cluster-ui/src/statementsTable/statementsTable.tsx index df1d33fb07f3..c8f440c8b41d 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementsTable/statementsTable.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/statementsTable/statementsTable.tsx @@ -72,6 +72,7 @@ export interface AggregateStatistics { diagnosticsReports?: StatementDiagnosticsReport[]; // totalWorkload is the sum of service latency of all statements listed on the table. totalWorkload?: Long; + regions?: string[]; regionNodes?: string[]; } @@ -250,16 +251,7 @@ export function makeStatementsColumns( (stmt.stats.service_lat.mean * longToInt(stmt.stats.count)) / totalWorkload, }, - { - name: "regionNodes", - title: statisticsTableTitles.regionNodes(statType), - className: cx("statements-table__col-regions"), - cell: (stmt: AggregateStatistics) => { - return longListWithTooltip(stmt.regionNodes.sort().join(", "), 50); - }, - sort: (stmt: AggregateStatistics) => stmt.regionNodes.sort().join(", "), - hideIfTenant: true, - }, + makeRegionsColumn(statType, isTenant), { name: "lastExecTimestamp", title: statisticsTableTitles.lastExecTimestamp(statType), @@ -302,6 +294,33 @@ export function makeStatementsColumns( return columns; } +function makeRegionsColumn( + statType: StatisticType, + isTenant: boolean, +): ColumnDescriptor { + if (isTenant) { + return { + name: "regions", + title: statisticsTableTitles.regions(statType), + className: cx("statements-table__col-regions"), + cell: (stmt: AggregateStatistics) => { + return longListWithTooltip(stmt.regions.sort().join(", "), 50); + }, + sort: (stmt: AggregateStatistics) => stmt.regions.sort().join(", "), + }; + } else { + return { + name: "regionNodes", + title: statisticsTableTitles.regionNodes(statType), + className: cx("statements-table__col-regions"), + cell: (stmt: AggregateStatistics) => { + return longListWithTooltip(stmt.regionNodes.sort().join(", "), 50); + }, + sort: (stmt: AggregateStatistics) => stmt.regionNodes.sort().join(", "), + }; + } +} + /** * For each statement, generate the list of regions and nodes it was * executed on. Each node is assigned to only one region and a region can @@ -311,19 +330,12 @@ export function makeStatementsColumns( * 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 = []; - return; - } const regions: { [region: string]: Set } = {}; // For each region, populate a list of all nodes where the statement was executed. // E.g. {"gcp-us-east1" : [1,3,4]} @@ -350,6 +362,7 @@ export function populateRegionNodeForStatements( ")", ); }); + stmt.regions = Object.keys(regions).sort(); stmt.regionNodes = regionNodes; }); } diff --git a/pkg/ui/workspaces/cluster-ui/src/statsTableUtil/statsTableUtil.tsx b/pkg/ui/workspaces/cluster-ui/src/statsTableUtil/statsTableUtil.tsx index a6f162dd529f..ba3e747c7cc5 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statsTableUtil/statsTableUtil.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/statsTableUtil/statsTableUtil.tsx @@ -51,6 +51,7 @@ export const statisticsColumnLabels = { executionCount: "Execution Count", maxMemUsage: "Max Memory", networkBytes: "Network", + regions: "Regions", regionNodes: "Regions/Nodes", retries: "Retries", rowsProcessed: "Rows Processed", @@ -782,6 +783,27 @@ export const statisticsTableTitles: StatisticTableTitleType = { ); }, + regions: (statType: StatisticType) => { + let contentModifier = ""; + switch (statType) { + case "transaction": + contentModifier = contentModifiers.transaction; + break; + case "statement": + contentModifier = contentModifiers.statement; + break; + } + + return ( + Regions in which the {contentModifier} was executed.

} + > + {getLabel("regions")} +
+ ); + }, regionNodes: (statType: StatisticType) => { let contentModifier = ""; switch (statType) { diff --git a/pkg/ui/workspaces/cluster-ui/src/transactionDetails/transactionDetails.tsx b/pkg/ui/workspaces/cluster-ui/src/transactionDetails/transactionDetails.tsx index 660655a169b1..a6e5c747be6b 100644 --- a/pkg/ui/workspaces/cluster-ui/src/transactionDetails/transactionDetails.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/transactionDetails/transactionDetails.tsx @@ -319,11 +319,7 @@ export class TransactionDetails extends React.Component< const aggregatedStatements = aggregateStatements( statementsForTransaction, ); - populateRegionNodeForStatements( - aggregatedStatements, - nodeRegions, - isTenant, - ); + populateRegionNodeForStatements(aggregatedStatements, nodeRegions); const duration = (v: number) => Duration(v * 1e9); const transactionSampled = diff --git a/pkg/ui/workspaces/cluster-ui/src/transactionsPage/transactionsPage.tsx b/pkg/ui/workspaces/cluster-ui/src/transactionsPage/transactionsPage.tsx index 58812518f1e3..05e8fb93939c 100644 --- a/pkg/ui/workspaces/cluster-ui/src/transactionsPage/transactionsPage.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/transactionsPage/transactionsPage.tsx @@ -29,6 +29,7 @@ import { TableStatistics } from "../tableStatistics"; import { statisticsClasses } from "./transactionsPageClasses"; import { aggregateAcrossNodeIDs, + generateRegion, generateRegionNode, getTrxAppFilterOptions, searchTransactionsData, @@ -409,14 +410,14 @@ export class TransactionsPage extends React.Component< // If the cluster is a tenant cluster we don't show info // about nodes/regions. - const nodes = isTenant - ? [] - : Object.keys(nodeRegions) - .map(n => Number(n)) - .sort(); - const regions = isTenant - ? [] - : unique(nodes.map(node => nodeRegions[node.toString()])).sort(); + const nodes = Object.keys(nodeRegions) + .map(n => Number(n)) + .sort(); + + const regions = unique( + nodes.map(node => nodeRegions[node.toString()]), + ).sort(); + // We apply the search filters and app name filters prior to aggregating across Node IDs // in order to match what's done on the Statements Page. // @@ -466,7 +467,7 @@ export class TransactionsPage extends React.Component< activeFilters={activeFilters} filters={filters} showRegions={regions.length > 1} - showNodes={nodes.length > 1} + showNodes={!isTenant && nodes.length > 1} />
@@ -495,9 +496,8 @@ export class TransactionsPage extends React.Component< t => ({ stats_data: t.stats_data, node_id: t.node_id, - regionNodes: isTenant - ? [] - : generateRegionNode(t, statements, nodeRegions), + regions: generateRegion(t, statements, nodeRegions), + regionNodes: generateRegionNode(t, statements, nodeRegions), }), ); const { current, pageSize } = pagination; @@ -513,6 +513,7 @@ export class TransactionsPage extends React.Component< isTenant, search, ) + .filter(c => !(c.name === "regions" && regions.length < 2)) .filter(c => !(c.name === "regionNodes" && regions.length < 2)) .filter(c => !(isTenant && c.hideIfTenant)); diff --git a/pkg/ui/workspaces/cluster-ui/src/transactionsPage/utils.ts b/pkg/ui/workspaces/cluster-ui/src/transactionsPage/utils.ts index 2278c235e04c..1bd1e5363485 100644 --- a/pkg/ui/workspaces/cluster-ui/src/transactionsPage/utils.ts +++ b/pkg/ui/workspaces/cluster-ui/src/transactionsPage/utils.ts @@ -215,10 +215,9 @@ export const filterTransactions = ( // and regions list (if the list is not empty). if (regions.length == 0 && nodes.length == 0) return true; // If the cluster is a tenant cluster we don't care - // about node/regions. - if (isTenant) return true; + // about nodes. let foundRegion: boolean = regions.length == 0; - let foundNode: boolean = nodes.length == 0; + let foundNode: boolean = isTenant || nodes.length == 0; getStatementsByFingerprintId( t.stats_data.statement_fingerprint_ids, @@ -245,6 +244,38 @@ export const filterTransactions = ( }; }; +/** + * For each transaction, generate the list of regions all + * its statements were executed on. + * E.g. of one element of the list: `gcp-us-east1` + * @param transaction: list of transactions. + * @param statements: list of all statements collected. + * @param nodeRegions: object with keys being the node id and the value + * which region it belongs to. + */ +export const generateRegion = ( + transaction: Transaction, + statements: Statement[], + nodeRegions: { [p: string]: string }, +): string[] => { + const regions: Set = new Set(); + // Get the list of statements that were executed on the transaction. Combine all + // nodes and regions of all the statements to a single list of `region: nodes` + // for the transaction. + // E.g. {"gcp-us-east1" : [1,3,4]} + getStatementsByFingerprintId( + transaction.stats_data.statement_fingerprint_ids, + statements, + ).forEach(stmt => { + stmt.stats.nodes && + stmt.stats.nodes.forEach(n => { + regions.add(nodeRegions[n.toString()]); + }); + }); + + return Array.from(regions).sort(); +}; + /** * For each transaction, generate the list of regions and nodes all * its statements were executed on. diff --git a/pkg/ui/workspaces/cluster-ui/src/transactionsTable/transactionsTable.tsx b/pkg/ui/workspaces/cluster-ui/src/transactionsTable/transactionsTable.tsx index 0c3e28e8d951..8053e956a25e 100644 --- a/pkg/ui/workspaces/cluster-ui/src/transactionsTable/transactionsTable.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/transactionsTable/transactionsTable.tsx @@ -61,6 +61,7 @@ interface TransactionsTable { } export interface TransactionInfo extends Transaction { + regions: string[]; regionNodes: string[]; } @@ -228,16 +229,7 @@ export function makeTransactionsColumns( sort: (item: TransactionInfo) => longToInt(Number(item.stats_data.stats.max_retries)), }, - { - name: "regionNodes", - title: statisticsTableTitles.regionNodes(statType), - className: cx("statements-table__col-regions"), - cell: (item: TransactionInfo) => { - return longListWithTooltip(item.regionNodes.sort().join(", "), 50); - }, - sort: (item: TransactionInfo) => item.regionNodes.sort().join(", "), - hideIfTenant: true, - }, + makeRegionsColumn(isTenant), { name: "statementsCount", title: statisticsTableTitles.statementsCount(statType), @@ -257,7 +249,33 @@ export function makeTransactionsColumns( item.stats_data?.transaction_fingerprint_id.toString(16), showByDefault: false, }, - ].filter(c => !(isTenant && c.hideIfTenant)); + ]; +} + +function makeRegionsColumn( + isTenant: boolean, +): ColumnDescriptor { + if (isTenant) { + return { + name: "regions", + title: statisticsTableTitles.regions("transaction"), + className: cx("statements-table__col-regions"), + cell: (item: TransactionInfo) => { + return longListWithTooltip(item.regions.sort().join(", "), 50); + }, + sort: (item: TransactionInfo) => item.regions.sort().join(", "), + }; + } else { + return { + name: "regionNodes", + title: statisticsTableTitles.regionNodes("transaction"), + className: cx("statements-table__col-regions"), + cell: (item: TransactionInfo) => { + return longListWithTooltip(item.regionNodes.sort().join(", "), 50); + }, + sort: (item: TransactionInfo) => item.regionNodes.sort().join(", "), + }; + } } export const TransactionsTable: React.FC = props => {