Merge pull request #12821 from grafana/davkal/explore-query-ux

Explore: prometheus query helpers
This commit is contained in:
David 2018-08-07 17:56:08 +02:00 committed by GitHub
commit 91d04b87ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 515 additions and 100 deletions

View File

@ -166,6 +166,7 @@
"mousetrap-global-bind": "^1.1.0",
"prismjs": "^1.6.0",
"prop-types": "^15.6.0",
"rc-cascader": "^0.14.0",
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-grid-layout": "0.16.6",

View File

@ -166,7 +166,7 @@ export class Explore extends React.Component<any, IExploreState> {
supportsTable,
datasourceLoading: false,
},
() => datasourceError === null && this.handleSubmit()
() => datasourceError === null && this.onSubmit()
);
}
@ -174,7 +174,7 @@ export class Explore extends React.Component<any, IExploreState> {
this.el = el;
};
handleAddQueryRow = index => {
onAddQueryRow = index => {
const { queries } = this.state;
const nextQueries = [
...queries.slice(0, index + 1),
@ -184,7 +184,7 @@ export class Explore extends React.Component<any, IExploreState> {
this.setState({ queries: nextQueries });
};
handleChangeDatasource = async option => {
onChangeDatasource = async option => {
this.setState({
datasource: null,
datasourceError: null,
@ -197,10 +197,10 @@ export class Explore extends React.Component<any, IExploreState> {
this.setDatasource(datasource);
};
handleChangeQuery = (value, index) => {
onChangeQuery = (value: string, index: number, override?: boolean) => {
const { queries } = this.state;
const prevQuery = queries[index];
const edited = prevQuery.query !== value;
const edited = override ? false : prevQuery.query !== value;
const nextQuery = {
...queries[index],
edited,
@ -208,65 +208,52 @@ export class Explore extends React.Component<any, IExploreState> {
};
const nextQueries = [...queries];
nextQueries[index] = nextQuery;
this.setState({ queries: nextQueries });
this.setState({ queries: nextQueries }, override ? () => this.onSubmit() : undefined);
};
handleChangeTime = nextRange => {
onChangeTime = nextRange => {
const range = {
from: nextRange.from,
to: nextRange.to,
};
this.setState({ range }, () => this.handleSubmit());
this.setState({ range }, () => this.onSubmit());
};
handleClickCloseSplit = () => {
onClickClear = () => {
this.setState({
graphResult: null,
logsResult: null,
queries: ensureQueries(),
tableResult: null,
});
};
onClickCloseSplit = () => {
const { onChangeSplit } = this.props;
if (onChangeSplit) {
onChangeSplit(false);
}
};
handleClickGraphButton = () => {
onClickGraphButton = () => {
this.setState(state => ({ showingGraph: !state.showingGraph }));
};
handleClickLogsButton = () => {
onClickLogsButton = () => {
this.setState(state => ({ showingLogs: !state.showingLogs }));
};
handleClickSplit = () => {
onClickSplit = () => {
const { onChangeSplit } = this.props;
if (onChangeSplit) {
onChangeSplit(true, this.state);
}
};
handleClickTableButton = () => {
onClickTableButton = () => {
this.setState(state => ({ showingTable: !state.showingTable }));
};
handleRemoveQueryRow = index => {
const { queries } = this.state;
if (queries.length <= 1) {
return;
}
const nextQueries = [...queries.slice(0, index), ...queries.slice(index + 1)];
this.setState({ queries: nextQueries }, () => this.handleSubmit());
};
handleSubmit = () => {
const { showingLogs, showingGraph, showingTable, supportsGraph, supportsLogs, supportsTable } = this.state;
if (showingTable && supportsTable) {
this.runTableQuery();
}
if (showingGraph && supportsGraph) {
this.runGraphQuery();
}
if (showingLogs && supportsLogs) {
this.runLogsQuery();
}
};
onClickTableCell = (columnKey: string, rowValue: string) => {
const { datasource, queries } = this.state;
if (datasource && datasource.modifyQuery) {
@ -275,7 +262,29 @@ export class Explore extends React.Component<any, IExploreState> {
edited: false,
query: datasource.modifyQuery(q.query, { addFilter: { key: columnKey, value: rowValue } }),
}));
this.setState({ queries: nextQueries }, () => this.handleSubmit());
this.setState({ queries: nextQueries }, () => this.onSubmit());
}
};
onRemoveQueryRow = index => {
const { queries } = this.state;
if (queries.length <= 1) {
return;
}
const nextQueries = [...queries.slice(0, index), ...queries.slice(index + 1)];
this.setState({ queries: nextQueries }, () => this.onSubmit());
};
onSubmit = () => {
const { showingLogs, showingGraph, showingTable, supportsGraph, supportsLogs, supportsTable } = this.state;
if (showingTable && supportsTable) {
this.runTableQuery();
}
if (showingGraph && supportsGraph) {
this.runGraphQuery();
}
if (showingLogs && supportsLogs) {
this.runLogsQuery();
}
};
@ -441,7 +450,7 @@ export class Explore extends React.Component<any, IExploreState> {
</div>
) : (
<div className="navbar-buttons explore-first-button">
<button className="btn navbar-button" onClick={this.handleClickCloseSplit}>
<button className="btn navbar-button" onClick={this.onClickCloseSplit}>
Close Split
</button>
</div>
@ -451,7 +460,7 @@ export class Explore extends React.Component<any, IExploreState> {
<Select
className="datasource-picker"
clearable={false}
onChange={this.handleChangeDatasource}
onChange={this.onChangeDatasource}
options={datasources}
placeholder="Loading datasources..."
value={selectedDatasource}
@ -461,31 +470,19 @@ export class Explore extends React.Component<any, IExploreState> {
<div className="navbar__spacer" />
{position === 'left' && !split ? (
<div className="navbar-buttons">
<button className="btn navbar-button" onClick={this.handleClickSplit}>
<button className="btn navbar-button" onClick={this.onClickSplit}>
Split
</button>
</div>
) : null}
<TimePicker range={range} onChangeTime={this.onChangeTime} />
<div className="navbar-buttons">
{supportsGraph ? (
<button className={`btn navbar-button ${graphButtonActive}`} onClick={this.handleClickGraphButton}>
Graph
<button className="btn navbar-button navbar-button--no-icon" onClick={this.onClickClear}>
Clear All
</button>
) : null}
{supportsTable ? (
<button className={`btn navbar-button ${tableButtonActive}`} onClick={this.handleClickTableButton}>
Table
</button>
) : null}
{supportsLogs ? (
<button className={`btn navbar-button ${logsButtonActive}`} onClick={this.handleClickLogsButton}>
Logs
</button>
) : null}
</div>
<TimePicker range={range} onChangeTime={this.handleChangeTime} />
<div className="navbar-buttons relative">
<button className="btn navbar-button--primary" onClick={this.handleSubmit}>
<button className="btn navbar-button--primary" onClick={this.onSubmit}>
Run Query <i className="fa fa-level-down run-icon" />
</button>
{loading || latency ? <ElapsedTime time={latency} className="text-info" /> : null}
@ -508,12 +505,31 @@ export class Explore extends React.Component<any, IExploreState> {
history={history}
queries={queries}
request={this.request}
onAddQueryRow={this.handleAddQueryRow}
onChangeQuery={this.handleChangeQuery}
onExecuteQuery={this.handleSubmit}
onRemoveQueryRow={this.handleRemoveQueryRow}
onAddQueryRow={this.onAddQueryRow}
onChangeQuery={this.onChangeQuery}
onExecuteQuery={this.onSubmit}
onRemoveQueryRow={this.onRemoveQueryRow}
/>
{queryError && !loading ? <div className="text-warning m-a-2">{queryError}</div> : null}
<div className="result-options">
{supportsGraph ? (
<button className={`btn navbar-button ${graphButtonActive}`} onClick={this.onClickGraphButton}>
Graph
</button>
) : null}
{supportsTable ? (
<button className={`btn navbar-button ${tableButtonActive}`} onClick={this.onClickTableButton}>
Table
</button>
) : null}
{supportsLogs ? (
<button className={`btn navbar-button ${logsButtonActive}`} onClick={this.onClickLogsButton}>
Logs
</button>
) : null}
</div>
<main className="m-t-2">
{supportsGraph && showingGraph ? (
<Graph

View File

@ -84,7 +84,9 @@ class Graph extends Component<any, any> {
draw() {
const { data, options: userOptions } = this.props;
const $el = $(`#${this.props.id}`);
if (!data) {
$el.empty();
return;
}
const series = data.map((ts: TimeSeries) => ({
@ -93,7 +95,6 @@ class Graph extends Component<any, any> {
data: ts.getFlotPairs('null'),
}));
const $el = $(`#${this.props.id}`);
const ticks = $el.width() / 100;
let { from, to } = userOptions.range;
if (!moment.isMoment(from)) {

View File

@ -2,6 +2,7 @@ import _ from 'lodash';
import moment from 'moment';
import React from 'react';
import { Value } from 'slate';
import Cascader from 'rc-cascader';
// dom also includes Element polyfills
import { getNextCharacter, getPreviousCousin } from './utils/dom';
@ -21,12 +22,14 @@ import TypeaheadField, {
const DEFAULT_KEYS = ['job', 'instance'];
const EMPTY_SELECTOR = '{}';
const HISTOGRAM_GROUP = '__histograms__';
const HISTOGRAM_SELECTOR = '{le!=""}'; // Returns all timeseries for histograms
const HISTORY_ITEM_COUNT = 5;
const HISTORY_COUNT_CUTOFF = 1000 * 60 * 60 * 24; // 24h
const METRIC_MARK = 'metric';
const PRISM_LANGUAGE = 'promql';
export const wrapLabel = label => ({ label });
export const wrapLabel = (label: string) => ({ label });
export const setFunctionMove = (suggestion: Suggestion): Suggestion => {
suggestion.move = -1;
return suggestion;
@ -48,6 +51,22 @@ export function addHistoryMetadata(item: Suggestion, history: any[]): Suggestion
};
}
export function groupMetricsByPrefix(metrics: string[], delimiter = '_'): CascaderOption[] {
return _.chain(metrics)
.groupBy(metric => metric.split(delimiter)[0])
.map((metricsForPrefix: string[], prefix: string): CascaderOption => {
const prefixIsMetric = metricsForPrefix.length === 1 && metricsForPrefix[0] === prefix;
const children = prefixIsMetric ? [] : metricsForPrefix.sort().map(m => ({ label: m, value: m }));
return {
children,
label: prefix,
value: prefix,
};
})
.sortBy('label')
.value();
}
export function willApplySuggestion(
suggestion: string,
{ typeaheadContext, typeaheadText }: TypeaheadFieldState
@ -78,22 +97,33 @@ export function willApplySuggestion(
return suggestion;
}
interface CascaderOption {
label: string;
value: string;
children?: CascaderOption[];
disabled?: boolean;
}
interface PromQueryFieldProps {
history?: any[];
histogramMetrics?: string[];
initialQuery?: string | null;
labelKeys?: { [index: string]: string[] }; // metric -> [labelKey,...]
labelValues?: { [index: string]: { [index: string]: string[] } }; // metric -> labelKey -> [labelValue,...]
metrics?: string[];
metricsByPrefix?: CascaderOption[];
onPressEnter?: () => void;
onQueryChange?: (value: string) => void;
onQueryChange?: (value: string, override?: boolean) => void;
portalPrefix?: string;
request?: (url: string) => any;
}
interface PromQueryFieldState {
histogramMetrics: string[];
labelKeys: { [index: string]: string[] }; // metric -> [labelKey,...]
labelValues: { [index: string]: { [index: string]: string[] } }; // metric -> labelKey -> [labelValue,...]
metrics: string[];
metricsByPrefix: CascaderOption[];
}
interface PromTypeaheadInput {
@ -107,7 +137,7 @@ interface PromTypeaheadInput {
class PromQueryField extends React.Component<PromQueryFieldProps, PromQueryFieldState> {
plugins: any[];
constructor(props, context) {
constructor(props: PromQueryFieldProps, context) {
super(props, context);
this.plugins = [
@ -117,21 +147,45 @@ class PromQueryField extends React.Component<PromQueryFieldProps, PromQueryField
];
this.state = {
histogramMetrics: props.histogramMetrics || [],
labelKeys: props.labelKeys || {},
labelValues: props.labelValues || {},
metrics: props.metrics || [],
metricsByPrefix: props.metricsByPrefix || [],
};
}
componentDidMount() {
this.fetchMetricNames();
this.fetchHistogramMetrics();
}
onChangeQuery = value => {
onChangeMetrics = (values: string[], selectedOptions: CascaderOption[]) => {
let query;
if (selectedOptions.length === 1) {
if (selectedOptions[0].children.length === 0) {
query = selectedOptions[0].value;
} else {
// Ignore click on group
return;
}
} else {
const prefix = selectedOptions[0].value;
const metric = selectedOptions[1].value;
if (prefix === HISTOGRAM_GROUP) {
query = `histogram_quantile(0.95, sum(rate(${metric}[5m])) by (le))`;
} else {
query = metric;
}
}
this.onChangeQuery(query, true);
};
onChangeQuery = (value: string, override?: boolean) => {
// Send text change to parent
const { onQueryChange } = this.props;
if (onQueryChange) {
onQueryChange(value);
onQueryChange(value, override);
}
};
@ -317,7 +371,17 @@ class PromQueryField extends React.Component<PromQueryFieldProps, PromQueryField
return fetch(url);
};
async fetchLabelValues(key) {
fetchHistogramMetrics() {
this.fetchSeriesLabels(HISTOGRAM_SELECTOR, true, () => {
const histogramSeries = this.state.labelValues[HISTOGRAM_SELECTOR];
if (histogramSeries && histogramSeries['__name__']) {
const histogramMetrics = histogramSeries['__name__'].slice().sort();
this.setState({ histogramMetrics });
}
});
}
async fetchLabelValues(key: string) {
const url = `/api/v1/label/${key}/values`;
try {
const res = await this.request(url);
@ -337,7 +401,7 @@ class PromQueryField extends React.Component<PromQueryFieldProps, PromQueryField
}
}
async fetchSeriesLabels(name, withName?) {
async fetchSeriesLabels(name: string, withName?: boolean, callback?: () => void) {
const url = `/api/v1/series?match[]=${name}`;
try {
const res = await this.request(url);
@ -351,7 +415,7 @@ class PromQueryField extends React.Component<PromQueryFieldProps, PromQueryField
...this.state.labelValues,
[name]: values,
};
this.setState({ labelKeys, labelValues });
this.setState({ labelKeys, labelValues }, callback);
} catch (e) {
console.error(e);
}
@ -362,14 +426,30 @@ class PromQueryField extends React.Component<PromQueryFieldProps, PromQueryField
try {
const res = await this.request(url);
const body = await (res.data || res.json());
this.setState({ metrics: body.data }, this.onReceiveMetrics);
const metrics = body.data;
const metricsByPrefix = groupMetricsByPrefix(metrics);
this.setState({ metrics, metricsByPrefix }, this.onReceiveMetrics);
} catch (error) {
console.error(error);
}
}
render() {
const { histogramMetrics, metricsByPrefix } = this.state;
const histogramOptions = histogramMetrics.map(hm => ({ label: hm, value: hm }));
const metricsOptions = [
{ label: 'Histograms', value: HISTOGRAM_GROUP, children: histogramOptions },
...metricsByPrefix,
];
return (
<div className="prom-query-field">
<div className="prom-query-field-tools">
<Cascader options={metricsOptions} onChange={this.onChangeMetrics}>
<button className="btn navbar-button navbar-button--tight">Metrics</button>
</Cascader>
</div>
<div className="slate-query-field-wrapper">
<TypeaheadField
additionalPlugins={this.plugins}
cleanText={cleanText}
@ -379,6 +459,8 @@ class PromQueryField extends React.Component<PromQueryFieldProps, PromQueryField
onValueChanged={this.onChangeQuery}
placeholder="Enter a PromQL query"
/>
</div>
</div>
);
}
}

View File

@ -3,28 +3,32 @@ import React, { PureComponent } from 'react';
import QueryField from './PromQueryField';
class QueryRow extends PureComponent<any, {}> {
handleChangeQuery = value => {
onChangeQuery = (value, override?: boolean) => {
const { index, onChangeQuery } = this.props;
if (onChangeQuery) {
onChangeQuery(value, index);
onChangeQuery(value, index, override);
}
};
handleClickAddButton = () => {
onClickAddButton = () => {
const { index, onAddQueryRow } = this.props;
if (onAddQueryRow) {
onAddQueryRow(index);
}
};
handleClickRemoveButton = () => {
onClickClearButton = () => {
this.onChangeQuery('', true);
};
onClickRemoveButton = () => {
const { index, onRemoveQueryRow } = this.props;
if (onRemoveQueryRow) {
onRemoveQueryRow(index);
}
};
handlePressEnter = () => {
onPressEnter = () => {
const { onExecuteQuery } = this.props;
if (onExecuteQuery) {
onExecuteQuery();
@ -35,24 +39,27 @@ class QueryRow extends PureComponent<any, {}> {
const { edited, history, query, request } = this.props;
return (
<div className="query-row">
<div className="query-row-tools">
<button className="btn navbar-button navbar-button--tight" onClick={this.handleClickAddButton}>
<i className="fa fa-plus" />
</button>
<button className="btn navbar-button navbar-button--tight" onClick={this.handleClickRemoveButton}>
<i className="fa fa-minus" />
</button>
</div>
<div className="slate-query-field-wrapper">
<div className="query-row-field">
<QueryField
initialQuery={edited ? null : query}
history={history}
portalPrefix="explore"
onPressEnter={this.handlePressEnter}
onQueryChange={this.handleChangeQuery}
onPressEnter={this.onPressEnter}
onQueryChange={this.onChangeQuery}
request={request}
/>
</div>
<div className="query-row-tools">
<button className="btn navbar-button navbar-button--tight" onClick={this.onClickClearButton}>
<i className="fa fa-times" />
</button>
<button className="btn navbar-button navbar-button--tight" onClick={this.onClickAddButton}>
<i className="fa fa-plus" />
</button>
<button className="btn navbar-button navbar-button--tight" onClick={this.onClickRemoveButton}>
<i className="fa fa-minus" />
</button>
</div>
</div>
);
}

View File

@ -66,7 +66,14 @@ export default class Table extends PureComponent<TableProps, {}> {
{tableModel.rows.map((row, i) => (
<tr key={i}>
{row.map((value, j) => (
<Cell key={j} columnIndex={j} rowIndex={i} value={value} table={data} onClickCell={onClickCell} />
<Cell
key={j}
columnIndex={j}
rowIndex={i}
value={String(value)}
table={data}
onClickCell={onClickCell}
/>
))}
</tr>
))}

View File

@ -1,6 +1,7 @@
// vendor
@import '../vendor/css/timepicker.css';
@import '../vendor/css/spectrum.css';
@import '../vendor/css/rc-cascader.scss';
// MIXINS
@import 'mixins/mixins';

View File

@ -47,6 +47,14 @@
background-color: $btn-active-bg;
}
.navbar-button--no-icon {
line-height: 18px;
}
.result-options {
margin-top: 2 * $panel-margin;
}
.elapsed-time {
position: absolute;
left: 0;
@ -99,7 +107,12 @@
}
.query-row-tools {
width: 4rem;
width: 6rem;
}
.query-row-field {
margin-right: 3px;
width: 100%;
}
.explore {
@ -138,3 +151,11 @@
}
}
}
// Prometheus-specifics, to be extracted to datasource soon
.explore {
.prom-query-field {
display: flex;
}
}

160
public/vendor/css/rc-cascader.scss vendored Normal file
View File

@ -0,0 +1,160 @@
.rc-cascader {
font-size: 12px;
}
.rc-cascader-menus {
font-size: 12px;
overflow: hidden;
background: $panel-bg;
position: absolute;
border: $panel-border;
border-radius: $border-radius;
box-shadow: $typeahead-shadow;
white-space: nowrap;
}
.rc-cascader-menus-hidden {
display: none;
}
.rc-cascader-menus.slide-up-enter,
.rc-cascader-menus.slide-up-appear {
animation-duration: .3s;
animation-fill-mode: both;
transform-origin: 0 0;
opacity: 0;
animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1);
animation-play-state: paused;
}
.rc-cascader-menus.slide-up-leave {
animation-duration: .3s;
animation-fill-mode: both;
transform-origin: 0 0;
opacity: 1;
animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34);
animation-play-state: paused;
}
.rc-cascader-menus.slide-up-enter.slide-up-enter-active.rc-cascader-menus-placement-bottomLeft,
.rc-cascader-menus.slide-up-appear.slide-up-appear-active.rc-cascader-menus-placement-bottomLeft {
animation-name: SlideUpIn;
animation-play-state: running;
}
.rc-cascader-menus.slide-up-enter.slide-up-enter-active.rc-cascader-menus-placement-topLeft,
.rc-cascader-menus.slide-up-appear.slide-up-appear-active.rc-cascader-menus-placement-topLeft {
animation-name: SlideDownIn;
animation-play-state: running;
}
.rc-cascader-menus.slide-up-leave.slide-up-leave-active.rc-cascader-menus-placement-bottomLeft {
animation-name: SlideUpOut;
animation-play-state: running;
}
.rc-cascader-menus.slide-up-leave.slide-up-leave-active.rc-cascader-menus-placement-topLeft {
animation-name: SlideDownOut;
animation-play-state: running;
}
.rc-cascader-menu {
display: inline-block;
/* width: 100px; */
max-width: 50vw;
height: 192px;
list-style: none;
margin: 0;
padding: 0;
border-right: $panel-border;
overflow: auto;
}
.rc-cascader-menu:last-child {
border-right: 0;
}
.rc-cascader-menu-item {
height: 32px;
line-height: 32px;
padding: 0 16px;
cursor: pointer;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: all 0.3s ease;
position: relative;
}
.rc-cascader-menu-item:hover {
background: $typeahead-selected-bg;
}
.rc-cascader-menu-item-disabled {
cursor: not-allowed;
color: $text-color-weak;
}
.rc-cascader-menu-item-disabled:hover {
background: transparent;
}
.rc-cascader-menu-item-loading:after {
position: absolute;
right: 12px;
content: 'loading';
color: $text-color-weak;
font-style: italic;
}
.rc-cascader-menu-item-active {
color: $typeahead-selected-color;
background: $typeahead-selected-bg;
}
.rc-cascader-menu-item-active:hover {
color: $typeahead-selected-color;
background: $typeahead-selected-bg;
}
.rc-cascader-menu-item-expand {
position: relative;
}
.rc-cascader-menu-item-expand:after {
content: '>';
font-size: 12px;
color: $text-color-weak;
position: absolute;
right: 16px;
line-height: 32px;
}
@keyframes SlideUpIn {
0% {
opacity: 0;
transform-origin: 0% 0%;
transform: scaleY(0.8);
}
100% {
opacity: 1;
transform-origin: 0% 0%;
transform: scaleY(1);
}
}
@keyframes SlideUpOut {
0% {
opacity: 1;
transform-origin: 0% 0%;
transform: scaleY(1);
}
100% {
opacity: 0;
transform-origin: 0% 0%;
transform: scaleY(0.8);
}
}
@keyframes SlideDownIn {
0% {
opacity: 0;
transform-origin: 0% 100%;
transform: scaleY(0.8);
}
100% {
opacity: 1;
transform-origin: 0% 100%;
transform: scaleY(1);
}
}
@keyframes SlideDownOut {
0% {
opacity: 1;
transform-origin: 0% 100%;
transform: scaleY(1);
}
100% {
opacity: 0;
transform-origin: 0% 100%;
transform: scaleY(0.8);
}
}

123
yarn.lock
View File

@ -478,6 +478,12 @@ acorn@~2.6.4:
version "2.6.4"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-2.6.4.tgz#eb1f45b4a43fa31d03701a5ec46f3b52673e90ee"
add-dom-event-listener@1.x:
version "1.0.2"
resolved "https://registry.yarnpkg.com/add-dom-event-listener/-/add-dom-event-listener-1.0.2.tgz#8faed2c41008721cf111da1d30d995b85be42bed"
dependencies:
object-assign "4.x"
after@0.8.2:
version "0.8.2"
resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f"
@ -771,6 +777,10 @@ array-slice@^0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-0.2.3.tgz#dd3cfb80ed7973a75117cdac69b0b99ec86186f5"
array-tree-filter@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/array-tree-filter/-/array-tree-filter-1.0.1.tgz#0a8ad1eefd38ce88858632f9cc0423d7634e4d5d"
array-union@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39"
@ -1514,7 +1524,7 @@ babel-register@^6.26.0, babel-register@^6.9.0:
mkdirp "^0.5.1"
source-map-support "^0.4.15"
babel-runtime@^6.0.0, babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0, babel-runtime@^6.9.2:
babel-runtime@6.x, babel-runtime@^6.0.0, babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0, babel-runtime@^6.9.2:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
dependencies:
@ -2246,6 +2256,10 @@ classnames@2.x, classnames@^2.2.4, classnames@^2.2.5:
version "2.2.5"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d"
classnames@^2.2.6:
version "2.2.6"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
clean-css@3.4.x, clean-css@~3.4.2:
version "3.4.28"
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-3.4.28.tgz#bf1945e82fc808f55695e6ddeaec01400efd03ff"
@ -2553,6 +2567,12 @@ component-bind@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1"
component-classes@^1.2.5:
version "1.2.6"
resolved "https://registry.yarnpkg.com/component-classes/-/component-classes-1.2.6.tgz#c642394c3618a4d8b0b8919efccbbd930e5cd691"
dependencies:
component-indexof "0.0.3"
component-emitter@1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.1.2.tgz#296594f2753daa63996d2af08d15a95116c9aec3"
@ -2561,6 +2581,10 @@ component-emitter@1.2.1, component-emitter@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
component-indexof@0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/component-indexof/-/component-indexof-0.0.3.tgz#11d091312239eb8f32c8f25ae9cb002ffe8d3c24"
component-inherit@0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143"
@ -2841,6 +2865,13 @@ crypto-random-string@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e"
css-animation@^1.3.2:
version "1.4.1"
resolved "https://registry.yarnpkg.com/css-animation/-/css-animation-1.4.1.tgz#5b8813125de0fbbbb0bbe1b472ae84221469b7a8"
dependencies:
babel-runtime "6.x"
component-classes "^1.2.5"
css-color-names@0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"
@ -3515,6 +3546,10 @@ doctrine@^1.2.2:
esutils "^2.0.2"
isarray "^1.0.0"
dom-align@^1.7.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/dom-align/-/dom-align-1.8.0.tgz#c0e89b5b674c6e836cd248c52c2992135f093654"
dom-converter@~0.1:
version "0.1.4"
resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.1.4.tgz#a45ef5727b890c9bffe6d7c876e7b19cb0e17f3b"
@ -7354,6 +7389,10 @@ lodash._createset@~4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/lodash._createset/-/lodash._createset-4.0.3.tgz#0f4659fbb09d75194fa9e2b88a6644d363c9fe26"
lodash._getnative@^3.0.0:
version "3.9.1"
resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5"
lodash._root@~3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692"
@ -7386,6 +7425,14 @@ lodash.flattendeep@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
lodash.isarguments@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
lodash.isarray@^3.0.0:
version "3.0.4"
resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55"
lodash.isequal@^4.0.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
@ -7406,6 +7453,14 @@ lodash.kebabcase@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36"
lodash.keys@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a"
dependencies:
lodash._getnative "^3.0.0"
lodash.isarguments "^3.0.0"
lodash.isarray "^3.0.0"
lodash.memoize@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
@ -8651,7 +8706,7 @@ object-assign@4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0"
object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
object-assign@4.x, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@ -9981,6 +10036,54 @@ raw-body@2.3.3:
iconv-lite "0.4.23"
unpipe "1.0.0"
rc-align@^2.4.0:
version "2.4.3"
resolved "https://registry.yarnpkg.com/rc-align/-/rc-align-2.4.3.tgz#b9b3c2a6d68adae71a8e1d041cd5e3b2a655f99a"
dependencies:
babel-runtime "^6.26.0"
dom-align "^1.7.0"
prop-types "^15.5.8"
rc-util "^4.0.4"
rc-animate@2.x:
version "2.4.4"
resolved "https://registry.yarnpkg.com/rc-animate/-/rc-animate-2.4.4.tgz#a05a784c747beef140d99ff52b6117711bef4b1e"
dependencies:
babel-runtime "6.x"
css-animation "^1.3.2"
prop-types "15.x"
rc-cascader@^0.14.0:
version "0.14.0"
resolved "https://registry.yarnpkg.com/rc-cascader/-/rc-cascader-0.14.0.tgz#a956c99896f10883bf63d46fb894d0cb326842a4"
dependencies:
array-tree-filter "^1.0.0"
prop-types "^15.5.8"
rc-trigger "^2.2.0"
rc-util "^4.0.4"
shallow-equal "^1.0.0"
warning "^4.0.1"
rc-trigger@^2.2.0:
version "2.5.4"
resolved "https://registry.yarnpkg.com/rc-trigger/-/rc-trigger-2.5.4.tgz#9088a24ba5a811b254f742f004e38a9e2f8843fb"
dependencies:
babel-runtime "6.x"
classnames "^2.2.6"
prop-types "15.x"
rc-align "^2.4.0"
rc-animate "2.x"
rc-util "^4.4.0"
rc-util@^4.0.4, rc-util@^4.4.0:
version "4.5.1"
resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-4.5.1.tgz#0e435057174c024901c7600ba8903dd03da3ab39"
dependencies:
add-dom-event-listener "1.x"
babel-runtime "6.x"
prop-types "^15.5.10"
shallowequal "^0.2.2"
rc@^1.0.1, rc@^1.1.6, rc@^1.1.7:
version "1.2.8"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
@ -10980,6 +11083,16 @@ shallow-clone@^1.0.0:
kind-of "^5.0.0"
mixin-object "^2.0.1"
shallow-equal@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/shallow-equal/-/shallow-equal-1.0.0.tgz#508d1838b3de590ab8757b011b25e430900945f7"
shallowequal@^0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-0.2.2.tgz#1e32fd5bcab6ad688a4812cb0cc04efc75c7014e"
dependencies:
lodash.keys "^3.1.2"
shallowequal@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.0.2.tgz#1561dbdefb8c01408100319085764da3fcf83f8f"
@ -12555,6 +12668,12 @@ walker@~1.0.5:
dependencies:
makeerror "1.0.x"
warning@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.1.tgz#66ce376b7fbfe8a887c22bdf0e7349d73d397745"
dependencies:
loose-envify "^1.0.0"
watch@~0.18.0:
version "0.18.0"
resolved "https://registry.yarnpkg.com/watch/-/watch-0.18.0.tgz#28095476c6df7c90c963138990c0a5423eb4b986"