LogLabels.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. import _ from 'lodash';
  2. import React, { PureComponent } from 'react';
  3. import classnames from 'classnames';
  4. import { calculateLogsLabelStats, LogsLabelStat, LogsStreamLabels, LogRow } from 'app/core/logs_model';
  5. function StatsRow({ active, count, proportion, value }: LogsLabelStat) {
  6. const percent = `${Math.round(proportion * 100)}%`;
  7. const barStyle = { width: percent };
  8. const className = classnames('logs-stats-row', { 'logs-stats-row--active': active });
  9. return (
  10. <div className={className}>
  11. <div className="logs-stats-row__label">
  12. <div className="logs-stats-row__value">{value}</div>
  13. <div className="logs-stats-row__count">{count}</div>
  14. <div className="logs-stats-row__percent">{percent}</div>
  15. </div>
  16. <div className="logs-stats-row__bar">
  17. <div className="logs-stats-row__innerbar" style={barStyle} />
  18. </div>
  19. </div>
  20. );
  21. }
  22. const STATS_ROW_LIMIT = 5;
  23. class Stats extends PureComponent<{
  24. stats: LogsLabelStat[];
  25. label: string;
  26. value: string;
  27. rowCount: number;
  28. onClickClose: () => void;
  29. }> {
  30. render() {
  31. const { label, rowCount, stats, value, onClickClose } = this.props;
  32. const topRows = stats.slice(0, STATS_ROW_LIMIT);
  33. let activeRow = topRows.find(row => row.value === value);
  34. let otherRows = stats.slice(STATS_ROW_LIMIT);
  35. const insertActiveRow = !activeRow;
  36. // Remove active row from other to show extra
  37. if (insertActiveRow) {
  38. activeRow = otherRows.find(row => row.value === value);
  39. otherRows = otherRows.filter(row => row.value !== value);
  40. }
  41. const otherCount = otherRows.reduce((sum, row) => sum + row.count, 0);
  42. const topCount = topRows.reduce((sum, row) => sum + row.count, 0);
  43. const total = topCount + otherCount;
  44. const otherProportion = otherCount / total;
  45. return (
  46. <>
  47. <div className="logs-stats__info">
  48. {label}: {total} of {rowCount} rows have that label
  49. <span className="logs-stats__icon fa fa-window-close" onClick={onClickClose} />
  50. </div>
  51. {topRows.map(stat => <StatsRow key={stat.value} {...stat} active={stat.value === value} />)}
  52. {insertActiveRow && <StatsRow key={activeRow.value} {...activeRow} active />}
  53. {otherCount > 0 && <StatsRow key="__OTHERS__" count={otherCount} value="Other" proportion={otherProportion} />}
  54. </>
  55. );
  56. }
  57. }
  58. class Label extends PureComponent<
  59. {
  60. allRows?: LogRow[];
  61. label: string;
  62. plain?: boolean;
  63. value: string;
  64. onClickLabel?: (label: string, value: string) => void;
  65. },
  66. { showStats: boolean; stats: LogsLabelStat[] }
  67. > {
  68. state = {
  69. stats: null,
  70. showStats: false,
  71. };
  72. onClickClose = () => {
  73. this.setState({ showStats: false });
  74. };
  75. onClickLabel = () => {
  76. const { onClickLabel, label, value } = this.props;
  77. if (onClickLabel) {
  78. onClickLabel(label, value);
  79. }
  80. };
  81. onClickStats = () => {
  82. this.setState(state => {
  83. if (state.showStats) {
  84. return { showStats: false, stats: null };
  85. }
  86. const stats = calculateLogsLabelStats(this.props.allRows, this.props.label);
  87. return { showStats: true, stats };
  88. });
  89. };
  90. render() {
  91. const { allRows, label, plain, value } = this.props;
  92. const { showStats, stats } = this.state;
  93. const tooltip = `${label}: ${value}`;
  94. return (
  95. <span className="logs-label">
  96. <span className="logs-label__value" title={tooltip}>
  97. {value}
  98. </span>
  99. {!plain && (
  100. <span title="Filter for label" onClick={this.onClickLabel} className="logs-label__icon fa fa-search-plus" />
  101. )}
  102. {!plain && allRows && <span onClick={this.onClickStats} className="logs-label__icon fa fa-signal" />}
  103. {showStats && (
  104. <span className="logs-label__stats">
  105. <Stats
  106. stats={stats}
  107. rowCount={allRows.length}
  108. label={label}
  109. value={value}
  110. onClickClose={this.onClickClose}
  111. />
  112. </span>
  113. )}
  114. </span>
  115. );
  116. }
  117. }
  118. export default class LogLabels extends PureComponent<{
  119. allRows?: LogRow[];
  120. labels: LogsStreamLabels;
  121. plain?: boolean;
  122. onClickLabel?: (label: string, value: string) => void;
  123. }> {
  124. render() {
  125. const { allRows, labels, onClickLabel, plain } = this.props;
  126. return Object.keys(labels).map(key => (
  127. <Label key={key} allRows={allRows} label={key} value={labels[key]} plain={plain} onClickLabel={onClickLabel} />
  128. ));
  129. }
  130. }