From 496aacb803bb41f0313f8305127b8e29ac416b6f Mon Sep 17 00:00:00 2001 From: maryliag Date: Tue, 14 Feb 2023 20:07:09 -0500 Subject: [PATCH] ui: show data when max size reached Previously, when the sql api returned a max size reached error, we were just showing the error, but not the data that was also being returned. This PR creates a new function to format the return of the api calls, so when is a max size error it doesn't throw an error, but still pass that info so we can display a warning on the pages. This commit updates the Insights Workload > Statement page with the new behaviour. Following PRs will update other usages of the sql api. Part Of: #96184 Release note (ui change): Still show data on the console (with a warning) for Statement Insights when we reach a "max size exceed" error from the sql api. --- .../workspaces/cluster-ui/src/api/sqlApi.ts | 30 +++++++++++++++++++ .../cluster-ui/src/api/stmtInsightsApi.ts | 21 ++++++------- .../statementInsightDetails.tsx | 2 +- .../statementInsightsView.tsx | 9 ++++++ .../workloadInsightsPageConnected.tsx | 2 ++ .../statementFingerprintInsights.reducer.ts | 6 ++-- .../statementFingerprintInsights.selectors.ts | 2 +- .../statementInsights.reducer.ts | 6 ++-- .../statementInsights.selectors.ts | 5 +++- .../db-console/src/redux/apiReducers.ts | 8 +++-- .../src/views/insights/insightsSelectors.ts | 15 ++++++++-- .../views/insights/workloadInsightsPage.tsx | 2 ++ .../src/views/statements/statementDetails.tsx | 2 +- 13 files changed, 84 insertions(+), 26 deletions(-) diff --git a/pkg/ui/workspaces/cluster-ui/src/api/sqlApi.ts b/pkg/ui/workspaces/cluster-ui/src/api/sqlApi.ts index 3fafc43c6c17..7c40acf12537 100644 --- a/pkg/ui/workspaces/cluster-ui/src/api/sqlApi.ts +++ b/pkg/ui/workspaces/cluster-ui/src/api/sqlApi.ts @@ -60,6 +60,11 @@ export type SqlExecutionErrorMessage = { source: { file: string; line: number; function: "string" }; }; +export type ApiResponse = { + maxSizeReached: boolean; + results: Array; +}; + export const SQL_API_PATH = "/api/v2/sql/"; /** @@ -159,3 +164,28 @@ export function sqlApiErrorMessage(message: string): string { return message; } + +function isMaxSizeError(message: string): boolean { + return !!message?.includes("max result size exceeded"); +} + +export function formatApiResult( + results: Array, + error: SqlExecutionErrorMessage, + errorMessageContext: string, +): ApiResponse { + const maxSizeError = isMaxSizeError(error?.message); + + if (error && !maxSizeError) { + throw new Error( + `Error while ${errorMessageContext}: ${sqlApiErrorMessage( + error?.message, + )}`, + ); + } + + return { + maxSizeReached: maxSizeError, + results: results, + }; +} diff --git a/pkg/ui/workspaces/cluster-ui/src/api/stmtInsightsApi.ts b/pkg/ui/workspaces/cluster-ui/src/api/stmtInsightsApi.ts index f35619149aaf..045eb9e6bd84 100644 --- a/pkg/ui/workspaces/cluster-ui/src/api/stmtInsightsApi.ts +++ b/pkg/ui/workspaces/cluster-ui/src/api/stmtInsightsApi.ts @@ -9,10 +9,11 @@ // licenses/APL.txt. import { + ApiResponse, executeInternalSql, + formatApiResult, LARGE_RESULT_SIZE, LONG_TIMEOUT, - sqlApiErrorMessage, SqlExecutionRequest, sqlResultsAreEmpty, SqlTxnResult, @@ -137,7 +138,7 @@ export const stmtInsightsByTxnExecutionQuery = (id: string): string => ` export async function getStmtInsightsApi( req?: StmtInsightsReq, -): Promise { +): Promise> { const request: SqlExecutionRequest = { statements: [ { @@ -150,21 +151,17 @@ export async function getStmtInsightsApi( }; const result = await executeInternalSql(request); - if (result.error) { - throw new Error( - `Error while retrieving insights information: ${sqlApiErrorMessage( - result.error.message, - )}`, - ); - } if (sqlResultsAreEmpty(result)) { - return []; + return formatApiResult([], result.error, "retrieving insights information"); } - const stmtInsightEvent = formatStmtInsights(result.execution?.txn_results[0]); await addStmtContentionInfoApi(stmtInsightEvent); - return stmtInsightEvent; + return formatApiResult( + stmtInsightEvent, + result.error, + "retrieving insights information", + ); } async function addStmtContentionInfoApi( diff --git a/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsightDetails/statementInsightDetails.tsx b/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsightDetails/statementInsightDetails.tsx index 50b9c7fbbc8d..f8abcf88e9cd 100644 --- a/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsightDetails/statementInsightDetails.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsightDetails/statementInsightDetails.tsx @@ -124,7 +124,7 @@ export const StatementInsightDetails: React.FC< getStmtInsightsApi({ stmtExecutionID: executionID, start, end }) .then(res => { setInsightDetails({ - details: res?.length ? res[0] : null, + details: res?.results?.length ? res.results[0] : null, loaded: true, }); }) 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 c43eae12da5e..04c271ec3f1b 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 @@ -56,6 +56,7 @@ import styles from "src/statementsPage/statementsPage.module.scss"; import sortableTableStyles from "src/sortedtable/sortedtable.module.scss"; import { commonStyles } from "../../../common"; import { useFetchDataWithPolling } from "src/util/hooks"; +import { InlineAlert } from "@cockroachlabs/ui-components"; const cx = classNames.bind(styles); const sortableTableCx = classNames.bind(sortableTableStyles); @@ -72,6 +73,7 @@ export type StatementInsightsViewStateProps = { isLoading?: boolean; dropDownSelect?: React.ReactElement; timeScale?: TimeScale; + maxSizeApiReached?: boolean; }; export type StatementInsightsViewDispatchProps = { @@ -105,6 +107,7 @@ export const StatementInsightsView: React.FC = ({ setTimeScale, selectedColumnNames, dropDownSelect, + maxSizeApiReached, }: StatementInsightsViewProps) => { const [pagination, setPagination] = useState({ current: 1, @@ -317,6 +320,12 @@ export const StatementInsightsView: React.FC = ({ total={filteredStatements?.length} onChange={onChangePage} /> + {maxSizeApiReached && ( + + )} 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 6a1d7ddbe93c..51be49fe4a69 100644 --- a/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsights/workloadInsightsPageConnected.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsights/workloadInsightsPageConnected.tsx @@ -34,6 +34,7 @@ import { selectStmtInsights, selectStmtInsightsError, selectStmtInsightsLoading, + selectStmtInsightsMaxApiReached, } from "src/store/insights/statementInsights"; import { actions as txnInsights, @@ -78,6 +79,7 @@ const statementMapStateToProps = ( selectedColumnNames: selectColumns(state), timeScale: selectTimeScale(state), isLoading: selectStmtInsightsLoading(state), + maxSizeApiReached: selectStmtInsightsMaxApiReached(state), }); const TransactionDispatchProps = ( diff --git a/pkg/ui/workspaces/cluster-ui/src/store/insights/statementFingerprintInsights/statementFingerprintInsights.reducer.ts b/pkg/ui/workspaces/cluster-ui/src/store/insights/statementFingerprintInsights/statementFingerprintInsights.reducer.ts index e5fa1313283c..7f9ac689fafd 100644 --- a/pkg/ui/workspaces/cluster-ui/src/store/insights/statementFingerprintInsights/statementFingerprintInsights.reducer.ts +++ b/pkg/ui/workspaces/cluster-ui/src/store/insights/statementFingerprintInsights/statementFingerprintInsights.reducer.ts @@ -11,11 +11,11 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { DOMAIN_NAME } from "../../utils"; import moment, { Moment } from "moment"; -import { ErrorWithKey, StmtInsightsReq } from "src/api"; +import { ApiResponse, ErrorWithKey, StmtInsightsReq } from "src/api"; import { StmtInsightEvent } from "../../../insights"; export type StatementFingerprintInsightsState = { - data: StmtInsightEvent[] | null; + data: ApiResponse | null; lastUpdated: Moment | null; lastError: Error; valid: boolean; @@ -26,7 +26,7 @@ export type StatementFingerprintInsightsCachedState = { }; export type FingerprintInsightResponseWithKey = { - response: StmtInsightEvent[]; + response: ApiResponse; key: string; }; diff --git a/pkg/ui/workspaces/cluster-ui/src/store/insights/statementFingerprintInsights/statementFingerprintInsights.selectors.ts b/pkg/ui/workspaces/cluster-ui/src/store/insights/statementFingerprintInsights/statementFingerprintInsights.selectors.ts index fece33225470..92476371d527 100644 --- a/pkg/ui/workspaces/cluster-ui/src/store/insights/statementFingerprintInsights/statementFingerprintInsights.selectors.ts +++ b/pkg/ui/workspaces/cluster-ui/src/store/insights/statementFingerprintInsights/statementFingerprintInsights.selectors.ts @@ -20,6 +20,6 @@ export const selectStatementFingerprintInsights = createSelector( if (!cachedFingerprintInsights) { return null; } - return cachedFingerprintInsights[fingerprintID]?.data; + return cachedFingerprintInsights[fingerprintID]?.data?.results; }, ); diff --git a/pkg/ui/workspaces/cluster-ui/src/store/insights/statementInsights/statementInsights.reducer.ts b/pkg/ui/workspaces/cluster-ui/src/store/insights/statementInsights/statementInsights.reducer.ts index 4c53e909a35a..e30ba98747f3 100644 --- a/pkg/ui/workspaces/cluster-ui/src/store/insights/statementInsights/statementInsights.reducer.ts +++ b/pkg/ui/workspaces/cluster-ui/src/store/insights/statementInsights/statementInsights.reducer.ts @@ -11,11 +11,11 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { DOMAIN_NAME } from "../../utils"; import { StmtInsightEvent } from "src/insights"; -import { StmtInsightsReq } from "src/api"; +import { ApiResponse, StmtInsightsReq } from "src/api"; import moment from "moment"; export type StmtInsightsState = { - data: StmtInsightEvent[]; + data: ApiResponse; lastError: Error; valid: boolean; inFlight: boolean; @@ -34,7 +34,7 @@ const statementInsightsSlice = createSlice({ name: `${DOMAIN_NAME}/statementInsightsSlice`, initialState, reducers: { - received: (state, action: PayloadAction) => { + received: (state, action: PayloadAction>) => { state.data = action.payload; state.valid = true; state.lastError = null; 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 4b46d13f1fee..7f4a3f39e3a2 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 @@ -20,11 +20,14 @@ import { selectStatementFingerprintID, selectID } from "src/selectors/common"; import { InsightEnumToLabel, StmtInsightEvent } from "src/insights"; export const selectStmtInsights = (state: AppState): StmtInsightEvent[] => - state.adminUI.stmtInsights?.data; + state.adminUI.stmtInsights?.data?.results; export const selectStmtInsightsError = (state: AppState): Error | null => state.adminUI.stmtInsights?.lastError; +export const selectStmtInsightsMaxApiReached = (state: AppState): boolean => + state.adminUI.stmtInsights?.data?.maxSizeReached; + export const selectStmtInsightDetails = createSelector( selectStmtInsights, selectID, diff --git a/pkg/ui/workspaces/db-console/src/redux/apiReducers.ts b/pkg/ui/workspaces/db-console/src/redux/apiReducers.ts index 091e63cde6d6..42bb79f2aa2b 100644 --- a/pkg/ui/workspaces/db-console/src/redux/apiReducers.ts +++ b/pkg/ui/workspaces/db-console/src/redux/apiReducers.ts @@ -557,11 +557,15 @@ export interface APIReducersState { userSQLRoles: CachedDataReducerState; hotRanges: PaginatedCachedDataReducerState; clusterLocks: CachedDataReducerState; - stmtInsights: CachedDataReducerState; + stmtInsights: CachedDataReducerState< + clusterUiApi.ApiResponse + >; txnInsightDetails: KeyedCachedDataReducerState; txnInsights: CachedDataReducerState; schemaInsights: CachedDataReducerState; - statementFingerprintInsights: KeyedCachedDataReducerState; + statementFingerprintInsights: KeyedCachedDataReducerState< + clusterUiApi.ApiResponse + >; schedules: KeyedCachedDataReducerState; schedule: KeyedCachedDataReducerState; snapshots: KeyedCachedDataReducerState; 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 fa07aa1cccb2..a2004c6bba96 100644 --- a/pkg/ui/workspaces/db-console/src/views/insights/insightsSelectors.ts +++ b/pkg/ui/workspaces/db-console/src/views/insights/insightsSelectors.ts @@ -70,8 +70,19 @@ const selectTxnInsight = createSelector( }, ); -export const selectStmtInsights = (state: AdminUIState) => - state.cachedData.stmtInsights?.data; +export const selectStmtInsights = (state: AdminUIState) => { + return state.cachedData.stmtInsights?.data + ? state.cachedData.stmtInsights.data["results"] + : null; +}; + +export const selectStmtInsightsMaxApiReached = ( + state: AdminUIState, +): boolean => { + return state.cachedData.stmtInsights?.data + ? state.cachedData.stmtInsights?.data["maxSizeReached"] + : false; +}; export const selectTxnInsightDetails = createSelector( selectTxnInsight, 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 97eae0e9ee00..dadccec16f82 100644 --- a/pkg/ui/workspaces/db-console/src/views/insights/workloadInsightsPage.tsx +++ b/pkg/ui/workspaces/db-console/src/views/insights/workloadInsightsPage.tsx @@ -30,6 +30,7 @@ import { selectStmtInsightsLoading, selectTransactionInsightsLoading, selectInsightTypes, + selectStmtInsightsMaxApiReached, } from "src/views/insights/insightsSelectors"; import { bindActionCreators } from "redux"; import { LocalSetting } from "src/redux/localsettings"; @@ -75,6 +76,7 @@ const statementMapStateToProps = ( insightStatementColumnsLocalSetting.selectorToArray(state), timeScale: selectTimeScale(state), isLoading: selectStmtInsightsLoading(state), + maxSizeApiReached: selectStmtInsightsMaxApiReached(state), }); const TransactionDispatchProps = { diff --git a/pkg/ui/workspaces/db-console/src/views/statements/statementDetails.tsx b/pkg/ui/workspaces/db-console/src/views/statements/statementDetails.tsx index e5fcbcefac14..5268377f8e0d 100644 --- a/pkg/ui/workspaces/db-console/src/views/statements/statementDetails.tsx +++ b/pkg/ui/workspaces/db-console/src/views/statements/statementDetails.tsx @@ -108,7 +108,7 @@ const selectStatementFingerprintInsights = createSelector( (_state: AdminUIState, props: RouteComponentProps): string => getMatchParamByName(props.match, statementAttr), (cachedFingerprintInsights, fingerprintID) => { - return cachedFingerprintInsights[fingerprintID]?.data; + return cachedFingerprintInsights[fingerprintID]?.data?.results; }, );