Skip to content

Commit

Permalink
db-console: statement details component extraction
Browse files Browse the repository at this point in the history
This change extracts Statements details page and its dependent
components and styles.
- most of the components moved without any changes, only import
paths were adjusted
- styles converted from Styl to SCSS
- modified route paths to ensure they start with "/" root path
(it ensures that routes behave the same way regardless to current
path).
- `util` directory now contain `network` and `nodes` subdirectories
with logic specific to particular entities. It allows avoid extra logic
in redux layer and keep it independently.
  • Loading branch information
koorosh committed Jan 12, 2021
1 parent 7edbdff commit ee7c427
Show file tree
Hide file tree
Showing 55 changed files with 3,832 additions and 42 deletions.
3 changes: 2 additions & 1 deletion packages/admin-ui-components/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"rules": {
"@typescript-eslint/interface-name-prefix": "off",
"@typescript-eslint/camelcase": "warn",
"@typescript-eslint/no-explicit-any": "warn"
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-namespace": "off"
}
}
4 changes: 2 additions & 2 deletions packages/admin-ui-components/jest.testing.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ module.exports = {
displayName: "test",
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
moduleNameMapper: {
"\\.(jpg|ico|jpeg|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "identity-obj-proxy",
"\\.(jpg|ico|jpeg|eot|otf|webp|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "identity-obj-proxy",
"\\.(css|scss|less)$": "identity-obj-proxy",
"\\.(gif|png)$": "<rootDir>/.jest/fileMock.js",
"\\.(gif|png|svg)$": "<rootDir>/.jest/fileMock.js",
},
"moduleDirectories": [
".",
Expand Down
2 changes: 1 addition & 1 deletion packages/admin-ui-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"keywords": [],
"license": "MIT",
"dependencies": {
"@cockroachlabs/crdb-protobuf-client": "^0.0.3",
"@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",
Expand Down
101 changes: 101 additions & 0 deletions packages/admin-ui-components/src/barCharts/genericBarChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import React from "react";
import classNames from "classnames/bind";
import { scaleLinear } from "d3-scale";
import { format as d3Format } from "d3-format";

import { stdDevLong } from "src/util/appStats";
import { Tooltip } from "src/tooltip";
import { NumericStat } from "../util";
import { clamp, longToInt, normalizeClosedDomain } from "./utils";
import styles from "./barCharts.module.scss";

const cx = classNames.bind(styles);

function renderNumericStatLegend(
count: number | Long,
stat: number,
sd: number,
formatter: (d: number) => string,
) {
return (
<table className={cx("numeric-stat-legend")}>
<tbody>
<tr>
<th>
<div
className={cx(
"numeric-stat-legend__bar",
"numeric-stat-legend__bar--mean",
)}
/>
Mean
</th>
<td>{formatter(stat)}</td>
</tr>
<tr>
<th>
<div
className={cx(
"numeric-stat-legend__bar",
"numeric-stat-legend__bar--dev",
)}
/>
Standard Deviation
</th>
<td>{longToInt(count) < 2 ? "-" : sd ? formatter(sd) : "0"}</td>
</tr>
</tbody>
</table>
);
}

export function genericBarChart(
s: NumericStat,
count: number | Long,
format?: (v: number) => string,
) {
if (!s) {
return () => <div />;
}
const mean = s.mean;
const sd = stdDevLong(s, count);

const max = mean + sd;
const scale = scaleLinear()
.domain(normalizeClosedDomain([0, max]))
.range([0, 100]);
if (!format) {
format = d3Format(".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 (
<Tooltip text={title} short>
<div className={cx("bar-chart", "bar-chart--breakdown")}>
<div className={cx("bar-chart__label")}>{format(mean)}</div>
<div className={cx("bar-chart__multiplebars")}>
<div
className={cx("bar-chart__parse", "bar-chart__bar")}
style={{ width: right + "%", position: "absolute", left: 0 }}
/>
<div
className={cx(
"bar-chart__parse-dev",
"bar-chart__bar",
"bar-chart__bar--dev",
)}
style={{
width: spread + "%",
position: "absolute",
left: width + "%",
}}
/>
</div>
</div>
</Tooltip>
);
};
}
4 changes: 4 additions & 0 deletions packages/admin-ui-components/src/barCharts/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
export * from "./barCharts";
export * from "./rowsBreakdown";
export * from "./utils";
export * from "./latencyBreakdown";
export * from "./genericBarChart";
4 changes: 4 additions & 0 deletions packages/admin-ui-components/src/declaration.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,7 @@ type FirstConstructorParameter<
> = ConstructorParameters<P>[0];

type Tuple<T> = [T, T];

type Dictionary<V> = {
[key: string]: V;
};
87 changes: 87 additions & 0 deletions packages/admin-ui-components/src/downloadFile/downloadFile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// 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:
* ```
* <DownloadFile fileName="example.txt" fileType="text/plain" content="Some text">
* <button>Download</download>
* </DownloadFile>
* ```
*
* `Imperative` mode allows initiate file download in async way, and trigger
* download manually.
*
* For example:
* ```
* downloadRef = React.createRef<DownloadFileRef>();
*
* fetchData = () => {
* Promise.resolve().then((someText) =>
* this.downloadRef.current.download("example.txt", "text/plain", someText))
* }
*
* <DownloadFile ref={downloadRef} />
* <button onClick={fetchData}>Download</button>
* ```
* */
// tslint:disable-next-line:variable-name
export const DownloadFile = forwardRef<DownloadFileRef, DownloadAsFileProps>(
(props, ref) => {
const { children, fileName, fileType, content } = props;
const anchorRef = useRef<HTMLAnchorElement>();

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 <a ref={anchorRef}>{children}</a>;
},
);
11 changes: 11 additions & 0 deletions packages/admin-ui-components/src/downloadFile/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// 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";
5 changes: 5 additions & 0 deletions packages/admin-ui-components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export * from "./anchor";
export * from "./badge";
export * from "./barCharts";
export * from "./button";
export * from "./downloadFile";
export * from "./dropdown";
export * from "./empty";
export * from "./highlightedText";
Expand All @@ -17,7 +18,11 @@ export * from "./search";
export * from "./sortedtable";
export * from "./statementsDiagnostics";
export * from "./statementsPage";
export * from "./statementDetails";
export * from "./statementsTable";
export * from "./statementDetails/statementDetails";
export * from "./sql";
export * from "./table";
export * from "./store";
export * from "./transactionsPage";
export * from "./text";
Expand Down
2 changes: 1 addition & 1 deletion packages/admin-ui-components/src/loading/loading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export const Loading: React.FC<LoadingProps> = props => {

return (
<div className={cx("alerts-container", props.errorClassName)}>
{errorAlerts}
{React.Children.toArray(errorAlerts)}
</div>
);
}
Expand Down
8 changes: 8 additions & 0 deletions packages/admin-ui-components/src/network/identity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import moment from "moment";

export interface Identity {
nodeID: number;
address: string;
locality?: string;
updatedAt: moment.Moment;
}
1 change: 1 addition & 0 deletions packages/admin-ui-components/src/network/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./identity";
32 changes: 32 additions & 0 deletions packages/admin-ui-components/src/nodes/getDisplayName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { cockroach } from "@cockroachlabs/crdb-protobuf-client";
import { NoConnection } from "./noConnection";

type INodeStatus = cockroach.server.status.statuspb.INodeStatus;

const LivenessStatus =
cockroach.kv.kvserver.liveness.livenesspb.NodeLivenessStatus;

function isNoConnection(
node: INodeStatus | NoConnection,
): node is NoConnection {
return (
(node as NoConnection).to !== undefined &&
(node as NoConnection).from !== undefined
);
}

export function getDisplayName(
node: INodeStatus | NoConnection,
livenessStatus = LivenessStatus.NODE_STATUS_LIVE,
) {
const decommissionedString =
livenessStatus === LivenessStatus.NODE_STATUS_DECOMMISSIONED
? "[decommissioned] "
: "";

if (isNoConnection(node)) {
return `${decommissionedString}(n${node.from.nodeID})`;
}
// as the only other type possible right now is INodeStatus we don't have a type guard for that
return `${decommissionedString}(n${node.desc.node_id}) ${node.desc.address.address_field}`;
}
4 changes: 4 additions & 0 deletions packages/admin-ui-components/src/nodes/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from "./getDisplayName";
export * from "./noConnection";
export * from "./nodeCapacityStats";
export * from "./nodeSummaryStats";
6 changes: 6 additions & 0 deletions packages/admin-ui-components/src/nodes/noConnection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { Identity } from "../network";

export interface NoConnection {
from: Identity;
to: Identity;
}
20 changes: 20 additions & 0 deletions packages/admin-ui-components/src/nodes/nodeCapacityStats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { cockroach } from "@cockroachlabs/crdb-protobuf-client";
import { MetricConstants } from "../util/proto";

type INodeStatus = cockroach.server.status.statuspb.INodeStatus;

export interface CapacityStats {
used: number;
usable: number;
available: number;
}

export function nodeCapacityStats(n: INodeStatus): CapacityStats {
const used = n.metrics[MetricConstants.usedCapacity];
const available = n.metrics[MetricConstants.availableCapacity];
return {
used,
available,
usable: used + available,
};
}
19 changes: 19 additions & 0 deletions packages/admin-ui-components/src/nodes/nodeSummaryStats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export type NodeSummaryStats = {
nodeCounts: {
total: number;
healthy: number;
suspect: number;
dead: number;
decommissioned: number;
};
capacityUsed: number;
capacityAvailable: number;
capacityTotal: number;
capacityUsable: number;
usedBytes: number;
usedMem: number;
totalRanges: number;
underReplicatedRanges: number;
unavailableRanges: number;
replicas: number;
};
3 changes: 2 additions & 1 deletion packages/admin-ui-components/src/search/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export class Search extends React.Component<TSearchProps, ISearchState> {

render() {
const { value, submitted } = this.state;
const { onClear, ...inputProps } = this.props;
const className = submitted ? cx("_submitted") : "";

return (
Expand All @@ -99,7 +100,7 @@ export class Search extends React.Component<TSearchProps, ISearchState> {
prefix={<SearchIcon className={cx("_prefix-icon")} />}
suffix={this.renderSuffix()}
value={value}
{...this.props}
{...inputProps}
/>
</Form.Item>
</Form>
Expand Down
Loading

0 comments on commit ee7c427

Please sign in to comment.