1// Copyright (c) 2017 Uber Technologies, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import * as React from 'react';
16import { sortBy as _sortBy } from 'lodash';
17import IoIosArrowDown from 'react-icons/lib/io/ios-arrow-down';
18import IoIosArrowRight from 'react-icons/lib/io/ios-arrow-right';
19import { css } from '@emotion/css';
20
21import AccordianKeyValues from './AccordianKeyValues';
22import { formatDuration } from '../utils';
23import { TNil } from '../../types';
24import { TraceLog, TraceKeyValuePair, TraceLink } from '../../types/trace';
25import { autoColor, createStyle, Theme, useTheme } from '../../Theme';
26import { uAlignIcon, ubMb1 } from '../../uberUtilityStyles';
27
28const getStyles = createStyle((theme: Theme) => {
29  return {
30    AccordianLogs: css`
31      label: AccordianLogs;
32      border: 1px solid ${autoColor(theme, '#d8d8d8')};
33      position: relative;
34      margin-bottom: 0.25rem;
35    `,
36    AccordianLogsHeader: css`
37      label: AccordianLogsHeader;
38      background: ${autoColor(theme, '#e4e4e4')};
39      color: inherit;
40      display: block;
41      padding: 0.25rem 0.5rem;
42      &:hover {
43        background: ${autoColor(theme, '#dadada')};
44      }
45    `,
46    AccordianLogsContent: css`
47      label: AccordianLogsContent;
48      background: ${autoColor(theme, '#f0f0f0')};
49      border-top: 1px solid ${autoColor(theme, '#d8d8d8')};
50      padding: 0.5rem 0.5rem 0.25rem 0.5rem;
51    `,
52    AccordianLogsFooter: css`
53      label: AccordianLogsFooter;
54      color: ${autoColor(theme, '#999')};
55    `,
56  };
57});
58
59type AccordianLogsProps = {
60  interactive?: boolean;
61  isOpen: boolean;
62  linksGetter: ((pairs: TraceKeyValuePair[], index: number) => TraceLink[]) | TNil;
63  logs: TraceLog[];
64  onItemToggle?: (log: TraceLog) => void;
65  onToggle?: () => void;
66  openedItems?: Set<TraceLog>;
67  timestamp: number;
68};
69
70export default function AccordianLogs(props: AccordianLogsProps) {
71  const { interactive, isOpen, linksGetter, logs, openedItems, onItemToggle, onToggle, timestamp } = props;
72  let arrow: React.ReactNode | null = null;
73  let HeaderComponent: 'span' | 'a' = 'span';
74  let headerProps: {} | null = null;
75  if (interactive) {
76    arrow = isOpen ? <IoIosArrowDown className={uAlignIcon} /> : <IoIosArrowRight className="u-align-icon" />;
77    HeaderComponent = 'a';
78    headerProps = {
79      'aria-checked': isOpen,
80      onClick: onToggle,
81      role: 'switch',
82    };
83  }
84
85  const styles = getStyles(useTheme());
86  return (
87    <div className={styles.AccordianLogs}>
88      <HeaderComponent className={styles.AccordianLogsHeader} {...headerProps}>
89        {arrow} <strong>Logs</strong> ({logs.length})
90      </HeaderComponent>
91      {isOpen && (
92        <div className={styles.AccordianLogsContent}>
93          {_sortBy(logs, 'timestamp').map((log, i) => (
94            <AccordianKeyValues
95              // `i` is necessary in the key because timestamps can repeat
96              key={`${log.timestamp}-${i}`}
97              className={i < logs.length - 1 ? ubMb1 : null}
98              data={log.fields || []}
99              highContrast
100              interactive={interactive}
101              isOpen={openedItems ? openedItems.has(log) : false}
102              label={`${formatDuration(log.timestamp - timestamp)}`}
103              linksGetter={linksGetter}
104              onToggle={interactive && onItemToggle ? () => onItemToggle(log) : null}
105            />
106          ))}
107          <small className={styles.AccordianLogsFooter}>
108            Log timestamps are relative to the start time of the full trace.
109          </small>
110        </div>
111      )}
112    </div>
113  );
114}
115
116AccordianLogs.defaultProps = {
117  interactive: true,
118  linksGetter: undefined,
119  onItemToggle: undefined,
120  onToggle: undefined,
121  openedItems: undefined,
122};
123