in dashboards-observability/public/components/trace_analytics/components/dashboard/dashboard_table.tsx [29:414]
export function DashboardTable(props: {
items: any[];
filters: FilterType[];
addFilter: (filter: FilterType) => void;
addPercentileFilter: (condition?: 'gte' | 'lte', additionalFilters?: FilterType[]) => void;
setRedirect: (redirect: boolean) => void;
loading: boolean;
}) {
const getVarianceProps = (items: any[]) => {
if (items.length === 0) {
return { minRange: 0, maxRange: 0, ticks: [0, 0], scale: '' };
}
const variances = [].concat(
...items
.filter((item) => item.dashboard_latency_variance)
.map((item) => item.dashboard_latency_variance)
);
const minRange = Math.min(...variances);
const maxRange = Math.max(...variances);
const ticks = calculateTicks(minRange, maxRange);
const maxDigits = ticks[ticks.length - 1].toString().length;
// pads spaces (\u00A0) in between ticks to construct a scale
// width of a character equals the width of two spaces, maximum 39 characters in a scale
const scale = ticks
.map((tick) => {
const tickStr = tick.toString();
return tickStr.padEnd(tickStr.length + 2 * (maxDigits - tickStr.length), '\u00A0');
})
.join(
'\u00A0'.repeat(
Math.max(
1,
Math.floor((2 * (39 - ticks.length * maxDigits)) / Math.max(1, ticks.length - 1))
)
)
);
return { minRange, maxRange, ticks, scale };
};
const getColumns = () =>
[
{
field: 'dashboard_trace_group_name',
name: (
<EuiToolTip
content={
<EuiText size="xs">
Traces of all requests that share a common API and operation at the start of
distributed tracing instrumentation.
</EuiText>
}
>
<>
<div>
Trace group name{' '}
<EuiIcon
size="s"
color="subdued"
type="questionInCircle"
className="eui-alignTop"
/>
</div>
<div> </div>
</>
</EuiToolTip>
),
align: 'left',
sortable: true,
render: (item) =>
item ? (
<EuiLink
data-test-subj="dashboard-table-trace-group-name-button"
onClick={() =>
props.addFilter({
field: 'traceGroup',
operator: 'is',
value: item,
inverted: false,
disabled: false,
})
}
>
{item.length < 48 ? item : <div title={item}>{_.truncate(item, { length: 48 })}</div>}
</EuiLink>
) : (
'-'
),
},
{
field: 'dashboard_latency_variance',
name: (
<>
<EuiToolTip
content={
<EuiText size="xs">
Range of latencies for traces within a trace group in the selected time range.
</EuiText>
}
>
<span>
Latency variance (ms){' '}
<EuiIcon
size="s"
color="subdued"
type="questionInCircle"
className="eui-alignTop"
/>
</span>
</EuiToolTip>
{varianceProps && (
<EuiText size="xs" color="subdued">
{varianceProps.scale}
</EuiText>
)}
</>
),
align: 'center',
sortable: ({ dashboard_latency_variance }) =>
dashboard_latency_variance?.length > 0
? dashboard_latency_variance[2] - dashboard_latency_variance[0]
: 0,
width: '300px',
render: (item, row) => {
const filter = props.filters.find(
(f) => f.field === 'Latency percentile within trace group'
);
const currPercentileFilter = filter ? filter.value : '';
return item ? (
// expand plot ranges to accommodate scale
<BoxPlt
plotParams={{
min:
varianceProps.ticks.length > 1
? varianceProps.ticks[0]
: varianceProps.ticks[0] / 1.03,
max: varianceProps.ticks[varianceProps.ticks.length - 1] * 1.03,
left: item[0],
mid: item[1],
right: item[2],
currPercentileFilter,
addFilter: (condition?: 'lte' | 'gte') => {
const traceGroupFilter = {
field: 'traceGroup',
operator: 'is',
value: row.dashboard_trace_group_name,
inverted: false,
disabled: false,
};
const additionalFilters = [traceGroupFilter];
for (const addedFilter of props.filters) {
if (
addedFilter.field === traceGroupFilter.field &&
addedFilter.operator === traceGroupFilter.operator &&
addedFilter.value === traceGroupFilter.value
) {
additionalFilters.pop();
}
}
props.addPercentileFilter(condition, additionalFilters);
},
}}
/>
) : (
'-'
);
},
},
{
field: 'dashboard_average_latency',
name: (
<EuiToolTip
content={
<EuiText size="xs">
Average latency of traces within a trace group in the selected time range.
</EuiText>
}
>
<>
{/* <div style={{ marginRight: 40 }}>Average</div> */}
<div>
Average latency (ms){' '}
<EuiIcon
size="s"
color="subdued"
type="questionInCircle"
className="eui-alignTop"
/>
</div>
<div> </div>
</>
</EuiToolTip>
),
align: 'right',
sortable: true,
dataType: 'number',
render: (item) => (item === 0 || item ? _.round(item, 2) : '-'),
},
{
field: '24_hour_latency_trend',
name: (
<EuiToolTip
content={
<EuiText size="xs">
24 hour time series view of hourly average, hourly percentile, and hourly range of
latency for traces within a trace group.
</EuiText>
}
>
<>
{/* <div style={{ marginRight: 44 }}>24-hour</div> */}
<div>
24-hour latency trend{' '}
<EuiIcon
size="s"
color="subdued"
type="questionInCircle"
className="eui-alignTop"
/>
</div>
<div> </div>
</>
</EuiToolTip>
),
align: 'right',
sortable: false,
render: (item, row) =>
item ? (
<LatencyTrendCell item={item} traceGroupName={row.dashboard_trace_group_name} />
) : (
'-'
),
},
{
field: 'dashboard_error_rate',
name: (
<EuiToolTip
content={
<EuiText size="xs">
Error rate based on count of trace errors within a trace group in the selected time
range.
</EuiText>
}
>
<>
<div>
Error rate{' '}
<EuiIcon
size="s"
color="subdued"
type="questionInCircle"
className="eui-alignTop"
/>
</div>
<div> </div>
</>
</EuiToolTip>
),
align: 'right',
sortable: true,
render: (item) =>
item === 0 || item ? <EuiText size="s">{`${_.round(item, 2)}%`}</EuiText> : '-',
},
{
field: 'dashboard_traces',
name: (
<EuiToolTip
content={
<EuiText size="xs">
Count of traces with unique trace identifiers in the selected time range.
</EuiText>
}
>
<>
<div>
Traces{' '}
<EuiIcon
size="s"
color="subdued"
type="questionInCircle"
className="eui-alignTop"
/>
</div>
<div> </div>
</>
</EuiToolTip>
),
align: 'right',
sortable: true,
render: (item, row) => (
<EuiLink
data-test-subj="dashboard-table-traces-button"
onClick={() => {
props.setRedirect(true);
props.addFilter({
field: 'traceGroup',
operator: 'is',
value: row.dashboard_trace_group_name,
inverted: false,
disabled: false,
});
location.assign('#/trace_analytics/traces');
}}
>
<EuiI18nNumber value={item} />
</EuiLink>
),
},
] as Array<EuiTableFieldDataColumnType<any>>;
const renderTitleBar = (totalItems: number) => {
return (
<EuiFlexGroup alignItems="center" gutterSize="s">
<EuiFlexItem grow={10}>
<PanelTitle title="Latency by trace group" totalItems={totalItems} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiLink
data-test-subj="dashboard-table-percentile-button-1"
onClick={() => props.addPercentileFilter('lte')}
>
<EuiText size="xs">
<span style={{ color: '#957ac9' }}>□</span> < 95 percentile
</EuiText>
</EuiLink>
</EuiFlexItem>
<EuiFlexItem grow={1} />
<EuiFlexItem grow={false}>
<EuiLink
data-test-subj="dashboard-table-percentile-button-2"
onClick={() => props.addPercentileFilter('gte')}
>
<EuiText size="xs">
<span style={{ color: '#957ac9' }}>■</span> >= 95 percentile
</EuiText>
</EuiLink>
</EuiFlexItem>
<EuiFlexItem grow={1} />
</EuiFlexGroup>
);
};
const varianceProps = useMemo(() => getVarianceProps(props.items), [props.items]);
const columns = useMemo(() => getColumns(), [props.items]);
const titleBar = useMemo(() => renderTitleBar(props.items?.length), [props.items]);
const [sorting, setSorting] = useState<{ sort: PropertySort }>({
sort: {
field: 'dashboard_latency_variance',
direction: 'desc',
},
});
const onTableChange = async ({ page, sort }: CriteriaWithPagination<any>) => {
if (typeof sort?.field !== 'string') return;
setSorting({ sort } as { sort: PropertySort });
};
return (
<>
<EuiPanel>
{titleBar}
<EuiSpacer size="m" />
<EuiHorizontalRule margin="none" />
{props.items?.length > 0 ? (
<EuiInMemoryTable
tableLayout="auto"
items={props.items}
columns={columns}
pagination={{
initialPageSize: 10,
pageSizeOptions: [5, 10, 15],
}}
sorting={sorting}
onTableChange={onTableChange}
loading={props.loading}
/>
) : (
<NoMatchMessage size="xl" />
)}
</EuiPanel>
</>
);
}