Merge pull request #1326 from influxdata/feature/template-varmojis

Feature/template varmojis
pull/1304/head
Andrew Watkins 2017-04-28 08:33:34 -07:00 committed by GitHub
commit 7db2d74ad3
18 changed files with 548 additions and 145 deletions

View File

@ -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)

View File

@ -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,

View File

@ -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",

View File

@ -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)
})
})
})

View File

@ -28,7 +28,6 @@ const CheckSources = React.createClass({
}).isRequired,
})
),
addFlashMessage: func,
children: node,
params: shape({
sourceID: string,

View File

@ -131,6 +131,7 @@ class CellEditorOverlay extends Component {
const {
source,
onCancel,
templates,
timeRange,
autoRefresh,
editQueryStatus,
@ -174,6 +175,7 @@ class CellEditorOverlay extends Component {
/>
<QueryMaker
source={source}
templates={templates}
queries={queriesWorkingDraft}
actions={queryActions}
autoRefresh={autoRefresh}
@ -190,12 +192,17 @@ class CellEditorOverlay extends Component {
}
}
const {func, number, shape, string} = PropTypes
const {arrayOf, func, number, shape, string} = PropTypes
CellEditorOverlay.propTypes = {
onCancel: func.isRequired,
onSave: func.isRequired,
cell: shape({}).isRequired,
templates: arrayOf(
shape({
tempVar: string.isRequired,
})
).isRequired,
timeRange: shape({
upper: string,
lower: string,

View File

@ -70,3 +70,5 @@ export const TEMPLATE_VARIABLE_QUERIES = {
tagKeys: 'SHOW TAG KEYS ON :database: FROM :measurement:',
tagValues: 'SHOW TAG VALUES ON :database: FROM :measurement: WITH KEY=:tagKey:',
}
export const TEMPLATE_MATCHER = /\B:\B|:\w+\b(?!:)/g

View File

@ -244,6 +244,7 @@ class DashboardPage extends Component {
{selectedCell
? <CellEditorOverlay
source={source}
templates={dashboard.templates}
cell={selectedCell}
timeRange={timeRange}
autoRefresh={autoRefresh}
@ -348,11 +349,11 @@ DashboardPage.propTypes = {
}),
values: arrayOf(
shape({
type: string.isRequired,
value: string.isRequired,
selected: bool,
selected: bool.isRequired,
type: string.isRequired,
})
).isRequired,
),
})
),
})

View File

@ -7,11 +7,7 @@ import TagList from './TagList'
import QueryEditor from './QueryEditor'
import buildInfluxQLQuery from 'utils/influxql'
const {
string,
shape,
func,
} = PropTypes
const {arrayOf, func, shape, string} = PropTypes
const QueryBuilder = React.createClass({
propTypes: {
@ -27,6 +23,11 @@ const QueryBuilder = React.createClass({
upper: string,
lower: string,
}).isRequired,
templates: arrayOf(
shape({
tempVar: string.isRequired,
})
),
actions: shape({
chooseNamespace: func.isRequired,
chooseMeasurement: func.isRequired,
@ -78,12 +79,12 @@ const QueryBuilder = React.createClass({
},
render() {
const {query, timeRange} = this.props
const {query, timeRange, templates} = this.props
const q = query.rawText || buildInfluxQLQuery(timeRange, query) || ''
return (
<div className="query-maker--tab-contents">
<QueryEditor query={q} config={query} onUpdate={this.handleEditRawText} />
<QueryEditor query={q} config={query} onUpdate={this.handleEditRawText} templates={templates} />
{this.renderLists()}
</div>
)

View File

@ -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 (
<div className="query-editor">
@ -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)}
<div
className={classNames('varmoji', {'varmoji-rotated': isTemplating})}
>
<div className="varmoji-container">
<div className="varmoji-front">{this.renderStatus(status)}</div>
<div className="varmoji-back">
{isTemplating
? <TemplateDrawer
onClickTempVar={this.handleClickTempVar}
templates={filteredTemplates}
selected={selectedTemplate}
onMouseOverTempVar={this.handleMouseOverTempVar}
handleClickOutside={this.handleCloseDrawer}
/>
: null}
</div>
</div>
</div>
</div>
)
}
renderStatus(status) {
if (!status) {
return (
<div className="query-editor--status">
<Dropdown
items={QUERY_TEMPLATES}
selected={'Query Templates'}
onChoose={this.handleChooseTemplate}
className="query-editor--templates"
/>
</div>
)
}
if (status.loading) {
return (
<div className="query-editor--status">
<LoadingDots />
<Dropdown
items={QUERY_TEMPLATES}
selected={'Query Templates'}
onChoose={this.handleChooseTemplate}
className="query-editor--templates"
/>
</div>
)
}
return (
<div className="query-editor--status">
<span
className={classNames('query-status-output', {
'query-status-output--error': status.error,
'query-status-output--success': status.success,
'query-status-output--warning': status.warn,
})}
>
<span
className={classNames('icon', {
stop: status.error,
checkmark: status.success,
'alert-triangle': status.warn,
})}
/>
{status.error || status.warn || status.success}
</span>
<Dropdown
items={QUERY_TEMPLATES}
selected={'Query Templates'}
@ -84,40 +280,20 @@ const QueryEditor = React.createClass({
/>
</div>
)
},
}
}
renderStatus(status) {
if (!status) {
return <div className="query-editor--status" />
}
const {arrayOf, func, shape, string} = PropTypes
if (status.loading) {
return (
<div className="query-editor--status">
<LoadingDots />
</div>
)
}
return (
<div
className={classNames('query-editor--status', {
'query-editor--error': status.error,
'query-editor--success': status.success,
'query-editor--warning': status.warn,
})}
>
<span
className={classNames('icon', {
stop: status.error,
checkmark: status.success,
'alert-triangle': status.warn,
})}
/>
{status.error || status.warn || status.success}
</div>
)
},
})
QueryEditor.propTypes = {
query: string.isRequired,
onUpdate: func.isRequired,
config: shape().isRequired,
templates: arrayOf(
shape({
tempVar: string.isRequired,
})
),
}
export default QueryEditor

View File

@ -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({
<QueryBuilder
source={source}
timeRange={timeRange}
templates={templates}
query={query}
actions={actions}
onAddQuery={this.handleAddQuery}

View File

@ -95,8 +95,8 @@ const AutoRefresh = ComposedComponent => {
)
},
async executeQueries(queries) {
const {templates, editQueryStatus} = this.props
executeQueries(queries) {
const {templates = [], editQueryStatus} = this.props
if (!queries.length) {
this.setState({timeSeries: []})

View File

@ -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})
}

View File

@ -0,0 +1,41 @@
import React, {PropTypes} from 'react'
import OnClickOutside from 'react-onclickoutside'
import classNames from 'classnames'
const TemplateDrawer = ({
templates,
selected,
onMouseOverTempVar,
onClickTempVar,
}) => (
<div className="template-drawer">
{templates.map(t => (
<div className={classNames('template-drawer--item', {'template-drawer--selected': t.tempVar === selected.tempVar})}
onMouseOver={() => {
onMouseOverTempVar(t)
}}
onClick={() => onClickTempVar(t)}
key={t.tempVar}
>
{' '}{t.tempVar}{' '}
</div>
))}
</div>
)
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)

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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"