import _ from 'lodash'; import BaseTable from './BaseTable.jsx'; import ErrorModal from './ErrorModal.jsx'; import GrafanaLink from './GrafanaLink.jsx'; import { processedMetricsPropType } from './util/MetricUtils.js'; import PropTypes from 'prop-types'; import React from 'react'; import { Tooltip } from 'antd'; import { withContext } from './util/AppContext.jsx'; import { metricToFormatter, numericSort } from './util/Utils.js'; /* Table to display Success Rate, Requests and Latency in tabs. Expects rollup and timeseries data. */ const withTooltip = (d, metricName) => { return ( {metricToFormatter[metricName](d)} ); }; const formatTitle = (title, tooltipText) => { if (!tooltipText) { return title; } else { return ( {title} ); } }; const columnDefinitions = (resource, namespaces, onFilterClick, showNamespaceColumn, ConduitLink) => { let nsColumn = [ { title: formatTitle("Namespace"), key: "namespace", dataIndex: "namespace", filters: namespaces, onFilterDropdownVisibleChange: onFilterClick, onFilter: (value, row) => row.namespace.indexOf(value) === 0, sorter: (a, b) => (a.namespace || "").localeCompare(b.namespace), render: ns => { return {ns}; } } ]; let columns = [ { title: formatTitle(resource), key: "name", defaultSortOrder: 'ascend', sorter: (a, b) => (a.name || "").localeCompare(b.name), render: row => { let nameContents; if (resource.toLowerCase() === "namespace") { nameContents = {row.name}; } else if (!row.added) { nameContents = row.name; } else { nameContents = ( ); } return ( {nameContents} { _.isEmpty(row.errors) ? null : } ); } }, { title: formatTitle("SR", "Success Rate"), dataIndex: "successRate", key: "successRateRollup", className: "numeric", sorter: (a, b) => numericSort(a.successRate, b.successRate), render: d => metricToFormatter["SUCCESS_RATE"](d) }, { title: formatTitle("RPS", "Request Rate"), dataIndex: "requestRate", key: "requestRateRollup", className: "numeric", sorter: (a, b) => numericSort(a.requestRate, b.requestRate), render: d => withTooltip(d, "REQUEST_RATE") }, { title: formatTitle("P50", "P50 Latency"), dataIndex: "P50", key: "p50LatencyRollup", className: "numeric", sorter: (a, b) => numericSort(a.P50, b.P50), render: metricToFormatter["LATENCY"] }, { title: formatTitle("P95", "P95 Latency"), dataIndex: "P95", key: "p95LatencyRollup", className: "numeric", sorter: (a, b) => numericSort(a.P95, b.P95), render: metricToFormatter["LATENCY"] }, { title: formatTitle("P99", "P99 Latency"), dataIndex: "P99", key: "p99LatencyRollup", className: "numeric", sorter: (a, b) => numericSort(a.P99, b.P99), render: metricToFormatter["LATENCY"] }, { title: formatTitle("Secured", "Percentage of TLS Traffic"), key: "securedTraffic", dataIndex: "tlsRequestPercent", className: "numeric", sorter: (a, b) => numericSort(a.tlsRequestPercent.get(), b.tlsRequestPercent.get()), render: d => _.isNil(d) || d.get() === -1 ? "---" : d.prettyRate() } ]; if (resource.toLowerCase() === "namespace" || !showNamespaceColumn) { return columns; } else { return _.concat(nsColumn, columns); } }; /** @extends React.Component */ export class MetricsTableBase extends BaseTable { static defaultProps = { showNamespaceColumn: true, } static propTypes = { api: PropTypes.shape({ ConduitLink: PropTypes.func.isRequired, }).isRequired, metrics: PropTypes.arrayOf(processedMetricsPropType.isRequired).isRequired, resource: PropTypes.string.isRequired, showNamespaceColumn: PropTypes.bool, } constructor(props) { super(props); this.api = this.props.api; this.onFilterDropdownVisibleChange = this.onFilterDropdownVisibleChange.bind(this); this.state = { preventTableUpdates: false }; } shouldComponentUpdate() { // prevent the table from updating if the filter dropdown menu is open // this is because if the table updates, the filters will reset which // makes it impossible to select a filter return !this.state.preventTableUpdates; } onFilterDropdownVisibleChange(dropdownVisible) { this.setState({ preventTableUpdates: dropdownVisible}); } preprocessMetrics() { let tableData = _.cloneDeep(this.props.metrics); let namespaces = []; _.each(tableData, datum => { namespaces.push(datum.namespace); _.each(datum.latency, (value, quantile) => { datum[quantile] = value; }); }); return { rows: tableData, namespaces: _.uniq(namespaces) }; } render() { let tableData = this.preprocessMetrics(); let namespaceFilterText = _.map(tableData.namespaces, ns => { return { text: ns, value: ns }; }); let columns = _.compact(columnDefinitions( this.props.resource, namespaceFilterText, this.onFilterDropdownVisibleChange, this.props.showNamespaceColumn, this.api.ConduitLink )); let locale = { emptyText: `No ${this.props.resource}s detected.` }; return ( `${r.namespace}/${r.name}`} locale={locale} size="middle" /> ); } } export default withContext(MetricsTableBase);