From 04020f864711cbb65adc134bb738fd7ea6b7ee2c Mon Sep 17 00:00:00 2001 From: maryliag Date: Fri, 24 Mar 2023 20:11:11 -0400 Subject: [PATCH] ui: update sort setting on search criteria Fixes #99397 When a new search criteria is applying, the table on Statement and Transactions are sorted to match the value selected by the search criteria. When a new column is selected on the table, a warning is displayed to let the users know they're looking into a subset od the data. If the new column selected is one of the options on the search criteria, we give a suggestion to update the search criteria with that value instead. This commit adds the counter per page on the Statement and Transaction tables. This commit also adds analytics to the apply button, sending the information about each criteria. Release note (ui change): Add a warning when a user select a sorting column on Statement and Transaction tables that were not the original selected sorting on the search criteria. --- .../schemaInsights/schemaInsightsView.tsx | 2 +- .../statementsPage/statementsPage.fixture.ts | 1 + .../statementsPage/statementsPage.module.scss | 4 + .../src/statementsPage/statementsPage.tsx | 69 +++++++++++++++-- .../statementsPageConnected.tsx | 10 +++ .../src/store/analytics/analytics.reducer.ts | 9 +++ .../src/transactionsPage/transactionsPage.tsx | 74 +++++++++++++++++-- .../transactionsPageConnected.tsx | 10 +++ .../src/util/sqlActivityConstants.ts | 38 ++++++++++ .../db-console/src/redux/analyticsActions.ts | 24 ++++++ .../src/views/statements/statementsPage.tsx | 2 + .../views/transactions/transactionsPage.tsx | 2 + 12 files changed, 234 insertions(+), 11 deletions(-) diff --git a/pkg/ui/workspaces/cluster-ui/src/insights/schemaInsights/schemaInsightsView.tsx b/pkg/ui/workspaces/cluster-ui/src/insights/schemaInsights/schemaInsightsView.tsx index fd3c07d84ac9..4b49858e46e0 100644 --- a/pkg/ui/workspaces/cluster-ui/src/insights/schemaInsights/schemaInsightsView.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/insights/schemaInsights/schemaInsightsView.tsx @@ -136,7 +136,7 @@ export const SchemaInsightsView: React.FC = ({ // redux changes and syncs the URL params with redux. syncHistory( { - ascending: sortSetting.ascending.toString(), + ascending: sortSetting.ascending?.toString(), columnTitle: sortSetting.columnTitle, ...getFullFiltersAsStringRecord(filters), [SCHEMA_INSIGHT_SEARCH_PARAM]: search, diff --git a/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.fixture.ts b/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.fixture.ts index 1710bafc6c51..c63da702fd4f 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.fixture.ts +++ b/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.fixture.ts @@ -621,6 +621,7 @@ const statementsPagePropsFixture: StatementsPageProps = { onFilterChange: noop, onChangeLimit: noop, onChangeReqSort: noop, + onApplySearchCriteria: noop, }; export const statementsPagePropsWithRequestError: StatementsPageProps = { diff --git a/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.module.scss b/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.module.scss index b9bb3991d0c8..01f93c8ee3a6 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.module.scss +++ b/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.module.scss @@ -56,6 +56,10 @@ cl-table-container { margin-bottom: 0px; } +.margin-bottom { + margin-bottom: 10px; +} + .root { flex-grow: 0; width: 100%; diff --git a/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.tsx b/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.tsx index 91f4ce6ab278..f4904b19d3b7 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.tsx @@ -21,7 +21,7 @@ import { updateSortSettingQueryParamsOnTab, } from "src/sortedtable"; import { Search } from "src/search"; -import { Pagination } from "src/pagination"; +import { Pagination, ResultsPerPageLabel } from "src/pagination"; import { calculateActiveFilters, defaultFilters, @@ -59,6 +59,7 @@ import { SqlStatsSortType, StatementsRequest, createCombinedStmtsRequest, + SqlStatsSortOptions, } from "src/api/statementsApi"; import ClearStats from "../sqlActivity/clearStats"; import LoadingError from "../sqlActivity/errorComponent"; @@ -83,8 +84,9 @@ import { STATS_LONG_LOADING_DURATION, stmtRequestSortOptions, getSortLabel, + getSortColumn, + getSubsetWarning, } from "src/util/sqlActivityConstants"; -import { Button } from "src/button"; import { SearchCriteria } from "src/searchCriteria/searchCriteria"; import timeScaleStyles from "../timeScaleDropdown/timeScale.module.scss"; @@ -125,6 +127,7 @@ export interface StatementsPageDispatchProps { onTimeScaleChange: (ts: TimeScale) => void; onChangeLimit: (limit: number) => void; onChangeReqSort: (sort: SqlStatsSortType) => void; + onApplySearchCriteria: (ts: TimeScale, limit: number, sort: string) => void; } export interface StatementsPageStateProps { statements: AggregateStatistics[]; @@ -275,6 +278,13 @@ export class StatementsPage extends React.Component< } }; + isSortSettingSameAsReqSort = (): boolean => { + return ( + getSortColumn(this.state.reqSortSetting) == + this.props.sortSetting.columnTitle + ); + }; + changeTimeScale = (ts: TimeScale): void => { this.setState(prevState => ({ ...prevState, @@ -283,10 +293,30 @@ export class StatementsPage extends React.Component< }; updateRequestParams = (): void => { - this.props.onChangeLimit(this.state.limit); - this.props.onChangeReqSort(this.state.reqSortSetting); - this.props.onTimeScaleChange(this.state.timeScale); + if (this.props.limit !== this.state.limit) { + this.props.onChangeLimit(this.state.limit); + } + + if (this.props.reqSortSetting !== this.state.reqSortSetting) { + this.props.onChangeReqSort(this.state.reqSortSetting); + } + + if (this.props.timeScale !== this.state.timeScale) { + this.props.onTimeScaleChange(this.state.timeScale); + } + if (this.props.onApplySearchCriteria) { + this.props.onApplySearchCriteria( + this.state.timeScale, + this.state.limit, + getSortLabel(this.state.reqSortSetting), + ); + } this.refreshStatements(); + const ss: SortSetting = { + ascending: false, + columnTitle: getSortColumn(this.state.reqSortSetting), + }; + this.changeSortSetting(ss); }; resetPagination = (): void => { @@ -478,6 +508,16 @@ export class StatementsPage extends React.Component< this.setState(prevState => ({ ...prevState, reqSortSetting: newSort })); }; + hasReqSortOption = (): boolean => { + let found = false; + Object.values(SqlStatsSortOptions).forEach((option: SqlStatsSortType) => { + if (getSortColumn(option) == this.props.sortSetting.columnTitle) { + found = true; + } + }); + return found; + }; + renderStatements = (): React.ReactElement => { const { pagination, filters, activeFilters } = this.state; const { @@ -598,6 +638,12 @@ export class StatementsPage extends React.Component<

Showing aggregated stats from{" "} {period} + {", "} +

{hasAdminRole && ( @@ -620,6 +666,19 @@ export class StatementsPage extends React.Component< onRemoveFilter={this.onSubmitFilters} onClearFilters={this.onClearFilters} /> + {!this.isSortSettingSameAsReqSort() && ( + + )} dispatch(updateStmsPageReqSortAction(sort)), + onApplySearchCriteria: (ts: TimeScale, limit: number, sort: string) => + dispatch( + analyticsActions.track({ + name: "Apply Search Criteria", + page: "Statements", + tsValue: ts.key, + limitValue: limit, + sortValue: sort, + }), + ), }, activePageProps: mapDispatchToRecentStatementsPageProps(dispatch), }), diff --git a/pkg/ui/workspaces/cluster-ui/src/store/analytics/analytics.reducer.ts b/pkg/ui/workspaces/cluster-ui/src/store/analytics/analytics.reducer.ts index 3f938e9e2ad0..74d299c67d51 100644 --- a/pkg/ui/workspaces/cluster-ui/src/store/analytics/analytics.reducer.ts +++ b/pkg/ui/workspaces/cluster-ui/src/store/analytics/analytics.reducer.ts @@ -26,6 +26,14 @@ type Page = | "Workload Insights - Statement" | "Workload Insights - Transaction"; +type ApplySearchCriteriaEvent = { + name: "Apply Search Criteria"; + page: Page; + tsValue: string; + limitValue: number; + sortValue: string; +}; + type BackButtonClick = { name: "Back Clicked"; page: Page; @@ -102,6 +110,7 @@ type TimeScaleChangeEvent = { }; type AnalyticsEvent = + | ApplySearchCriteriaEvent | BackButtonClick | ColumnsChangeEvent | FilterEvent diff --git a/pkg/ui/workspaces/cluster-ui/src/transactionsPage/transactionsPage.tsx b/pkg/ui/workspaces/cluster-ui/src/transactionsPage/transactionsPage.tsx index 683cf90321a8..070c3c6038b9 100644 --- a/pkg/ui/workspaces/cluster-ui/src/transactionsPage/transactionsPage.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/transactionsPage/transactionsPage.tsx @@ -24,7 +24,7 @@ import { SortSetting, updateSortSettingQueryParamsOnTab, } from "../sortedtable"; -import { Pagination } from "../pagination"; +import { Pagination, ResultsPerPageLabel } from "../pagination"; import { statisticsClasses } from "./transactionsPageClasses"; import { aggregateAcrossNodeIDs, @@ -54,6 +54,7 @@ import { SqlStatsSortType, createCombinedStmtsRequest, StatementsRequest, + SqlStatsSortOptions, } from "src/api/statementsApi"; import ColumnsSelector from "../columnsSelector/columnsSelector"; import { SelectOption } from "../multiSelectCheckbox/multiSelectCheckbox"; @@ -79,8 +80,9 @@ import { STATS_LONG_LOADING_DURATION, txnRequestSortOptions, getSortLabel, + getSortColumn, + getSubsetWarning, } from "src/util/sqlActivityConstants"; -import { Button } from "src/button"; import { SearchCriteria } from "src/searchCriteria/searchCriteria"; import timeScaleStyles from "../timeScaleDropdown/timeScale.module.scss"; @@ -131,6 +133,7 @@ export interface TransactionsPageDispatchProps { columnTitle: string, ascending: boolean, ) => void; + onApplySearchCriteria: (ts: TimeScale, limit: number, sort: string) => void; } export type TransactionsPageProps = TransactionsPageStateProps & @@ -289,6 +292,13 @@ export class TransactionsPage extends React.Component< } }; + isSortSettingSameAsReqSort = (): boolean => { + return ( + getSortColumn(this.state.reqSortSetting) == + this.props.sortSetting.columnTitle + ); + }; + onChangePage = (current: number): void => { const { pagination } = this.state; this.setState({ pagination: { ...pagination, current } }); @@ -391,10 +401,41 @@ export class TransactionsPage extends React.Component< }; updateRequestParams = (): void => { - this.props.onChangeLimit(this.state.limit); - this.props.onChangeReqSort(this.state.reqSortSetting); - this.props.onTimeScaleChange(this.state.timeScale); + if (this.props.limit !== this.state.limit) { + this.props.onChangeLimit(this.state.limit); + } + + if (this.props.reqSortSetting !== this.state.reqSortSetting) { + this.props.onChangeReqSort(this.state.reqSortSetting); + } + + if (this.props.timeScale !== this.state.timeScale) { + this.props.onTimeScaleChange(this.state.timeScale); + } + + if (this.props.onApplySearchCriteria) { + this.props.onApplySearchCriteria( + this.state.timeScale, + this.state.limit, + getSortLabel(this.state.reqSortSetting), + ); + } this.refreshData(); + const ss: SortSetting = { + ascending: false, + columnTitle: getSortColumn(this.state.reqSortSetting), + }; + this.onChangeSortSetting(ss); + }; + + hasReqSortOption = (): boolean => { + let found = false; + Object.values(SqlStatsSortOptions).forEach((option: SqlStatsSortType) => { + if (getSortColumn(option) == this.props.sortSetting.columnTitle) { + found = true; + } + }); + return found; }; renderTransactions(): React.ReactElement { @@ -488,6 +529,7 @@ export class TransactionsPage extends React.Component< const period = timeScaleToString(this.props.timeScale); const sortSettingLabel = getSortLabel(this.props.reqSortSetting); + return ( <>
@@ -527,6 +569,15 @@ export class TransactionsPage extends React.Component<

Showing aggregated stats from{" "} {period} + {", "} +

{hasAdminRole && ( @@ -549,6 +600,19 @@ export class TransactionsPage extends React.Component< onRemoveFilter={this.onSubmitFilters} onClearFilters={this.onClearFilters} /> + {!this.isSortSettingSameAsReqSort() && ( + + )} dispatch(updateTxnsPageReqSortAction(sort)), + onApplySearchCriteria: (ts: TimeScale, limit: number, sort: string) => + dispatch( + analyticsActions.track({ + name: "Apply Search Criteria", + page: "Transactions", + tsValue: ts.key, + limitValue: limit, + sortValue: sort, + }), + ), }, activePageProps: mapDispatchToRecentTransactionsPageProps(dispatch), }), diff --git a/pkg/ui/workspaces/cluster-ui/src/util/sqlActivityConstants.ts b/pkg/ui/workspaces/cluster-ui/src/util/sqlActivityConstants.ts index 65b7fbe66069..1352039eaab5 100644 --- a/pkg/ui/workspaces/cluster-ui/src/util/sqlActivityConstants.ts +++ b/pkg/ui/workspaces/cluster-ui/src/util/sqlActivityConstants.ts @@ -10,6 +10,10 @@ import { duration } from "moment"; import { SqlStatsSortOptions, SqlStatsSortType } from "src/api/statementsApi"; +import { + getLabel, + StatisticTableColumnKeys, +} from "../statsTableUtil/statsTableUtil"; export const limitOptions = [ { value: 25, label: "25" }, @@ -37,6 +41,25 @@ export function getSortLabel(sort: SqlStatsSortType): string { } } +export function getSortColumn(sort: SqlStatsSortType): string { + switch (sort) { + case SqlStatsSortOptions.SERVICE_LAT: + return "time"; + case SqlStatsSortOptions.EXECUTION_COUNT: + return "executionCount"; + case SqlStatsSortOptions.CPU_TIME: + return "cpu"; + case SqlStatsSortOptions.P99_STMTS_ONLY: + return "latencyP99"; + case SqlStatsSortOptions.CONTENTION_TIME: + return "contention"; + case SqlStatsSortOptions.PCT_RUNTIME: + return "workloadPct"; + default: + return ""; + } +} + export const stmtRequestSortOptions = Object.values(SqlStatsSortOptions) .map(sortVal => ({ value: sortVal as SqlStatsSortType, @@ -55,3 +78,18 @@ export const txnRequestSortOptions = stmtRequestSortOptions.filter( ); export const STATS_LONG_LOADING_DURATION = duration(2, "s"); + +export function getSubsetWarning( + type: "statement" | "transaction", + limit: number, + sortLabel: string, + showSuggestion: boolean, + columnTitle: StatisticTableColumnKeys, +): string { + const warningSuggestion = showSuggestion + ? `Update the search criteria to see the ${type} fingerprints + sorted on ${getLabel(columnTitle, type)}.` + : ""; + return `You are viewing a subset (Top ${limit}) of fingerprints by ${sortLabel}. + ${warningSuggestion}`; +} diff --git a/pkg/ui/workspaces/db-console/src/redux/analyticsActions.ts b/pkg/ui/workspaces/db-console/src/redux/analyticsActions.ts index 39c8e380e776..7363c1762a1d 100644 --- a/pkg/ui/workspaces/db-console/src/redux/analyticsActions.ts +++ b/pkg/ui/workspaces/db-console/src/redux/analyticsActions.ts @@ -9,6 +9,7 @@ // licenses/APL.txt. import { PayloadAction } from "src/interfaces/action"; +import { TimeScale } from "@cockroachlabs/cluster-ui"; export const TRACK_STATEMENTS_SEARCH = "cockroachui/analytics/TRACK_STATEMENTS_SEARCH"; @@ -21,6 +22,8 @@ export const TRACK_CANCEL_DIAGNOSTIC_BUNDLE = "cockroachui/analytics/TRACK_CANCEL_DIAGNOSTIC_BUNDLE"; export const TRACK_STATEMENT_DETAILS_SUBNAV_SELECTION = "cockroachui/analytics/TRACK_STATEMENT_DETAILS_SUBNAV_SELECTION"; +export const TRACK_APPLY_SEARCH_CRITERIA = + "cockroachui/analytics/TRACK_APPLY_SEARCH_CRITERIA"; export interface TableSortActionPayload { tableName: string; @@ -28,6 +31,12 @@ export interface TableSortActionPayload { ascending?: boolean; } +export interface ApplySearchCriteriaPayload { + ts: TimeScale; + limit: number; + sort: string; +} + export function trackStatementsSearchAction( searchResults: number, ): PayloadAction { @@ -87,3 +96,18 @@ export function trackStatementDetailsSubnavSelectionAction( payload: tabName, }; } + +export function trackApplySearchCriteriaAction( + ts: TimeScale, + limit: number, + sort: string, +): PayloadAction { + return { + type: TRACK_APPLY_SEARCH_CRITERIA, + payload: { + ts, + limit, + sort, + }, + }; +} diff --git a/pkg/ui/workspaces/db-console/src/views/statements/statementsPage.tsx b/pkg/ui/workspaces/db-console/src/views/statements/statementsPage.tsx index 04adb3a1855e..44c0347d480b 100644 --- a/pkg/ui/workspaces/db-console/src/views/statements/statementsPage.tsx +++ b/pkg/ui/workspaces/db-console/src/views/statements/statementsPage.tsx @@ -56,6 +56,7 @@ import { setGlobalTimeScaleAction, } from "src/redux/statements"; import { + trackApplySearchCriteriaAction, trackCancelDiagnosticsBundleAction, trackDownloadDiagnosticsBundleAction, trackStatementsPaginationAction, @@ -363,6 +364,7 @@ const fingerprintsPageActions = { ), onChangeLimit: (newLimit: number) => limitSetting.set(newLimit), onChangeReqSort: (sort: api.SqlStatsSortType) => reqSortSetting.set(sort), + onApplySearchCriteria: trackApplySearchCriteriaAction, }; type StateProps = { diff --git a/pkg/ui/workspaces/db-console/src/views/transactions/transactionsPage.tsx b/pkg/ui/workspaces/db-console/src/views/transactions/transactionsPage.tsx index 232d05007f6f..219428954c48 100644 --- a/pkg/ui/workspaces/db-console/src/views/transactions/transactionsPage.tsx +++ b/pkg/ui/workspaces/db-console/src/views/transactions/transactionsPage.tsx @@ -50,6 +50,7 @@ import { selectTxnsDataValid, selectTxnsDataInFlight, } from "src/selectors/executionFingerprintsSelectors"; +import { trackApplySearchCriteriaAction } from "src/redux/analyticsActions"; // selectData returns the array of AggregateStatistics to show on the // TransactionsPage, based on if the appAttr route parameter is set. @@ -142,6 +143,7 @@ const fingerprintsPageActions = { onSearchComplete: (query: string) => searchLocalSetting.set(query), onChangeLimit: (newLimit: number) => limitSetting.set(newLimit), onChangeReqSort: (sort: api.SqlStatsSortType) => reqSortSetting.set(sort), + onApplySearchCriteria: trackApplySearchCriteriaAction, }; type StateProps = {