diff --git a/package.json b/package.json index c7cc8a83644..35eb408ef02 100644 --- a/package.json +++ b/package.json @@ -224,6 +224,7 @@ "react-sizeme": "2.5.2", "react-table": "6.9.2", "react-transition-group": "2.6.1", + "react-use": "9.0.0", "react-virtualized": "9.21.0", "react-window": "1.7.1", "redux": "4.0.1", diff --git a/packages/grafana-ui/src/types/datasource.ts b/packages/grafana-ui/src/types/datasource.ts index 2b40064d7d9..759ee05d681 100644 --- a/packages/grafana-ui/src/types/datasource.ts +++ b/packages/grafana-ui/src/types/datasource.ts @@ -172,6 +172,11 @@ export abstract class DataSourceApi< */ getQueryDisplayText?(query: TQuery): string; + /** + * Retrieve context for a given log row + */ + getLogRowContext?(row: any, limit?: number): Promise; + /** * Set after constructor call, as the data source instance is the most common thing to pass around * we attach the components to this instance for easy access @@ -282,6 +287,10 @@ export interface DataQueryResponse { data: DataQueryResponseData[]; } +export interface LogRowContextQueryResponse { + data: Array>; +} + export interface DataQuery { /** * A - Z diff --git a/public/app/features/explore/LogRow.tsx b/public/app/features/explore/LogRow.tsx index 8e3de04749b..37229ee5b75 100644 --- a/public/app/features/explore/LogRow.tsx +++ b/public/app/features/explore/LogRow.tsx @@ -8,6 +8,16 @@ import { LogLabels } from './LogLabels'; import { findHighlightChunksInText } from 'app/core/utils/text'; import { LogLabelStats } from './LogLabelStats'; import { LogMessageAnsi } from './LogMessageAnsi'; +import { css, cx } from 'emotion'; +import { + LogRowContextProvider, + LogRowContextRows, + HasMoreContextRows, + LogRowContextQueryErrors, +} from './LogRowContextProvider'; +import { ThemeContext, selectThemeVariant, GrafanaTheme, DataQueryResponse } from '@grafana/ui'; +import { LogRowContext } from './LogRowContext'; +import tinycolor from 'tinycolor2'; interface Props { highlighterExpressions?: string[]; @@ -18,6 +28,9 @@ interface Props { showUtc: boolean; getRows: () => LogRowModel[]; onClickLabel?: (label: string, value: string) => void; + onContextClick?: () => void; + getRowContext?: (row: LogRowModel, limit: number) => Promise; + className?: string; } interface State { @@ -29,6 +42,7 @@ interface State { parser?: LogsParser; parsedFieldHighlights: string[]; showFieldStats: boolean; + showContext: boolean; } /** @@ -44,6 +58,32 @@ const FieldHighlight = onClick => props => { ); }; +const logRowStyles = css` + position: relative; + /* z-index: 0; */ + /* outline: none; */ +`; + +const getLogRowWithContextStyles = (theme: GrafanaTheme, state: State) => { + const outlineColor = selectThemeVariant( + { + light: theme.colors.white, + dark: theme.colors.black, + }, + theme.type + ); + + return { + row: css` + z-index: 1; + outline: 9999px solid + ${tinycolor(outlineColor) + .setAlpha(0.7) + .toRgbString()}; + `, + }; +}; + /** * Renders a log line. * @@ -63,6 +103,7 @@ export class LogRow extends PureComponent { parser: undefined, parsedFieldHighlights: [], showFieldStats: false, + showContext: false, }; componentWillUnmount() { @@ -89,11 +130,21 @@ export class LogRow extends PureComponent { }; onMouseOverMessage = () => { + if (this.state.showContext) { + // When showing context we don't want to the LogRow rerender as it will mess up state of context block + // making the "after" context to be scrolled to the top, what is desired only on open + // The log row message needs to be refactored to separate component that encapsulates parsing and parsed message state + return; + } // Don't parse right away, user might move along this.mouseMessageTimer = setTimeout(this.parseMessage, 500); }; onMouseOutMessage = () => { + if (this.state.showContext) { + // See comment in onMouseOverMessage method + return; + } clearTimeout(this.mouseMessageTimer); this.setState({ parsed: false }); }; @@ -110,7 +161,25 @@ export class LogRow extends PureComponent { } }; - render() { + toggleContext = () => { + this.setState(state => { + return { + showContext: !state.showContext, + }; + }); + }; + + onContextToggle = (e: React.SyntheticEvent) => { + e.stopPropagation(); + this.toggleContext(); + }; + + renderLogRow( + context?: LogRowContextRows, + errors?: LogRowContextQueryErrors, + hasMoreContextRows?: HasMoreContextRows, + updateLimit?: () => void + ) { const { getRows, highlighterExpressions, @@ -129,6 +198,7 @@ export class LogRow extends PureComponent { parsed, parsedFieldHighlights, showFieldStats, + showContext, } = this.state; const { entry, hasAnsi, raw } = row; const previewHighlights = highlighterExpressions && !_.isEqual(highlighterExpressions, row.searchWords); @@ -139,59 +209,132 @@ export class LogRow extends PureComponent { }); return ( -
- {showDuplicates && ( -
{row.duplicates > 0 ? `${row.duplicates + 1}x` : null}
- )} -
- {showUtc && ( -
- {row.timestamp} -
- )} - {showLocalTime && ( -
- {row.timeLocal} -
- )} - {showLabels && ( -
- -
- )} -
- {parsed && ( - - )} - {!parsed && needsHighlighter && ( - - )} - {hasAnsi && !parsed && !needsHighlighter && } - {!hasAnsi && !parsed && !needsHighlighter && entry} - {showFieldStats && ( -
- + + {theme => { + const styles = this.state.showContext + ? cx(logRowStyles, getLogRowWithContextStyles(theme, this.state).row) + : logRowStyles; + console.log(styles); + return ( +
+ {showDuplicates && ( +
{row.duplicates > 0 ? `${row.duplicates + 1}x` : null}
+ )} +
+ {showUtc && ( +
+ {row.timestamp} +
+ )} + {showLocalTime && ( +
+ {row.timeLocal} +
+ )} + {showLabels && ( +
+ +
+ )} +
+
+ {showContext && context && ( + { + if (updateLimit) { + updateLimit(); + } + }} + /> + )} + + {parsed && ( + + )} + {!parsed && needsHighlighter && ( + + )} + {hasAnsi && !parsed && !needsHighlighter && } + {!hasAnsi && !parsed && !needsHighlighter && entry} + {showFieldStats && ( +
+ +
+ )} +
+ {row.searchWords && row.searchWords.length > 0 && ( + + {showContext ? 'Hide' : 'Show'} context + + )} +
+
- )} -
-
+ ); + }} + ); } + + render() { + const { showContext } = this.state; + + if (showContext) { + return ( + <> + + {({ result, errors, hasMoreContextRows, updateLimit }) => { + return <>{this.renderLogRow(result, errors, hasMoreContextRows, updateLimit)}; + }} + + + ); + } + + return this.renderLogRow(); + } } diff --git a/public/app/features/explore/LogRowContext.tsx b/public/app/features/explore/LogRowContext.tsx new file mode 100644 index 00000000000..f873b50958b --- /dev/null +++ b/public/app/features/explore/LogRowContext.tsx @@ -0,0 +1,239 @@ +import React, { useContext, useRef, useState, useLayoutEffect } from 'react'; +import { + ThemeContext, + List, + GrafanaTheme, + selectThemeVariant, + ClickOutsideWrapper, + CustomScrollbar, + DataQueryError, +} from '@grafana/ui'; +import { css, cx } from 'emotion'; +import { LogRowContextRows, HasMoreContextRows, LogRowContextQueryErrors } from './LogRowContextProvider'; +import { LogRowModel } from 'app/core/logs_model'; +import { Alert } from './Error'; + +interface LogRowContextProps { + row: LogRowModel; + context: LogRowContextRows; + errors?: LogRowContextQueryErrors; + hasMoreContextRows: HasMoreContextRows; + onOutsideClick: () => void; + onLoadMoreContext: () => void; +} + +const getLogRowContextStyles = (theme: GrafanaTheme) => { + const gradientTop = selectThemeVariant( + { + light: theme.colors.white, + dark: theme.colors.dark1, + }, + theme.type + ); + const gradientBottom = selectThemeVariant( + { + light: theme.colors.gray7, + dark: theme.colors.dark2, + }, + theme.type + ); + + const boxShadowColor = selectThemeVariant( + { + light: theme.colors.gray5, + dark: theme.colors.black, + }, + theme.type + ); + const borderColor = selectThemeVariant( + { + light: theme.colors.gray5, + dark: theme.colors.dark9, + }, + theme.type + ); + + return { + commonStyles: css` + position: absolute; + width: calc(100% + 20px); + left: -10px; + height: 250px; + z-index: 2; + overflow: hidden; + background: ${theme.colors.pageBg}; + background: linear-gradient(180deg, ${gradientTop} 0%, ${gradientBottom} 104.25%); + box-shadow: 0px 2px 4px ${boxShadowColor}, 0px 0px 2px ${boxShadowColor}; + border: 1px solid ${borderColor}; + border-radius: ${theme.border.radius.md}; + `, + header: css` + height: 30px; + padding: 0 10px; + display: flex; + align-items: center; + background: ${borderColor}; + `, + logs: css` + height: 220px; + padding: 10px; + `, + }; +}; + +interface LogRowContextGroupHeaderProps { + row: LogRowModel; + rows: Array; + onLoadMoreContext: () => void; + shouldScrollToBottom?: boolean; + canLoadMoreRows?: boolean; +} +interface LogRowContextGroupProps extends LogRowContextGroupHeaderProps { + rows: Array; + className: string; + error?: string; +} + +const LogRowContextGroupHeader: React.FunctionComponent = ({ + row, + rows, + onLoadMoreContext, + canLoadMoreRows, +}) => { + const theme = useContext(ThemeContext); + const { header } = getLogRowContextStyles(theme); + + // Filtering out the original row from the context. + // Loki requires a rowTimestamp+1ns for the following logs to be queried. + // We don't to ns-precision calculations in Loki log row context retrieval, hence the filtering here + // Also see: https://github.com/grafana/loki/issues/597 + const logRowsToRender = rows.filter(contextRow => contextRow !== row.raw); + + return ( +
+ + Found {logRowsToRender.length} rows. + + {(rows.length >= 10 || (rows.length > 10 && rows.length % 10 !== 0)) && canLoadMoreRows && ( + onLoadMoreContext()} + > + Load 10 more + + )} +
+ ); +}; + +const LogRowContextGroup: React.FunctionComponent = ({ + row, + rows, + error, + className, + shouldScrollToBottom, + canLoadMoreRows, + onLoadMoreContext, +}) => { + const theme = useContext(ThemeContext); + const { commonStyles, logs } = getLogRowContextStyles(theme); + const [scrollTop, setScrollTop] = useState(0); + const listContainerRef = useRef(); + + useLayoutEffect(() => { + if (shouldScrollToBottom && listContainerRef.current) { + setScrollTop(listContainerRef.current.offsetHeight); + } + }); + + const headerProps = { + row, + rows, + onLoadMoreContext, + canLoadMoreRows, + }; + + return ( +
+ {/* When displaying "after" context */} + {shouldScrollToBottom && !error && } +
+ +
+ {!error && ( + { + return ( +
+ {item} +
+ ); + }} + /> + )} + {error && } +
+
+
+ {/* When displaying "before" context */} + {!shouldScrollToBottom && !error && } +
+ ); +}; + +export const LogRowContext: React.FunctionComponent = ({ + row, + context, + errors, + onOutsideClick, + onLoadMoreContext, + hasMoreContextRows, +}) => { + return ( + +
+ {context.after && ( + + )} + + {context.before && ( + + )} +
+
+ ); +}; diff --git a/public/app/features/explore/LogRowContextProvider.tsx b/public/app/features/explore/LogRowContextProvider.tsx new file mode 100644 index 00000000000..d3fa0bfdcc8 --- /dev/null +++ b/public/app/features/explore/LogRowContextProvider.tsx @@ -0,0 +1,104 @@ +import { LogRowModel } from 'app/core/logs_model'; +import { LogRowContextQueryResponse, SeriesData, DataQueryResponse, DataQueryError } from '@grafana/ui'; +import { useState, useEffect } from 'react'; +import useAsync from 'react-use/lib/useAsync'; + +export interface LogRowContextRows { + before?: Array; + after?: Array; +} +export interface LogRowContextQueryErrors { + before?: string; + after?: string; +} + +export interface HasMoreContextRows { + before: boolean; + after: boolean; +} + +interface LogRowContextProviderProps { + row: LogRowModel; + getRowContext: (row: LogRowModel, limit: number) => Promise; + children: (props: { + result: LogRowContextRows; + errors: LogRowContextQueryErrors; + hasMoreContextRows: HasMoreContextRows; + updateLimit: () => void; + }) => JSX.Element; +} + +export const LogRowContextProvider: React.FunctionComponent = ({ + getRowContext, + row, + children, +}) => { + const [limit, setLimit] = useState(10); + const [result, setResult] = useState(null); + const [errors, setErrors] = useState(null); + const [hasMoreContextRows, setHasMoreContextRows] = useState({ + before: true, + after: true, + }); + + const { value } = useAsync(async () => { + const context = await getRowContext(row, limit); + return { + data: context.data.map(series => { + if ((series as SeriesData).rows) { + return (series as SeriesData).rows.map(row => row[1]); + } else { + return [series]; + } + return []; + }), + }; + }, [limit]); + + useEffect(() => { + if (value) { + setResult(currentResult => { + let hasMoreLogsBefore = true, + hasMoreLogsAfter = true; + let beforeContextError, afterContextError; + + if (currentResult && currentResult.data[0].length === value.data[0].length) { + hasMoreLogsBefore = false; + } + + if (currentResult && currentResult.data[1].length === value.data[1].length) { + hasMoreLogsAfter = false; + } + + if (value.data[0] && value.data[0].length > 0 && value.data[0][0].message) { + beforeContextError = value.data[0][0].message; + } + if (value.data[1] && value.data[1].length > 0 && value.data[1][0].message) { + afterContextError = value.data[1][0].message; + } + + setHasMoreContextRows({ + before: hasMoreLogsBefore, + after: hasMoreLogsAfter, + }); + + setErrors({ + before: beforeContextError, + after: afterContextError, + }); + + return value; + }); + } + }, [value]); + + return children({ + result: { + before: result ? result.data[0] : [], + after: result ? result.data[1] : [], + }, + errors, + hasMoreContextRows, + updateLimit: () => setLimit(limit + 10), + }); +}; diff --git a/public/app/features/explore/Logs.tsx b/public/app/features/explore/Logs.tsx index 6603ac0330d..3719ed0e88f 100644 --- a/public/app/features/explore/Logs.tsx +++ b/public/app/features/explore/Logs.tsx @@ -5,7 +5,7 @@ import * as rangeUtil from '@grafana/ui/src/utils/rangeutil'; import { RawTimeRange, Switch, LogLevel, TimeZone, TimeRange, AbsoluteTimeRange } from '@grafana/ui'; import TimeSeries from 'app/core/time_series2'; -import { LogsDedupDescription, LogsDedupStrategy, LogsModel, LogsMetaKind } from 'app/core/logs_model'; +import { LogsDedupDescription, LogsDedupStrategy, LogsModel, LogsMetaKind, LogRowModel } from 'app/core/logs_model'; import ToggleButtonGroup, { ToggleButton } from 'app/core/components/ToggleButtonGroup/ToggleButtonGroup'; @@ -60,6 +60,7 @@ interface Props { onStopScanning?: () => void; onDedupStrategyChange: (dedupStrategy: LogsDedupStrategy) => void; onToggleLogLevel: (hiddenLogLevels: Set) => void; + getRowContext?: (row: LogRowModel, limit: number) => Promise; } interface State { @@ -252,6 +253,7 @@ export default class Logs extends PureComponent { { { }); }; + getLogRowContext = async (row: LogRowModel, limit: number) => { + const { datasourceInstance } = this.props; + + if (datasourceInstance) { + return datasourceInstance.getLogRowContext(row, limit); + } + + return []; + }; + render() { const { exploreId, + loading, logsHighlighterExpressions, logsResult, @@ -97,6 +118,7 @@ export class LogsContainer extends PureComponent { scanRange={scanRange} width={width} hiddenLogLevels={hiddenLogLevels} + getRowContext={this.getLogRowContext} /> ); @@ -106,7 +128,7 @@ export class LogsContainer extends PureComponent { function mapStateToProps(state: StoreState, { exploreId }) { const explore = state.explore; const item: ExploreItemState = explore[exploreId]; - const { logsHighlighterExpressions, logsResult, logIsLoading, scanning, scanRange, range } = item; + const { logsHighlighterExpressions, logsResult, logIsLoading, scanning, scanRange, range, datasourceInstance } = item; const loading = logIsLoading; const { dedupStrategy } = exploreItemUIStateSelector(item); const hiddenLogLevels = new Set(item.hiddenLogLevels); @@ -124,6 +146,7 @@ function mapStateToProps(state: StoreState, { exploreId }) { dedupStrategy, hiddenLogLevels, dedupedResult, + datasourceInstance, }; } diff --git a/public/app/features/explore/state/reducers.ts b/public/app/features/explore/state/reducers.ts index 743294f0e74..58fcc301c87 100644 --- a/public/app/features/explore/state/reducers.ts +++ b/public/app/features/explore/state/reducers.ts @@ -379,7 +379,6 @@ export const itemReducer = reducerFactory({} as ExploreItemSta const { queryIntervals } = state; const { result, resultType, latency } = action.payload; const results = calculateResultsFromQueryTransactions(result, resultType, queryIntervals.intervalMs); - return { ...state, graphResult: resultType === 'Graph' ? results.graphResult : state.graphResult, diff --git a/public/app/plugins/datasource/loki/datasource.ts b/public/app/plugins/datasource/loki/datasource.ts index 04f84658491..906f5cb2fc8 100644 --- a/public/app/plugins/datasource/loki/datasource.ts +++ b/public/app/plugins/datasource/loki/datasource.ts @@ -21,6 +21,7 @@ import { LokiQuery, LokiOptions } from './types'; import { BackendSrv } from 'app/core/services/backend_srv'; import { TemplateSrv } from 'app/features/templating/template_srv'; import { safeStringifyValue } from 'app/core/utils/explore'; +import { LogRowModel } from 'app/core/logs_model'; export const DEFAULT_MAX_LINES = 1000; @@ -187,6 +188,83 @@ export class LokiDatasource extends DataSourceApi { return Math.ceil(date.valueOf() * 1e6); } + prepareLogRowContextQueryTargets = (row: LogRowModel, limit: number) => { + const query = Object.keys(row.labels) + .map(label => { + return `${label}="${row.labels[label]}"`; + }) + .join(','); + const contextTimeBuffer = 2 * 60 * 60 * 1000 * 1e6; // 2h buffer + const timeEpochNs = row.timeEpochMs * 1e6; + + const commontTargetOptons = { + limit, + query: `{${query}}`, + }; + return [ + // Target for "before" context + { + ...commontTargetOptons, + start: timeEpochNs - contextTimeBuffer, + end: timeEpochNs, + direction: 'BACKWARD', + }, + // Target for "after" context + { + ...commontTargetOptons, + start: timeEpochNs, // TODO: We should add 1ns here for the original row not no be included in the result + end: timeEpochNs + contextTimeBuffer, + direction: 'FORWARD', + }, + ]; + }; + + getLogRowContext = (row: LogRowModel, limit?: number) => { + // Preparing two targets, for preceeding and following log queries + const targets = this.prepareLogRowContextQueryTargets(row, limit || 10); + + return Promise.all( + targets.map(target => { + return this._request('/api/prom/query', target).catch(e => { + const error: DataQueryError = { + message: 'Error during context query. Please check JS console logs.', + status: e.status, + statusText: e.statusText, + }; + return error; + }); + }) + ).then((results: any[]) => { + const series: Array> = []; + const emptySeries = { + fields: [], + rows: [], + } as SeriesData; + + for (let i = 0; i < results.length; i++) { + const result = results[i]; + series[i] = []; + if (result.data) { + for (const stream of result.data.streams || []) { + const seriesData = logStreamToSeriesData(stream); + series[i].push(seriesData); + } + } else { + series[i].push(result); + } + } + + // Following context logs are requested in "forward" direction. + // This means, that we need to reverse those to make them sorted + // in descending order (by timestamp) + if (series[1][0] && (series[1][0] as SeriesData).rows) { + (series[1][0] as SeriesData).rows.reverse(); + } + + return { data: [series[0][0] || emptySeries, series[1][0] || emptySeries] }; + }); + }; + testDatasource() { return this._request('/api/prom/label') .then(res => { diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index 6f70ecaa25b..b7ed76dee82 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -179,6 +179,7 @@ export interface ExploreItemState { * Log query result to be displayed in the logs result viewer. */ logsResult?: LogsModel; + /** * Query intervals for graph queries to determine how many datapoints to return. * Needs to be updated when `datasourceInstance` or `containerWidth` is changed. diff --git a/public/sass/components/_panel_logs.scss b/public/sass/components/_panel_logs.scss index 8007e77a81d..3c6ffd83a6f 100644 --- a/public/sass/components/_panel_logs.scss +++ b/public/sass/components/_panel_logs.scss @@ -73,6 +73,7 @@ $column-horizontal-spacing: 10px; padding-right: $column-horizontal-spacing; border-top: 1px solid transparent; border-bottom: 1px solid transparent; + height: 100%; } &:hover { diff --git a/yarn.lock b/yarn.lock index 1710b7e8710..3163fadbb36 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4054,6 +4054,11 @@ boolbase@^1.0.0, boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= +bowser@^1.7.3: + version "1.9.4" + resolved "https://registry.yarnpkg.com/bowser/-/bowser-1.9.4.tgz#890c58a2813a9d3243704334fa81b96a5c150c9a" + integrity sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ== + boxen@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b" @@ -5143,7 +5148,7 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= -copy-to-clipboard@^3.0.8: +copy-to-clipboard@^3.0.8, copy-to-clipboard@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.2.0.tgz#d2724a3ccbfed89706fac8a894872c979ac74467" integrity sha512-eOZERzvCmxS8HWzugj4Uxl8OJxa7T2k1Gi0X5qavwydHIfuSHq2dTD09LOg/XyGq4Zpb5IsR/2OJ5lbOegz78w== @@ -5366,6 +5371,14 @@ css-declaration-sorter@^4.0.1: postcss "^7.0.1" timsort "^0.3.0" +css-in-js-utils@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/css-in-js-utils/-/css-in-js-utils-2.0.1.tgz#3b472b398787291b47cfe3e44fecfdd9e914ba99" + integrity sha512-PJF0SpJT+WdbVVt0AOYp9C8GnuruRlL/UFW7932nLWmFLQTaWEzTBQEx7/hn4BuV+WON75iAViSUJLiU3PKbpA== + dependencies: + hyphenate-style-name "^1.0.2" + isobject "^3.0.1" + css-loader@2.1.1, css-loader@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-2.1.1.tgz#d8254f72e412bb2238bb44dd674ffbef497333ea" @@ -5416,7 +5429,7 @@ css-tree@1.0.0-alpha.28: mdn-data "~1.1.0" source-map "^0.5.3" -css-tree@1.0.0-alpha.29: +css-tree@1.0.0-alpha.29, css-tree@^1.0.0-alpha.28: version "1.0.0-alpha.29" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.29.tgz#3fa9d4ef3142cbd1c301e7664c1f352bd82f5a39" integrity sha512-sRNb1XydwkW9IOci6iB2xmy8IGCj6r/fr+JWitvJ2JxQRPzN3T4AGGVWCMlVmVwM1gtgALJRmGIlWv5ppnGGkg== @@ -5541,7 +5554,7 @@ cssstyle@^1.0.0: dependencies: cssom "0.3.x" -csstype@^2.2.0, csstype@^2.5.2, csstype@^2.5.7: +csstype@^2.2.0, csstype@^2.5.2, csstype@^2.5.5, csstype@^2.5.7: version "2.6.4" resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.4.tgz#d585a6062096e324e7187f80e04f92bd0f00e37f" integrity sha512-lAJUJP3M6HxFXbqtGRc0iZrdyeN+WzOWeY0q/VnFzI+kqVrYIzC7bWlKqCW7oCIdzoPkvfp82EVvrTlQ8zsWQg== @@ -6801,6 +6814,13 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" +error-stack-parser@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.0.2.tgz#4ae8dbaa2bf90a8b450707b9149dcabca135520d" + integrity sha512-E1fPutRDdIj/hohG0UpT5mayXNCxXP9d+snxFsPU9X0XgccOumKraa3juDMwTUyi7+Bu5+mCGagjg4IYeNbOdw== + dependencies: + stackframe "^1.0.4" + es-abstract@^1.10.0, es-abstract@^1.11.0, es-abstract@^1.12.0, es-abstract@^1.13.0, es-abstract@^1.4.3, es-abstract@^1.5.0, es-abstract@^1.5.1, es-abstract@^1.7.0, es-abstract@^1.9.0: version "1.13.0" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9" @@ -7355,6 +7375,11 @@ fast-text-encoding@^1.0.0: resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.0.tgz#3e5ce8293409cfaa7177a71b9ca84e1b1e6f25ef" integrity sha512-R9bHCvweUxxwkDwhjav5vxpFvdPGlVngtqmx4pIZfSUhM/Q4NiIUHB456BAf+Q1Nwu3HEZYONtu+Rya+af4jiQ== +fastest-stable-stringify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/fastest-stable-stringify/-/fastest-stable-stringify-1.0.1.tgz#9122d406d4c9d98bea644a6b6853d5874b87b028" + integrity sha1-kSLUBtTJ2YvqZEpraFPVh0uHsCg= + fastparse@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9" @@ -8837,6 +8862,11 @@ husky@1.3.1: run-node "^1.0.0" slash "^2.0.0" +hyphenate-style-name@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz#097bb7fa0b8f1a9cf0bd5c734cf95899981a9b48" + integrity sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ== + iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -9024,6 +9054,14 @@ init-package-json@^1.10.3: validate-npm-package-license "^3.0.1" validate-npm-package-name "^3.0.0" +inline-style-prefixer@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-4.0.2.tgz#d390957d26f281255fe101da863158ac6eb60911" + integrity sha512-N8nVhwfYga9MiV9jWlwfdj1UDIaZlBFu4cJSJkIr7tZX7sHpHhGR5su1qdpW+7KPL8ISTvCIkcaFi/JdBknvPg== + dependencies: + bowser "^1.7.3" + css-in-js-utils "^2.0.0" + inquirer@6.2.1: version "6.2.1" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.2.1.tgz#9943fc4882161bdb0b0c9276769c75b32dbfcd52" @@ -11565,6 +11603,20 @@ nan@^2.10.0, nan@^2.12.1, nan@^2.6.2: resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.2.tgz#f51dc7ae66ba7d5d55e1e6d4d8092e802c9aefe7" integrity sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw== +nano-css@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/nano-css/-/nano-css-5.1.0.tgz#03c2b4ea2edefd445ac0c0e0f2565ea62e2aa81a" + integrity sha512-08F1rBmp0JuAteOR/uk/c40q/+UxWr224m/ZCHjjgy8dhkFQptvNwj/408KYQc13PIV9aGvqmtUD49PqBB5Ppg== + dependencies: + css-tree "^1.0.0-alpha.28" + csstype "^2.5.5" + fastest-stable-stringify "^1.0.1" + inline-style-prefixer "^4.0.0" + rtl-css-js "^1.9.0" + sourcemap-codec "^1.4.1" + stacktrace-js "^2.0.0" + stylis "3.5.0" + nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" @@ -14100,7 +14152,7 @@ react-error-overlay@^5.1.4: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-5.1.5.tgz#884530fd055476c764eaa8ab13b8ecf1f57bbf2c" integrity sha512-O9JRum1Zq/qCPFH5qVEvDDrVun8Jv9vbHtZXCR1EuRj9sKg1xJTlHxBzU6AkCzpvxRLuiY4OKImy3cDLQ+UTdg== -react-fast-compare@^2.0.2: +react-fast-compare@^2.0.2, react-fast-compare@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== @@ -14379,6 +14431,19 @@ react-transition-group@^2.2.1: prop-types "^15.6.2" react-lifecycles-compat "^3.0.4" +react-use@9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/react-use/-/react-use-9.0.0.tgz#142bec53fa465db2a6e43c68a8c9ef2acc000592" + integrity sha512-jlXJneB96yl4VvAXDKyE6cmdIeWk0cO7Gomh870Qu0vXZ9YM2JjjR09E9vIPPPI2M27RWo2dZKXspv44Wxtoog== + dependencies: + copy-to-clipboard "^3.1.0" + nano-css "^5.1.0" + react-fast-compare "^2.0.4" + react-wait "^0.3.0" + screenfull "^4.1.0" + throttle-debounce "^2.0.1" + ts-easing "^0.2.0" + react-virtualized@9.21.0: version "9.21.0" resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.21.0.tgz#8267c40ffb48db35b242a36dea85edcf280a6506" @@ -14391,6 +14456,11 @@ react-virtualized@9.21.0: prop-types "^15.6.0" react-lifecycles-compat "^3.0.4" +react-wait@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/react-wait/-/react-wait-0.3.0.tgz#0cdd4d919012451a5bc3ab0a16d00c6fd9a8c10b" + integrity sha512-kB5x/kMKWcn0uVr9gBdNz21/oGbQwEQnF3P9p6E9yLfJ9DRcKS0fagbgYMFI0YFOoyKDj+2q6Rwax0kTYJF37g== + react-window@1.7.1: version "1.7.1" resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.7.1.tgz#c1db640415b97b85bc0a1c66eb82dadabca39b86" @@ -15206,6 +15276,13 @@ rsvp@^4.8.4: resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.4.tgz#b50e6b34583f3dd89329a2f23a8a2be072845911" integrity sha512-6FomvYPfs+Jy9TfXmBpBuMWNH94SgCsZmJKcanySzgNNP6LjWxBvyLTa9KaMfDDM5oxRfrKDB0r/qeRsLwnBfA== +rtl-css-js@^1.9.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/rtl-css-js/-/rtl-css-js-1.11.0.tgz#a7151930ef9d54656607d754ebb172ddfc9ef836" + integrity sha512-YnZ6jWxZxlWlcQAGF9vOmiF9bEmoQmSHE+wsrsiILkdK9HqiRPAIll4SY/QDzbvEu2lB2h62+hfg3TYzjnldbA== + dependencies: + "@babel/runtime" "^7.1.2" + run-async@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/run-async/-/run-async-0.1.0.tgz#c8ad4a5e110661e402a7d21b530e009f25f8e389" @@ -15400,6 +15477,11 @@ schema-utils@^1.0.0: ajv-errors "^1.0.0" ajv-keywords "^3.1.0" +screenfull@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/screenfull/-/screenfull-4.2.0.tgz#d5252a5a0f56504719abbed9ebbcd9208115da03" + integrity sha512-qpyI9XbwuMJElWRP5vTgxkFAl4k7HpyhIqBFOZEwX9QBXn0MAuRSpn7LOc6/4CeSwoz61oBu1VPV+2fbIWC+5Q== + scss-tokenizer@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1" @@ -15978,7 +16060,7 @@ source-map@^0.7.2, source-map@^0.7.3: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== -sourcemap-codec@^1.4.4: +sourcemap-codec@^1.4.1, sourcemap-codec@^1.4.4: version "1.4.4" resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.4.tgz#c63ea927c029dd6bd9a2b7fa03b3fec02ad56e9f" integrity sha512-CYAPYdBu34781kLHkaW3m6b/uUSyMOC2R61gcYMWooeuaGtjof86ZA/8T+qVPPt7np1085CR9hmMGrySwEc8Xg== @@ -16093,6 +16175,13 @@ stable@^0.1.8, stable@~0.1.3, stable@~0.1.5: resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== +stack-generator@^2.0.1: + version "2.0.3" + resolved "https://registry.yarnpkg.com/stack-generator/-/stack-generator-2.0.3.tgz#bb74385c67ffc4ccf3c4dee5831832d4e509c8a0" + integrity sha512-kdzGoqrnqsMxOEuXsXyQTmvWXZmG0f3Ql2GDx5NtmZs59sT2Bt9Vdyq0XdtxUi58q/+nxtbF9KOQ9HkV1QznGg== + dependencies: + stackframe "^1.0.4" + stack-parser@^0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/stack-parser/-/stack-parser-0.0.1.tgz#7d3b63a17887e9e2c2bf55dbd3318fe34a39d1e7" @@ -16103,6 +16192,28 @@ stack-utils@^1.0.1: resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8" integrity sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA== +stackframe@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.0.4.tgz#357b24a992f9427cba6b545d96a14ed2cbca187b" + integrity sha512-to7oADIniaYwS3MhtCa/sQhrxidCCQiF/qp4/m5iN3ipf0Y7Xlri0f6eG29r08aL7JYl8n32AF3Q5GYBZ7K8vw== + +stacktrace-gps@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/stacktrace-gps/-/stacktrace-gps-3.0.2.tgz#33f8baa4467323ab2bd1816efa279942ba431ccc" + integrity sha512-9o+nWhiz5wFnrB3hBHs2PTyYrS60M1vvpSzHxwxnIbtY2q9Nt51hZvhrG1+2AxD374ecwyS+IUwfkHRE/2zuGg== + dependencies: + source-map "0.5.6" + stackframe "^1.0.4" + +stacktrace-js@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/stacktrace-js/-/stacktrace-js-2.0.0.tgz#776ca646a95bc6c6b2b90776536a7fc72c6ddb58" + integrity sha1-d2ymRqlbxsayuQd2U2p/xyxt21g= + dependencies: + error-stack-parser "^2.0.1" + stack-generator "^2.0.1" + stacktrace-gps "^3.0.1" + staged-git-files@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/staged-git-files/-/staged-git-files-1.1.2.tgz#4326d33886dc9ecfa29a6193bf511ba90a46454b" @@ -16410,6 +16521,11 @@ stylis-rule-sheet@^0.0.10: resolved "https://registry.yarnpkg.com/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz#44e64a2b076643f4b52e5ff71efc04d8c3c4a430" integrity sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw== +stylis@3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.0.tgz#016fa239663d77f868fef5b67cf201c4b7c701e1" + integrity sha512-pP7yXN6dwMzAR29Q0mBrabPCe0/mNO1MSr93bhay+hcZondvMMTpeGyd8nbhYJdyperNT2DRxONQuUGcJr5iPw== + stylis@^3.5.0: version "3.5.4" resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.4.tgz#f665f25f5e299cf3d64654ab949a57c768b73fbe" @@ -16665,6 +16781,11 @@ throat@^4.0.0: resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a" integrity sha1-iQN8vJLFarGJJua6TLsgDhVnKmo= +throttle-debounce@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-2.1.0.tgz#257e648f0a56bd9e54fe0f132c4ab8611df4e1d5" + integrity sha512-AOvyNahXQuU7NN+VVvOOX+uW6FPaWdAOdRP5HfwYxAfCzXTFKRMoIMk+n+po318+ktcChx+F1Dd91G3YHeMKyg== + throttleit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c"