diff --git a/LICENSE_OF_DEPENDENCIES.md b/LICENSE_OF_DEPENDENCIES.md index edff0fea66..78e1310378 100644 --- a/LICENSE_OF_DEPENDENCIES.md +++ b/LICENSE_OF_DEPENDENCIES.md @@ -676,7 +676,7 @@ * node-libs-browser 0.6.0 [MIT](https://github.com/webpack/node-libs-browser) * node-notifier 4.6.1 [MIT](ssh://git@github.com/mikaelbr/node-notifier) * node-pre-gyp 0.6.29 [BSD-3-Clause](http://github.com/mapbox/node-pre-gyp) -* node-sass 3.11.3 [MIT](https://github.com/sass/node-sass) +* node-sass 4.5.2 [MIT](https://github.com/sass/node-sass) * node-uuid 1.4.7 [MIT](https://github.com/broofa/node-uuid) * nopt 3.0.6 [ISC](https://github.com/npm/nopt) * normalize-package-data 2.3.5 [BSD;BSD-2-Clause](http://github.com/npm/normalize-package-data) diff --git a/ui/.eslintrc b/ui/.eslintrc index 60e941694b..05e5ffd83a 100644 --- a/ui/.eslintrc +++ b/ui/.eslintrc @@ -149,7 +149,7 @@ 'eol-last': 0, // TODO: revisit 'id-length': 0, 'id-match': 0, - 'indent': [2, 2, {SwitchCase: 1}], + 'indent': [0, 2, {SwitchCase: 1}], 'key-spacing': [2, {beforeColon: false, afterColon: true}], 'linebreak-style': [2, 'unix'], 'lines-around-comment': 0, diff --git a/ui/package.json b/ui/package.json index c8771b5c78..b1108ffde1 100644 --- a/ui/package.json +++ b/ui/package.json @@ -70,7 +70,7 @@ "mocha": "^2.4.5", "mocha-loader": "^0.7.1", "mustache": "^2.2.1", - "node-sass": "^3.5.3", + "node-sass": "^4.5.2", "postcss-browser-reporter": "^0.4.0", "postcss-calc": "^5.2.0", "postcss-loader": "^0.8.0", diff --git a/ui/spec/dashboards/templatingSpec.js b/ui/spec/dashboards/templatingSpec.js new file mode 100644 index 0000000000..0e349f5e38 --- /dev/null +++ b/ui/spec/dashboards/templatingSpec.js @@ -0,0 +1,86 @@ +import {TEMPLATE_MATCHER} from 'src/dashboards/constants' + +describe('templating', () => { + describe('matching', () => { + it('can match the expected strings', () => { + const matchingStrings = [ + 'SELECT : FROM "db1"."rp1"."m1" WHERE time > now() - 15m', + 'SELECT :t, "f1" FROM "db1"."rp1"."m1" WHERE time > now() - 15m', + 'SELECT :tv1, "f1" FROM "db1"."rp1"."m1" WHERE time > now() - 15m', + 'SELECT "f1" FROM "db1"."rp1"."m1" WHERE time > now() - :tv', + ] + + matchingStrings.forEach(s => { + const result = s.match(TEMPLATE_MATCHER) + expect(result.length).to.be.above(0) + }) + }) + + it('does not match unexpected strings', () => { + const nonMatchingStrings = [ + 'SELECT "foo", "f1" FROM "db1"."rp1"."m1" WHERE time > now() - 15m', + 'SELECT :tv1:, :tv2: FROM "db1"."rp1"."m1" WHERE time > now() - 15m', + ] + + nonMatchingStrings.forEach(s => { + const result = s.match(TEMPLATE_MATCHER) + expect(result).to.equal(null) + }) + }) + + it('only matches when starts with : but does not end in :', () => { + const matchingStrings = [ + 'SELECT :tv1, :tv2: FROM "db1"."rp1"."m1" WHERE time > now() - 15m', + 'SELECT :tv1:, :tv2 FROM "db1"."rp1"."m1" WHERE time > now() - 15m', + ] + + matchingStrings.forEach(s => { + const result = s.match(TEMPLATE_MATCHER) + expect(result.length).to.equal(1) + }) + }) + }) + + describe('replacing', () => { + const tempVar = ':tv1:' + it('can replace the expected strings', () => { + const s = 'SELECT :fasdf FROM "db1"."rp1"."m1"' + const actual = s.replace(TEMPLATE_MATCHER, tempVar) + const expected = `SELECT ${tempVar} FROM "db1"."rp1"."m1"` + + expect(actual).to.equal(expected) + }) + + it('can replace a string with a numeric character', () => { + const s = 'SELECT :fas0df FROM "db1"."rp1"."m1"' + const actual = s.replace(TEMPLATE_MATCHER, tempVar) + const expected = `SELECT ${tempVar} FROM "db1"."rp1"."m1"` + + expect(actual).to.equal(expected) + }) + + it('can replace the expected strings that are next to ,', () => { + const s = 'SELECT :fasdf, "f1" FROM "db1"."rp1"."m1"' + const actual = s.replace(TEMPLATE_MATCHER, tempVar) + const expected = `SELECT ${tempVar}, "f1" FROM "db1"."rp1"."m1"` + + expect(actual).to.equal(expected) + }) + + it('can replace the expected strings that are next to .', () => { + const s = 'SELECT "f1" FROM "db1".:asdf."m1"' + const actual = s.replace(TEMPLATE_MATCHER, tempVar) + const expected = `SELECT "f1" FROM "db1".${tempVar}."m1"` + + expect(actual).to.equal(expected) + }) + + it('can does not replace other tempVars', () => { + const s = 'SELECT :foo: FROM "db1".:asdfasd."m1"' + const actual = s.replace(TEMPLATE_MATCHER, tempVar) + const expected = `SELECT :foo: FROM "db1".${tempVar}."m1"` + + expect(actual).to.equal(expected) + }) + }) +}) diff --git a/ui/src/CheckSources.js b/ui/src/CheckSources.js index 5456a8ef81..d671c1a2a4 100644 --- a/ui/src/CheckSources.js +++ b/ui/src/CheckSources.js @@ -28,7 +28,6 @@ const CheckSources = React.createClass({ }).isRequired, }) ), - addFlashMessage: func, children: node, params: shape({ sourceID: string, diff --git a/ui/src/dashboards/components/CellEditorOverlay.js b/ui/src/dashboards/components/CellEditorOverlay.js index 5cff2643e3..e21b76ba00 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.js +++ b/ui/src/dashboards/components/CellEditorOverlay.js @@ -131,6 +131,7 @@ class CellEditorOverlay extends Component { const { source, onCancel, + templates, timeRange, autoRefresh, editQueryStatus, @@ -174,6 +175,7 @@ class CellEditorOverlay extends Component { /> - + {this.renderLists()} ) diff --git a/ui/src/data_explorer/components/QueryEditor.js b/ui/src/data_explorer/components/QueryEditor.js index 19d57a0142..18fb38aa6b 100644 --- a/ui/src/data_explorer/components/QueryEditor.js +++ b/ui/src/data_explorer/components/QueryEditor.js @@ -1,66 +1,195 @@ -import React, {PropTypes} from 'react' +import React, {PropTypes, Component} from 'react' +import _ from 'lodash' import classNames from 'classnames' + import Dropdown from 'src/shared/components/Dropdown' import LoadingDots from 'src/shared/components/LoadingDots' +import TemplateDrawer from 'src/shared/components/TemplateDrawer' import {QUERY_TEMPLATES} from 'src/data_explorer/constants' +import {TEMPLATE_MATCHER} from 'src/dashboards/constants' -const ENTER = 13 -const ESCAPE = 27 -const {bool, func, shape, string} = PropTypes -const QueryEditor = React.createClass({ - propTypes: { - query: string.isRequired, - onUpdate: func.isRequired, - config: shape({ - status: shape({ - error: string, - loading: bool, - success: string, - warn: string, - }), - }).isRequired, - }, - - getInitialState() { - return { +class QueryEditor extends Component { + constructor(props) { + super(props) + this.state = { value: this.props.query, + isTemplating: false, + selectedTemplate: { + tempVar: _.get(this.props.templates, ['0', 'tempVar'], ''), + }, + filteredTemplates: this.props.templates, } - }, + + this.handleKeyDown = ::this.handleKeyDown + this.handleChange = ::this.handleChange + this.handleUpdate = ::this.handleUpdate + this.handleChooseTemplate = ::this.handleChooseTemplate + this.handleCloseDrawer = ::this.handleCloseDrawer + this.findTempVar = ::this.findTempVar + this.handleTemplateReplace = ::this.handleTemplateReplace + this.handleMouseOverTempVar = ::this.handleMouseOverTempVar + this.handleClickTempVar = ::this.handleClickTempVar + this.closeDrawer = ::this.closeDrawer + } componentWillReceiveProps(nextProps) { if (this.props.query !== nextProps.query) { this.setState({value: nextProps.query}) } - }, + } + + handleCloseDrawer() { + this.setState({isTemplating: false}) + } + + handleMouseOverTempVar(template) { + this.handleTemplateReplace(template) + } + + handleClickTempVar(template) { + // Clicking a tempVar does the same thing as hitting 'Enter' + this.handleTemplateReplace(template, 'Enter') + this.closeDrawer() + } + + closeDrawer() { + this.setState({ + isTemplating: false, + selectedTemplate: { + tempVar: _.get(this.props.templates, ['0', 'tempVar'], ''), + }, + }) + } handleKeyDown(e) { - if (e.keyCode === ENTER) { + const {isTemplating, value} = this.state + + if (isTemplating) { + switch (e.key) { + case 'ArrowRight': + case 'ArrowDown': + e.preventDefault() + return this.handleTemplateReplace(this.findTempVar('next')) + case 'ArrowLeft': + case 'ArrowUp': + e.preventDefault() + return this.handleTemplateReplace(this.findTempVar('previous')) + case 'Enter': + e.preventDefault() + this.handleTemplateReplace(this.state.selectedTemplate, e.key) + return this.closeDrawer() + case 'Escape': + e.preventDefault() + return this.closeDrawer() + } + } else if (e.key === 'Escape') { + e.preventDefault() + this.setState({value, isTemplating: false}) + } else if (e.key === 'Enter') { e.preventDefault() this.handleUpdate() - } else if (e.keyCode === ESCAPE) { - this.setState({value: this.state.value}, () => { - this.editor.blur() - }) } - }, + } + + handleTemplateReplace(selectedTemplate, key) { + const {selectionStart, value} = this.editor + const isEnter = key === 'Enter' + const {tempVar} = selectedTemplate + + let templatedValue + const matched = value.match(TEMPLATE_MATCHER) + if (matched) { + const newTempVar = isEnter + ? tempVar + : tempVar.substring(0, tempVar.length - 1) + templatedValue = value.replace(TEMPLATE_MATCHER, newTempVar) + } + + const enterModifier = isEnter ? 0 : -1 + const diffInLength = tempVar.length - matched[0].length + enterModifier + + this.setState({value: templatedValue, selectedTemplate}, () => + this.editor.setSelectionRange( + selectionStart + diffInLength, + selectionStart + diffInLength + ) + ) + } + + findTempVar(direction) { + const {filteredTemplates: templates} = this.state + const {selectedTemplate} = this.state + + const i = _.findIndex(templates, selectedTemplate) + const lastIndex = templates.length - 1 + + if (i >= 0) { + if (direction === 'next') { + return templates[(i + 1) % templates.length] + } + + if (direction === 'previous') { + if (i === 0) { + return templates[lastIndex] + } + + return templates[i - 1] + } + } + + return templates[0] + } handleChange() { - this.setState({ - value: this.editor.value, - }) - }, + const {templates} = this.props + const {selectedTemplate} = this.state + const value = this.editor.value + const matches = value.match(TEMPLATE_MATCHER) + if (matches) { + // maintain cursor poition + const start = this.editor.selectionStart + const end = this.editor.selectionEnd + const filteredTemplates = templates.filter(t => + t.tempVar.includes(matches[0].substring(1)) + ) + + const found = filteredTemplates.find( + t => t.tempVar === selectedTemplate && selectedTemplate.tempVar + ) + const newTemplate = found ? found : filteredTemplates[0] + + this.setState({ + isTemplating: true, + selectedTemplate: newTemplate, + filteredTemplates, + value, + }) + this.editor.setSelectionRange(start, end) + } else { + this.setState({isTemplating: false, value}) + } + } handleUpdate() { this.props.onUpdate(this.state.value) - }, + } handleChooseTemplate(template) { this.setState({value: template.query}) - }, + } + + handleSelectTempVar(tempVar) { + this.setState({selectedTemplate: tempVar}) + } render() { const {config: {status}} = this.props - const {value} = this.state + const { + value, + isTemplating, + selectedTemplate, + filteredTemplates, + } = this.state return (
@@ -69,13 +198,80 @@ const QueryEditor = React.createClass({ onChange={this.handleChange} onKeyDown={this.handleKeyDown} onBlur={this.handleUpdate} - ref={editor => (this.editor = editor)} + ref={editor => this.editor = editor} value={value} placeholder="Enter a query or select database, measurement, and field below and have us build one for you..." autoComplete="off" spellCheck="false" /> - {this.renderStatus(status)} +
+
+
{this.renderStatus(status)}
+
+ {isTemplating + ? + : null} +
+
+
+
+ ) + } + + renderStatus(status) { + if (!status) { + return ( +
+ +
+ ) + } + + if (status.loading) { + return ( +
+ + +
+ ) + } + + return ( +
+ + + {status.error || status.warn || status.success} +
) - }, + } +} - renderStatus(status) { - if (!status) { - return
- } +const {arrayOf, func, shape, string} = PropTypes - if (status.loading) { - return ( -
- -
- ) - } - - return ( -
- - {status.error || status.warn || status.success} -
- ) - }, -}) +QueryEditor.propTypes = { + query: string.isRequired, + onUpdate: func.isRequired, + config: shape().isRequired, + templates: arrayOf( + shape({ + tempVar: string.isRequired, + }) + ), +} export default QueryEditor diff --git a/ui/src/data_explorer/components/QueryMaker.js b/ui/src/data_explorer/components/QueryMaker.js index 8dbba312ae..f471a2c7be 100644 --- a/ui/src/data_explorer/components/QueryMaker.js +++ b/ui/src/data_explorer/components/QueryMaker.js @@ -4,14 +4,7 @@ import QueryBuilder from './QueryBuilder' import QueryMakerTab from './QueryMakerTab' import buildInfluxQLQuery from 'utils/influxql' -const { - arrayOf, - func, - node, - number, - shape, - string, -} = PropTypes +const {arrayOf, func, node, number, shape, string} = PropTypes const QueryMaker = React.createClass({ propTypes: { @@ -25,6 +18,11 @@ const QueryMaker = React.createClass({ upper: string, lower: string, }).isRequired, + templates: arrayOf( + shape({ + tempVar: string.isRequired, + }) + ), actions: shape({ chooseNamespace: func.isRequired, chooseMeasurement: func.isRequired, @@ -76,7 +74,7 @@ const QueryMaker = React.createClass({ }, renderQueryBuilder() { - const {timeRange, actions, source} = this.props + const {timeRange, actions, source, templates} = this.props const query = this.getActiveQuery() if (!query) { @@ -93,6 +91,7 @@ const QueryMaker = React.createClass({ { ) }, - async executeQueries(queries) { - const {templates, editQueryStatus} = this.props + executeQueries(queries) { + const {templates = [], editQueryStatus} = this.props if (!queries.length) { this.setState({timeSeries: []}) diff --git a/ui/src/shared/components/Dropdown.js b/ui/src/shared/components/Dropdown.js index 574c3c0840..6332a2f05b 100644 --- a/ui/src/shared/components/Dropdown.js +++ b/ui/src/shared/components/Dropdown.js @@ -36,9 +36,6 @@ class Dropdown extends Component { if (e) { e.stopPropagation() } - if (this.props.onClick) { - this.props.onClick(e) - } this.setState({isOpen: !this.state.isOpen}) } diff --git a/ui/src/shared/components/TemplateDrawer.js b/ui/src/shared/components/TemplateDrawer.js new file mode 100644 index 0000000000..877d3fa673 --- /dev/null +++ b/ui/src/shared/components/TemplateDrawer.js @@ -0,0 +1,41 @@ +import React, {PropTypes} from 'react' +import OnClickOutside from 'react-onclickoutside' +import classNames from 'classnames' + +const TemplateDrawer = ({ + templates, + selected, + onMouseOverTempVar, + onClickTempVar, +}) => ( +
+ {templates.map(t => ( +
{ + onMouseOverTempVar(t) + }} + onClick={() => onClickTempVar(t)} + key={t.tempVar} + > + {' '}{t.tempVar}{' '} +
+ ))} +
+) + +const {arrayOf, func, shape, string} = PropTypes + +TemplateDrawer.propTypes = { + templates: arrayOf( + shape({ + tempVar: string.isRequired, + }) + ), + selected: shape({ + tempVar: string, + }), + onMouseOverTempVar: func.isRequired, + onClickTempVar: func.isRequired, +} + +export default OnClickOutside(TemplateDrawer) diff --git a/ui/src/style/components/query-editor.scss b/ui/src/style/components/query-editor.scss index b55a015fc0..994d396250 100644 --- a/ui/src/style/components/query-editor.scss +++ b/ui/src/style/components/query-editor.scss @@ -11,19 +11,10 @@ border-radius: 0 $radius 0 0; background-color: $query-editor--bg; position: relative; -} -.query-editor--field, -.query-editor--status { - font-family: $code-font; - transition: - color 0.25s ease, - background-color 0.25s ease, - border-color 0.25s ease; - border: 2px solid $query-editor--bg; - background-color: $query-editor--field-bg; - + z-index: 2; /* Minimum amount to obcure the toggle flip within Query Builder. Will fix later */ } .query-editor--field { + font-family: $code-font; font-size: 12px; line-height: 14px; font-weight: 600; @@ -34,7 +25,13 @@ resize: none; width: 100%; height: $query-editor--field-height; + transition: + color 0.25s ease, + background-color 0.25s ease, + border-color 0.25s ease; + border: 2px solid $query-editor--bg; border-bottom: 0; + background-color: $query-editor--field-bg; color: $query-editor--field-text; padding: 12px 10px 0 10px; border-radius: $radius $radius 0 0; @@ -49,33 +46,16 @@ color: $query-editor--field-text !important; border-color: $c-pool; } - &:focus + .query-editor--status { + &:focus + .varmoji { border-color: $c-pool; } } .query-editor--status { + display: flex; + align-items: center; + justify-content: flex-end; height: $query-editor--status-height; - line-height: $query-editor--status-height; - font-size: 12px; - padding: 0 10px; - padding-right: ($query-editor--templates-width + ($query-editor--templates-offset * 2)) !important; - border-radius: 0 0 $radius $radius; - border-top: 0; - color: $query-editor--status-default; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - span.icon { - margin-right: 5px; - } - - /* Error State */ - &.query-editor--error { color: $query-editor--status-error; } - /* Warning State */ - &.query-editor--warning { color: $query-editor--status-warning; } - /* Success State */ - &.query-editor--success { color: $query-editor--status-success; } /* Loading State */ .loading-dots { bottom: $query-editor--templates-offset; @@ -83,10 +63,30 @@ transform: translateY(50%); } } +.query-status-output { + flex: 1 0 0; + display: inline-block; + color: $query-editor--status-default; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + padding: 0 10px; + line-height: $query-editor--status-height; + font-size: 12px; + font-family: $code-font; + + span.icon { + margin-right: 5px; + } + /* Error State */ + &.query-status-output--error { color: $query-editor--status-error; } + /* Warning State */ + &.query-status-output--warning { color: $query-editor--status-warning; } + /* Success State */ + &.query-status-output--success { color: $query-editor--status-success; } +} .dropdown.query-editor--templates { - position: absolute; - bottom: ($query-editor--templates-offset - 8px); - right: $query-editor--templates-offset; + margin: 0 4px 0 0 ; div.dropdown-toggle.btn.btn-sm { width: $query-editor--templates-width; @@ -103,4 +103,85 @@ min-width: $query-editor--templates-menu-width; max-width: $query-editor--templates-menu-width; } +} + + +/* + Varmoji Flipper + ------------------------------------- + Handles the 3D flip transition between two states (isTemplating) + Contents could in theory be anything +*/ +.varmoji { + transition: border-color 0.25s ease; + border: 2px solid $query-editor--bg; + border-top: 0; + background-color: $query-editor--field-bg; + border-radius: 0 0 $radius $radius; + height: $query-editor--status-height; + width: 100%; + perspective: 1000px; +} +.varmoji-container { + transition: transform 0.6s ease; + transform-style: preserve-3d; + position: relative; + transform-origin: 100% #{$query-editor--status-height / 2}; +} +.varmoji-front, +.varmoji-back { + backface-visibility: hidden; + width: 100%; + height: $query-editor--status-height; + position: absolute; + top: 0; + left: 0; +} +.varmoji-front { + z-index: 3; + transform: rotateX(0deg); +} +.varmoji-back { + z-index: 2; + transform: rotateX(180deg); +} +.varmoji.varmoji-rotated .varmoji-container { + transform: rotateX(-180deg); +} + +/* + Template Drawer + ------------------------------------- + Not sure if this needs its own stylesheet +*/ + +.template-drawer { + height: $query-editor--status-height; + width: 100%; + display: flex; + align-items: center; + padding: 0 4px; +} +.template-drawer--item { + margin-right: 2px; + display: inline-block; + font-family: $code-font; + font-weight: 700; + font-size: 12px; + height: ($query-editor--status-height - 14px); + line-height: ($query-editor--status-height - 14px); + padding: 0 6px; + background-color: $query-editor--field-bg; + color: $c-comet; + border-radius: $radius-small; + cursor: pointer; + transition: + color 0.25s ease, + background-color 0.25s ease; + + /* Selected State */ + &.template-drawer--selected { + color: $g20-white; + background-color: $c-star; + } } \ No newline at end of file diff --git a/ui/src/style/components/template-variables-manager.scss b/ui/src/style/components/template-variables-manager.scss index 66be5eadc0..a67c54db95 100644 --- a/ui/src/style/components/template-variables-manager.scss +++ b/ui/src/style/components/template-variables-manager.scss @@ -12,7 +12,7 @@ $tvmp-table-gutter: 8px; .template-variable-manager { max-width: $tvmp-panel-max-width; - margin: 0 $tvmp-gutter; + margin: 0 auto; } .template-variable-manager--header { height: $chronograf-page-header-height; @@ -220,4 +220,4 @@ $tvmp-table-gutter: 8px; .confirm-buttons + .btn-edit { display: none; } -} \ No newline at end of file +} diff --git a/ui/src/style/pages/overlay-technology.scss b/ui/src/style/pages/overlay-technology.scss index 83b254ec83..912b236f37 100644 --- a/ui/src/style/pages/overlay-technology.scss +++ b/ui/src/style/pages/overlay-technology.scss @@ -14,7 +14,8 @@ $overlay-z: 100; left: 0; right: 0; z-index: $overlay-z; - + padding: 0 30px; + &:before { content: ''; display: block; @@ -124,4 +125,4 @@ $overlay-z: 100; } .overlay-technology .query-maker--tab-contents { margin-bottom: 8px; -} \ No newline at end of file +} diff --git a/ui/yarn.lock b/ui/yarn.lock index e038b442a4..9632f32033 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -4357,6 +4357,10 @@ lodash.merge@^4.4.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.0.tgz#69884ba144ac33fe699737a6086deffadd0f89c5" +lodash.mergewith@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.0.tgz#150cf0a16791f5903b8891eab154609274bdea55" + lodash.pick@^4.2.0, lodash.pick@^4.2.1, lodash.pick@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" @@ -4784,9 +4788,9 @@ node-pre-gyp@^0.6.29: tar "~2.2.1" tar-pack "~3.3.0" -node-sass@^3.5.3: - version "3.13.1" - resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-3.13.1.tgz#7240fbbff2396304b4223527ed3020589c004fc2" +node-sass@^4.5.2: + version "4.5.2" + resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.5.2.tgz#4012fa2bd129b1d6365117e88d9da0500d99da64" dependencies: async-foreach "^0.1.3" chalk "^1.1.1" @@ -4797,13 +4801,15 @@ node-sass@^3.5.3: in-publish "^2.0.0" lodash.assign "^4.2.0" lodash.clonedeep "^4.3.2" + lodash.mergewith "^4.6.0" meow "^3.7.0" mkdirp "^0.5.1" nan "^2.3.2" node-gyp "^3.3.1" npmlog "^4.0.0" - request "^2.61.0" + request "^2.79.0" sass-graph "^2.1.1" + stdout-stream "^1.4.0" node-uuid@^1.4.7: version "1.4.7" @@ -6157,7 +6163,7 @@ request-progress@~2.0.1: dependencies: throttleit "^1.0.0" -request@2, request@^2.55.0, request@^2.61.0, request@^2.74.0, request@^2.79.0, request@~2.79.0: +request@2, request@^2.55.0, request@^2.74.0, request@^2.79.0, request@~2.79.0: version "2.79.0" resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" dependencies: @@ -6325,11 +6331,11 @@ sell@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/sell/-/sell-1.0.0.tgz#3baca7e51f78ddee9e22eea1ac747a6368bd1630" -"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@~5.3.0: +"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.1.0, semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" -"semver@2.x || 3.x || 4 || 5", semver@~4.3.3: +semver@~4.3.3: version "4.3.6" resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da" @@ -6676,6 +6682,12 @@ sshpk@^1.7.0: version "1.3.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" +stdout-stream@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.0.tgz#a2c7c8587e54d9427ea9edb3ac3f2cd522df378b" + dependencies: + readable-stream "^2.0.1" + stream-browserify@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db"