From 7355765b4ab3280c99c7104b448dbb0dddab1c32 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 | 56 +++++++++++++++++- .../statementsPageConnected.tsx | 10 ++++ .../src/store/analytics/analytics.reducer.ts | 9 +++ .../src/transactionsPage/transactionsPage.tsx | 59 ++++++++++++++++++- .../transactionsPageConnected.tsx | 10 ++++ .../src/util/sqlActivityConstants.ts | 36 ++++++++++- .../db-console/src/redux/analyticsActions.ts | 24 ++++++++ .../src/views/statements/statementsPage.tsx | 2 + .../views/transactions/transactionsPage.tsx | 2 + 12 files changed, 208 insertions(+), 7 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 c56ea2fb50b7..9329b0effb2e 100644 --- a/pkg/ui/workspaces/cluster-ui/src/insights/schemaInsights/schemaInsightsView.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/insights/schemaInsights/schemaInsightsView.tsx @@ -134,7 +134,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 ee958f6129f6..1a527ac140a4 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.fixture.ts +++ b/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.fixture.ts @@ -907,6 +907,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 546e46ff6715..868a9c4849d8 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.module.scss +++ b/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.module.scss @@ -46,6 +46,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 69f0e600267d..0d15e7ab83b5 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, @@ -61,6 +61,7 @@ import { SqlStatsSortType, StatementsRequest, createCombinedStmtsRequest, + SqlStatsSortOptions, } from "src/api/statementsApi"; import ClearStats from "../sqlActivity/clearStats"; import LoadingError from "../sqlActivity/errorComponent"; @@ -80,8 +81,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"; @@ -126,6 +128,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[]; @@ -276,6 +279,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, @@ -295,8 +305,19 @@ export class StatementsPage extends React.Component< 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 => { @@ -556,6 +577,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 { @@ -668,6 +699,12 @@ export class StatementsPage extends React.Component<

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

{hasAdminRole && ( @@ -690,6 +727,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: mapDispatchToActiveStatementsPageProps(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 3fb6b54a5207..4de09fcecd13 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, @@ -53,6 +53,7 @@ import { SqlStatsSortType, createCombinedStmtsRequest, StatementsRequest, + SqlStatsSortOptions, } from "src/api/statementsApi"; import ColumnsSelector from "../columnsSelector/columnsSelector"; import { SelectOption } from "../multiSelectCheckbox/multiSelectCheckbox"; @@ -78,8 +79,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"; @@ -130,6 +132,7 @@ export interface TransactionsPageDispatchProps { columnTitle: string, ascending: boolean, ) => void; + onApplySearchCriteria: (ts: TimeScale, limit: number, sort: string) => void; } export type TransactionsPageProps = TransactionsPageStateProps & @@ -287,6 +290,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 } }); @@ -401,7 +411,29 @@ export class TransactionsPage extends React.Component< 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 { @@ -492,6 +524,7 @@ export class TransactionsPage extends React.Component< const period = timeScaleToString(this.props.timeScale); const sortSettingLabel = getSortLabel(this.props.reqSortSetting); + return ( <>
@@ -531,6 +564,15 @@ export class TransactionsPage extends React.Component<

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

{hasAdminRole && ( @@ -553,6 +595,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: mapDispatchToActiveTransactionsPageProps(dispatch), }), diff --git a/pkg/ui/workspaces/cluster-ui/src/util/sqlActivityConstants.ts b/pkg/ui/workspaces/cluster-ui/src/util/sqlActivityConstants.ts index a799c473df93..465b10ed80e1 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" }, @@ -27,7 +31,22 @@ export function getSortLabel(sort: SqlStatsSortType): string { case SqlStatsSortOptions.CONTENTION_TIME: return "Contention Time"; case SqlStatsSortOptions.PCT_RUNTIME: - return "% Of All Runtime"; + return "% of All Runtime"; + default: + return ""; + } +} + +export function getSortColumn(sort: SqlStatsSortType): string { + switch (sort) { + case SqlStatsSortOptions.SERVICE_LAT: + return "time"; + case SqlStatsSortOptions.EXECUTION_COUNT: + return "executionCount"; + case SqlStatsSortOptions.CONTENTION_TIME: + return "contention"; + case SqlStatsSortOptions.PCT_RUNTIME: + return "workloadPct"; default: return ""; } @@ -49,3 +68,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 544febe5de84..543261b87197 100644 --- a/pkg/ui/workspaces/db-console/src/views/statements/statementsPage.tsx +++ b/pkg/ui/workspaces/db-console/src/views/statements/statementsPage.tsx @@ -55,6 +55,7 @@ import { setGlobalTimeScaleAction, } from "src/redux/statements"; import { + trackApplySearchCriteriaAction, trackCancelDiagnosticsBundleAction, trackDownloadDiagnosticsBundleAction, trackStatementsPaginationAction, @@ -359,6 +360,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 9af56d3699b6..3aca1582110f 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 = {