diff --git a/pkg/ui/package.json b/pkg/ui/package.json index e1fb73a0ded0..1f5d542014bb 100644 --- a/pkg/ui/package.json +++ b/pkg/ui/package.json @@ -15,7 +15,7 @@ "cypress:update-snapshots": "yarn cypress run --env updateSnapshots=true --spec 'cypress/integration/**/*.visual.spec.ts'" }, "dependencies": { - "@cockroachlabs/admin-ui-components": "^0.1.28", + "@cockroachlabs/admin-ui-components": "^0.1.37", "analytics-node": "^3.4.0-beta.1", "antd": "^3.25.2", "babel-polyfill": "^6.26.0", diff --git a/pkg/ui/src/app.spec.tsx b/pkg/ui/src/app.spec.tsx index 7be5890f4df2..06f7653abcbb 100644 --- a/pkg/ui/src/app.spec.tsx +++ b/pkg/ui/src/app.spec.tsx @@ -32,8 +32,10 @@ import { } from "src/views/databases/containers/databases"; import { TableMain } from "src/views/databases/containers/tableDetails"; import { DataDistributionPage } from "src/views/cluster/containers/dataDistribution"; -import { StatementsPage } from "@cockroachlabs/admin-ui-components"; -import { StatementDetails } from "src/views/statements/statementDetails"; +import { + StatementsPage, + StatementDetails, +} from "@cockroachlabs/admin-ui-components"; import Debug from "src/views/reports/containers/debug"; import { ReduxDebug } from "src/views/reports/containers/redux"; import { CustomChart } from "src/views/reports/containers/customChart"; diff --git a/pkg/ui/src/redux/analyticsActions.ts b/pkg/ui/src/redux/analyticsActions.ts index e322cf94ce04..397bb62ba547 100644 --- a/pkg/ui/src/redux/analyticsActions.ts +++ b/pkg/ui/src/redux/analyticsActions.ts @@ -15,6 +15,10 @@ export const TRACK_STATEMENTS_SEARCH = export const TRACK_STATEMENTS_PAGINATION = "cockroachui/analytics/TRACK_STATEMENTS_PAGINATION"; export const TRACK_TABLE_SORT = "cockroachui/analytics/TRACK_TABLE_SORT"; +export const TRACK_DOWNLOAD_DIAGNOSTIC_BUNDLE = + "cockroachui/analytics/TRACK_DOWNLOAD_DIAGNOSTIC_BUNDLE"; +export const TRACK_STATEMENT_DETAILS_SUBNAV_SELECTION = + "cockroachui/analytics/TRACK_STATEMENT_DETAILS_SUBNAV_SELECTION"; export interface TableSortActionPayload { tableName: string; @@ -54,3 +58,21 @@ export function trackTableSortAction( }, }; } + +export function trackDownloadDiagnosticsBundleAction( + statementFingerprint: string, +): PayloadAction { + return { + type: TRACK_DOWNLOAD_DIAGNOSTIC_BUNDLE, + payload: statementFingerprint, + }; +} + +export function trackStatementDetailsSubnavSelectionAction( + tabName: string, +): PayloadAction { + return { + type: TRACK_STATEMENT_DETAILS_SUBNAV_SELECTION, + payload: tabName, + }; +} diff --git a/pkg/ui/src/redux/analyticsSagas.ts b/pkg/ui/src/redux/analyticsSagas.ts index 04c2d9a03a63..322b78846eec 100644 --- a/pkg/ui/src/redux/analyticsSagas.ts +++ b/pkg/ui/src/redux/analyticsSagas.ts @@ -21,12 +21,16 @@ import { trackPaginate, trackSearch, trackTableSort, + trackDownloadDiagnosticsBundle, + trackSubnavSelection, } from "src/util/analytics"; import { TRACK_STATEMENTS_SEARCH, TRACK_STATEMENTS_PAGINATION, TRACK_TABLE_SORT, TableSortActionPayload, + TRACK_DOWNLOAD_DIAGNOSTIC_BUNDLE, + TRACK_STATEMENT_DETAILS_SUBNAV_SELECTION, } from "./analyticsActions"; export function* trackActivateStatementsDiagnostics( @@ -58,6 +62,18 @@ export function* trackTableSortChange( yield call(trackTableSort, tableName, columnName, ascending); } +export function* trackDownloadDiagnosticBundleSaga( + action: PayloadAction, +) { + yield call(trackDownloadDiagnosticsBundle, action.payload); +} + +export function* trackStatementDetailsSubnavSelectionSaga( + action: PayloadAction, +) { + yield call(trackSubnavSelection, action.payload); +} + export function* analyticsSaga() { yield all([ takeEvery( @@ -68,5 +84,13 @@ export function* analyticsSaga() { takeEvery(TRACK_STATEMENTS_SEARCH, trackStatementsSearch), takeEvery(TRACK_STATEMENTS_PAGINATION, trackStatementsPagination), takeEvery(TRACK_TABLE_SORT, trackTableSortChange), + takeEvery( + TRACK_DOWNLOAD_DIAGNOSTIC_BUNDLE, + trackDownloadDiagnosticBundleSaga, + ), + takeEvery( + TRACK_STATEMENT_DETAILS_SUBNAV_SELECTION, + trackStatementDetailsSubnavSelectionSaga, + ), ]); } diff --git a/pkg/ui/src/views/statements/statementDetails.fixture.ts b/pkg/ui/src/views/statements/statementDetails.fixture.ts deleted file mode 100644 index 0f73b81426d5..000000000000 --- a/pkg/ui/src/views/statements/statementDetails.fixture.ts +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright 2020 The Cockroach Authors. -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -import Long from "long"; -import { createMemoryHistory } from "history"; -import { StatementDetailsProps } from "src/views/statements/statementDetails"; -import { - refreshStatementDiagnosticsRequests, - refreshStatements, -} from "oss/src/redux/apiReducers"; - -const history = createMemoryHistory({ initialEntries: ["/statements"] }); - -const statementStats: any = { - count: Long.fromNumber(36958), - first_attempt_count: Long.fromNumber(36958), - max_retries: Long.fromNumber(0), - num_rows: { - mean: 11.651577466313078, - squared_diffs: 1493154.3630337175, - }, - parse_lat: { - mean: 0, - squared_diffs: 0, - }, - plan_lat: { - mean: 0.00022804377942529385, - squared_diffs: 0.0030062544511648935, - }, - run_lat: { - mean: 0.00098355830943233, - squared_diffs: 0.04090499253784317, - }, - service_lat: { - mean: 0.0013101634016992284, - squared_diffs: 0.055668241814216965, - }, - overhead_lat: { - mean: 0.00009856131284160407, - squared_diffs: 0.0017520019405651047, - }, - bytes_read: { - mean: 4160407, - squared_diffs: 47880000000000, - }, - rows_read: { - mean: 7, - squared_diffs: 1000000, - }, - sensitive_info: { - last_err: "", - most_recent_plan_description: { - name: "render", - attrs: [ - { - key: "render", - value: "city", - }, - { - key: "render", - value: "id", - }, - ], - children: [ - { - name: "scan", - attrs: [ - { - key: "table", - value: "vehicles@vehicles_auto_index_fk_city_ref_users", - }, - { - key: "spans", - value: "1 span", - }, - ], - children: [], - }, - ], - }, - }, -}; - -export const statementDetailsPropsFixture: StatementDetailsProps = { - history, - location: { - pathname: - "/statement/true/SELECT city%2C id FROM vehicles WHERE city %3D %241", - search: "", - hash: "", - state: null, - }, - match: { - path: "/statement/:implicitTxn/:statement", - url: "/statement/true/SELECT city%2C id FROM vehicles WHERE city %3D %241", - isExact: true, - params: { - implicitTxn: "true", - statement: "SELECT city%2C id FROM vehicles WHERE city %3D %241", - }, - }, - statement: { - statement: "SELECT city, id FROM vehicles WHERE city = $1", - stats: statementStats, - byNode: [ - { - label: "4", - implicitTxn: true, - stats: statementStats, - }, - { - label: "3", - implicitTxn: true, - stats: statementStats, - }, - { - label: "2", - implicitTxn: true, - stats: statementStats, - }, - { - label: "1", - implicitTxn: true, - stats: statementStats, - }, - ], - app: ["movr"], - distSQL: { - numerator: 0, - denominator: 36958, - }, - vec: { - numerator: 36958, - denominator: 36958, - }, - opt: { - numerator: 36958, - denominator: 36958, - }, - implicit_txn: { - numerator: 36958, - denominator: 36958, - }, - failed: { - numerator: 0, - denominator: 36958, - }, - node_id: [4, 3, 2, 1], - }, - statementsError: null, - nodeNames: { - "1": "127.0.0.1:55529 (n1)", - "2": "127.0.0.1:55532 (n2)", - "3": "127.0.0.1:55538 (n3)", - "4": "127.0.0.1:55546 (n4)", - }, - diagnosticsCount: 1, - nodesSummary: undefined, - refreshStatements: (() => {}) as typeof refreshStatements, - refreshStatementDiagnosticsRequests: (() => {}) as typeof refreshStatementDiagnosticsRequests, -}; diff --git a/pkg/ui/src/views/statements/statementDetails.module.styl b/pkg/ui/src/views/statements/statementDetails.module.styl deleted file mode 100644 index cbddc585bbcd..000000000000 --- a/pkg/ui/src/views/statements/statementDetails.module.styl +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2020 The Cockroach Authors. -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -@require '~styl/base/palette.styl' -@require '~src/views/shared/util/table.styl' -@require '~src/components/core/index' - -.app-name - white-space nowrap - - &__unset - color $tooltip-color - font-style italic - -.section - flex 0 0 auto - padding 12px 24px - max-width $max-window-width - clearfix() - - &--heading - padding-top 0 - padding-bottom 0 - - &--container - padding 0 24px 0 0 - -.page--header - padding 0 - &__title - font-family $font-family--base - font-size 20px - line-height 1.6 - letter-spacing -0.2px - color $colors--neutral-8 - margin-bottom 25px - -.base-heading - composes base-heading from '~styl/base/typography.styl' - -.back-link - text-decoration none - color $link-color - -.cockroach--tabs - :global(.ant-tabs-bar) - border-bottom 1px solid $grey2 - :global(.ant-tabs-tab) - font-family SourceSansPro-Regular - font-size 16px - line-height 1.5 - letter-spacing normal - color $placeholder - &:hover - color $adminui-grey-1 - :global(.ant-tabs-tab-active) - color $adminui-grey-1 - :global(.ant-tabs-ink-bar) - height 3px - border-radius 40px - background-color $blue - -@require "~src/components/core/index.styl" - -.table-details - .summary--card__counting - margin-bottom 15px - &--value - margin 0 - color $colors--neutral-8 - line-height 32px - &--label - font-size 14px - line-height 22px - letter-spacing 0.1px - -.last-cleared-tooltip, .numeric-stats-table, .plan-view-table - &__tooltip - width 36px // Reserve space for 10px padding around centered 16px icon - height 16px - display inline-block - - // Overrides to let the tooltip sit inside a table header. - text-transform none - font-weight normal - white-space normal - letter-spacing normal - font-size 14px - - &__tooltip-hover-area - width 100% - padding 0px 10px - - &__info-icon - width 16px - height 16px - border-radius 50% - border 1px solid $tooltip-color - font-size 12px - line-height 14px // Visual tweak to vertically center the "i" - text-align center - color $tooltip-color - - .hover-tooltip--hovered &__info-icon - border-color $body-color - color $body-color - -.statements-table - &__col-query-text - font-family $font-family--monospace - white-space pre-wrap diff --git a/pkg/ui/src/views/statements/statementDetails.stories.tsx b/pkg/ui/src/views/statements/statementDetails.stories.tsx deleted file mode 100644 index b268c1c4a1a0..000000000000 --- a/pkg/ui/src/views/statements/statementDetails.stories.tsx +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2020 The Cockroach Authors. -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -import React from "react"; -import { storiesOf } from "@storybook/react"; -import { Location } from "history"; - -import { - withBackgroundFactory, - withRouterProvider, -} from ".storybook/decorators"; -import { StatementDetails } from "./statementDetails"; -import { statementDetailsPropsFixture } from "./statementDetails.fixture"; - -// (koorosh) Note: Diagnostics tab isn't added here because it is independent -// connected view and has to be managed as separate story. - -storiesOf("StatementDetails", module) - .addDecorator(withRouterProvider) - .addDecorator(withBackgroundFactory()) - .add("Overview tab", () => ( - - )) - .add("Logical Plan tab", () => { - const location: Location = { - ...statementDetailsPropsFixture.history.location, - search: new URLSearchParams([["tab", "logical-plan"]]).toString(), - }; - return ( - - ); - }) - .add("Execution Stats tab", () => { - const location: Location = { - ...statementDetailsPropsFixture.history.location, - search: new URLSearchParams([["tab", "execution-stats"]]).toString(), - }; - return ( - - ); - }); diff --git a/pkg/ui/src/views/statements/statementDetails.tsx b/pkg/ui/src/views/statements/statementDetails.tsx index 19850f422116..db90c64031ab 100644 --- a/pkg/ui/src/views/statements/statementDetails.tsx +++ b/pkg/ui/src/views/statements/statementDetails.tsx @@ -7,839 +7,50 @@ // the Business Source License, use of this software will be governed // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. - -import { Col, Row, Tabs } from "antd"; -import _ from "lodash"; -import React, { ReactNode } from "react"; -import { Helmet } from "react-helmet"; import { connect } from "react-redux"; import { - Link, RouteComponentProps, match as Match, withRouter, } from "react-router-dom"; import { createSelector } from "reselect"; +import _ from "lodash"; import { refreshStatementDiagnosticsRequests, refreshStatements, } from "src/redux/apiReducers"; -import { nodeDisplayNameByIDSelector, NodesSummary } from "src/redux/nodes"; +import { nodeDisplayNameByIDSelector } from "src/redux/nodes"; import { AdminUIState } from "src/redux/state"; import { combineStatementStats, ExecutionStatistics, flattenStatementStats, - NumericStat, StatementStatistics, - stdDev, } from "src/util/appStats"; import { appAttr, implicitTxnAttr, statementAttr } from "src/util/constants"; import { FixLong } from "src/util/fixLong"; -import { Bytes, Duration } from "src/util/format"; -import { intersperse } from "src/util/intersperse"; -import { Pick } from "src/util/pick"; -import { Loading } from "@cockroachlabs/admin-ui-components"; -import { SortSetting } from "src/views/shared/components/sortabletable"; -import SqlBox from "src/views/shared/components/sql/box"; -import { formatNumberForDisplay } from "src/views/shared/components/summaryBar"; -import { ToolTipWrapper } from "src/views/shared/components/toolTip"; -import { PlanView } from "src/views/statements/planView"; -import { SummaryCard } from "../shared/components/summaryCard"; +import { AggregateStatistics } from "./statementsTable"; +import { getMatchParamByName } from "src/util/query"; +import { selectDiagnosticsReportsByStatementFingerprint } from "src/redux/statements/statementsSelectors"; import { - approximify, - latencyBreakdown, - genericBarChart, - longToInt, - rowsBreakdown, -} from "./barCharts"; + StatementDetails, + StatementDetailsDispatchProps, + StatementDetailsStateProps, + StatementDetailsProps, +} from "@cockroachlabs/admin-ui-components"; +import { createStatementDiagnosticsReportAction } from "src/redux/statements"; +import { createStatementDiagnosticsAlertLocalSetting } from "src/redux/alerts"; import { - AggregateStatistics, - makeNodesColumns, - StatementsSortedTable, -} from "./statementsTable"; -import { getMatchParamByName } from "src/util/query"; -import DiagnosticsView from "./diagnostics"; -import classNames from "classnames/bind"; -import { selectDiagnosticsReportsCountByStatementFingerprint } from "src/redux/statements/statementsSelectors"; -import { Button } from "@cockroachlabs/admin-ui-components"; -import { ArrowLeft } from "@cockroachlabs/icons"; -import { trackSubnavSelection } from "src/util/analytics"; -import styles from "./statementDetails.module.styl"; -import sortableTableStyles from "src/views/shared/components/sortabletable/sortabletable.module.styl"; -import summaryCardStyles from "src/views/shared/components/summaryCard/summaryCard.module.styl"; -import d3 from "d3"; - -const { TabPane } = Tabs; + trackDownloadDiagnosticsBundleAction, + trackStatementDetailsSubnavSelectionAction, +} from "src/redux/analyticsActions"; interface Fraction { numerator: number; denominator: number; } -interface SingleStatementStatistics { - statement: string; - app: string[]; - distSQL: Fraction; - vec: Fraction; - opt: Fraction; - implicit_txn: Fraction; - failed: Fraction; - node_id: number[]; - stats: StatementStatistics; - byNode: AggregateStatistics[]; -} - -const cx = classNames.bind(styles); -const sortableTableCx = classNames.bind(sortableTableStyles); -const summaryCardStylesCx = classNames.bind(summaryCardStyles); - -function AppLink(props: { app: string }) { - if (!props.app) { - return (unset); - } - - return ( - - {props.app} - - ); -} - -interface StatementDetailsOwnProps { - statement: SingleStatementStatistics; - statementsError: Error | null; - nodeNames: { [nodeId: string]: string }; - refreshStatements: typeof refreshStatements; - refreshStatementDiagnosticsRequests: typeof refreshStatementDiagnosticsRequests; - nodesSummary: NodesSummary; - diagnosticsCount: number; -} - -export type StatementDetailsProps = StatementDetailsOwnProps & - RouteComponentProps; - -interface StatementDetailsState { - sortSetting: SortSetting; - currentTab?: string; -} - -interface NumericStatRow { - name: string; - value: NumericStat; - bar?: () => ReactNode; - summary?: boolean; - // You can override the table's formatter on a per-row basis with this format - // method. - format?: (v: number) => string; -} - -interface NumericStatTableProps { - title?: string; - description?: string; - measure: string; - rows: NumericStatRow[]; - count: number; - format?: (v: number) => string; -} - -class NumericStatTable extends React.Component { - static defaultProps = { - format: (v: number) => `${v}`, - }; - - render() { - const { rows } = this.props; - return ( - - - - - - - - - - {rows.map((row: NumericStatRow) => { - let { format } = this.props; - if (row.format) { - format = row.format; - } - const className = sortableTableCx( - "sort-table__row", - "sort-table__row--body", - { - "sort-table__row--summary": row.summary, - }, - ); - return ( - - - - - - ); - })} - -
- {this.props.title} - - Mean {this.props.measure} - - Standard Deviation -
- {row.name} - - {row.bar ? row.bar() : null} - - {format(stdDev(row.value, this.props.count))} -
- ); - } -} - -export class StatementDetails extends React.Component< - StatementDetailsProps, - StatementDetailsState -> { - constructor(props: StatementDetailsProps) { - super(props); - const searchParams = new URLSearchParams(props.history.location.search); - this.state = { - sortSetting: { - sortKey: 5, // Latency - ascending: false, - }, - currentTab: searchParams.get("tab") || "overview", - }; - } - - changeSortSetting = (ss: SortSetting) => { - this.setState({ - sortSetting: ss, - }); - }; - - componentDidMount() { - this.props.refreshStatements(); - this.props.refreshStatementDiagnosticsRequests(); - } - - componentDidUpdate() { - this.props.refreshStatements(); - this.props.refreshStatementDiagnosticsRequests(); - } - - backToStatementsPage = () => { - const { history, location } = this.props; - history.push({ - ...location, - pathname: "/statements", - }); - }; - - onTabChange = (tabId: string) => { - const { history } = this.props; - const searchParams = new URLSearchParams(history.location.search); - searchParams.set("tab", tabId); - trackSubnavSelection(tabId); - history.replace({ - ...history.location, - search: searchParams.toString(), - }); - this.setState({ - currentTab: tabId, - }); - }; - - render() { - const app = getMatchParamByName(this.props.match, appAttr); - return ( -
- -
- -

- Statement Details -

-
-
- -
-
- ); - } - - renderContent = () => { - const { diagnosticsCount } = this.props; - const { currentTab } = this.state; - - if (!this.props.statement) { - return null; - } - const { - stats, - statement, - app, - distSQL, - vec, - opt, - failed, - implicit_txn, - } = this.props.statement; - - if (!stats) { - const sourceApp = getMatchParamByName(this.props.match, appAttr); - const listUrl = "/statements" + (sourceApp ? "/" + sourceApp : ""); - - return ( - -
- -
-
-

Unable to find statement

- There are no execution statistics for this statement.{" "} - - Back to Statements - -
-
- ); - } - - const count = FixLong(stats.count).toInt(); - - const { rowsBarChart } = rowsBreakdown(this.props.statement); - const { - parseBarChart, - planBarChart, - runBarChart, - overheadBarChart, - overallBarChart, - } = latencyBreakdown(this.props.statement); - - const totalCountBarChart = longToInt(this.props.statement.stats.count); - const firstAttemptsBarChart = longToInt( - this.props.statement.stats.first_attempt_count, - ); - const retriesBarChart = totalCountBarChart - firstAttemptsBarChart; - const maxRetriesBarChart = longToInt( - this.props.statement.stats.max_retries, - ); - - const statsByNode = this.props.statement.byNode; - const logicalPlan = - stats.sensitive_info && stats.sensitive_info.most_recent_plan_description; - const duration = (v: number) => Duration(v * 1e9); - return ( - - - - - - - - - - -
-

- {formatNumberForDisplay( - count * stats.service_lat.mean, - duration, - )} -

-

- Total Time -

-
- - -
-

- {formatNumberForDisplay( - stats.service_lat.mean, - duration, - )} -

-

- Mean Service Latency -

-
- -
-

-

-

- App: -

-

- {intersperse( - app.map((a) => ), - ", ", - )} -

-
-
-

- Transaction Type -

-

- {renderTransactionType(implicit_txn)} -

-
-
-

- Distributed execution? -

-

- {renderBools(distSQL)} -

-
-
-

- Vectorized execution? -

-

- {renderBools(vec)} -

-
-
-

- Used cost-based optimizer? -

-

- {renderBools(opt)} -

-
-
-

- Failed? -

-

- {renderBools(failed)} -

-
-
- -

- Execution Count -

-
-

- First Attempts -

-

- {firstAttemptsBarChart} -

-
-
-

- Retries -

-

0, - }, - )} - > - {retriesBarChart} -

-
-
-

- Max Retries -

-

0, - }, - )} - > - {maxRetriesBarChart} -

-
-
-

- Total -

-

- {totalCountBarChart} -

-
-

-

- Rows Affected -

-
-

- Mean Rows -

-

- {rowsBarChart(true)} -

-
-
-

- Standard Deviation -

-

- {rowsBarChart()} -

-
-
- -
-
- 0 ? `(${diagnosticsCount})` : "" - }`} - key="diagnostics" - > - - - - - - - - - -

- Execution Latency By Phase -
- -
-
- i -
-
-
-
-

- Duration(v * 1e9)} - rows={[ - { name: "Parse", value: stats.parse_lat, bar: parseBarChart }, - { name: "Plan", value: stats.plan_lat, bar: planBarChart }, - { name: "Run", value: stats.run_lat, bar: runBarChart }, - { - name: "Overhead", - value: stats.overhead_lat, - bar: overheadBarChart, - }, - { - name: "Overall", - summary: true, - value: stats.service_lat, - bar: overallBarChart, - }, - ]} - /> -
- -

- Other Execution Statistics -

- -
- -

- Stats By Node -
- -
-
- i -
-
-
-
-

- -
-
-
- ); - }; -} - -function renderTransactionType(implicitTxn: Fraction) { - if (Number.isNaN(implicitTxn.numerator)) { - return "(unknown)"; - } - if (implicitTxn.numerator === 0) { - return "Explicit"; - } - if (implicitTxn.numerator === implicitTxn.denominator) { - return "Implicit"; - } - const fraction = - approximify(implicitTxn.numerator) + - " of " + - approximify(implicitTxn.denominator); - return `${fraction} were Implicit Txns`; -} - -function renderBools(fraction: Fraction) { - if (Number.isNaN(fraction.numerator)) { - return "(unknown)"; - } - if (fraction.numerator === 0) { - return "No"; - } - if (fraction.numerator === fraction.denominator) { - return "Yes"; - } - return ( - approximify(fraction.numerator) + " of " + approximify(fraction.denominator) - ); -} - -type StatementsState = Pick; - interface StatementDetailsData { nodeId: number; implicitTxn: boolean; @@ -925,8 +136,8 @@ function filterByRouterParamsPredicate( } export const selectStatement = createSelector( - (state: StatementsState) => state.cachedData.statements, - (_state: StatementsState, props: RouteComponentProps) => props, + (state: AdminUIState) => state.cachedData.statements, + (_state: AdminUIState, props: RouteComponentProps) => props, (statementsState, props) => { const statements = statementsState.data?.statements; if (!statements) { @@ -956,26 +167,36 @@ export const selectStatement = createSelector( }, ); -const mapStateToProps = (state: AdminUIState, props: StatementDetailsProps) => { +const mapStateToProps = ( + state: AdminUIState, + props: StatementDetailsProps, +): StatementDetailsStateProps => { const statement = selectStatement(state, props); + const statementFingerprint = statement?.statement; return { statement, statementsError: state.cachedData.statements.lastError, nodeNames: nodeDisplayNameByIDSelector(state), - diagnosticsCount: selectDiagnosticsReportsCountByStatementFingerprint( + diagnosticsReports: selectDiagnosticsReportsByStatementFingerprint( state, - statement?.statement, + statementFingerprint, ), }; }; -const mapDispatchToProps = { +const mapDispatchToProps: StatementDetailsDispatchProps = { refreshStatements, refreshStatementDiagnosticsRequests, + dismissStatementDiagnosticsAlertMessage: () => + createStatementDiagnosticsAlertLocalSetting.set({ show: false }), + createStatementDiagnosticsReport: createStatementDiagnosticsReportAction, + onTabChanged: trackStatementDetailsSubnavSelectionAction, + onDiagnosticBundleDownload: trackDownloadDiagnosticsBundleAction, }; -const StatementDetailsConnected = withRouter( - connect(mapStateToProps, mapDispatchToProps)(StatementDetails), +export default withRouter( + connect( + mapStateToProps, + mapDispatchToProps, + )(StatementDetails), ); - -export default StatementDetailsConnected; diff --git a/pkg/ui/src/views/statements/statementsPage.tsx b/pkg/ui/src/views/statements/statementsPage.tsx index 121ce86d9d9e..f8b084ce668b 100644 --- a/pkg/ui/src/views/statements/statementsPage.tsx +++ b/pkg/ui/src/views/statements/statementsPage.tsx @@ -42,11 +42,11 @@ import { createStatementDiagnosticsReportAction, } from "src/redux/statements"; import { + trackDownloadDiagnosticsBundleAction, trackStatementsPaginationAction, trackStatementsSearchAction, trackTableSortAction, } from "src/redux/analyticsActions"; -import { trackDownloadDiagnosticsBundle } from "src/util/analytics"; type ICollectedStatementStatistics = protos.cockroach.server.serverpb.StatementsResponse.ICollectedStatementStatistics; type IStatementDiagnosticsReport = protos.cockroach.server.serverpb.IStatementDiagnosticsReport; @@ -187,7 +187,7 @@ export const selectLastReset = createSelector( }, ); -const StatementsPageConnected = withRouter( +export default withRouter( connect( (state: AdminUIState, props: RouteComponentProps) => ({ statements: selectStatements(state, props), @@ -208,9 +208,7 @@ const StatementsPageConnected = withRouter( onPageChanged: trackStatementsPaginationAction, onSortingChange: trackTableSortAction, onDiagnosticsReportDownload: (report: IStatementDiagnosticsReport) => - trackDownloadDiagnosticsBundle(report.statement_fingerprint), + trackDownloadDiagnosticsBundleAction(report.statement_fingerprint), }, )(StatementsPage), ); - -export default StatementsPageConnected; diff --git a/pkg/ui/yarn-vendor b/pkg/ui/yarn-vendor index d27dd45245a0..e27b89eae0a9 160000 --- a/pkg/ui/yarn-vendor +++ b/pkg/ui/yarn-vendor @@ -1 +1 @@ -Subproject commit d27dd45245a09c58bb7d904bec2eecf921cb94b1 +Subproject commit e27b89eae0a9536fbd32378e06efe9f9a888134f diff --git a/pkg/ui/yarn.lock b/pkg/ui/yarn.lock index 7405dd585dd5..0dfe120bb2d3 100644 --- a/pkg/ui/yarn.lock +++ b/pkg/ui/yarn.lock @@ -1806,14 +1806,14 @@ lodash "^4.17.13" to-fast-properties "^2.0.0" -"@cockroachlabs/admin-ui-components@^0.1.28": - version "0.1.28" - resolved "https://registry.yarnpkg.com/@cockroachlabs/admin-ui-components/-/admin-ui-components-0.1.28.tgz#6dc00be6d4ea41dd9853e2f754c85936f701950c" - integrity sha512-i12d+wtCgxBM25ZpkcLtyuIRlhl/jZreFmJV1SpQYLVLV7OxLTb5WHlUCMMQFC85+UFVPs7evr2l575Ne5AzvA== - dependencies: - "@cockroachlabs/crdb-protobuf-client" "^0.0.3" - "@cockroachlabs/icons" "^0.2.2" - "@cockroachlabs/ui-components" "^0.2.11" +"@cockroachlabs/admin-ui-components@^0.1.37": + version "0.1.37" + resolved "https://registry.yarnpkg.com/@cockroachlabs/admin-ui-components/-/admin-ui-components-0.1.37.tgz#0862de4864777e0ddf1201febba24cfbd7f897f8" + integrity sha512-tvZ0skbITu4uky9xPxrq5kVXbSgLKikOKqUflH05o/Li8wudoJX4Br71S3lwENeTKlOB2FF3YvXwQYtdDhdB9A== + dependencies: + "@cockroachlabs/crdb-protobuf-client" "^0.0.4" + "@cockroachlabs/icons" "0.3.0" + "@cockroachlabs/ui-components" "0.2.14-alpha.0" "@popperjs/core" "^2.4.0" "@reduxjs/toolkit" "^1.5.0" babel-polyfill "^6.26.0" @@ -1822,15 +1822,16 @@ d3-scale "^3.2.3" highlight.js "^10.2.0" long "^4.0.0" + npm-run-all "^4.1.5" react-helmet "^5.2.0" react-popper "^2.2.3" react-select "^1.2.1" reselect "^4.0.0" -"@cockroachlabs/crdb-protobuf-client@^0.0.3": - version "0.0.3" - resolved "https://registry.yarnpkg.com/@cockroachlabs/crdb-protobuf-client/-/crdb-protobuf-client-0.0.3.tgz#3ce8dd4953a1209f1895c713cf90595a15c54ab1" - integrity sha512-AXHWWW7hI05hj5fTdXgIIjfZrqfacQ/zsT83LoUsrnFUOeWZCa6qSF3qVonaR2h8FloRfEeFhC+27TDsi8RI0A== +"@cockroachlabs/crdb-protobuf-client@^0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@cockroachlabs/crdb-protobuf-client/-/crdb-protobuf-client-0.0.4.tgz#9b53ef7cbb187cd7d73b4269c95b0f573caafd45" + integrity sha512-n/SEmLzU7i9h5m8cAw9NPRAcQSgzHNdm5+F0dN3myfQSCuEMTgTlbWsfm6EnjTRL1FnZlieqY2gZzgyx4Ke0Ug== "@cockroachlabs/eslint-config@^0.1.11": version "0.1.11" @@ -1840,20 +1841,15 @@ "@typescript-eslint/parser" "^2.34.0" eslint-config-prettier "^6.11.0" -"@cockroachlabs/icons@^0.2.2": - version "0.2.9" - resolved "https://registry.yarnpkg.com/@cockroachlabs/icons/-/icons-0.2.9.tgz#b626fe409ea49be66b19a9a0358a082d50ade2d0" - integrity sha512-s1kH8sU/DeIGrGUwUMVMAjxGTOn6SHAK8q0tmJY6zcIUaoYb/5UfnQjzdG+ybflgBdyMTB41/bWTXnpsYxxSpw== - -"@cockroachlabs/icons@^0.3.0": +"@cockroachlabs/icons@0.3.0", "@cockroachlabs/icons@^0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@cockroachlabs/icons/-/icons-0.3.0.tgz#160573074396f266e92fcbe5e520c5ba1d8750f9" integrity sha512-GJxhlXy8Z3/PYFb9C3iM1dvU9wajGoaA/+VCj0an2ipfbkI2fhToq+h0b33vu7JuZ3dS4QMRjfVE4uhlyIUH2Q== -"@cockroachlabs/ui-components@^0.2.11": - version "0.2.12" - resolved "https://registry.yarnpkg.com/@cockroachlabs/ui-components/-/ui-components-0.2.12.tgz#617f97351606026a0a8e5f17730f313b6cc6000c" - integrity sha512-RHRvKAsaUDJpCefA3VqmQSbu1jc1/TM2cEKkhN9G4gg2KgePcaqHupUc4wTD+rw6OqZHwbOHQe9ESXBMhiMVLw== +"@cockroachlabs/ui-components@0.2.14-alpha.0": + version "0.2.14-alpha.0" + resolved "https://registry.yarnpkg.com/@cockroachlabs/ui-components/-/ui-components-0.2.14-alpha.0.tgz#d90a7ce6fbede9d8b177d9a940f24218c262d267" + integrity sha512-8zOZswstmBbJQxXmQ7yfEXEOEUJ4JT5xlzGQ5FD7z8sjy67UDXFoe6FMqFXrkpiFwlCrkK2Vd36LKhLvGY2xkA== dependencies: "@cockroachlabs/icons" "^0.3.0" "@popperjs/core" "^2.4.3" @@ -5121,7 +5117,7 @@ create-react-context@0.3.0, create-react-context@^0.3.0: gud "^1.0.0" warning "^4.0.3" -cross-spawn@6.0.5, cross-spawn@^6.0.0: +cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== @@ -8683,6 +8679,16 @@ load-json-file@^1.0.0, load-json-file@^1.1.0: pinkie-promise "^2.0.0" strip-bom "^2.0.0" +load-json-file@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" + integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= + dependencies: + graceful-fs "^4.1.2" + parse-json "^4.0.0" + pify "^3.0.0" + strip-bom "^3.0.0" + loader-runner@^2.3.1, loader-runner@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" @@ -9025,6 +9031,11 @@ memory-fs@^0.5.0: errno "^0.1.3" readable-stream "^2.0.1" +memorystream@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" + integrity sha1-htcJCzDORV1j+64S3aUaR93K+bI= + merge-deep@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/merge-deep/-/merge-deep-3.0.2.tgz#f39fa100a4f1bd34ff29f7d2bf4508fbb8d83ad2" @@ -9569,6 +9580,21 @@ npm-packlist@^1.1.6: ignore-walk "^3.0.1" npm-bundled "^1.0.1" +npm-run-all@^4.1.5: + version "4.1.5" + resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.5.tgz#04476202a15ee0e2e214080861bff12a51d98fba" + integrity sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ== + dependencies: + ansi-styles "^3.2.1" + chalk "^2.4.1" + cross-spawn "^6.0.5" + memorystream "^0.3.1" + minimatch "^3.0.4" + pidtree "^0.3.0" + read-pkg "^3.0.0" + shell-quote "^1.6.1" + string.prototype.padend "^3.0.0" + npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -10212,6 +10238,11 @@ picomatch@^2.2.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== +pidtree@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.1.tgz#ef09ac2cc0533df1f3250ccf2c4d366b0d12114a" + integrity sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA== + pify@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -11607,6 +11638,15 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" +read-pkg@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" + integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k= + dependencies: + load-json-file "^4.0.0" + normalize-package-data "^2.3.2" + path-type "^3.0.0" + "readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" @@ -12369,6 +12409,11 @@ shell-quote@1.6.1: array-reduce "~0.0.0" jsonify "~0.0.0" +shell-quote@^1.6.1: + version "1.7.2" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" + integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== + shelljs@^0.8.2: version "0.8.3" resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.3.tgz#a7f3319520ebf09ee81275b2368adb286659b097" @@ -12965,6 +13010,11 @@ strip-bom@^2.0.0: dependencies: is-utf8 "^0.2.0" +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= + strip-eof@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"