From 312ddccebdefe30af30e70e0e9d0bd79da77ca25 Mon Sep 17 00:00:00 2001 From: maryliag Date: Wed, 29 Mar 2023 14:22:52 -0400 Subject: [PATCH] ui: add checks for values Fixes #99655 Fixes #99538 Fixes #99539 Add checks to usages that could cause `Cannot read properties of undefined`. Release note: None --- pkg/ui/workspaces/cluster-ui/package.json | 2 +- .../cluster-ui/src/api/schemaInsightsApi.ts | 3 +++ .../src/highlightedText/highlightedText.tsx | 2 +- .../src/multiSelectCheckbox/multiSelectCheckbox.tsx | 11 ++++++----- .../src/sessions/sessionsPageConnected.tsx | 6 +++--- .../src/statementDetails/planDetails/planDetails.tsx | 3 +++ .../src/statementsPage/statementsPage.selectors.ts | 12 ++++++------ .../cluster-ui/src/statementsPage/statementsPage.tsx | 2 +- .../statementInsights/statementInsights.selectors.ts | 2 +- .../cluster-ui/src/store/jobs/jobs.selectors.ts | 2 +- .../src/store/sessions/sessions.selectors.ts | 2 +- .../transactionsPage/transactionsPage.selectors.ts | 2 +- .../cluster-ui/src/transactionsPage/utils.ts | 4 ++-- pkg/ui/workspaces/cluster-ui/src/util/format.ts | 2 +- .../workspaces/cluster-ui/src/util/formatNumber.ts | 2 +- .../redux/indexUsageStats/indexUsageStatsSagas.ts | 3 +++ .../src/selectors/activeExecutionsSelectors.ts | 2 +- .../db-console/src/util/highlightedText.tsx | 2 +- .../db-console/src/views/sessions/sessionDetails.tsx | 2 +- .../db-console/src/views/sessions/sessionsPage.tsx | 4 ++-- .../src/views/statements/statementsPage.tsx | 10 +++++----- .../src/views/transactions/transactionsPage.tsx | 2 +- 22 files changed, 46 insertions(+), 36 deletions(-) diff --git a/pkg/ui/workspaces/cluster-ui/package.json b/pkg/ui/workspaces/cluster-ui/package.json index 7f73220cab7a..5b0341ea62f8 100644 --- a/pkg/ui/workspaces/cluster-ui/package.json +++ b/pkg/ui/workspaces/cluster-ui/package.json @@ -1,6 +1,6 @@ { "name": "@cockroachlabs/cluster-ui", - "version": "22.2.9", + "version": "22.2.10", "description": "Cluster UI is a library of large features shared between CockroachDB and CockroachCloud", "repository": { "type": "git", diff --git a/pkg/ui/workspaces/cluster-ui/src/api/schemaInsightsApi.ts b/pkg/ui/workspaces/cluster-ui/src/api/schemaInsightsApi.ts index 3a0aa1504b54..8ad6a42d1462 100644 --- a/pkg/ui/workspaces/cluster-ui/src/api/schemaInsightsApi.ts +++ b/pkg/ui/workspaces/cluster-ui/src/api/schemaInsightsApi.ts @@ -95,6 +95,9 @@ function createIndexRecommendationsToSchemaInsight( txn_result.rows.forEach(row => { row.index_recommendations.forEach(rec => { + if (!rec.includes(" : ")) { + return; + } const recSplit = rec.split(" : "); const recType = recSplit[0]; const recQuery = recSplit[1]; diff --git a/pkg/ui/workspaces/cluster-ui/src/highlightedText/highlightedText.tsx b/pkg/ui/workspaces/cluster-ui/src/highlightedText/highlightedText.tsx index 7858a0ce3abc..6025a275dfa4 100644 --- a/pkg/ui/workspaces/cluster-ui/src/highlightedText/highlightedText.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/highlightedText/highlightedText.tsx @@ -97,7 +97,7 @@ export function getHighlightedText( }) .join("|"); const parts = isOriginalText - ? text.split(new RegExp(`(${search})`, "gi")) + ? text?.split(new RegExp(`(${search})`, "gi")) : rebaseText(text, highlight).split(new RegExp(`(${search})`, "gi")); const highlightClass = hasDarkBkg ? "_text-bold-light" : "_text-bold"; return parts.map((part, i) => { diff --git a/pkg/ui/workspaces/cluster-ui/src/multiSelectCheckbox/multiSelectCheckbox.tsx b/pkg/ui/workspaces/cluster-ui/src/multiSelectCheckbox/multiSelectCheckbox.tsx index 417f9440ba4c..fc8ebce86ecc 100644 --- a/pkg/ui/workspaces/cluster-ui/src/multiSelectCheckbox/multiSelectCheckbox.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/multiSelectCheckbox/multiSelectCheckbox.tsx @@ -102,11 +102,12 @@ export const MultiSelectCheckbox = (props: MultiSelectCheckboxProps) => { field: string, parent: any, ) => { - const selected = selectedOptions - .map(function (option: SelectOption) { - return option.label; - }) - .toString(); + const selected = + selectedOptions + ?.map(function (option: SelectOption) { + return option.label; + }) + .toString() || ""; parent.setState({ filters: { ...parent.state.filters, diff --git a/pkg/ui/workspaces/cluster-ui/src/sessions/sessionsPageConnected.tsx b/pkg/ui/workspaces/cluster-ui/src/sessions/sessionsPageConnected.tsx index b6e8732ba1c8..633d6145b42b 100644 --- a/pkg/ui/workspaces/cluster-ui/src/sessions/sessionsPageConnected.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/sessions/sessionsPageConnected.tsx @@ -36,7 +36,7 @@ export const selectSessionsData = createSelector( export const selectSessions = createSelector( (state: AppState) => state.adminUI?.sessions, (state: SessionsState) => { - if (!state.data) { + if (!state?.data) { return null; } return state.data.sessions.map(session => { @@ -48,7 +48,7 @@ export const selectSessions = createSelector( export const selectAppName = createSelector( (state: AppState) => state.adminUI?.sessions, (state: SessionsState) => { - if (!state.data) { + if (!state?.data) { return null; } return state.data.internal_app_name_prefix; @@ -64,7 +64,7 @@ export const selectColumns = createSelector( localStorageSelector, localStorage => localStorage["showColumns/SessionsPage"] - ? localStorage["showColumns/SessionsPage"].split(",") + ? localStorage["showColumns/SessionsPage"]?.split(",") : null, ); diff --git a/pkg/ui/workspaces/cluster-ui/src/statementDetails/planDetails/planDetails.tsx b/pkg/ui/workspaces/cluster-ui/src/statementDetails/planDetails/planDetails.tsx index 356f2eeeb413..a6e79cdf2597 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementDetails/planDetails/planDetails.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/statementDetails/planDetails/planDetails.tsx @@ -167,6 +167,9 @@ function formatIdxRecommendations( for (let i = 0; i < idxRecs.length; i++) { const rec = idxRecs[i]; let idxType: InsightType; + if (!rec?.includes(" : ")) { + continue; + } const t = rec.split(" : ")[0]; switch (t) { case "creation": diff --git a/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.selectors.ts b/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.selectors.ts index 65697ac9cace..087b31877669 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.selectors.ts +++ b/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.selectors.ts @@ -56,7 +56,7 @@ export const selectStatementsLastUpdated = createSelector( // selectApps returns the array of all apps with statement statistics present // in the data. export const selectApps = createSelector(sqlStatsSelector, sqlStatsState => { - if (!sqlStatsState.data || !sqlStatsState.valid) { + if (!sqlStatsState?.data || !sqlStatsState?.valid) { return []; } @@ -90,7 +90,7 @@ export const selectApps = createSelector(sqlStatsSelector, sqlStatsState => { export const selectDatabases = createSelector( sqlStatsSelector, sqlStatsState => { - if (!sqlStatsState.data) { + if (!sqlStatsState?.data) { return []; } @@ -111,7 +111,7 @@ export const selectDatabases = createSelector( export const selectTotalFingerprints = createSelector( sqlStatsSelector, state => { - if (!state.data) { + if (!state?.data) { return 0; } const aggregated = aggregateStatementStats(state.data.statements); @@ -122,7 +122,7 @@ export const selectTotalFingerprints = createSelector( // selectLastReset returns a string displaying the last time the statement // statistics were reset. export const selectLastReset = createSelector(sqlStatsSelector, state => { - if (!state.data) { + if (!state?.data) { return ""; } @@ -153,7 +153,7 @@ export const selectStatements = createSelector( diagnosticsReportsPerStatement, ): AggregateStatistics[] => { // State is valid if we successfully fetched data, and the data has not yet been invalidated. - if (!state.data || !state.valid) { + if (!state?.data || !state?.valid) { return null; } let statements = flattenStatementStats(state.data.statements); @@ -238,7 +238,7 @@ export const selectColumns = createSelector( // return array of columns if user have customized it or `null` otherwise localStorage => localStorage["showColumns/StatementsPage"] - ? localStorage["showColumns/StatementsPage"].split(",") + ? localStorage["showColumns/StatementsPage"]?.split(",") : null, ); diff --git a/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.tsx b/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.tsx index ddbe6bebbae6..42a17e35a88a 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.tsx @@ -546,7 +546,7 @@ export class StatementsPage extends React.Component< // hiding columns that won't be displayed for tenants. const columns = makeStatementsColumns( statements, - filters.app.split(","), + filters.app?.split(","), totalWorkload, nodeRegions, "statement", 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 94e57f8d47c2..10cd3e6c1441 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 @@ -38,6 +38,6 @@ export const selectColumns = createSelector( localStorageSelector, localStorage => localStorage["showColumns/StatementInsightsPage"] - ? localStorage["showColumns/StatementInsightsPage"].split(",") + ? localStorage["showColumns/StatementInsightsPage"]?.split(",") : null, ); diff --git a/pkg/ui/workspaces/cluster-ui/src/store/jobs/jobs.selectors.ts b/pkg/ui/workspaces/cluster-ui/src/store/jobs/jobs.selectors.ts index f5aa7de346b8..2d5fc6288f5d 100644 --- a/pkg/ui/workspaces/cluster-ui/src/store/jobs/jobs.selectors.ts +++ b/pkg/ui/workspaces/cluster-ui/src/store/jobs/jobs.selectors.ts @@ -42,6 +42,6 @@ export const selectColumns = createSelector( // return array of columns if user have customized it or `null` otherwise localStorage => localStorage["showColumns/JobsPage"] - ? localStorage["showColumns/JobsPage"].split(",") + ? localStorage["showColumns/JobsPage"]?.split(",") : null, ); diff --git a/pkg/ui/workspaces/cluster-ui/src/store/sessions/sessions.selectors.ts b/pkg/ui/workspaces/cluster-ui/src/store/sessions/sessions.selectors.ts index b9a4c0e2f46c..65ff4ce97e23 100644 --- a/pkg/ui/workspaces/cluster-ui/src/store/sessions/sessions.selectors.ts +++ b/pkg/ui/workspaces/cluster-ui/src/store/sessions/sessions.selectors.ts @@ -20,7 +20,7 @@ export const selectSession = createSelector( (state: AppState) => state.adminUI?.sessions, (_state: AppState, props: RouteComponentProps) => props, (state: SessionsState, props: RouteComponentProps) => { - if (!state.data) { + if (!state?.data) { return null; } const sessionID = getMatchParamByName(props.match, sessionAttr); 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 d95f71ecfeee..5104eba48ffd 100644 --- a/pkg/ui/workspaces/cluster-ui/src/transactionsPage/transactionsPage.selectors.ts +++ b/pkg/ui/workspaces/cluster-ui/src/transactionsPage/transactionsPage.selectors.ts @@ -30,7 +30,7 @@ export const selectTxnColumns = createSelector( // return array of columns if user have customized it or `null` otherwise localStorage => localStorage["showColumns/TransactionPage"] - ? localStorage["showColumns/TransactionPage"].split(",") + ? localStorage["showColumns/TransactionPage"]?.split(",") : null, ); diff --git a/pkg/ui/workspaces/cluster-ui/src/transactionsPage/utils.ts b/pkg/ui/workspaces/cluster-ui/src/transactionsPage/utils.ts index 84dc6d93ee5b..efe9c04fd16e 100644 --- a/pkg/ui/workspaces/cluster-ui/src/transactionsPage/utils.ts +++ b/pkg/ui/workspaces/cluster-ui/src/transactionsPage/utils.ts @@ -170,8 +170,8 @@ export const filterTransactions = ( activeFilters: 0, }; const timeValue = getTimeValueInSeconds(filters); - const regions = filters.regions.length > 0 ? filters.regions.split(",") : []; - const nodes = filters.nodes.length > 0 ? filters.nodes.split(",") : []; + const regions = filters.regions?.length > 0 ? filters.regions.split(",") : []; + const nodes = filters.nodes?.length > 0 ? filters.nodes.split(",") : []; const activeFilters = calculateActiveFilters(filters); diff --git a/pkg/ui/workspaces/cluster-ui/src/util/format.ts b/pkg/ui/workspaces/cluster-ui/src/util/format.ts index 2a88f3952ffd..c9c0857c536a 100644 --- a/pkg/ui/workspaces/cluster-ui/src/util/format.ts +++ b/pkg/ui/workspaces/cluster-ui/src/util/format.ts @@ -259,7 +259,7 @@ function add(a: string, b: string): string { // to an int64 (in string form). export function HexStringToInt64String(s: string): string { let dec = "0"; - s.split("").forEach(function (chr: string) { + s?.split("").forEach(function (chr: string) { const n = parseInt(chr, 16); for (let t = 8; t; t >>= 1) { dec = add(dec, dec); diff --git a/pkg/ui/workspaces/cluster-ui/src/util/formatNumber.ts b/pkg/ui/workspaces/cluster-ui/src/util/formatNumber.ts index 5352f0009e8d..b106895a63d2 100644 --- a/pkg/ui/workspaces/cluster-ui/src/util/formatNumber.ts +++ b/pkg/ui/workspaces/cluster-ui/src/util/formatNumber.ts @@ -11,7 +11,7 @@ import { isNumber } from "lodash"; function numberToString(n: number) { - return n.toString(); + return n?.toString() || ""; } export function formatNumberForDisplay( diff --git a/pkg/ui/workspaces/db-console/src/redux/indexUsageStats/indexUsageStatsSagas.ts b/pkg/ui/workspaces/db-console/src/redux/indexUsageStats/indexUsageStatsSagas.ts index c030a03a8141..be75504e64fc 100644 --- a/pkg/ui/workspaces/db-console/src/redux/indexUsageStats/indexUsageStatsSagas.ts +++ b/pkg/ui/workspaces/db-console/src/redux/indexUsageStats/indexUsageStatsSagas.ts @@ -36,6 +36,9 @@ export const selectIndexStatsKeys = createSelector( ); export const KeyToTableRequest = (key: string): TableIndexStatsRequest => { + if (!key?.includes("/")) { + return new TableIndexStatsRequest({ database: "", table: "" }); + } const s = key.split("/"); const database = s[0]; const table = s[1]; diff --git a/pkg/ui/workspaces/db-console/src/selectors/activeExecutionsSelectors.ts b/pkg/ui/workspaces/db-console/src/selectors/activeExecutionsSelectors.ts index ace1c112710b..e0440808a7fe 100644 --- a/pkg/ui/workspaces/db-console/src/selectors/activeExecutionsSelectors.ts +++ b/pkg/ui/workspaces/db-console/src/selectors/activeExecutionsSelectors.ts @@ -46,7 +46,7 @@ export const selectActiveStatement = createSelector( export const selectAppName = createSelector( (state: AdminUIState) => state.cachedData.sessions, (state?: CachedDataReducerState) => { - if (!state.data) { + if (!state?.data) { return null; } return state.data.internal_app_name_prefix; diff --git a/pkg/ui/workspaces/db-console/src/util/highlightedText.tsx b/pkg/ui/workspaces/db-console/src/util/highlightedText.tsx index f9f8cd916885..9c547295253c 100644 --- a/pkg/ui/workspaces/db-console/src/util/highlightedText.tsx +++ b/pkg/ui/workspaces/db-console/src/util/highlightedText.tsx @@ -37,7 +37,7 @@ export default function getHighlightedText( }) .join("|"); const parts = isOriginalText - ? text.split(new RegExp(`(${search})`, "gi")) + ? text?.split(new RegExp(`(${search})`, "gi")) : rebaseText(text, highlight).split(new RegExp(`(${search})`, "gi")); const highlightClass = hasDarkBkg ? "_text-bold-light" : "_text-bold"; return parts.map((part, i) => { diff --git a/pkg/ui/workspaces/db-console/src/views/sessions/sessionDetails.tsx b/pkg/ui/workspaces/db-console/src/views/sessions/sessionDetails.tsx index 5c10b78d1780..8dfe58ab436b 100644 --- a/pkg/ui/workspaces/db-console/src/views/sessions/sessionDetails.tsx +++ b/pkg/ui/workspaces/db-console/src/views/sessions/sessionDetails.tsx @@ -39,7 +39,7 @@ export const selectSession = createSelector( state: CachedDataReducerState, props: RouteComponentProps, ) => { - if (!state.data) { + if (!state?.data) { return null; } const sessionID = getMatchParamByName(props.match, sessionAttr); diff --git a/pkg/ui/workspaces/db-console/src/views/sessions/sessionsPage.tsx b/pkg/ui/workspaces/db-console/src/views/sessions/sessionsPage.tsx index 7f19ce38e2eb..19944c1cbc53 100644 --- a/pkg/ui/workspaces/db-console/src/views/sessions/sessionsPage.tsx +++ b/pkg/ui/workspaces/db-console/src/views/sessions/sessionsPage.tsx @@ -48,7 +48,7 @@ export const selectSessions = createSelector( state: CachedDataReducerState, _: RouteComponentProps, ) => { - if (!state.data) { + if (!state?.data) { return null; } return state.data.sessions.map(session => { @@ -64,7 +64,7 @@ export const selectAppName = createSelector( state: CachedDataReducerState, _: RouteComponentProps, ) => { - if (!state.data) { + if (!state?.data) { return null; } return state.data.internal_app_name_prefix; 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 10e57058128b..facf6a8b6605 100644 --- a/pkg/ui/workspaces/db-console/src/views/statements/statementsPage.tsx +++ b/pkg/ui/workspaces/db-console/src/views/statements/statementsPage.tsx @@ -111,7 +111,7 @@ export const selectStatements = createSelector( props: RouteComponentProps, diagnosticsReportsPerStatement, ): AggregateStatistics[] => { - if (!state.data || !state.valid) { + if (!state?.data || !state?.valid) { return null; } let statements = flattenStatementStats(state.data.statements); @@ -191,7 +191,7 @@ export const selectStatements = createSelector( export const selectApps = createSelector( (state: AdminUIState) => state.cachedData.statements, (state: CachedDataReducerState) => { - if (!state.data) { + if (!state?.data) { return []; } @@ -227,7 +227,7 @@ export const selectApps = createSelector( export const selectDatabases = createSelector( (state: AdminUIState) => state.cachedData.statements, (state: CachedDataReducerState) => { - if (!state.data) { + if (!state?.data) { return []; } return Array.from( @@ -247,7 +247,7 @@ export const selectDatabases = createSelector( export const selectTotalFingerprints = createSelector( (state: AdminUIState) => state.cachedData.statements, (state: CachedDataReducerState) => { - if (!state.data) { + if (!state?.data) { return 0; } const aggregated = aggregateStatementStats(state.data.statements); @@ -260,7 +260,7 @@ export const selectTotalFingerprints = createSelector( export const selectLastReset = createSelector( (state: AdminUIState) => state.cachedData.statements, (state: CachedDataReducerState) => { - if (!state.data) { + if (!state?.data) { return "unknown"; } return PrintTime(util.TimestampToMoment(state.data.last_reset)); 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 0e76952a821f..4c3895a6fc65 100644 --- a/pkg/ui/workspaces/db-console/src/views/transactions/transactionsPage.tsx +++ b/pkg/ui/workspaces/db-console/src/views/transactions/transactionsPage.tsx @@ -65,7 +65,7 @@ export const selectData = createSelector( export const selectLastReset = createSelector( (state: AdminUIState) => state.cachedData.statements, (state: CachedDataReducerState) => { - if (!state.data) { + if (!state?.data) { return "unknown"; }