LogLabels.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  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. getRows?: () => 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 allRows = this.props.getRows();
  93. const stats = calculateLogsLabelStats(allRows, this.props.label);
  94. return { showStats: true, stats };
  95. });
  96. };
  97. render() {
  98. const { getRows, label, plain, value } = this.props;
  99. const { showStats, stats } = this.state;
  100. const tooltip = `${label}: ${value}`;
  101. return (
  102. <span className="logs-label">
  103. <span className="logs-label__value" title={tooltip}>
  104. {value}
  105. </span>
  106. {!plain && (
  107. <span title="Filter for label" onClick={this.onClickLabel} className="logs-label__icon fa fa-search-plus" />
  108. )}
  109. {!plain && getRows && <span onClick={this.onClickStats} className="logs-label__icon fa fa-signal" />}
  110. {showStats && (
  111. <span className="logs-label__stats">
  112. <Stats
  113. stats={stats}
  114. rowCount={getRows().length}
  115. label={label}
  116. value={value}
  117. onClickClose={this.onClickClose}
  118. />
  119. </span>
  120. )}
  121. </span>
  122. );
  123. }
  124. }
  125. export default class LogLabels extends PureComponent<{
  126. getRows?: () => LogRow[];
  127. labels: LogsStreamLabels;
  128. plain?: boolean;
  129. onClickLabel?: (label: string, value: string) => void;
  130. }> {
  131. render() {
  132. const { getRows, labels, onClickLabel, plain } = this.props;
  133. return Object.keys(labels).map(key => (
  134. <Label key={key} getRows={getRows} label={key} value={labels[key]} plain={plain} onClickLabel={onClickLabel} />
  135. ));
  136. }
  137. }