LogLabels.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  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. export 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. <div className="logs-stats">
  47. <div className="logs-stats__header">
  48. <span className="logs-stats__title">
  49. {label}: {total} of {rowCount} rows have that label
  50. </span>
  51. <span className="logs-stats__close fa fa-remove" onClick={onClickClose} />
  52. </div>
  53. <div className="logs-stats__body">
  54. {topRows.map(stat => <StatsRow key={stat.value} {...stat} active={stat.value === value} />)}
  55. {insertActiveRow && activeRow && <StatsRow key={activeRow.value} {...activeRow} active />}
  56. {otherCount > 0 && (
  57. <StatsRow key="__OTHERS__" count={otherCount} value="Other" proportion={otherProportion} />
  58. )}
  59. </div>
  60. </div>
  61. );
  62. }
  63. }
  64. class Label extends PureComponent<
  65. {
  66. allRows?: LogRow[];
  67. label: string;
  68. plain?: boolean;
  69. value: string;
  70. onClickLabel?: (label: string, value: string) => void;
  71. },
  72. { showStats: boolean; stats: LogsLabelStat[] }
  73. > {
  74. state = {
  75. stats: null,
  76. showStats: false,
  77. };
  78. onClickClose = () => {
  79. this.setState({ showStats: false });
  80. };
  81. onClickLabel = () => {
  82. const { onClickLabel, label, value } = this.props;
  83. if (onClickLabel) {
  84. onClickLabel(label, value);
  85. }
  86. };
  87. onClickStats = () => {
  88. this.setState(state => {
  89. if (state.showStats) {
  90. return { showStats: false, stats: null };
  91. }
  92. const stats = calculateLogsLabelStats(this.props.allRows, this.props.label);
  93. return { showStats: true, stats };
  94. });
  95. };
  96. render() {
  97. const { allRows, 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 && allRows && <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={allRows.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. allRows?: LogRow[];
  126. labels: LogsStreamLabels;
  127. plain?: boolean;
  128. onClickLabel?: (label: string, value: string) => void;
  129. }> {
  130. render() {
  131. const { allRows, labels, onClickLabel, plain } = this.props;
  132. return Object.keys(labels).map(key => (
  133. <Label key={key} allRows={allRows} label={key} value={labels[key]} plain={plain} onClickLabel={onClickLabel} />
  134. ));
  135. }
  136. }