LogLabels.tsx 4.6 KB

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