From 3124c0a52842d0219d7b4f9df268f974c01aa67b Mon Sep 17 00:00:00 2001 From: Andrii Vorobiov Date: Mon, 14 Dec 2020 12:15:16 +0200 Subject: [PATCH 1/2] ui: re-import statement details page Statement details and Diagnostics view pages are moved out to `admin-ui-components` package and now they are imported back as a reusable components. In addition to changed imports, StatementDetails component now exposes additional props for Diagnostics view tab. Before, StatementDetails component used Diagnostics view connected to store and now it uses pure component. This change were made to provide single point of integration to redux store (the same way as other components connected). Release note: None --- pkg/ui/package.json | 2 +- pkg/ui/src/app.spec.tsx | 6 +- pkg/ui/src/redux/analyticsActions.ts | 22 + pkg/ui/src/redux/analyticsSagas.ts | 24 + .../statements/statementDetails.fixture.ts | 168 ---- .../statements/statementDetails.module.styl | 118 --- .../statements/statementDetails.stories.tsx | 60 -- .../src/views/statements/statementDetails.tsx | 849 +----------------- .../src/views/statements/statementsPage.tsx | 8 +- pkg/ui/yarn-vendor | 2 +- pkg/ui/yarn.lock | 96 +- 11 files changed, 163 insertions(+), 1192 deletions(-) delete mode 100644 pkg/ui/src/views/statements/statementDetails.fixture.ts delete mode 100644 pkg/ui/src/views/statements/statementDetails.module.styl delete mode 100644 pkg/ui/src/views/statements/statementDetails.stories.tsx 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" From 724cf5294f5238b65ad39e8834aeec381a7ab52c Mon Sep 17 00:00:00 2001 From: Andrii Vorobiov Date: Wed, 13 Jan 2021 13:03:37 +0200 Subject: [PATCH 2/2] ui: reuse components from admin-ui-components package Currently, many components were exported to `admin-ui-components` package and these components weren't removed from this repo. To avoid duplicates, these components removed and re-imported back. Release note: None --- .../clusterviz/containers/map/breadcrumbs.tsx | 4 +- .../components/downloadFile/downloadFile.tsx | 86 --- pkg/ui/src/components/downloadFile/index.ts | 11 - pkg/ui/src/components/index.ts | 2 - pkg/ui/src/components/table/index.ts | 11 - pkg/ui/src/components/table/table.styl | 97 --- pkg/ui/src/components/table/table.tsx | 63 -- pkg/ui/src/redux/nodes.ts | 5 +- pkg/ui/src/util/intersperse.spec.ts | 30 - pkg/ui/src/util/intersperse.ts | 21 - .../containers/nodesOverview/index.tsx | 15 +- .../nodeHistory/decommissionedNodeHistory.tsx | 3 +- .../statementDiagnosticsHistory/index.tsx | 20 +- pkg/ui/src/views/sessions/sessionDetails.tsx | 2 +- pkg/ui/src/views/sessions/sessionsTable.tsx | 2 +- .../views/statements/barCharts.module.styl | 77 --- .../views/statements/barCharts.stories.tsx | 105 ---- pkg/ui/src/views/statements/barCharts.tsx | 581 ------------------ .../diagnostics/diagnosticsUtils.ts | 79 --- .../diagnostics/diagnosticsView.module.styl | 66 -- .../diagnostics/diagnosticsView.spec.tsx | 130 ---- .../diagnostics/diagnosticsView.tsx | 274 --------- .../src/views/statements/diagnostics/index.ts | 6 +- pkg/ui/src/views/statements/planView/index.ts | 11 - .../statements/planView/planView.fixtures.tsx | 193 ------ .../statements/planView/planView.module.styl | 199 ------ .../statements/planView/planView.spec.tsx | 415 ------------- .../statements/planView/planView.stories.tsx | 18 - .../views/statements/planView/planView.tsx | 373 ----------- .../src/views/statements/statementDetails.tsx | 2 +- .../statements/statementsTable.module.styl | 50 -- .../statements/statementsTable.stories.tsx | 39 -- .../src/views/statements/statementsTable.tsx | 180 ------ .../statements/statementsTableContent.tsx | 291 --------- 34 files changed, 28 insertions(+), 3433 deletions(-) delete mode 100644 pkg/ui/src/components/downloadFile/downloadFile.tsx delete mode 100644 pkg/ui/src/components/downloadFile/index.ts delete mode 100644 pkg/ui/src/components/table/index.ts delete mode 100644 pkg/ui/src/components/table/table.styl delete mode 100644 pkg/ui/src/components/table/table.tsx delete mode 100644 pkg/ui/src/util/intersperse.spec.ts delete mode 100644 pkg/ui/src/util/intersperse.ts delete mode 100644 pkg/ui/src/views/statements/barCharts.module.styl delete mode 100644 pkg/ui/src/views/statements/barCharts.stories.tsx delete mode 100644 pkg/ui/src/views/statements/barCharts.tsx delete mode 100644 pkg/ui/src/views/statements/diagnostics/diagnosticsUtils.ts delete mode 100644 pkg/ui/src/views/statements/diagnostics/diagnosticsView.module.styl delete mode 100644 pkg/ui/src/views/statements/diagnostics/diagnosticsView.spec.tsx delete mode 100644 pkg/ui/src/views/statements/diagnostics/diagnosticsView.tsx delete mode 100644 pkg/ui/src/views/statements/planView/index.ts delete mode 100644 pkg/ui/src/views/statements/planView/planView.fixtures.tsx delete mode 100644 pkg/ui/src/views/statements/planView/planView.module.styl delete mode 100644 pkg/ui/src/views/statements/planView/planView.spec.tsx delete mode 100644 pkg/ui/src/views/statements/planView/planView.stories.tsx delete mode 100644 pkg/ui/src/views/statements/planView/planView.tsx delete mode 100644 pkg/ui/src/views/statements/statementsTable.module.styl delete mode 100644 pkg/ui/src/views/statements/statementsTable.stories.tsx delete mode 100644 pkg/ui/src/views/statements/statementsTable.tsx delete mode 100644 pkg/ui/src/views/statements/statementsTableContent.tsx diff --git a/pkg/ui/ccl/src/views/clusterviz/containers/map/breadcrumbs.tsx b/pkg/ui/ccl/src/views/clusterviz/containers/map/breadcrumbs.tsx index d092a24aea9e..322488863530 100644 --- a/pkg/ui/ccl/src/views/clusterviz/containers/map/breadcrumbs.tsx +++ b/pkg/ui/ccl/src/views/clusterviz/containers/map/breadcrumbs.tsx @@ -12,7 +12,7 @@ import { Link } from "react-router-dom"; import { generateLocalityRoute } from "src/util/localities"; import { LocalityTier } from "src/redux/localities"; -import { intersperse } from "src/util/intersperse"; +import { util } from "@cockroachlabs/admin-ui-components"; import { getLocalityLabel } from "src/util/localities"; import mapPinIcon from "!!raw-loader!assets/mapPin.svg"; import { trustIcon } from "src/util/trust"; @@ -24,6 +24,8 @@ interface BreadcrumbsProps { tiers: LocalityTier[]; } +const { intersperse } = util; + export class Breadcrumbs extends React.Component { render() { const paths = breadcrumbPaths(this.props.tiers); diff --git a/pkg/ui/src/components/downloadFile/downloadFile.tsx b/pkg/ui/src/components/downloadFile/downloadFile.tsx deleted file mode 100644 index 0eedae621d32..000000000000 --- a/pkg/ui/src/components/downloadFile/downloadFile.tsx +++ /dev/null @@ -1,86 +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, { - useRef, - useEffect, - forwardRef, - useImperativeHandle, -} from "react"; - -type FileTypes = "text/plain" | "application/json"; - -export interface DownloadAsFileProps { - fileName?: string; - fileType?: FileTypes; - content?: string; -} - -export interface DownloadFileRef { - download: (name: string, type: FileTypes, body: string) => void; -} - -/* - * DownloadFile can download file in two modes `default` and `imperative`. - * `Default` mode - when DownloadFile wraps component which should trigger - * downloading and can work only if content of file is already available. - * - * For example: - * ``` - * - * - * ``` - * */ -export const DownloadFile = forwardRef( - (props, ref) => { - const { children, fileName, fileType, content } = props; - const anchorRef = useRef(); - - const bootstrapFile = (name: string, type: FileTypes, body: string) => { - const anchorElement = anchorRef.current; - const file = new Blob([body], { type }); - anchorElement.href = URL.createObjectURL(file); - anchorElement.download = name; - }; - - useEffect(() => { - if (content === undefined) { - return; - } - bootstrapFile(fileName, fileType, content); - }, [fileName, fileType, content]); - - useImperativeHandle(ref, () => ({ - download: (name: string, type: FileTypes, body: string) => { - bootstrapFile(name, type, body); - anchorRef.current.click(); - }, - })); - - return {children}; - }, -); diff --git a/pkg/ui/src/components/downloadFile/index.ts b/pkg/ui/src/components/downloadFile/index.ts deleted file mode 100644 index 8b04ed62d58e..000000000000 --- a/pkg/ui/src/components/downloadFile/index.ts +++ /dev/null @@ -1,11 +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. - -export * from "./downloadFile"; diff --git a/pkg/ui/src/components/index.ts b/pkg/ui/src/components/index.ts index 8d8514394c7c..1bb4c4ff3a94 100644 --- a/pkg/ui/src/components/index.ts +++ b/pkg/ui/src/components/index.ts @@ -18,10 +18,8 @@ export * from "./sideNavigation"; export * from "./pageHeader"; export * from "./text"; export * from "./input"; -export * from "./table"; export * from "./tooltip"; export * from "./select"; -export * from "./downloadFile"; export * from "./modal"; export * from "./rangeCalendar"; export * from "./link"; diff --git a/pkg/ui/src/components/table/index.ts b/pkg/ui/src/components/table/index.ts deleted file mode 100644 index cd7da8927732..000000000000 --- a/pkg/ui/src/components/table/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2019 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. - -export * from "./table"; diff --git a/pkg/ui/src/components/table/table.styl b/pkg/ui/src/components/table/table.styl deleted file mode 100644 index 30da384597eb..000000000000 --- a/pkg/ui/src/components/table/table.styl +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2019 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 '~src/components/core/index.styl' - -.crl-table-wrapper - .ant-table - color $colors--primary-text - - // Table header - .ant-table-thead - background-color $colors--neutral-1 - @extend $text--heading-6 - - .ant-table-thead > tr > th - padding $spacing-base $spacing-base - color $colors--neutral-6 - font-family $font-family--semi-bold - font-weight $font-weight--bold - letter-spacing 1.5px - .ant-table-column-sorter - vertical-align baseline - // END: Table header - - // Table Column - .column--align-right - text-align end - // END: Table Column - - // Expand/Collapse icon - .ant-table-row-expand-icon - border none - background-color transparent - - .ant-table-row-collapsed::after - content 'â–¶' - font-size 8px - - .ant-table-row-expanded::after - content 'â–¼' - font-size 8px - // END: Expand/Collapse icon - - // Table row - .ant-table-row - @extend $text--body - - .ant-table-row .cell--show-on-hover - visibility hidden - - .ant-table-row:hover .cell--show-on-hover - visibility visible - // END: Table row - - // Table cell - .ant-table-tbody > tr > td - padding $spacing-smaller $spacing-smaller - border-bottom-color $colors--neutral-3 - - // Increase right padding for columns aligned by right - .ant-table-tbody > tr > td.column--align-right - padding-right $spacing-mid-large - - // show column with right border - .ant-table-tbody > tr > td.column--border-right - border-right $colors--neutral-3 solid 1px - // END: Table cell - - // Table cell on hover - .ant-table-thead > tr.ant-table-row-hover:not(.ant-table-expanded-row):not(.ant-table-row-selected) > td, - .ant-table-tbody > tr.ant-table-row-hover:not(.ant-table-expanded-row):not(.ant-table-row-selected) > td, - .ant-table-thead > tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected) > td, - .ant-table-tbody > tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected) > td - background $colors--neutral-1 - // END: Table cell on hover - - .ant-table-placeholder - border $colors--neutral-1 solid 1px - - .empty-table__message - @extend $text--body - text-align center - - .ant-pagination.ant-table-pagination - text-align center - float unset - - &__empty - .ant-table-placeholder - border none diff --git a/pkg/ui/src/components/table/table.tsx b/pkg/ui/src/components/table/table.tsx deleted file mode 100644 index 39e27ea4bc09..000000000000 --- a/pkg/ui/src/components/table/table.tsx +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2019 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 * as React from "react"; -import { default as AntTable, ColumnProps } from "antd/es/table"; -import ConfigProvider from "antd/es/config-provider"; -import cn from "classnames"; - -import "antd/es/table/style/css"; -import "./table.styl"; - -export type ColumnsConfig = Array>; - -export interface TableProps { - columns: Array>; - dataSource: Array; - noDataMessage?: React.ReactNode; - tableLayout?: "fixed" | "auto"; - pageSize?: number; - className?: string; -} - -const customizeRenderEmpty = (node: React.ReactNode) => () => ( -
{node}
-); - -Table.defaultProps = { - noDataMessage: "No data to display", - tableLayout: "auto", - className: "", -}; - -export function Table(props: TableProps) { - const { - columns, - dataSource, - noDataMessage, - tableLayout, - pageSize, - className, - } = props; - return ( - - - className={cn(`crl-table-wrapper ${className}`, { - "crl-table-wrapper__empty": dataSource.length === 0, - })} - columns={columns} - dataSource={dataSource} - expandRowByClick - tableLayout={tableLayout} - pagination={{ hideOnSinglePage: true, pageSize }} - /> - - ); -} diff --git a/pkg/ui/src/redux/nodes.ts b/pkg/ui/src/redux/nodes.ts index 58adda225de4..8f69925cfa94 100644 --- a/pkg/ui/src/redux/nodes.ts +++ b/pkg/ui/src/redux/nodes.ts @@ -13,9 +13,9 @@ import { createSelector } from "reselect"; import * as protos from "src/js/protos"; import { AdminUIState } from "./state"; +import { util } from "@cockroachlabs/admin-ui-components"; import { Pick } from "src/util/pick"; import { NoConnection } from "src/views/reports/containers/network"; -import { INodeStatus, MetricConstants, BytesUsed } from "src/util/proto"; import { nullOfReturnType } from "src/util/types"; /** @@ -26,6 +26,9 @@ import { nullOfReturnType } from "src/util/types"; export import LivenessStatus = protos.cockroach.kv.kvserver.liveness.livenesspb.NodeLivenessStatus; import { cockroach } from "src/js/protos"; import MembershipStatus = cockroach.kv.kvserver.liveness.livenesspb.MembershipStatus; +import INodeStatus = cockroach.server.status.statuspb.INodeStatus; + +const { MetricConstants, BytesUsed } = util; /** * livenessNomenclature resolves a mismatch between the terms used for liveness diff --git a/pkg/ui/src/util/intersperse.spec.ts b/pkg/ui/src/util/intersperse.spec.ts deleted file mode 100644 index 7dd6f1a665ff..000000000000 --- a/pkg/ui/src/util/intersperse.spec.ts +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2018 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 { assert } from "chai"; - -import { intersperse } from "src/util/intersperse"; - -describe("intersperse", () => { - it("puts separator in between array items", () => { - const result = intersperse(["foo", "bar", "baz"], "-"); - assert.deepEqual(result, ["foo", "-", "bar", "-", "baz"]); - }); - - it("puts separator in between array items when given a one-item array", () => { - const result = intersperse(["baz"], "-"); - assert.deepEqual(result, ["baz"]); - }); - - it("puts separator in between array items when given an empty array", () => { - const result = intersperse([], "-"); - assert.deepEqual(result, []); - }); -}); diff --git a/pkg/ui/src/util/intersperse.ts b/pkg/ui/src/util/intersperse.ts deleted file mode 100644 index 41fb14553216..000000000000 --- a/pkg/ui/src/util/intersperse.ts +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2018 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. - -// e.g. intersperse(["foo", "bar", "baz"], "-") => ["foo", "-", "bar", "-", "baz"] -export function intersperse(array: T[], sep: T): T[] { - const output = []; - for (let i = 0; i < array.length; i++) { - if (i > 0) { - output.push(sep); - } - output.push(array[i]); - } - return output; -} diff --git a/pkg/ui/src/views/cluster/containers/nodesOverview/index.tsx b/pkg/ui/src/views/cluster/containers/nodesOverview/index.tsx index 45ae5d3d715b..cd0748267dde 100644 --- a/pkg/ui/src/views/cluster/containers/nodesOverview/index.tsx +++ b/pkg/ui/src/views/cluster/containers/nodesOverview/index.tsx @@ -28,15 +28,8 @@ import { LocalSetting } from "src/redux/localsettings"; import { SortSetting } from "src/views/shared/components/sortabletable"; import { LongToMoment } from "src/util/convert"; import { INodeStatus, MetricConstants } from "src/util/proto"; -import { - ColumnsConfig, - Table, - Text, - TextTypes, - Tooltip, - Badge, - BadgeProps, -} from "src/components"; +import { Text, TextTypes, Tooltip, Badge, BadgeProps } from "src/components"; +import { ColumnsConfig, Table } from "@cockroachlabs/admin-ui-components"; import { Percentage } from "src/util/format"; import { FixLong } from "src/util/fixLong"; import { getNodeLocalityTiers } from "src/util/localities"; @@ -162,6 +155,7 @@ const getBadgeTypeByNodeStatus = ( } }; +// tslint:disable-next-line:variable-name const NodeNameColumn: React.FC<{ record: NodeStatusRow | DecommissionedNodeStatusRow; }> = ({ record }) => { @@ -173,6 +167,7 @@ const NodeNameColumn: React.FC<{ ); }; +// tslint:disable-next-line:variable-name const NodeLocalityColumn: React.FC<{ record: NodeStatusRow }> = ({ record, }) => { @@ -606,6 +601,7 @@ export const decommissionedNodesTableDataSelector = createSelector( /** * LiveNodesConnected is a redux-connected HOC of LiveNodeList. */ +// tslint:disable-next-line:variable-name const NodesConnected = connect( (state: AdminUIState) => { const liveNodes = partitionedStatuses(state).live || []; @@ -625,6 +621,7 @@ const NodesConnected = connect( /** * DecommissionedNodesConnected is a redux-connected HOC of NotLiveNodeList. */ +// tslint:disable-next-line:variable-name const DecommissionedNodesConnected = connect( (state: AdminUIState) => { return { diff --git a/pkg/ui/src/views/reports/containers/nodeHistory/decommissionedNodeHistory.tsx b/pkg/ui/src/views/reports/containers/nodeHistory/decommissionedNodeHistory.tsx index f71fee7c34de..5fcab5398d2d 100644 --- a/pkg/ui/src/views/reports/containers/nodeHistory/decommissionedNodeHistory.tsx +++ b/pkg/ui/src/views/reports/containers/nodeHistory/decommissionedNodeHistory.tsx @@ -24,7 +24,8 @@ import { SortSetting } from "src/views/shared/components/sortabletable"; import { LocalSetting } from "src/redux/localsettings"; import "./decommissionedNodeHistory.styl"; -import { ColumnsConfig, Table, Text } from "src/components"; +import { Text } from "src/components"; +import { ColumnsConfig, Table } from "@cockroachlabs/admin-ui-components"; import { createSelector } from "reselect"; const decommissionedNodesSortSetting = new LocalSetting< diff --git a/pkg/ui/src/views/reports/containers/statementDiagnosticsHistory/index.tsx b/pkg/ui/src/views/reports/containers/statementDiagnosticsHistory/index.tsx index 7af163f114ba..0dc6b89cf953 100644 --- a/pkg/ui/src/views/reports/containers/statementDiagnosticsHistory/index.tsx +++ b/pkg/ui/src/views/reports/containers/statementDiagnosticsHistory/index.tsx @@ -17,15 +17,7 @@ import Long from "long"; import { Link } from "react-router-dom"; import { isUndefined } from "lodash"; -import { - Anchor, - Button, - DownloadFile, - DownloadFileRef, - Text, - TextTypes, - Tooltip, -} from "src/components"; +import { Anchor, Button, Text, TextTypes, Tooltip } from "src/components"; import HeaderSection from "src/views/shared/components/headerSection"; import { AdminUIState } from "src/redux/state"; import { getStatementDiagnostics } from "src/util/api"; @@ -45,7 +37,6 @@ import "./statementDiagnosticsHistoryView.styl"; import { cockroach } from "src/js/protos"; import IStatementDiagnosticsReport = cockroach.server.serverpb.IStatementDiagnosticsReport; import StatementDiagnosticsRequest = cockroach.server.serverpb.StatementDiagnosticsRequest; -import { getDiagnosticsStatus } from "src/views/statements/diagnostics"; import { SortedTable, ColumnDescriptor, @@ -53,10 +44,15 @@ import { import { SortSetting } from "src/views/shared/components/sortabletable"; import { statementDiagnostics } from "src/util/docs"; import { summarize } from "src/util/sql/summarize"; -import { shortStatement } from "src/views/statements/statementsTable"; import { trackDownloadDiagnosticsBundle } from "src/util/analytics"; import EmptyTableIcon from "!!url-loader!assets/emptyState/empty-table-results.svg"; -import { EmptyTable } from "@cockroachlabs/admin-ui-components"; +import { + DownloadFile, + DownloadFileRef, + EmptyTable, + shortStatement, + getDiagnosticsStatus, +} from "@cockroachlabs/admin-ui-components"; type StatementDiagnosticsHistoryViewProps = MapStateToProps & MapDispatchToProps; diff --git a/pkg/ui/src/views/sessions/sessionDetails.tsx b/pkg/ui/src/views/sessions/sessionDetails.tsx index 8378cb23047c..9ffa59590b2f 100644 --- a/pkg/ui/src/views/sessions/sessionDetails.tsx +++ b/pkg/ui/src/views/sessions/sessionDetails.tsx @@ -36,7 +36,7 @@ import { nodeDisplayNameByIDSelector } from "src/redux/nodes"; import { NodeLink, StatementLinkTarget, -} from "src/views/statements/statementsTableContent"; +} from "@cockroachlabs/admin-ui-components"; import TerminateSessionModal, { TerminateSessionModalRef, } from "src/views/sessions/terminateSessionModal"; diff --git a/pkg/ui/src/views/sessions/sessionsTable.tsx b/pkg/ui/src/views/sessions/sessionsTable.tsx index f733ad89df22..e34e89bdbb25 100644 --- a/pkg/ui/src/views/sessions/sessionsTable.tsx +++ b/pkg/ui/src/views/sessions/sessionsTable.tsx @@ -23,7 +23,7 @@ import { Link } from "react-router-dom"; import React from "react"; import { Button, Tooltip } from "src/components"; import { Moment } from "moment"; -import { StatementLink } from "src/views/statements/statementsTableContent"; +import { StatementLink } from "@cockroachlabs/admin-ui-components"; import ISession = cockroach.server.serverpb.ISession; import { TerminateSessionModalRef } from "src/views/sessions/terminateSessionModal"; import { TerminateQueryModalRef } from "src/views/sessions/terminateQueryModal"; diff --git a/pkg/ui/src/views/statements/barCharts.module.styl b/pkg/ui/src/views/statements/barCharts.module.styl deleted file mode 100644 index a46266f08bfb..000000000000 --- a/pkg/ui/src/views/statements/barCharts.module.styl +++ /dev/null @@ -1,77 +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/components/core/index' - -.bar-chart - height 14px - position relative - display flex - align-items center - flex-direction row - > span - width 100% - display flex - align-items center - flex-direction row - - &__multiplebars - width calc(100% - 75px) - border-radius 3px - position relative - display flex - align-items center - - &__label - position relative - font-family SourceSansPro-Regular - font-size 12px - line-height 1.83 - color $adminui-grey-1 - width 75px - - &__bar - display inline-block - height 13px - border-radius 3px - - &--dev - position absolute - height 3px - - .count-first-try, .count-total, .count-retry, .count-max-retries - border-radius 3px - position absolute - left 40px - - .count-first-try, .count-total - background-color $grey-light - .count-retry, .count-max-retries - background-color $alert-color - - .rows - background-color $grey-light - border-radius 3px - - .rows-dev - background-color $colors--primary-blue-3 - - .bar-chart - &__parse, &__plan, &__run, &__overhead, &__overall - background-color $colors--neutral-4 - - &-red - .bar-chart - &__parse, &__plan, &__run, &__overhead, &__overall - background-color $colors--functional-red-2 - - &__parse-dev, &__plan-dev, &__run-dev, &__overhead-dev, &__overall-dev - background-color $colors--primary-blue-3 diff --git a/pkg/ui/src/views/statements/barCharts.stories.tsx b/pkg/ui/src/views/statements/barCharts.stories.tsx deleted file mode 100644 index ec705765f31b..000000000000 --- a/pkg/ui/src/views/statements/barCharts.stories.tsx +++ /dev/null @@ -1,105 +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, RenderFunction } from "@storybook/react"; - -import { - countBarChart, - genericBarChart, - latencyBarChart, - retryBarChart, - rowsBarChart, -} from "./barCharts"; -import statementsPagePropsFixture from "./statementsPage.fixture"; -import { cockroach } from "src/js/protos"; -import NumericStat = cockroach.sql.NumericStat; -import Long from "long"; - -const { statements } = statementsPagePropsFixture; - -const withinColumn = (width = "150px") => (storyFn: RenderFunction) => { - const rowStyle = { - borderTop: "1px solid #e7ecf3", - borderBottom: "1px solid #e7ecf3", - }; - - const cellStyle = { - width: "190px", - padding: "10px 20px", - }; - - return ( - - - - - - -
-
{storyFn()}
-
- ); -}; - -storiesOf("BarCharts", module) - .add("countBarChart", () => { - const chartFactory = countBarChart(statements); - return chartFactory(statements[0]); - }) - .add("latencyBarChart", () => { - const chartFactory = latencyBarChart(statements); - return chartFactory(statements[0]); - }) - .add("retryBarChart", () => { - const chartFactory = retryBarChart(statements); - return chartFactory(statements[0]); - }) - .add("rowsBarChart", () => { - const chartFactory = rowsBarChart(statements); - return chartFactory(statements[0]); - }) - .add("genericBarChart", () => { - const stat = new NumericStat(); - stat.mean = 25; - stat.squared_diffs = 25; - const barChartFactory = genericBarChart(stat, Long.fromInt(10)); - return barChartFactory(); - }) - .add("genericBarChart (missing data)", () => { - const barChartFactory = genericBarChart(null, Long.fromInt(10)); - return barChartFactory(); - }); - -storiesOf("BarCharts/within column (150px)", module) - .addDecorator(withinColumn()) - .add("countBarChart", () => { - const chartFactory = countBarChart(statements); - return chartFactory(statements[0]); - }) - .add("latencyBarChart", () => { - const chartFactory = latencyBarChart(statements); - return chartFactory(statements[0]); - }) - .add("retryBarChart", () => { - const chartFactory = retryBarChart(statements); - return chartFactory(statements[0]); - }) - .add("rowsBarChart", () => { - const chartFactory = rowsBarChart(statements); - return chartFactory(statements[0]); - }) - .add("genericBarChart", () => { - const stat = new NumericStat(); - stat.mean = 25; - stat.squared_diffs = 25; - const barChartFactory = genericBarChart(stat, Long.fromInt(10)); - return barChartFactory(); - }); diff --git a/pkg/ui/src/views/statements/barCharts.tsx b/pkg/ui/src/views/statements/barCharts.tsx deleted file mode 100644 index 1a8a6b724fb6..000000000000 --- a/pkg/ui/src/views/statements/barCharts.tsx +++ /dev/null @@ -1,581 +0,0 @@ -// Copyright 2018 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 d3 from "d3"; -import _ from "lodash"; -import Long from "long"; -import React from "react"; -import * as protos from "src/js/protos"; -import { stdDevLong } from "src/util/appStats"; -import { FixLong } from "src/util/fixLong"; -import { Duration } from "src/util/format"; -import { ToolTipWrapper } from "src/views/shared/components/toolTip"; -import classNames from "classnames/bind"; -import styles from "./barCharts.module.styl"; - -type StatementStatistics = protos.cockroach.server.serverpb.StatementsResponse.ICollectedStatementStatistics; -type NumericStat = protos.cockroach.sql.INumericStat; - -const cx = classNames.bind(styles); - -interface BarChartOptions { - classes?: { - root?: string; - label?: string; - }; -} - -export const longToInt = (d: number | Long) => - Long.fromValue(FixLong(d)).toInt(); -const clamp = (i: number) => (i < 0 ? 0 : i); - -const formatTwoPlaces = d3.format(".2f"); - -const countBars = [ - bar("count-first-try", (d: StatementStatistics) => - longToInt(d.stats.first_attempt_count), - ), -]; - -const retryBars = [ - bar( - "count-retry", - (d: StatementStatistics) => - longToInt(d.stats.count) - longToInt(d.stats.first_attempt_count), - ), -]; - -const rowsBars = [ - bar("rows", (d: StatementStatistics) => d.stats.num_rows.mean), -]; - -const latencyBars = [ - bar("bar-chart__parse", (d: StatementStatistics) => d.stats.parse_lat.mean), - bar("bar-chart__plan", (d: StatementStatistics) => d.stats.plan_lat.mean), - bar("bar-chart__run", (d: StatementStatistics) => d.stats.run_lat.mean), - bar( - "bar-chart__overhead", - (d: StatementStatistics) => d.stats.overhead_lat.mean, - ), -]; - -const latencyStdDev = bar( - cx("bar-chart__overall-dev"), - (d: StatementStatistics) => stdDevLong(d.stats.service_lat, d.stats.count), -); -const rowsStdDev = bar(cx("rows-dev"), (d: StatementStatistics) => - stdDevLong(d.stats.num_rows, d.stats.count), -); - -function bar(name: string, value: (d: StatementStatistics) => number) { - return { name, value }; -} - -function renderNumericStatLegend( - count: number | Long, - stat: number, - sd: number, - formatter: (d: number) => string, -) { - return ( - - - - - - - - - - - -
-
- Mean -
{formatter(stat)}
-
- Standard Deviation -
{longToInt(count) < 2 ? "-" : sd ? formatter(sd) : "0"}
- ); -} - -const makeBarChart = ( - type: "grey" | "red", - accessors: { name: string; value: (d: StatementStatistics) => number }[], - formatter: (d: number) => string = (x) => `${x}`, - stdDevAccessor?: { name: string; value: (d: StatementStatistics) => number }, - legendFormatter?: (d: number) => string, -) => { - if (!legendFormatter) { - legendFormatter = formatter; - } - - return (rows: StatementStatistics[] = [], options: BarChartOptions = {}) => { - const getTotal = (d: StatementStatistics) => - _.sum(_.map(accessors, ({ value }) => value(d))); - const getTotalWithStdDev = (d: StatementStatistics) => - getTotal(d) + stdDevAccessor.value(d); - - const extent = d3.extent( - rows, - stdDevAccessor ? getTotalWithStdDev : getTotal, - ); - - const scale = d3.scale.linear().domain([0, extent[1]]).range([0, 100]); - - return (d: StatementStatistics) => { - if (rows.length === 0) { - scale.domain([0, getTotal(d)]); - } - - let sum = 0; - _.map(accessors, ({ name, value }) => { - const v = value(d); - sum += v; - return ( -
- ); - }); - - const renderStdDev = () => { - if (!stdDevAccessor) { - return null; - } - - const { name, value } = stdDevAccessor; - - const stddev = value(d); - const width = stddev + (stddev > sum ? sum : stddev); - const left = stddev > sum ? 0 : sum - stddev; - const cn = cx(name, "bar-chart__bar", "bar-chart__bar--dev"); - const style = { - width: scale(width) + "%", - left: scale(left) + "%", - }; - return
; - }; - - const className = cx("bar-chart", `bar-chart-${type}`, { - "bar-chart--singleton": rows.length === 0, - [options?.classes?.root]: !!options?.classes?.root, - }); - if (stdDevAccessor) { - const sd = stdDevAccessor.value(d); - const titleText = renderNumericStatLegend( - rows.length, - sum, - sd, - legendFormatter, - ); - return ( -
- -
- {formatter(getTotal(d))} -
-
-
- {renderStdDev()} -
- -
- ); - } else { - return ( -
-
- {formatter(getTotal(d))} -
-
-
- ); - } - }; - }; -}; - -const SCALE_FACTORS: { factor: number; key: string }[] = [ - { factor: 1000000000, key: "b" }, - { factor: 1000000, key: "m" }, - { factor: 1000, key: "k" }, -]; - -export function approximify(value: number) { - for (let i = 0; i < SCALE_FACTORS.length; i++) { - const scale = SCALE_FACTORS[i]; - if (value > scale.factor) { - return "" + Math.round(value / scale.factor) + scale.key; - } - } - - return "" + Math.round(value); -} - -export const countBarChart = makeBarChart("grey", countBars, approximify); -export const retryBarChart = makeBarChart("red", retryBars, approximify); -export const rowsBarChart = makeBarChart( - "grey", - rowsBars, - approximify, - rowsStdDev, - formatTwoPlaces, -); -export const latencyBarChart = makeBarChart( - "grey", - latencyBars, - (v) => Duration(v * 1e9), - latencyStdDev, -); - -export function rowsBreakdown(s: StatementStatistics) { - const mean = s.stats.num_rows.mean; - const sd = stdDevLong(s.stats.num_rows, s.stats.count); - - const scale = d3.scale - .linear() - .domain([0, mean + sd]) - .range([0, 100]); - - return { - rowsBarChart(meanRow?: boolean) { - const spread = scale(sd + (sd > mean ? mean : sd)); - if (meanRow) { - return formatTwoPlaces(mean); - } else { - return spread; - } - }, - }; -} - -export function genericBarChart( - s: NumericStat, - count: number | Long, - format?: (v: number) => string, -) { - if (!s) { - return () =>
; - } - const mean = s.mean; - const sd = stdDevLong(s, count); - - const max = mean + sd; - const scale = d3.scale.linear().domain([0, max]).range([0, 100]); - if (!format) { - format = d3.format(".2f"); - } - return function MakeGenericBarChart() { - const width = scale(clamp(mean - sd)); - const right = scale(mean); - const spread = scale(sd + (sd > mean ? mean : sd)); - const title = renderNumericStatLegend(count, mean, sd, format); - return ( - -
-
{format(mean)}
-
-
-
-
-
- - ); - }; -} - -export function latencyBreakdown(s: StatementStatistics) { - const parseMean = s.stats.parse_lat.mean; - const parseSd = stdDevLong(s.stats.parse_lat, s.stats.count); - - const planMean = s.stats.plan_lat.mean; - const planSd = stdDevLong(s.stats.plan_lat, s.stats.count); - - const runMean = s.stats.run_lat.mean; - const runSd = stdDevLong(s.stats.run_lat, s.stats.count); - - const overheadMean = s.stats.overhead_lat.mean; - const overheadSd = stdDevLong(s.stats.overhead_lat, s.stats.count); - - const overallMean = s.stats.service_lat.mean; - const overallSd = stdDevLong(s.stats.service_lat, s.stats.count); - - const max = Math.max( - parseMean + parseSd, - parseMean + planMean + planSd, - parseMean + planMean + runMean + runSd, - parseMean + planMean + runMean + overheadMean + overheadSd, - overallMean + overallSd, - ); - - const format = (v: number) => Duration(v * 1e9); - - const scale = d3.scale.linear().domain([0, max]).range([0, 100]); - - return { - parseBarChart() { - const width = scale(clamp(parseMean - parseSd)); - const right = scale(parseMean); - const spread = scale( - parseSd + (parseSd > parseMean ? parseMean : parseSd), - ); - const title = renderNumericStatLegend( - s.stats.count, - parseMean, - parseSd, - format, - ); - return ( - -
-
- {Duration(parseMean * 1e9)} -
-
-
-
-
-
- - ); - }, - - planBarChart() { - const left = scale(parseMean); - const width = scale(clamp(planMean - planSd)); - const right = scale(planMean); - const spread = scale(planSd + (planSd > planMean ? planMean : planSd)); - const title = renderNumericStatLegend( - s.stats.count, - planMean, - planSd, - format, - ); - return ( - -
-
- {Duration(planMean * 1e9)} -
-
-
-
-
-
- - ); - }, - - runBarChart() { - const left = scale(parseMean + planMean); - const width = scale(clamp(runMean - runSd)); - const right = scale(runMean); - const spread = scale(runSd + (runSd > runMean ? runMean : runSd)); - const title = renderNumericStatLegend( - s.stats.count, - runMean, - runSd, - format, - ); - return ( - -
-
- {Duration(runMean * 1e9)} -
-
-
-
-
-
- - ); - }, - - overheadBarChart() { - const left = scale(parseMean + planMean + runMean); - const width = scale(clamp(overheadMean - overheadSd)); - const right = scale(overheadMean); - const spread = scale( - overheadSd + (overheadSd > overheadMean ? overheadMean : overheadSd), - ); - const title = renderNumericStatLegend( - s.stats.count, - overheadMean, - overheadSd, - format, - ); - return ( - -
-
- {Duration(overheadMean * 1e9)} -
-
-
-
-
-
- - ); - }, - - overallBarChart() { - const parse = scale(parseMean); - const plan = scale(planMean); - const run = scale(runMean); - const overhead = scale(overheadMean); - const width = scale(clamp(overallMean - overallSd)); - const spread = scale( - overallSd + (overallSd > overallMean ? overallMean : overallSd), - ); - const title = renderNumericStatLegend( - s.stats.count, - overallMean, - overallSd, - format, - ); - return ( - -
-
- {Duration(overallMean * 1e9)} -
-
-
-
-
-
- - ); - }, - }; -} diff --git a/pkg/ui/src/views/statements/diagnostics/diagnosticsUtils.ts b/pkg/ui/src/views/statements/diagnostics/diagnosticsUtils.ts deleted file mode 100644 index eb824e9dee46..000000000000 --- a/pkg/ui/src/views/statements/diagnostics/diagnosticsUtils.ts +++ /dev/null @@ -1,79 +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 { isUndefined } from "lodash"; - -import { cockroach } from "src/js/protos"; -import IStatementDiagnosticsReport = cockroach.server.serverpb.IStatementDiagnosticsReport; -import { DiagnosticStatuses } from "src/views/statements/diagnostics/diagnosticStatuses"; - -export function getDiagnosticsStatus( - diagnosticsRequest: IStatementDiagnosticsReport, -): DiagnosticStatuses { - if (diagnosticsRequest.completed) { - return "READY"; - } - - return "WAITING"; -} - -export function sortByRequestedAtField( - a: IStatementDiagnosticsReport, - b: IStatementDiagnosticsReport, -) { - const activatedOnA = a.requested_at?.seconds?.toNumber(); - const activatedOnB = b.requested_at?.seconds?.toNumber(); - if (isUndefined(activatedOnA) && isUndefined(activatedOnB)) { - return 0; - } - if (activatedOnA < activatedOnB) { - return -1; - } - if (activatedOnA > activatedOnB) { - return 1; - } - return 0; -} - -export function sortByCompletedField( - a: IStatementDiagnosticsReport, - b: IStatementDiagnosticsReport, -) { - const completedA = a.completed ? 1 : -1; - const completedB = b.completed ? 1 : -1; - if (completedA < completedB) { - return -1; - } - if (completedA > completedB) { - return 1; - } - return 0; -} - -export function sortByStatementFingerprintField( - a: IStatementDiagnosticsReport, - b: IStatementDiagnosticsReport, -) { - const statementFingerprintA = a.statement_fingerprint; - const statementFingerprintB = b.statement_fingerprint; - if ( - isUndefined(statementFingerprintA) && - isUndefined(statementFingerprintB) - ) { - return 0; - } - if (statementFingerprintA < statementFingerprintB) { - return -1; - } - if (statementFingerprintA > statementFingerprintB) { - return 1; - } - return 0; -} diff --git a/pkg/ui/src/views/statements/diagnostics/diagnosticsView.module.styl b/pkg/ui/src/views/statements/diagnostics/diagnosticsView.module.styl deleted file mode 100644 index 14d7f4c19d46..000000000000 --- a/pkg/ui/src/views/statements/diagnostics/diagnosticsView.module.styl +++ /dev/null @@ -1,66 +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 '~src/components/core/index.styl' - -.crl-statements-diagnostics-view - display flex - flex-direction column - - &__title - display flex - flex-direction row - justify-content space-between - margin-bottom $spacing-mid-large - - &__footer - margin $spacing-medium 0 0 $spacing-smaller - - &__content - max-width 650px - - &__main - margin-bottom $spacing-small - color $colors--secondary-text - - &__actions-column - display flex - flex-direction row - flex-wrap nowrap - justify-content flex-end - - &__vertical-line - width 1px - min-height 100% - border-left 1px solid $colors--neutral-3 - margin 0 $spacing-x-small - - &__icon - display inline-block - fill inherit - font-style normal - line-height 0 - text-align center - text-transform none - vertical-align -0.125em - margin-right $spacing-x-small - - &__statements-link - color $colors--primary-text - &:hover - color $colors--link - -.summary--card__empty-state - background-color $colors--white - background-image url("../../../../assets/statementsPage/emptyTracingBackground.svg") - background-repeat no-repeat - background-position-x right - padding 0 - diff --git a/pkg/ui/src/views/statements/diagnostics/diagnosticsView.spec.tsx b/pkg/ui/src/views/statements/diagnostics/diagnosticsView.spec.tsx deleted file mode 100644 index 50964796da2b..000000000000 --- a/pkg/ui/src/views/statements/diagnostics/diagnosticsView.spec.tsx +++ /dev/null @@ -1,130 +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 { assert } from "chai"; -import { mount, ReactWrapper } from "enzyme"; -import sinon, { SinonSpy } from "sinon"; -import Long from "long"; -import classNames from "classnames/bind"; -import { MemoryRouter } from "react-router-dom"; - -import "src/enzymeInit"; -import { DiagnosticsView } from "./diagnosticsView"; -import { Table } from "src/components"; -import { connectedMount } from "src/test-utils"; -import { cockroach } from "src/js/protos"; -import IStatementDiagnosticsReport = cockroach.server.serverpb.IStatementDiagnosticsReport; -import buttonStyles from "src/components/button/button.module.styl"; - -const cx = classNames.bind(buttonStyles); -const sandbox = sinon.createSandbox(); - -describe("DiagnosticsView", () => { - let wrapper: ReactWrapper; - let activateFn: SinonSpy; - const statementFingerprint = "some-id"; - - beforeEach(() => { - sandbox.reset(); - activateFn = sandbox.spy(); - }); - - describe("With Empty state", () => { - beforeEach(() => { - wrapper = mount( - - {}} - /> - , - ); - }); - - it("calls activate callback with statementId when click on Activate button", () => { - const activateButtonComponent = wrapper - .find(`.${cx("crl-button")}`) - .first(); - activateButtonComponent.simulate("click"); - activateFn.calledOnceWith(statementFingerprint); - }); - }); - - describe("With tracing data", () => { - beforeEach(() => { - const diagnosticsRequests: IStatementDiagnosticsReport[] = [ - generateDiagnosticsRequest(), - generateDiagnosticsRequest(), - ]; - - wrapper = connectedMount(() => ( - {}} - /> - )); - }); - - it("renders Table component when diagnostics data is provided", () => { - assert.isTrue(wrapper.find(Table).exists()); - }); - - it("calls activate callback with statementId when click on Activate button", () => { - const activateButtonComponent = wrapper - .find(`.${cx("crl-button")}`) - .first(); - activateButtonComponent.simulate("click"); - activateFn.calledOnceWith(statementFingerprint); - }); - - it("Activate button is hidden if diagnostics is requested and waiting query", () => { - const diagnosticsRequests: IStatementDiagnosticsReport[] = [ - generateDiagnosticsRequest({ completed: false }), - generateDiagnosticsRequest(), - ]; - wrapper = connectedMount(() => ( - {}} - /> - )); - - const activateButtonComponent = wrapper - .find(".crl-statements-diagnostics-view__activate-button") - .first(); - assert.isFalse(activateButtonComponent.exists()); - }); - }); -}); - -function generateDiagnosticsRequest( - extendObject: Partial = {}, -): IStatementDiagnosticsReport { - const diagnosticsRequest = { - statement_fingerprint: "SELECT * FROM table", - completed: true, - requested_at: { - seconds: Long.fromNumber(Date.now()), - nanos: Math.random() * 1000000, - }, - }; - Object.assign(diagnosticsRequest, extendObject); - return diagnosticsRequest; -} diff --git a/pkg/ui/src/views/statements/diagnostics/diagnosticsView.tsx b/pkg/ui/src/views/statements/diagnostics/diagnosticsView.tsx deleted file mode 100644 index c052a3e544bb..000000000000 --- a/pkg/ui/src/views/statements/diagnostics/diagnosticsView.tsx +++ /dev/null @@ -1,274 +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 { connect } from "react-redux"; -import moment from "moment"; -import Long from "long"; -import classnames from "classnames/bind"; - -import { - Button, - Text, - TextTypes, - Table, - ColumnsConfig, - DownloadFile, - DownloadFileRef, - Anchor, - Link, -} from "src/components"; -import { AdminUIState } from "src/redux/state"; -import { getStatementDiagnostics } from "src/util/api"; -import { SummaryCard } from "src/views/shared/components/summaryCard"; -import { - selectDiagnosticsReportsByStatementFingerprint, - selectDiagnosticsReportsCountByStatementFingerprint, -} from "src/redux/statements/statementsSelectors"; -import { createStatementDiagnosticsReportAction } from "src/redux/statements"; -import { DiagnosticStatusBadge } from "./diagnosticStatusBadge"; -import EmptyListIcon from "!!url-loader!assets/emptyState/empty-list-results.svg"; -import styles from "./diagnosticsView.module.styl"; -import { cockroach } from "src/js/protos"; -import IStatementDiagnosticsReport = cockroach.server.serverpb.IStatementDiagnosticsReport; -import StatementDiagnosticsRequest = cockroach.server.serverpb.StatementDiagnosticsRequest; -import { - getDiagnosticsStatus, - sortByCompletedField, - sortByRequestedAtField, -} from "./diagnosticsUtils"; -import { statementDiagnostics } from "src/util/docs"; -import { createStatementDiagnosticsAlertLocalSetting } from "src/redux/alerts"; -import { - trackActivateDiagnostics, - trackDownloadDiagnosticsBundle, -} from "src/util/analytics"; -import { EmptyTable } from "@cockroachlabs/admin-ui-components"; -import { Download } from "@cockroachlabs/icons"; - -interface DiagnosticsViewOwnProps { - statementFingerprint?: string; -} - -type DiagnosticsViewProps = DiagnosticsViewOwnProps & - MapStateToProps & - MapDispatchToProps; - -interface DiagnosticsViewState { - traces: { - [diagnosticsId: string]: string; - }; -} - -const cx = classnames.bind(styles); - -export class DiagnosticsView extends React.Component< - DiagnosticsViewProps, - DiagnosticsViewState -> { - columns: ColumnsConfig = [ - { - key: "activatedOn", - title: "Activated on", - sorter: sortByRequestedAtField, - defaultSortOrder: "descend", - render: (_text, record) => { - const timestamp = record.requested_at.seconds.toNumber() * 1000; - return moment(timestamp).format("LL[ at ]h:mm a"); - }, - }, - { - key: "status", - title: "status", - sorter: sortByCompletedField, - width: "160px", - render: (_text, record) => { - const status = getDiagnosticsStatus(record); - return ( - - ); - }, - }, - { - key: "actions", - title: "", - sorter: false, - width: "160px", - render: (_text, record) => { - if (record.completed) { - return ( -
- - trackDownloadDiagnosticsBundle(record.statement_fingerprint) - } - > - - -
- ); - } - return null; - }, - }, - ]; - - downloadRef = React.createRef(); - - getStatementDiagnostics = async (diagnosticsId: Long) => { - const request = new StatementDiagnosticsRequest({ - statement_diagnostics_id: diagnosticsId, - }); - const response = await getStatementDiagnostics(request); - const trace = response.diagnostics?.trace; - this.downloadRef.current?.download( - "statement-diagnostics.json", - "application/json", - trace, - ); - }; - - onActivateButtonClick = () => { - const { activate, statementFingerprint } = this.props; - activate(statementFingerprint); - trackActivateDiagnostics(statementFingerprint); - }; - - componentWillUnmount() { - this.props.dismissAlertMessage(); - } - - render() { - const { diagnosticsReports } = this.props; - - const canRequestDiagnostics = diagnosticsReports.every( - (diagnostic) => diagnostic.completed, - ); - - const dataSource = diagnosticsReports.map((diagnosticsReport, idx) => ({ - ...diagnosticsReport, - key: idx, - })); - - return ( - -
- Statement diagnostics - {canRequestDiagnostics && ( - - )} -
- - - {"When you activate statement diagnostics, CockroachDB will wait for the next query that" + - " matches this statement fingerprint. A download button will appear on the statement list and" + - " detail pages when the query is ready. The statement diagnostic will include EXPLAIN plans, table" + - " statistics, and traces. "} - - - Learn More - - - } - footer={ - - } - /> - } - dataSource={dataSource} - columns={this.columns} - /> -
- - All statement diagnostics - -
- - - ); - } -} - -interface MapStateToProps { - hasData: boolean; - diagnosticsReports: IStatementDiagnosticsReport[]; -} - -interface MapDispatchToProps { - activate: (statementFingerprint: string) => void; - dismissAlertMessage: () => void; -} - -const mapStateToProps = ( - state: AdminUIState, - props: DiagnosticsViewProps, -): MapStateToProps => { - const { statementFingerprint } = props; - const hasData = - selectDiagnosticsReportsCountByStatementFingerprint( - state, - statementFingerprint, - ) > 0; - const diagnosticsReports = selectDiagnosticsReportsByStatementFingerprint( - state, - statementFingerprint, - ); - return { - hasData, - diagnosticsReports, - }; -}; - -const mapDispatchToProps: MapDispatchToProps = { - activate: createStatementDiagnosticsReportAction, - dismissAlertMessage: () => - createStatementDiagnosticsAlertLocalSetting.set({ show: false }), -}; - -export default connect< - MapStateToProps, - MapDispatchToProps, - DiagnosticsViewOwnProps ->( - mapStateToProps, - mapDispatchToProps, -)(DiagnosticsView); diff --git a/pkg/ui/src/views/statements/diagnostics/index.ts b/pkg/ui/src/views/statements/diagnostics/index.ts index d3f8b9f159eb..7b0ee512512e 100644 --- a/pkg/ui/src/views/statements/diagnostics/index.ts +++ b/pkg/ui/src/views/statements/diagnostics/index.ts @@ -8,7 +8,5 @@ // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. -import DiagnosticsView from "./diagnosticsView"; - -export default DiagnosticsView; -export * from "./diagnosticsUtils"; +export * from "./activateDiagnosticsModal"; +export * from "./diagnosticStatusBadge"; diff --git a/pkg/ui/src/views/statements/planView/index.ts b/pkg/ui/src/views/statements/planView/index.ts deleted file mode 100644 index ecd89e159553..000000000000 --- a/pkg/ui/src/views/statements/planView/index.ts +++ /dev/null @@ -1,11 +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. - -export * from "./planView"; diff --git a/pkg/ui/src/views/statements/planView/planView.fixtures.tsx b/pkg/ui/src/views/statements/planView/planView.fixtures.tsx deleted file mode 100644 index 2ace0ae87db1..000000000000 --- a/pkg/ui/src/views/statements/planView/planView.fixtures.tsx +++ /dev/null @@ -1,193 +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 { cockroach } from "src/js/protos"; -import IExplainTreePlanNode = cockroach.sql.IExplainTreePlanNode; - -export const logicalPlan: IExplainTreePlanNode = { - name: "root", - attrs: [], - children: [ - { - name: "count", - attrs: [], - children: [ - { - name: "upsert", - attrs: [ - { - key: "into", - value: - "vehicle_location_histories(city, ride_id, timestamp, lat, long)", - }, - { - key: "strategy", - value: "opt upserter", - }, - ], - children: [ - { - name: "buffer node", - attrs: [ - { - key: "label", - value: "buffer 1", - }, - ], - children: [ - { - name: "row source to plan node", - attrs: [], - children: [ - { - name: "render", - attrs: [ - { - key: "render", - value: "column1", - }, - { - key: "render", - value: "column2", - }, - { - key: "render", - value: "column3", - }, - { - key: "render", - value: "column4", - }, - { - key: "render", - value: "column5", - }, - { - key: "render", - value: "column4", - }, - { - key: "render", - value: "column5", - }, - ], - children: [ - { - name: "values", - attrs: [ - { - key: "size", - value: "5 columns, 1 row", - }, - { - key: "row 0, expr", - value: "_", - }, - { - key: "row 0, expr", - value: "_", - }, - { - key: "row 0, expr", - value: "now()", - }, - { - key: "row 0, expr", - value: "_", - }, - { - key: "row 0, expr", - value: "_", - }, - ], - children: [], - }, - ], - }, - ], - }, - ], - }, - ], - }, - ], - }, - { - name: "postquery", - attrs: [], - children: [ - { - name: "error if rows", - attrs: [], - children: [ - { - name: "row source to plan node", - attrs: [], - children: [ - { - name: "lookup-join", - attrs: [ - { - key: "table", - value: "rides@primary", - }, - { - key: "type", - value: "anti", - }, - { - key: "equality", - value: "(column1, column2) = (city, id)", - }, - { - key: "equality cols are key", - value: "", - }, - { - key: "parallel", - value: "", - }, - ], - children: [ - { - name: "render", - attrs: [ - { - key: "render", - value: "column1", - }, - { - key: "render", - value: "column2", - }, - ], - children: [ - { - name: "scan buffer node", - children: [], - attrs: [ - { - key: "label", - value: "buffer 1", - }, - ], - }, - ], - }, - ], - }, - ], - }, - ], - }, - ], - }, - ], -}; diff --git a/pkg/ui/src/views/statements/planView/planView.module.styl b/pkg/ui/src/views/statements/planView/planView.module.styl deleted file mode 100644 index f7d4a2ab51ff..000000000000 --- a/pkg/ui/src/views/statements/planView/planView.module.styl +++ /dev/null @@ -1,199 +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' - -.base-heading - composes base-heading from '~styl/base/typography.styl' - -.plan-view-table - @extend $table-base - .plan-view-table__cell - padding 0 - .summary--card__title - font-family SourceSansPro-Regular - line-height 1.6 - letter-spacing -0.2px - color $popover-color - font-size 16px - display inline-block - margin-bottom 10px - padding 0 - text-transform none - &__row - &--body - border-top none - &:hover - background-color $adminui-white - &__tooltip - .hover-tooltip__text - width 520px - margin-left 15px - -.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 - -.plan-view - color $body-color - position relative - - .plan-view-container - height 100% - max-height 100% - overflow hidden - - .plan-view-container-scroll - max-height 400px - overflow-y scroll - - .plan-view-container-directions - text-align center - cursor pointer - text-transform uppercase - color $main-blue-color - font-size smaller - - .node-icon - margin 0 10px 0 0 - color $grey-light - .warning-icon - margin 0 4px 0 4px - position relative - top 3px - path - fill $colors--functional-orange-4 - - .warn - position relative - left -5px - color $colors--functional-orange-4 - background-color $plan-node-warning-background-color - border-radius 2px - padding 2px - - .nodeDetails - position relative - padding 6px 0 - border 1px solid transparent - b - font-family SourceSansPro-SemiBold - font-size 12px - font-weight 600 - line-height 1.67 - letter-spacing 0.3px - color $text-color - - .nodeAttributes - color $adminui-grey-2 - padding 7px 16px 0px 18px - margin-left 3px - border-left 1px solid $grey-light - font-family RobotoMono-Medium - font-size 12px - font-weight 500 - line-height 1.83 - - .nodeAttributeKey - color $colors--primary-green-3 - - ul - padding 0 - margin 0 - li - padding 0 - margin 0 - position relative - list-style-type none - - // vertical line, to previous node (above) - &:not(:first-child):after - content '' - width 1px - height 19px - background-color $grey-light - position absolute - top -10px - left 4px - - ul - padding-left 27px - position relative - &:last-child - &:before - content '' - width 28px - height 29px - position absolute - border-left 1px solid $grey-light - border-bottom 1px solid $grey-light - top -10px - left 4px - border-bottom-left-radius 10px - li - &:before - content none - &:first-child:after - content none - li - // first node: horizontal line, to parent - .nodeDetails - margin-left 12px - &:not(:first-child):after - left 16px - &:last-child - .nodeAttributes - border-color transparent - &:first-child - &:after - content '' - height 1px - width 27px - background-color $grey-light - position absolute - top 18px - left -22px - &:before - content '' - width 1px - height 100% - background-color $grey-light - position absolute - top -10px - left -23px diff --git a/pkg/ui/src/views/statements/planView/planView.spec.tsx b/pkg/ui/src/views/statements/planView/planView.spec.tsx deleted file mode 100644 index b4cfc6f81121..000000000000 --- a/pkg/ui/src/views/statements/planView/planView.spec.tsx +++ /dev/null @@ -1,415 +0,0 @@ -// Copyright 2018 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 { assert } from "chai"; - -import { cockroach } from "src/js/protos"; -import { - FlatPlanNode, - FlatPlanNodeAttribute, - flattenTree, - flattenAttributes, -} from "src/views/statements/planView"; -import IAttr = cockroach.sql.ExplainTreePlanNode.IAttr; -import IExplainTreePlanNode = cockroach.sql.IExplainTreePlanNode; - -const testAttrs1: IAttr[] = [ - { - key: "key1", - value: "value1", - }, - { - key: "key2", - value: "value2", - }, -]; - -const testAttrs2: IAttr[] = [ - { - key: "key3", - value: "value3", - }, - { - key: "key4", - value: "value4", - }, -]; - -const testFlatAttrs1: FlatPlanNodeAttribute[] = [ - { - key: "key1", - values: ["value1"], - warn: false, - }, - { - key: "key2", - values: ["value2"], - warn: false, - }, -]; - -const testFlatAttrs2: FlatPlanNodeAttribute[] = [ - { - key: "key3", - values: ["value3"], - warn: false, - }, - { - key: "key4", - values: ["value4"], - warn: false, - }, -]; - -const treePlanWithSingleChildPaths: IExplainTreePlanNode = { - name: "root", - attrs: null, - children: [ - { - name: "single_grandparent", - attrs: testAttrs1, - children: [ - { - name: "single_parent", - attrs: null, - children: [ - { - name: "single_child", - attrs: testAttrs2, - children: [], - }, - ], - }, - ], - }, - ], -}; - -const expectedFlatPlanWithSingleChildPaths: FlatPlanNode[] = [ - { - name: "root", - attrs: [], - children: [], - }, - { - name: "single_grandparent", - attrs: testFlatAttrs1, - children: [], - }, - { - name: "single_parent", - attrs: [], - children: [], - }, - { - name: "single_child", - attrs: testFlatAttrs2, - children: [], - }, -]; - -const treePlanWithChildren1: IExplainTreePlanNode = { - name: "root", - attrs: testAttrs1, - children: [ - { - name: "single_grandparent", - attrs: testAttrs1, - children: [ - { - name: "parent_1", - attrs: null, - children: [ - { - name: "single_child", - attrs: testAttrs2, - children: [], - }, - ], - }, - { - name: "parent_2", - attrs: null, - children: [], - }, - ], - }, - ], -}; - -const expectedFlatPlanWithChildren1: FlatPlanNode[] = [ - { - name: "root", - attrs: testFlatAttrs1, - children: [], - }, - { - name: "single_grandparent", - attrs: testFlatAttrs1, - children: [ - [ - { - name: "parent_1", - attrs: [], - children: [], - }, - { - name: "single_child", - attrs: testFlatAttrs2, - children: [], - }, - ], - [ - { - name: "parent_2", - attrs: [], - children: [], - }, - ], - ], - }, -]; - -const treePlanWithChildren2: IExplainTreePlanNode = { - name: "root", - attrs: null, - children: [ - { - name: "single_grandparent", - attrs: null, - children: [ - { - name: "single_parent", - attrs: null, - children: [ - { - name: "child_1", - attrs: testAttrs1, - children: [], - }, - { - name: "child_2", - attrs: testAttrs2, - children: [], - }, - ], - }, - ], - }, - ], -}; - -const expectedFlatPlanWithChildren2: FlatPlanNode[] = [ - { - name: "root", - attrs: [], - children: [], - }, - { - name: "single_grandparent", - attrs: [], - children: [], - }, - { - name: "single_parent", - attrs: [], - children: [ - [ - { - name: "child_1", - attrs: testFlatAttrs1, - children: [], - }, - ], - [ - { - name: "child_2", - attrs: testFlatAttrs2, - children: [], - }, - ], - ], - }, -]; - -const treePlanWithNoChildren: IExplainTreePlanNode = { - name: "root", - attrs: testAttrs1, - children: [], -}; - -const expectedFlatPlanWithNoChildren: FlatPlanNode[] = [ - { - name: "root", - attrs: testFlatAttrs1, - children: [], - }, -]; - -describe("flattenTree", () => { - describe("when node has children", () => { - it("flattens single child paths.", () => { - assert.deepEqual( - flattenTree(treePlanWithSingleChildPaths), - expectedFlatPlanWithSingleChildPaths, - ); - }); - it("increases level if multiple children.", () => { - assert.deepEqual( - flattenTree(treePlanWithChildren1), - expectedFlatPlanWithChildren1, - ); - assert.deepEqual( - flattenTree(treePlanWithChildren2), - expectedFlatPlanWithChildren2, - ); - }); - }); - describe("when node has no children", () => { - it("returns valid flattened plan.", () => { - assert.deepEqual( - flattenTree(treePlanWithNoChildren), - expectedFlatPlanWithNoChildren, - ); - }); - }); -}); - -describe("flattenAttributes", () => { - describe("when all attributes have different keys", () => { - it("creates array with exactly one value for each attribute", () => { - const testAttrs: IAttr[] = [ - { - key: "key1", - value: "value1", - }, - { - key: "key2", - value: "value2", - }, - ]; - const expectedTestAttrs: FlatPlanNodeAttribute[] = [ - { - key: "key1", - values: ["value1"], - warn: false, - }, - { - key: "key2", - values: ["value2"], - warn: false, - }, - ]; - - assert.deepEqual(flattenAttributes(testAttrs), expectedTestAttrs); - }); - }); - describe("when there are multiple attributes with same key", () => { - it("collects values into one array for same key", () => { - const testAttrs: IAttr[] = [ - { - key: "key1", - value: "key1-value1", - }, - { - key: "key2", - value: "key2-value1", - }, - { - key: "key1", - value: "key1-value2", - }, - ]; - const expectedTestAttrs: FlatPlanNodeAttribute[] = [ - { - key: "key1", - values: ["key1-value1", "key1-value2"], - warn: false, - }, - { - key: "key2", - values: ["key2-value1"], - warn: false, - }, - ]; - - assert.deepEqual(flattenAttributes(testAttrs), expectedTestAttrs); - }); - }); - describe("when attribute key/value is `spans FULL SCAN`", () => { - it("sets warn to true", () => { - const testAttrs: IAttr[] = [ - { - key: "foo", - value: "bar", - }, - { - key: "spans", - value: "FULL SCAN", - }, - ]; - const expectedTestAttrs: FlatPlanNodeAttribute[] = [ - { - key: "foo", - values: ["bar"], - warn: false, - }, - { - key: "spans", - values: ["FULL SCAN"], - warn: true, - }, - ]; - - assert.deepEqual(flattenAttributes(testAttrs), expectedTestAttrs); - }); - }); - describe("when keys are unsorted", () => { - it("puts table key first, and sorts remaining keys alphabetically", () => { - const testAttrs: IAttr[] = [ - { - key: "zebra", - value: "foo", - }, - { - key: "table", - value: "foo", - }, - { - key: "cheetah", - value: "foo", - }, - { - key: "table", - value: "bar", - }, - ]; - const expectedTestAttrs: FlatPlanNodeAttribute[] = [ - { - key: "table", - values: ["foo", "bar"], - warn: false, - }, - { - key: "cheetah", - values: ["foo"], - warn: false, - }, - { - key: "zebra", - values: ["foo"], - warn: false, - }, - ]; - - assert.deepEqual(flattenAttributes(testAttrs), expectedTestAttrs); - }); - }); -}); diff --git a/pkg/ui/src/views/statements/planView/planView.stories.tsx b/pkg/ui/src/views/statements/planView/planView.stories.tsx deleted file mode 100644 index 7a832a1fedcc..000000000000 --- a/pkg/ui/src/views/statements/planView/planView.stories.tsx +++ /dev/null @@ -1,18 +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 { PlanView } from "./planView"; -import { logicalPlan } from "./planView.fixtures"; - -storiesOf("PlanView", module).add("default", () => ( - -)); diff --git a/pkg/ui/src/views/statements/planView/planView.tsx b/pkg/ui/src/views/statements/planView/planView.tsx deleted file mode 100644 index 436956619be0..000000000000 --- a/pkg/ui/src/views/statements/planView/planView.tsx +++ /dev/null @@ -1,373 +0,0 @@ -// Copyright 2018 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 _ from "lodash"; -import React, { Fragment } from "react"; -import classNames from "classnames/bind"; -import { cockroach } from "src/js/protos"; -import { ToolTipWrapper } from "src/views/shared/components/toolTip"; -import styles from "./planView.module.styl"; - -import IAttr = cockroach.sql.ExplainTreePlanNode.IAttr; -import IExplainTreePlanNode = cockroach.sql.IExplainTreePlanNode; - -const cx = classNames.bind(styles); - -const WARNING_ICON = ( - - - -); -const NODE_ICON = ; - -// FlatPlanNodeAttribute contains a flattened representation of IAttr[]. -export interface FlatPlanNodeAttribute { - key: string; - values: string[]; - warn: boolean; -} - -// FlatPlanNode contains details for the flattened representation of -// IExplainTreePlanNode. -// -// Note that the function that flattens IExplainTreePlanNode returns -// an array of FlatPlanNode (not a single FlatPlanNode). E.g.: -// -// flattenTree(IExplainTreePlanNode) => FlatPlanNode[] -// -export interface FlatPlanNode { - name: string; - attrs: FlatPlanNodeAttribute[]; - children: FlatPlanNode[][]; -} - -/* ************************* HELPER FUNCTIONS ************************* */ - -// flattenTree takes a tree representation of a logical plan -// (IExplainTreePlanNode) and flattens any single child paths. -// For example, if an IExplainTreePlanNode was visually displayed -// as: -// -// root -// | -// |___ single_grandparent -// | -// |____ parent_1 -// | | -// | |______ single_child -// | -// |____ parent_2 -// -// Then its FlatPlanNode[] equivalent would be visually displayed -// as: -// -// root -// | -// single_grandparent -// | -// |____ parent_1 -// | | -// | single_child -// | -// |____ parent_2 -// -export function flattenTree(treePlan: IExplainTreePlanNode): FlatPlanNode[] { - const flattenedPlan: FlatPlanNode[] = [ - { - name: treePlan.name, - attrs: flattenAttributes(treePlan.attrs), - children: [], - }, - ]; - - if (treePlan.children.length === 0) { - return flattenedPlan; - } - const flattenedChildren = treePlan.children.map((child) => - flattenTree(child), - ); - if (treePlan.children.length === 1) { - // Append single child into same list that contains parent node. - flattenedPlan.push(...flattenedChildren[0]); - } else { - // Only add to children property if there are multiple children. - flattenedPlan[0].children = flattenedChildren; - } - return flattenedPlan; -} - -// flattenAttributes takes a list of attrs (IAttr[]) and collapses -// all the values for the same key (FlatPlanNodeAttribute). For example, -// if attrs was: -// -// attrs: IAttr[] = [ -// { -// key: "render", -// value: "name", -// }, -// { -// key: "render", -// value: "title", -// }, -// ]; -// -// The returned FlatPlanNodeAttribute would be: -// -// flattenedAttr: FlatPlanNodeAttribute = { -// key: "render", -// value: ["name", "title"], -// }; -// -export function flattenAttributes( - attrs: IAttr[] | null, -): FlatPlanNodeAttribute[] { - if (attrs === null) { - return []; - } - const flattenedAttrsMap: { [key: string]: FlatPlanNodeAttribute } = {}; - attrs.forEach((attr) => { - const existingAttr = flattenedAttrsMap[attr.key]; - const warn = warnForAttribute(attr); - if (!existingAttr) { - flattenedAttrsMap[attr.key] = { - key: attr.key, - values: [attr.value], - warn: warn, - }; - } else { - existingAttr.values.push(attr.value); - if (warn) { - existingAttr.warn = true; - } - } - }); - const flattenedAttrs = _.values(flattenedAttrsMap); - return _.sortBy(flattenedAttrs, (attr) => - attr.key === "table" ? "table" : "z" + attr.key, - ); -} - -function warnForAttribute(attr: IAttr): boolean { - // TODO(yuzefovich): 'spans ALL' is pre-20.1 attribute (and it might show up - // during an upgrade), so we should remove the check for it after 20.2 - // release. - if ( - attr.key === "spans" && - (attr.value === "FULL SCAN" || attr.value === "ALL") - ) { - return true; - } - return false; -} - -// shouldHideNode looks at node name to determine whether we should hide -// node from logical plan tree. -// -// Currently we're hiding `row source to planNode`, which is a node -// generated during execution (e.g. this is an internal implementation -// detail that will add more confusion than help to user). See #34594 -// for details. -function shouldHideNode(nodeName: string): boolean { - if (nodeName === "row source to plan node") { - return true; - } - return false; -} - -/* ************************* PLAN NODES ************************* */ - -interface PlanNodeDetailProps { - node: FlatPlanNode; -} - -class PlanNodeDetails extends React.Component { - constructor(props: PlanNodeDetailProps) { - super(props); - } - - renderAttributeValues(values: string[]) { - if (!values.length || !values[0].length) { - return; - } - if (values.length === 1) { - return = {values[0]}; - } - return = [{values.join(", ")}]; - } - - renderAttribute(attr: FlatPlanNodeAttribute) { - let attrClassName = ""; - let keyClassName = "nodeAttributeKey"; - if (attr.warn) { - attrClassName = "warn"; - keyClassName = ""; - } - return ( -
- {attr.warn && WARNING_ICON} - {attr.key} - {this.renderAttributeValues(attr.values)} -
- ); - } - - renderNodeDetails() { - const node = this.props.node; - if (node.attrs && node.attrs.length > 0) { - return ( -
- {node.attrs.map((attr) => this.renderAttribute(attr))} -
- ); - } - } - - render() { - const node = this.props.node; - return ( -
- {NODE_ICON} {_.capitalize(node.name)} - {this.renderNodeDetails()} -
- ); - } -} - -function PlanNodes(props: { nodes: FlatPlanNode[] }): React.ReactElement<{}> { - const nodes = props.nodes; - return ( -
    - {nodes.map((node) => { - return ; - })} -
- ); -} - -interface PlanNodeProps { - node: FlatPlanNode; -} - -class PlanNode extends React.Component { - render() { - if (shouldHideNode(this.props.node.name)) { - return null; - } - const node = this.props.node; - return ( -
  • - - {node.children && - node.children.map((child) => )} -
  • - ); - } -} - -interface PlanViewProps { - title: string; - plan: IExplainTreePlanNode; -} - -interface PlanViewState { - expanded: boolean; - showExpandDirections: boolean; -} - -export class PlanView extends React.Component { - private innerContainer: React.RefObject; - constructor(props: PlanViewProps) { - super(props); - this.state = { - expanded: false, - showExpandDirections: true, - }; - this.innerContainer = React.createRef(); - } - - toggleExpanded = () => { - this.setState((state) => ({ - expanded: !state.expanded, - })); - }; - - showExpandDirections() { - // Only show directions to show/hide the full plan if content is longer than its max-height. - const containerObj = this.innerContainer.current; - return containerObj.scrollHeight > containerObj.clientHeight; - } - - componentDidMount() { - this.setState({ showExpandDirections: this.showExpandDirections() }); - } - - render() { - const flattenedPlanNodes = flattenTree(this.props.plan); - - const lastSampledHelpText = ( - - If the time from the last sample is greater than 5 minutes, a new plan - will be sampled. This frequency can be configured with the cluster - setting{" "} - -
    -            sql.metrics.statement_details.plan_collection.period
    -          
    -
    - . -
    - ); - - return ( -
    - - - - - - - - - - -
    -

    - {this.props.title} -

    -
    - -
    -
    i
    -
    -
    -
    -
    -
    -
    - -
    -
    -
    - ); - } -} diff --git a/pkg/ui/src/views/statements/statementDetails.tsx b/pkg/ui/src/views/statements/statementDetails.tsx index db90c64031ab..2359791272c8 100644 --- a/pkg/ui/src/views/statements/statementDetails.tsx +++ b/pkg/ui/src/views/statements/statementDetails.tsx @@ -30,7 +30,6 @@ import { } from "src/util/appStats"; import { appAttr, implicitTxnAttr, statementAttr } from "src/util/constants"; import { FixLong } from "src/util/fixLong"; -import { AggregateStatistics } from "./statementsTable"; import { getMatchParamByName } from "src/util/query"; import { selectDiagnosticsReportsByStatementFingerprint } from "src/redux/statements/statementsSelectors"; import { @@ -38,6 +37,7 @@ import { StatementDetailsDispatchProps, StatementDetailsStateProps, StatementDetailsProps, + AggregateStatistics, } from "@cockroachlabs/admin-ui-components"; import { createStatementDiagnosticsReportAction } from "src/redux/statements"; import { createStatementDiagnosticsAlertLocalSetting } from "src/redux/alerts"; diff --git a/pkg/ui/src/views/statements/statementsTable.module.styl b/pkg/ui/src/views/statements/statementsTable.module.styl deleted file mode 100644 index 4bb7547592f7..000000000000 --- a/pkg/ui/src/views/statements/statementsTable.module.styl +++ /dev/null @@ -1,50 +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/components/core/index' - -.statements-table__col-time - white-space nowrap - -.statements-table__col-count--bar-chart - width 100px - -.statements-table__col-retries--bar-chart - width 80px - -.numeric-stats-table - .bar-chart - width 200px - -.statements-table__col-rows, .statements-table__col-latency - &--bar-chart - min-width 150px - -.statements-table__col-count, .statements-table__col-retries, .statements-table__col-rows - &--bar-chart - margin-left 0 - - &__label - left 0 - width 40px - min-width 40px - -.cl-table__col-query-text a - font-family RobotoMono-Medium - font-size 12px - line-height 1.83 - color $adminui-grey-1 - width 400px - text-decoration none - cursor pointer - &:hover - color $colors--primary-blue-3 - text-decoration underline diff --git a/pkg/ui/src/views/statements/statementsTable.stories.tsx b/pkg/ui/src/views/statements/statementsTable.stories.tsx deleted file mode 100644 index 7ee3a214189f..000000000000 --- a/pkg/ui/src/views/statements/statementsTable.stories.tsx +++ /dev/null @@ -1,39 +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 { - makeStatementsColumns, - StatementsSortedTable, -} from "./statementsTable"; -import statementsPagePropsFixture from "src/views/statements/statementsPage.fixture"; -import { withRouterProvider } from ".storybook/decorators"; - -const { statements } = statementsPagePropsFixture; - -storiesOf("StatementsSortedTable", module) - .addDecorator(withRouterProvider) - .add("with data", () => ( - - )) - .add("empty table", () => ); diff --git a/pkg/ui/src/views/statements/statementsTable.tsx b/pkg/ui/src/views/statements/statementsTable.tsx deleted file mode 100644 index 2515f97f30fd..000000000000 --- a/pkg/ui/src/views/statements/statementsTable.tsx +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright 2018 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 classNames from "classnames/bind"; - -import { StatementStatistics } from "src/util/appStats"; -import { FixLong } from "src/util/fixLong"; -import { StatementSummary } from "src/util/sql/summarize"; -import { - ColumnDescriptor, - SortedTable, -} from "src/views/shared/components/sortedtable"; -import { - countBarChart, - latencyBarChart, - retryBarChart, - rowsBarChart, -} from "./barCharts"; -import "./statements.styl"; -import { cockroach } from "src/js/protos"; -import IStatementDiagnosticsReport = cockroach.server.serverpb.IStatementDiagnosticsReport; -import { ActivateDiagnosticsModalRef } from "./diagnostics/activateDiagnosticsModal"; -import styles from "./statementsTable.module.styl"; -import { - StatementTableTitle, - StatementTableCell, - NodeNames, -} from "./statementsTableContent"; -import { getDiagnosticsStatus } from "src/views/statements/diagnostics"; - -const cx = classNames.bind(styles); -const longToInt = (d: number | Long) => FixLong(d).toInt(); - -export interface AggregateStatistics { - // label is either shortStatement (StatementsPage) or nodeId (StatementDetails). - label: string; - implicitTxn: boolean; - stats: StatementStatistics; - drawer?: boolean; - firstCellBordered?: boolean; - diagnosticsReport?: IStatementDiagnosticsReport; -} - -export class StatementsSortedTable extends SortedTable {} - -export function shortStatement(summary: StatementSummary, original: string) { - switch (summary.statement) { - case "update": - return "UPDATE " + summary.table; - case "insert": - return "INSERT INTO " + summary.table; - case "select": - return "SELECT FROM " + summary.table; - case "delete": - return "DELETE FROM " + summary.table; - case "create": - return "CREATE TABLE " + summary.table; - case "set": - return "SET " + summary.table; - default: - return original; - } -} - -export function makeStatementsColumns( - statements: AggregateStatistics[], - selectedApp: string, - search?: string, - activateDiagnosticsRef?: React.RefObject, -): ColumnDescriptor[] { - const columns: ColumnDescriptor[] = [ - { - title: StatementTableTitle.statements, - className: cx("cl-table__col-query-text"), - cell: StatementTableCell.statements(search, selectedApp), - sort: (stmt) => stmt.label, - }, - { - title: StatementTableTitle.txtType, - className: cx("statements-table__col-time"), - cell: (stmt) => (stmt.implicitTxn ? "Implicit" : "Explicit"), - sort: (stmt) => (stmt.implicitTxn ? "Implicit" : "Explicit"), - }, - ]; - columns.push(...makeCommonColumns(statements)); - - if (activateDiagnosticsRef) { - const diagnosticsColumn: ColumnDescriptor = { - title: StatementTableTitle.diagnostics, - cell: StatementTableCell.diagnostics(activateDiagnosticsRef), - sort: (stmt) => { - if (stmt.diagnosticsReport) { - return getDiagnosticsStatus(stmt.diagnosticsReport); - } - return null; - }, - }; - columns.push(diagnosticsColumn); - } - return columns; -} - -export function makeNodesColumns( - statements: AggregateStatistics[], - nodeNames: NodeNames, -): ColumnDescriptor[] { - const original: ColumnDescriptor[] = [ - { - title: null, - cell: StatementTableCell.nodeLink(nodeNames), - // sort: (stmt) => stmt.label, - }, - ]; - - return original.concat(makeCommonColumns(statements)); -} - -function makeCommonColumns( - statements: AggregateStatistics[], -): ColumnDescriptor[] { - const countBar = countBarChart(statements, { - classes: { - root: cx("statements-table__col-count--bar-chart"), - label: cx("statements-table__col-count--bar-chart__label"), - }, - }); - const retryBar = retryBarChart(statements, { - classes: { - root: cx("statements-table__col-retries--bar-chart"), - label: cx("statements-table__col-retries--bar-chart__label"), - }, - }); - const rowsBar = rowsBarChart(statements, { - classes: { - root: cx("statements-table__col-rows--bar-chart"), - label: cx("statements-table__col-rows--bar-chart__label"), - }, - }); - const latencyBar = latencyBarChart(statements, { - classes: { - root: cx("statements-table__col-latency--bar-chart"), - }, - }); - - return [ - { - title: StatementTableTitle.retries, - className: cx("statements-table__col-retries"), - cell: retryBar, - sort: (stmt) => - longToInt(stmt.stats.count) - longToInt(stmt.stats.first_attempt_count), - }, - { - title: StatementTableTitle.executionCount, - className: cx("statements-table__col-count"), - cell: countBar, - sort: (stmt) => FixLong(stmt.stats.count).toInt(), - }, - { - title: StatementTableTitle.rowsAffected, - className: cx("statements-table__col-rows"), - cell: rowsBar, - sort: (stmt) => stmt.stats.num_rows.mean, - }, - { - title: StatementTableTitle.latency, - className: cx("statements-table__col-latency"), - cell: latencyBar, - sort: (stmt) => stmt.stats.service_lat.mean, - }, - ]; -} diff --git a/pkg/ui/src/views/statements/statementsTableContent.tsx b/pkg/ui/src/views/statements/statementsTableContent.tsx deleted file mode 100644 index 1fac494015d6..000000000000 --- a/pkg/ui/src/views/statements/statementsTableContent.tsx +++ /dev/null @@ -1,291 +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 { Link } from "react-router-dom"; -import classNames from "classnames/bind"; -import { Anchor, Tooltip } from "src/components"; -import { - statementDiagnostics, - statementsRetries, - statementsSql, - statementsTimeInterval, - transactionalPipelining, -} from "src/util/docs"; -import getHighlightedText from "src/util/highlightedText"; -import { summarize } from "src/util/sql/summarize"; -import { ActivateDiagnosticsModalRef } from "./diagnostics/activateDiagnosticsModal"; -import { DiagnosticStatusBadge } from "./diagnostics/diagnosticStatusBadge"; -import { shortStatement } from "./statementsTable"; -import styles from "./statementsTableContent.module.styl"; -import { getDiagnosticsStatus } from "src/views/statements/diagnostics"; - -export type NodeNames = { [nodeId: string]: string }; - -const cx = classNames.bind(styles); - -export const StatementTableTitle = { - statements: ( - -

    - {"SQL statement "} - - fingerprint. - -

    -

    - To view additional details of a SQL statement fingerprint, click - this to open the Statement Details page. -

    -
    - } - > - Statements - - ), - txtType: ( - -

    - { - "Type of transaction (implicit or explicit). Explicit transactions refer to statements that are wrapped by " - } - BEGIN - {" and "} - COMMIT - {" statements by the client. Explicit transactions employ "} - - transactional pipelining - - { - " and therefore report latencies that do not account for replication." - } -

    -

    - For statements not in explicit transactions, CockroachDB wraps each - statement in individual implicit transactions. -

    -
    - } - > - TXN Type - - ), - diagnostics: ( - -

    - {"Option to activate "} - - diagnostics - - { - " for each statement. If activated, this displays the status of diagnostics collection (" - } - WAITING, READY, OR ERROR). -

    -
    - } - > - Diagnostics - - ), - retries: ( - -

    - {"Cumulative number of "} - - retries - - { - " of statements with this fingerprint within the last hour or specified time interval." - } -

    -
    - } - > - Retries - - ), - executionCount: ( - -

    - { - "Cumulative number of executions of statements with this fingerprint within the last hour or specified " - } - - time interval - - . -

    -

    - {"The bar indicates the ratio of runtime success (gray) to "} - - retries - - {" (red) for the SQL statement fingerprint."} -

    -
    - } - > - Execution Count - - ), - rowsAffected: ( - -

    - { - "Average number of rows returned while executing statements with this fingerprint within the last hour or specified " - } - - time interval - - . -

    -

    - The gray bar indicates the mean number of rows returned. The blue - bar indicates one standard deviation from the mean. -

    -
    - } - > - Rows Affected - - ), - latency: ( - -

    - Average service latency of statements with this fingerprint within - the last hour or specified time interval. -

    -

    - The gray bar indicates the mean latency. The blue bar indicates one - standard deviation from the mean. -

    -
    - } - > - Latency - - ), -}; - -export const StatementTableCell = { - statements: (search?: string, selectedApp?: string) => (stmt: any) => ( - - ), - diagnostics: ( - activateDiagnosticsRef: React.RefObject, - ) => (stmt: any) => { - if (stmt.diagnosticsReport) { - return ( - - ); - } - return ( - - activateDiagnosticsRef?.current?.showModalFor(stmt.label) - } - > - Activate - - ); - }, - nodeLink: (nodeNames: NodeNames) => (stmt: any) => ( - - ), -}; - -interface StatementLinkProps { - statement: string; - app: string; - implicitTxn: boolean; - search: string; - anonStatement?: string; -} - -// StatementLinkTarget returns the link to the relevant statement page, given -// the input statement details. -export const StatementLinkTarget = (props: StatementLinkProps) => { - let base: string; - if (props.app && props.app.length > 0) { - base = `/statements/${props.app}/${props.implicitTxn}`; - } else { - base = `/statement/${props.implicitTxn}`; - } - - let linkStatement = props.statement; - if (props.anonStatement) { - linkStatement = props.anonStatement; - } - return `${base}/${encodeURIComponent(linkStatement)}`; -}; - -export const StatementLink = (props: StatementLinkProps) => { - const summary = summarize(props.statement); - return ( - -
    - - {getHighlightedText(props.statement, props.search)} - - } - overlayClassName={cx("cl-table-link__statement-tooltip--fixed-width")} - > -
    - {getHighlightedText( - shortStatement(summary, props.statement), - props.search, - true, - )} -
    -
    -
    - - ); -}; - -export const NodeLink = (props: { nodeId: string; nodeNames: NodeNames }) => ( - - - {props.nodeNames[props.nodeId]} - - -);