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/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/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/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/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.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..2359791272c8 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 { - approximify, - latencyBreakdown, - genericBarChart, - longToInt, - rowsBreakdown, -} from "./barCharts"; +import { getMatchParamByName } from "src/util/query"; +import { selectDiagnosticsReportsByStatementFingerprint } from "src/redux/statements/statementsSelectors"; import { + StatementDetails, + StatementDetailsDispatchProps, + StatementDetailsStateProps, + StatementDetailsProps, 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; +} from "@cockroachlabs/admin-ui-components"; +import { createStatementDiagnosticsReportAction } from "src/redux/statements"; +import { createStatementDiagnosticsAlertLocalSetting } from "src/redux/alerts"; +import { + 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/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]} - - -); 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"