Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
N
nexpie-grafana-theme
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Registry
Registry
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Kornkitt Poolsup
nexpie-grafana-theme
Commits
dfe1b20f
Unverified
Commit
dfe1b20f
authored
Jan 17, 2019
by
Torkel Ödegaard
Committed by
GitHub
Jan 17, 2019
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #14930 from grafana/react-query-editor
React query editor (part1)
parents
6f6c4652
3cb73e79
Show whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
450 additions
and
274 deletions
+450
-274
packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx
+33
-0
public/app/features/dashboard/panel_editor/EditorTabBody.tsx
+4
-2
public/app/features/dashboard/panel_editor/QueriesTab.tsx
+46
-77
public/app/features/dashboard/panel_editor/QueryEditorRow.tsx
+237
-0
public/app/features/dashboard/panel_editor/VisualizationTab.tsx
+11
-3
public/app/features/panel/metrics_tab.ts
+0
-31
public/app/features/panel/partials/metrics_tab.html
+0
-24
public/app/features/panel/partials/query_editor_row.html
+1
-43
public/app/features/panel/query_editor_row.ts
+7
-70
public/app/features/plugins/plugin_component.ts
+5
-11
public/app/plugins/datasource/graphite/query_ctrl.ts
+4
-0
public/app/plugins/datasource/postgres/partials/query.editor.html
+0
-0
public/app/types/plugins.ts
+1
-0
public/app/types/series.ts
+5
-1
public/sass/components/_panel_editor.scss
+1
-0
public/sass/components/_query_editor.scss
+95
-12
No files found.
packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx
View file @
dfe1b20f
import
React
,
{
PureComponent
}
from
'react'
;
import
React
,
{
PureComponent
}
from
'react'
;
import
_
from
'lodash'
;
import
Scrollbars
from
'react-custom-scrollbars'
;
import
Scrollbars
from
'react-custom-scrollbars'
;
interface
Props
{
interface
Props
{
...
@@ -8,6 +9,8 @@ interface Props {
...
@@ -8,6 +9,8 @@ interface Props {
autoHideDuration
?:
number
;
autoHideDuration
?:
number
;
autoMaxHeight
?:
string
;
autoMaxHeight
?:
string
;
hideTracksWhenNotNeeded
?:
boolean
;
hideTracksWhenNotNeeded
?:
boolean
;
scrollTop
?:
number
;
setScrollTop
:
(
value
:
React
.
MouseEvent
<
HTMLElement
>
)
=>
void
;
autoHeightMin
?:
number
|
string
;
autoHeightMin
?:
number
|
string
;
}
}
...
@@ -22,14 +25,44 @@ export class CustomScrollbar extends PureComponent<Props> {
...
@@ -22,14 +25,44 @@ export class CustomScrollbar extends PureComponent<Props> {
autoHideDuration
:
200
,
autoHideDuration
:
200
,
autoMaxHeight
:
'100%'
,
autoMaxHeight
:
'100%'
,
hideTracksWhenNotNeeded
:
false
,
hideTracksWhenNotNeeded
:
false
,
scrollTop
:
0
,
setScrollTop
:
()
=>
{},
autoHeightMin
:
'0'
autoHeightMin
:
'0'
};
};
private
ref
:
React
.
RefObject
<
Scrollbars
>
;
constructor
(
props
:
Props
)
{
super
(
props
);
this
.
ref
=
React
.
createRef
<
Scrollbars
>
();
}
updateScroll
()
{
const
ref
=
this
.
ref
.
current
;
if
(
ref
&&
!
_
.
isNil
(
this
.
props
.
scrollTop
))
{
if
(
this
.
props
.
scrollTop
>
10000
)
{
ref
.
scrollToBottom
();
}
else
{
ref
.
scrollTop
(
this
.
props
.
scrollTop
);
}
}
}
componentDidMount
()
{
this
.
updateScroll
();
}
componentDidUpdate
()
{
this
.
updateScroll
();
}
render
()
{
render
()
{
const
{
customClassName
,
children
,
autoMaxHeight
}
=
this
.
props
;
const
{
customClassName
,
children
,
autoMaxHeight
}
=
this
.
props
;
return
(
return
(
<
Scrollbars
<
Scrollbars
ref=
{
this
.
ref
}
className=
{
customClassName
}
className=
{
customClassName
}
autoHeight=
{
true
}
autoHeight=
{
true
}
// These autoHeightMin & autoHeightMax options affect firefox and chrome differently.
// These autoHeightMin & autoHeightMax options affect firefox and chrome differently.
...
...
public/app/features/dashboard/panel_editor/EditorTabBody.tsx
View file @
dfe1b20f
...
@@ -10,6 +10,8 @@ interface Props {
...
@@ -10,6 +10,8 @@ interface Props {
heading
:
string
;
heading
:
string
;
renderToolbar
?:
()
=>
JSX
.
Element
;
renderToolbar
?:
()
=>
JSX
.
Element
;
toolbarItems
?:
EditorToolbarView
[];
toolbarItems
?:
EditorToolbarView
[];
scrollTop
?:
number
;
setScrollTop
?:
(
value
:
React
.
MouseEvent
<
HTMLElement
>
)
=>
void
;
}
}
export
interface
EditorToolbarView
{
export
interface
EditorToolbarView
{
...
@@ -103,7 +105,7 @@ export class EditorTabBody extends PureComponent<Props, State> {
...
@@ -103,7 +105,7 @@ export class EditorTabBody extends PureComponent<Props, State> {
}
}
render
()
{
render
()
{
const
{
children
,
renderToolbar
,
heading
,
toolbarItems
}
=
this
.
props
;
const
{
children
,
renderToolbar
,
heading
,
toolbarItems
,
scrollTop
,
setScrollTop
}
=
this
.
props
;
const
{
openView
,
fadeIn
,
isOpen
}
=
this
.
state
;
const
{
openView
,
fadeIn
,
isOpen
}
=
this
.
state
;
return
(
return
(
...
@@ -119,7 +121,7 @@ export class EditorTabBody extends PureComponent<Props, State> {
...
@@ -119,7 +121,7 @@ export class EditorTabBody extends PureComponent<Props, State> {
)
}
)
}
</
div
>
</
div
>
<
div
className=
"panel-editor__scroll"
>
<
div
className=
"panel-editor__scroll"
>
<
CustomScrollbar
autoHide=
{
false
}
>
<
CustomScrollbar
autoHide=
{
false
}
scrollTop=
{
scrollTop
}
setScrollTop=
{
setScrollTop
}
>
<
div
className=
"panel-editor__content"
>
<
div
className=
"panel-editor__content"
>
<
FadeIn
in=
{
isOpen
}
duration=
{
200
}
unmountOnExit=
{
true
}
>
<
FadeIn
in=
{
isOpen
}
duration=
{
200
}
unmountOnExit=
{
true
}
>
{
openView
&&
this
.
renderOpenView
(
openView
)
}
{
openView
&&
this
.
renderOpenView
(
openView
)
}
...
...
public/app/features/dashboard/panel_editor/QueriesTab.tsx
View file @
dfe1b20f
...
@@ -3,18 +3,16 @@ import React, { PureComponent } from 'react';
...
@@ -3,18 +3,16 @@ import React, { PureComponent } from 'react';
import
_
from
'lodash'
;
import
_
from
'lodash'
;
// Components
// Components
import
'app/features/panel/metrics_tab'
;
import
{
EditorTabBody
,
EditorToolbarView
}
from
'./EditorTabBody'
;
import
{
EditorTabBody
,
EditorToolbarView
}
from
'./EditorTabBody'
;
import
{
DataSourcePicker
}
from
'app/core/components/Select/DataSourcePicker'
;
import
{
DataSourcePicker
}
from
'app/core/components/Select/DataSourcePicker'
;
import
{
QueryInspector
}
from
'./QueryInspector'
;
import
{
QueryInspector
}
from
'./QueryInspector'
;
import
{
QueryOptions
}
from
'./QueryOptions'
;
import
{
QueryOptions
}
from
'./QueryOptions'
;
import
{
AngularQueryComponentScope
}
from
'app/features/panel/metrics_tab'
;
import
{
PanelOptionsGroup
}
from
'@grafana/ui'
;
import
{
PanelOptionsGroup
}
from
'@grafana/ui'
;
import
{
QueryEditorRow
}
from
'./QueryEditorRow'
;
// Services
// Services
import
{
getDatasourceSrv
}
from
'app/features/plugins/datasource_srv'
;
import
{
getDatasourceSrv
}
from
'app/features/plugins/datasource_srv'
;
import
{
BackendSrv
,
getBackendSrv
}
from
'app/core/services/backend_srv'
;
import
{
BackendSrv
,
getBackendSrv
}
from
'app/core/services/backend_srv'
;
import
{
AngularComponent
,
getAngularLoader
}
from
'app/core/services/AngularLoader'
;
import
config
from
'app/core/config'
;
import
config
from
'app/core/config'
;
// Types
// Types
...
@@ -34,66 +32,27 @@ interface State {
...
@@ -34,66 +32,27 @@ interface State {
isLoadingHelp
:
boolean
;
isLoadingHelp
:
boolean
;
isPickerOpen
:
boolean
;
isPickerOpen
:
boolean
;
isAddingMixed
:
boolean
;
isAddingMixed
:
boolean
;
scrollTop
:
number
;
}
}
export
class
QueriesTab
extends
PureComponent
<
Props
,
State
>
{
export
class
QueriesTab
extends
PureComponent
<
Props
,
State
>
{
element
:
HTMLElement
;
component
:
AngularComponent
;
datasources
:
DataSourceSelectItem
[]
=
getDatasourceSrv
().
getMetricSources
();
datasources
:
DataSourceSelectItem
[]
=
getDatasourceSrv
().
getMetricSources
();
backendSrv
:
BackendSrv
=
getBackendSrv
();
backendSrv
:
BackendSrv
=
getBackendSrv
();
constructor
(
props
)
{
state
:
State
=
{
super
(
props
);
this
.
state
=
{
isLoadingHelp
:
false
,
isLoadingHelp
:
false
,
currentDS
:
this
.
findCurrentDataSource
(),
currentDS
:
this
.
findCurrentDataSource
(),
helpContent
:
null
,
helpContent
:
null
,
isPickerOpen
:
false
,
isPickerOpen
:
false
,
isAddingMixed
:
false
,
isAddingMixed
:
false
,
scrollTop
:
0
,
};
};
}
findCurrentDataSource
():
DataSourceSelectItem
{
findCurrentDataSource
():
DataSourceSelectItem
{
const
{
panel
}
=
this
.
props
;
const
{
panel
}
=
this
.
props
;
return
this
.
datasources
.
find
(
datasource
=>
datasource
.
value
===
panel
.
datasource
)
||
this
.
datasources
[
0
];
return
this
.
datasources
.
find
(
datasource
=>
datasource
.
value
===
panel
.
datasource
)
||
this
.
datasources
[
0
];
}
}
getAngularQueryComponentScope
():
AngularQueryComponentScope
{
const
{
panel
,
dashboard
}
=
this
.
props
;
return
{
panel
:
panel
,
dashboard
:
dashboard
,
refresh
:
()
=>
panel
.
refresh
(),
render
:
()
=>
panel
.
render
,
addQuery
:
this
.
onAddQuery
,
moveQuery
:
this
.
onMoveQuery
,
removeQuery
:
this
.
onRemoveQuery
,
events
:
panel
.
events
,
};
}
componentDidMount
()
{
if
(
!
this
.
element
)
{
return
;
}
const
loader
=
getAngularLoader
();
const
template
=
'<metrics-tab />'
;
const
scopeProps
=
{
ctrl
:
this
.
getAngularQueryComponentScope
(),
};
this
.
component
=
loader
.
load
(
this
.
element
,
scopeProps
,
template
);
}
componentWillUnmount
()
{
if
(
this
.
component
)
{
this
.
component
.
destroy
();
}
}
onChangeDataSource
=
datasource
=>
{
onChangeDataSource
=
datasource
=>
{
const
{
panel
}
=
this
.
props
;
const
{
panel
}
=
this
.
props
;
const
{
currentDS
}
=
this
.
state
;
const
{
currentDS
}
=
this
.
state
;
...
@@ -137,7 +96,7 @@ export class QueriesTab extends PureComponent<Props, State> {
...
@@ -137,7 +96,7 @@ export class QueriesTab extends PureComponent<Props, State> {
onAddQuery
=
(
query
?:
Partial
<
DataQuery
>
)
=>
{
onAddQuery
=
(
query
?:
Partial
<
DataQuery
>
)
=>
{
this
.
props
.
panel
.
addQuery
(
query
);
this
.
props
.
panel
.
addQuery
(
query
);
this
.
forceUpdate
(
);
this
.
setState
({
scrollTop
:
this
.
state
.
scrollTop
+
100000
}
);
};
};
onAddQueryClick
=
()
=>
{
onAddQueryClick
=
()
=>
{
...
@@ -146,9 +105,7 @@ export class QueriesTab extends PureComponent<Props, State> {
...
@@ -146,9 +105,7 @@ export class QueriesTab extends PureComponent<Props, State> {
return
;
return
;
}
}
this
.
props
.
panel
.
addQuery
();
this
.
onAddQuery
();
this
.
component
.
digest
();
this
.
forceUpdate
();
};
};
onRemoveQuery
=
(
query
:
DataQuery
)
=>
{
onRemoveQuery
=
(
query
:
DataQuery
)
=>
{
...
@@ -171,9 +128,21 @@ export class QueriesTab extends PureComponent<Props, State> {
...
@@ -171,9 +128,21 @@ export class QueriesTab extends PureComponent<Props, State> {
};
};
renderToolbar
=
()
=>
{
renderToolbar
=
()
=>
{
const
{
currentDS
}
=
this
.
state
;
const
{
currentDS
,
isAddingMixed
}
=
this
.
state
;
return
<
DataSourcePicker
datasources=
{
this
.
datasources
}
onChange=
{
this
.
onChangeDataSource
}
current=
{
currentDS
}
/>;
return
(
<>
<
DataSourcePicker
datasources=
{
this
.
datasources
}
onChange=
{
this
.
onChangeDataSource
}
current=
{
currentDS
}
/>
<
div
className=
"m-l-2"
>
{
!
isAddingMixed
&&
(
<
button
className=
"btn navbar-button navbar-button--primary"
onClick=
{
this
.
onAddQueryClick
}
>
Add Query
</
button
>
)
}
{
isAddingMixed
&&
this
.
renderMixedPicker
()
}
</
div
>
</>
);
};
};
renderMixedPicker
=
()
=>
{
renderMixedPicker
=
()
=>
{
...
@@ -190,17 +159,21 @@ export class QueriesTab extends PureComponent<Props, State> {
...
@@ -190,17 +159,21 @@ export class QueriesTab extends PureComponent<Props, State> {
onAddMixedQuery
=
datasource
=>
{
onAddMixedQuery
=
datasource
=>
{
this
.
onAddQuery
({
datasource
:
datasource
.
name
});
this
.
onAddQuery
({
datasource
:
datasource
.
name
});
this
.
component
.
digest
();
this
.
setState
({
isAddingMixed
:
false
,
scrollTop
:
this
.
state
.
scrollTop
+
10000
});
this
.
setState
({
isAddingMixed
:
false
});
};
};
onMixedPickerBlur
=
()
=>
{
onMixedPickerBlur
=
()
=>
{
this
.
setState
({
isAddingMixed
:
false
});
this
.
setState
({
isAddingMixed
:
false
});
};
};
setScrollTop
=
(
event
:
React
.
MouseEvent
<
HTMLElement
>
)
=>
{
const
target
=
event
.
target
as
HTMLElement
;
this
.
setState
({
scrollTop
:
target
.
scrollTop
});
};
render
()
{
render
()
{
const
{
panel
}
=
this
.
props
;
const
{
panel
}
=
this
.
props
;
const
{
currentDS
,
isAddingMixed
}
=
this
.
state
;
const
{
currentDS
,
scrollTop
}
=
this
.
state
;
const
queryInspector
:
EditorToolbarView
=
{
const
queryInspector
:
EditorToolbarView
=
{
title
:
'Query Inspector'
,
title
:
'Query Inspector'
,
...
@@ -214,32 +187,28 @@ export class QueriesTab extends PureComponent<Props, State> {
...
@@ -214,32 +187,28 @@ export class QueriesTab extends PureComponent<Props, State> {
};
};
return
(
return
(
<
EditorTabBody
heading=
"Queries"
renderToolbar=
{
this
.
renderToolbar
}
toolbarItems=
{
[
queryInspector
,
dsHelp
]
}
>
<
EditorTabBody
heading=
"Queries to"
renderToolbar=
{
this
.
renderToolbar
}
toolbarItems=
{
[
queryInspector
,
dsHelp
]
}
setScrollTop=
{
this
.
setScrollTop
}
scrollTop=
{
scrollTop
}
>
<>
<>
<
PanelOptionsGroup
>
<
div
className=
"query-editor-rows"
>
<
div
className=
"query-editor-rows"
>
<
div
ref=
{
element
=>
(
this
.
element
=
element
)
}
/>
{
panel
.
targets
.
map
((
query
,
index
)
=>
(
<
QueryEditorRow
<
div
className=
"gf-form-query"
>
datasourceName=
{
query
.
datasource
||
panel
.
datasource
}
<
div
className=
"gf-form gf-form-query-letter-cell"
>
key=
{
query
.
refId
}
<
label
className=
"gf-form-label"
>
panel=
{
panel
}
<
span
className=
"gf-form-query-letter-cell-carret muted"
>
query=
{
query
}
<
i
className=
"fa fa-caret-down"
/>
onRemoveQuery=
{
this
.
onRemoveQuery
}
</
span
>
{
' '
}
onAddQuery=
{
this
.
onAddQuery
}
<
span
className=
"gf-form-query-letter-cell-letter"
>
{
panel
.
getNextQueryLetter
()
}
</
span
>
onMoveQuery=
{
this
.
onMoveQuery
}
</
label
>
inMixedMode=
{
currentDS
.
meta
.
mixed
}
</
div
>
/>
<
div
className=
"gf-form"
>
))
}
{
!
isAddingMixed
&&
(
<
button
className=
"btn btn-secondary gf-form-btn"
onClick=
{
this
.
onAddQueryClick
}
>
Add Query
</
button
>
)
}
{
isAddingMixed
&&
this
.
renderMixedPicker
()
}
</
div
>
</
div
>
</
div
>
</
div
>
</
PanelOptionsGroup
>
<
PanelOptionsGroup
>
<
PanelOptionsGroup
>
<
QueryOptions
panel=
{
panel
}
datasource=
{
currentDS
}
/>
<
QueryOptions
panel=
{
panel
}
datasource=
{
currentDS
}
/>
</
PanelOptionsGroup
>
</
PanelOptionsGroup
>
...
...
public/app/features/dashboard/panel_editor/QueryEditorRow.tsx
0 → 100644
View file @
dfe1b20f
// Libraries
import
React
,
{
PureComponent
}
from
'react'
;
import
classNames
from
'classnames'
;
import
_
from
'lodash'
;
// Utils & Services
import
{
getDatasourceSrv
}
from
'app/features/plugins/datasource_srv'
;
import
{
AngularComponent
,
getAngularLoader
}
from
'app/core/services/AngularLoader'
;
import
{
Emitter
}
from
'app/core/utils/emitter'
;
// Types
import
{
PanelModel
}
from
'../panel_model'
;
import
{
DataQuery
,
DataSourceApi
}
from
'app/types/series'
;
interface
Props
{
panel
:
PanelModel
;
query
:
DataQuery
;
onAddQuery
:
(
query
?:
DataQuery
)
=>
void
;
onRemoveQuery
:
(
query
:
DataQuery
)
=>
void
;
onMoveQuery
:
(
query
:
DataQuery
,
direction
:
number
)
=>
void
;
datasourceName
:
string
|
null
;
inMixedMode
:
boolean
;
}
interface
State
{
datasource
:
DataSourceApi
|
null
;
isCollapsed
:
boolean
;
angularScope
:
AngularQueryComponentScope
|
null
;
}
export
class
QueryEditorRow
extends
PureComponent
<
Props
,
State
>
{
element
:
HTMLElement
|
null
=
null
;
angularQueryEditor
:
AngularComponent
|
null
=
null
;
state
:
State
=
{
datasource
:
null
,
isCollapsed
:
false
,
angularScope
:
null
,
};
componentDidMount
()
{
this
.
loadDatasource
();
}
getAngularQueryComponentScope
():
AngularQueryComponentScope
{
const
{
panel
,
query
}
=
this
.
props
;
const
{
datasource
}
=
this
.
state
;
return
{
datasource
:
datasource
,
target
:
query
,
panel
:
panel
,
refresh
:
()
=>
panel
.
refresh
(),
render
:
()
=>
panel
.
render
,
events
:
panel
.
events
,
};
}
async
loadDatasource
()
{
const
{
query
,
panel
}
=
this
.
props
;
const
dataSourceSrv
=
getDatasourceSrv
();
const
datasource
=
await
dataSourceSrv
.
get
(
query
.
datasource
||
panel
.
datasource
);
this
.
setState
({
datasource
});
}
componentDidUpdate
()
{
const
{
datasource
}
=
this
.
state
;
// check if we need to load another datasource
if
(
datasource
&&
datasource
.
name
!==
this
.
props
.
datasourceName
)
{
if
(
this
.
angularQueryEditor
)
{
this
.
angularQueryEditor
.
destroy
();
this
.
angularQueryEditor
=
null
;
}
this
.
loadDatasource
();
return
;
}
if
(
!
this
.
element
||
this
.
angularQueryEditor
)
{
return
;
}
const
loader
=
getAngularLoader
();
const
template
=
'<plugin-component type="query-ctrl" />'
;
const
scopeProps
=
{
ctrl
:
this
.
getAngularQueryComponentScope
()
};
this
.
angularQueryEditor
=
loader
.
load
(
this
.
element
,
scopeProps
,
template
);
// give angular time to compile
setTimeout
(()
=>
{
this
.
setState
({
angularScope
:
scopeProps
.
ctrl
});
},
10
);
}
componentWillUnmount
()
{
if
(
this
.
angularQueryEditor
)
{
this
.
angularQueryEditor
.
destroy
();
}
}
onToggleCollapse
=
()
=>
{
this
.
setState
({
isCollapsed
:
!
this
.
state
.
isCollapsed
});
};
renderPluginEditor
()
{
const
{
datasource
}
=
this
.
state
;
if
(
datasource
.
pluginExports
.
QueryCtrl
)
{
return
<
div
ref=
{
element
=>
(
this
.
element
=
element
)
}
/>;
}
if
(
datasource
.
pluginExports
.
QueryEditor
)
{
const
QueryEditor
=
datasource
.
pluginExports
.
QueryEditor
;
return
<
QueryEditor
/>;
}
return
<
div
>
Data source plugin does not export any Query Editor component
</
div
>;
}
onToggleEditMode
=
()
=>
{
const
{
angularScope
}
=
this
.
state
;
if
(
angularScope
&&
angularScope
.
toggleEditorMode
)
{
angularScope
.
toggleEditorMode
();
this
.
angularQueryEditor
.
digest
();
}
if
(
this
.
state
.
isCollapsed
)
{
this
.
setState
({
isCollapsed
:
false
});
}
};
get
hasTextEditMode
()
{
const
{
angularScope
}
=
this
.
state
;
return
angularScope
&&
angularScope
.
toggleEditorMode
;
}
onRemoveQuery
=
()
=>
{
this
.
props
.
onRemoveQuery
(
this
.
props
.
query
);
};
onCopyQuery
=
()
=>
{
const
copy
=
_
.
cloneDeep
(
this
.
props
.
query
);
this
.
props
.
onAddQuery
(
copy
);
};
onDisableQuery
=
()
=>
{
this
.
props
.
query
.
hide
=
!
this
.
props
.
query
.
hide
;
this
.
forceUpdate
();
};
renderCollapsedText
():
string
|
null
{
const
{
angularScope
}
=
this
.
state
;
if
(
angularScope
&&
angularScope
.
getCollapsedText
)
{
return
angularScope
.
getCollapsedText
();
}
return
null
;
}
render
()
{
const
{
query
,
datasourceName
,
inMixedMode
}
=
this
.
props
;
const
{
datasource
,
isCollapsed
}
=
this
.
state
;
const
isDisabled
=
query
.
hide
;
const
bodyClasses
=
classNames
(
'query-editor-row__body gf-form-query'
,
{
'query-editor-row__body--collapsed'
:
isCollapsed
,
});
const
rowClasses
=
classNames
(
'query-editor-row'
,
{
'query-editor-row--disabled'
:
isDisabled
,
'gf-form-disabled'
:
isDisabled
,
});
if
(
!
datasource
)
{
return
null
;
}
return
(
<
div
className=
{
rowClasses
}
>
<
div
className=
"query-editor-row__header"
>
<
div
className=
"query-editor-row__ref-id"
onClick=
{
this
.
onToggleCollapse
}
>
{
isCollapsed
&&
<
i
className=
"fa fa-caret-right"
/>
}
{
!
isCollapsed
&&
<
i
className=
"fa fa-caret-down"
/>
}
<
span
>
{
query
.
refId
}
</
span
>
{
inMixedMode
&&
<
em
className=
"query-editor-row__context-info"
>
(
{
datasourceName
}
)
</
em
>
}
{
isDisabled
&&
<
em
className=
"query-editor-row__context-info"
>
Disabled
</
em
>
}
</
div
>
<
div
className=
"query-editor-row__collapsed-text"
>
{
isCollapsed
&&
<
div
>
{
this
.
renderCollapsedText
()
}
</
div
>
}
</
div
>
<
div
className=
"query-editor-row__actions"
>
{
this
.
hasTextEditMode
&&
(
<
button
className=
"query-editor-row__action"
onClick=
{
this
.
onToggleEditMode
}
title=
"Toggle text edit mode"
>
<
i
className=
"fa fa-fw fa-pencil"
/>
</
button
>
)
}
<
button
className=
"query-editor-row__action"
onClick=
{
()
=>
this
.
props
.
onMoveQuery
(
query
,
1
)
}
>
<
i
className=
"fa fa-fw fa-arrow-down"
/>
</
button
>
<
button
className=
"query-editor-row__action"
onClick=
{
()
=>
this
.
props
.
onMoveQuery
(
query
,
-
1
)
}
>
<
i
className=
"fa fa-fw fa-arrow-up"
/>
</
button
>
<
button
className=
"query-editor-row__action"
onClick=
{
this
.
onCopyQuery
}
title=
"Duplicate query"
>
<
i
className=
"fa fa-fw fa-copy"
/>
</
button
>
<
button
className=
"query-editor-row__action"
onClick=
{
this
.
onDisableQuery
}
title=
"Disable/enable query"
>
{
isDisabled
&&
<
i
className=
"fa fa-fw fa-eye-slash"
/>
}
{
!
isDisabled
&&
<
i
className=
"fa fa-fw fa-eye"
/>
}
</
button
>
<
button
className=
"query-editor-row__action"
onClick=
{
this
.
onRemoveQuery
}
title=
"Remove query"
>
<
i
className=
"fa fa-fw fa-trash"
/>
</
button
>
</
div
>
</
div
>
<
div
className=
{
bodyClasses
}
>
{
this
.
renderPluginEditor
()
}
</
div
>
</
div
>
);
}
}
export
interface
AngularQueryComponentScope
{
target
:
DataQuery
;
panel
:
PanelModel
;
events
:
Emitter
;
refresh
:
()
=>
void
;
render
:
()
=>
void
;
datasource
:
DataSourceApi
;
toggleEditorMode
?:
()
=>
void
;
getCollapsedText
?:
()
=>
string
;
}
public/app/features/dashboard/panel_editor/VisualizationTab.tsx
View file @
dfe1b20f
...
@@ -26,6 +26,7 @@ interface Props {
...
@@ -26,6 +26,7 @@ interface Props {
interface
State
{
interface
State
{
isVizPickerOpen
:
boolean
;
isVizPickerOpen
:
boolean
;
searchQuery
:
string
;
searchQuery
:
string
;
scrollTop
:
number
;
}
}
export
class
VisualizationTab
extends
PureComponent
<
Props
,
State
>
{
export
class
VisualizationTab
extends
PureComponent
<
Props
,
State
>
{
...
@@ -39,6 +40,7 @@ export class VisualizationTab extends PureComponent<Props, State> {
...
@@ -39,6 +40,7 @@ export class VisualizationTab extends PureComponent<Props, State> {
this
.
state
=
{
this
.
state
=
{
isVizPickerOpen
:
false
,
isVizPickerOpen
:
false
,
searchQuery
:
''
,
searchQuery
:
''
,
scrollTop
:
0
,
};
};
}
}
...
@@ -143,7 +145,7 @@ export class VisualizationTab extends PureComponent<Props, State> {
...
@@ -143,7 +145,7 @@ export class VisualizationTab extends PureComponent<Props, State> {
};
};
onOpenVizPicker
=
()
=>
{
onOpenVizPicker
=
()
=>
{
this
.
setState
({
isVizPickerOpen
:
true
});
this
.
setState
({
isVizPickerOpen
:
true
,
scrollTop
:
0
});
};
};
onCloseVizPicker
=
()
=>
{
onCloseVizPicker
=
()
=>
{
...
@@ -201,9 +203,14 @@ export class VisualizationTab extends PureComponent<Props, State> {
...
@@ -201,9 +203,14 @@ export class VisualizationTab extends PureComponent<Props, State> {
renderHelp
=
()
=>
<
PluginHelp
plugin=
{
this
.
props
.
plugin
}
type=
"help"
/>;
renderHelp
=
()
=>
<
PluginHelp
plugin=
{
this
.
props
.
plugin
}
type=
"help"
/>;
setScrollTop
=
(
event
:
React
.
MouseEvent
<
HTMLElement
>
)
=>
{
const
target
=
event
.
target
as
HTMLElement
;
this
.
setState
({
scrollTop
:
target
.
scrollTop
});
};
render
()
{
render
()
{
const
{
plugin
}
=
this
.
props
;
const
{
plugin
}
=
this
.
props
;
const
{
isVizPickerOpen
,
searchQuery
}
=
this
.
state
;
const
{
isVizPickerOpen
,
searchQuery
,
scrollTop
}
=
this
.
state
;
const
pluginHelp
:
EditorToolbarView
=
{
const
pluginHelp
:
EditorToolbarView
=
{
heading
:
'Help'
,
heading
:
'Help'
,
...
@@ -212,7 +219,8 @@ export class VisualizationTab extends PureComponent<Props, State> {
...
@@ -212,7 +219,8 @@ export class VisualizationTab extends PureComponent<Props, State> {
};
};
return
(
return
(
<
EditorTabBody
heading=
"Visualization"
renderToolbar=
{
this
.
renderToolbar
}
toolbarItems=
{
[
pluginHelp
]
}
>
<
EditorTabBody
heading=
"Visualization"
renderToolbar=
{
this
.
renderToolbar
}
toolbarItems=
{
[
pluginHelp
]
}
scrollTop=
{
scrollTop
}
setScrollTop=
{
this
.
setScrollTop
}
>
<>
<>
<
FadeIn
in=
{
isVizPickerOpen
}
duration=
{
200
}
unmountOnExit=
{
true
}
>
<
FadeIn
in=
{
isVizPickerOpen
}
duration=
{
200
}
unmountOnExit=
{
true
}
>
<
VizTypePicker
<
VizTypePicker
...
...
public/app/features/panel/metrics_tab.ts
deleted
100644 → 0
View file @
6f6c4652
// Services & utils
import
coreModule
from
'app/core/core_module'
;
import
{
Emitter
}
from
'app/core/utils/emitter'
;
// Types
import
{
DashboardModel
}
from
'../dashboard/dashboard_model'
;
import
{
PanelModel
}
from
'../dashboard/panel_model'
;
import
{
DataQuery
}
from
'app/types'
;
export
interface
AngularQueryComponentScope
{
panel
:
PanelModel
;
dashboard
:
DashboardModel
;
events
:
Emitter
;
refresh
:
()
=>
void
;
render
:
()
=>
void
;
removeQuery
:
(
query
:
DataQuery
)
=>
void
;
addQuery
:
(
query
?:
DataQuery
)
=>
void
;
moveQuery
:
(
query
:
DataQuery
,
direction
:
number
)
=>
void
;
}
/** @ngInject */
export
function
metricsTabDirective
()
{
'use strict'
;
return
{
restrict
:
'E'
,
scope
:
true
,
templateUrl
:
'public/app/features/panel/partials/metrics_tab.html'
,
};
}
coreModule
.
directive
(
'metricsTab'
,
metricsTabDirective
);
public/app/features/panel/partials/metrics_tab.html
deleted
100644 → 0
View file @
6f6c4652
<div
ng-repeat=
"target in ctrl.panel.targets"
ng-class=
"{'gf-form-disabled': target.hide}"
>
<rebuild-on-change
property=
"ctrl.panel.datasource || target.datasource"
show-null=
"true"
>
<plugin-component
type=
"query-ctrl"
>
</plugin-component>
</rebuild-on-change>
</div>
<!-- <div class="gf-form-query"> -->
<!-- <div class="gf-form gf-form-query-letter-cell"> -->
<!-- <label class="gf-form-label"> -->
<!-- <span class="gf-form-query-letter-cell-carret"> -->
<!-- <i class="fa fa-caret-down"></i> -->
<!-- </span> -->
<!-- <span class="gf-form-query-letter-cell-letter">{{ctrl.nextRefId}}</span> -->
<!-- </label> -->
<!-- <button class="btn btn-secondary gf-form-btn" ng-click="ctrl.addQuery()" ng-hide="ctrl.datasourceInstance.meta.mixed"> -->
<!-- Add Query -->
<!-- </button> -->
<!-- <div class="dropdown" ng-if="ctrl.datasourceInstance.meta.mixed"> -->
<!-- <gf-form-dropdown model="ctrl.addQueryDropdown" get-options="ctrl.getOptions(false)" on-change="ctrl.addMixedQuery($option)"> -->
<!-- </gf-form-dropdown> -->
<!-- </div> -->
<!-- </div> -->
<!-- </div> -->
public/app/features/panel/partials/query_editor_row.html
View file @
dfe1b20f
<div
class=
"gf-form-query"
>
<div
ng-transclude
class=
"gf-form-query-content"
></div>
<div
ng-if=
"!ctrl.hideEditorRowActions"
class=
"gf-form gf-form-query-letter-cell"
>
<label
class=
"gf-form-label"
>
<a
class=
"pointer"
tabindex=
"1"
ng-click=
"ctrl.toggleCollapse()"
>
<span
ng-class=
"{muted: !ctrl.canCollapse}"
class=
"gf-form-query-letter-cell-carret"
>
<i
class=
"fa fa-caret-down"
ng-hide=
"ctrl.collapsed"
></i>
<i
class=
"fa fa-caret-right"
ng-show=
"ctrl.collapsed"
></i>
</span>
<span
class=
"gf-form-query-letter-cell-letter"
>
{{ ctrl.target.refId }}
</span>
<em
class=
"gf-form-query-letter-cell-ds"
ng-show=
"ctrl.target.datasource"
>
({{ ctrl.target.datasource }})
</em>
</a>
</label>
</div>
<div
class=
"gf-form-query-content gf-form-query-content--collapsed"
ng-if=
"ctrl.collapsed"
>
<div
class=
"gf-form"
>
<label
class=
"gf-form-label pointer gf-form-label--grow"
ng-click=
"ctrl.toggleCollapse()"
>
{{ ctrl.collapsedText }}
</label>
</div>
</div>
<div
ng-transclude
class=
"gf-form-query-content"
ng-if=
"!ctrl.collapsed"
></div>
<div
ng-if=
"!ctrl.hideEditorRowActions"
class=
"gf-form"
>
<label
class=
"gf-form-label dropdown"
>
<a
class=
"pointer dropdown-toggle"
data-toggle=
"dropdown"
tabindex=
"1"
>
<i
class=
"fa fa-bars"
></i>
</a>
<ul
class=
"dropdown-menu pull-right"
role=
"menu"
>
<li
role=
"menuitem"
ng-if=
"ctrl.hasTextEditMode"
>
<a
tabindex=
"1"
ng-click=
"ctrl.toggleEditorMode()"
>
Toggle Edit Mode
</a>
</li>
<li
role=
"menuitem"
><a
tabindex=
"1"
ng-click=
"ctrl.duplicateQuery()"
>
Duplicate
</a></li>
<li
role=
"menuitem"
><a
tabindex=
"1"
ng-click=
"ctrl.moveQuery(-1)"
>
Move up
</a></li>
<li
role=
"menuitem"
><a
tabindex=
"1"
ng-click=
"ctrl.moveQuery(1)"
>
Move down
</a></li>
</ul>
</label>
<label
class=
"gf-form-label"
>
<a
ng-click=
"ctrl.toggleHideQuery()"
role=
"menuitem"
>
<i
class=
"fa fa-eye"
></i>
</a>
</label>
<label
class=
"gf-form-label"
>
<a
class=
"pointer"
tabindex=
"1"
ng-click=
"ctrl.removeQuery(ctrl.target)"
>
<i
class=
"fa fa-trash"
></i>
</a>
</label>
</div>
</div>
public/app/features/panel/query_editor_row.ts
View file @
dfe1b20f
...
@@ -3,89 +3,26 @@ import angular from 'angular';
...
@@ -3,89 +3,26 @@ import angular from 'angular';
const
module
=
angular
.
module
(
'grafana.directives'
);
const
module
=
angular
.
module
(
'grafana.directives'
);
export
class
QueryRowCtrl
{
export
class
QueryRowCtrl
{
collapsedText
:
string
;
canCollapse
:
boolean
;
getCollapsedText
:
any
;
target
:
any
;
target
:
any
;
queryCtrl
:
any
;
queryCtrl
:
any
;
panelCtrl
:
any
;
panelCtrl
:
any
;
panel
:
any
;
panel
:
any
;
collapsed
:
any
;
hasTextEditMode
:
boolean
;
hideEditorRowActions
:
boolean
;
constructor
()
{
constructor
()
{
this
.
panelCtrl
=
this
.
queryCtrl
.
panelCtrl
;
this
.
panelCtrl
=
this
.
queryCtrl
.
panelCtrl
;
this
.
target
=
this
.
queryCtrl
.
target
;
this
.
target
=
this
.
queryCtrl
.
target
;
this
.
panel
=
this
.
panelCtrl
.
panel
;
this
.
panel
=
this
.
panelCtrl
.
panel
;
this
.
hideEditorRowActions
=
this
.
panelCtrl
.
hideEditorRowActions
;
if
(
!
this
.
target
.
refId
)
{
if
(
this
.
hasTextEditMode
)
{
this
.
target
.
refId
=
this
.
panel
.
getNextQueryLetter
();
// expose this function to react parent component
this
.
panelCtrl
.
toggleEditorMode
=
this
.
queryCtrl
.
toggleEditorMode
.
bind
(
this
.
queryCtrl
);
}
}
this
.
toggleCollapse
(
true
);
if
(
this
.
queryCtrl
.
getCollapsedText
)
{
if
(
this
.
target
.
isNew
)
{
// expose this function to react parent component
delete
this
.
target
.
isNew
;
this
.
panelCtrl
.
getCollapsedText
=
this
.
queryCtrl
.
getCollapsedText
.
bind
(
this
.
queryCtrl
);
this
.
toggleCollapse
(
false
);
}
}
if
(
this
.
panel
.
targets
.
length
<
4
)
{
this
.
collapsed
=
false
;
}
}
toggleHideQuery
()
{
this
.
target
.
hide
=
!
this
.
target
.
hide
;
this
.
panelCtrl
.
refresh
();
}
toggleCollapse
(
init
)
{
if
(
!
this
.
canCollapse
)
{
return
;
}
if
(
!
this
.
panelCtrl
.
__collapsedQueryCache
)
{
this
.
panelCtrl
.
__collapsedQueryCache
=
{};
}
if
(
init
)
{
this
.
collapsed
=
this
.
panelCtrl
.
__collapsedQueryCache
[
this
.
target
.
refId
]
!==
false
;
}
else
{
this
.
collapsed
=
!
this
.
collapsed
;
this
.
panelCtrl
.
__collapsedQueryCache
[
this
.
target
.
refId
]
=
this
.
collapsed
;
}
try
{
this
.
collapsedText
=
this
.
queryCtrl
.
getCollapsedText
();
}
catch
(
e
)
{
const
err
=
e
.
message
||
e
.
toString
();
this
.
collapsedText
=
'Error: '
+
err
;
}
}
toggleEditorMode
()
{
if
(
this
.
canCollapse
&&
this
.
collapsed
)
{
this
.
collapsed
=
false
;
}
this
.
queryCtrl
.
toggleEditorMode
();
}
removeQuery
()
{
if
(
this
.
panelCtrl
.
__collapsedQueryCache
)
{
delete
this
.
panelCtrl
.
__collapsedQueryCache
[
this
.
target
.
refId
];
}
this
.
panelCtrl
.
removeQuery
(
this
.
target
);
}
duplicateQuery
()
{
const
clone
=
angular
.
copy
(
this
.
target
);
this
.
panelCtrl
.
addQuery
(
clone
);
}
moveQuery
(
direction
)
{
this
.
panelCtrl
.
moveQuery
(
this
.
target
,
direction
);
}
}
}
}
...
...
public/app/features/plugins/plugin_component.ts
View file @
dfe1b20f
...
@@ -105,23 +105,17 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $
...
@@ -105,23 +105,17 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $
switch
(
attrs
.
type
)
{
switch
(
attrs
.
type
)
{
// QueryCtrl
// QueryCtrl
case
'query-ctrl'
:
{
case
'query-ctrl'
:
{
const
datasource
=
scope
.
target
.
datasource
||
scope
.
ctrl
.
panel
.
datasource
;
const
ds
=
scope
.
ctrl
.
datasource
;
return
datasourceSrv
.
get
(
datasource
).
then
(
ds
=>
{
return
$q
.
when
({
scope
.
datasource
=
ds
;
return
importPluginModule
(
ds
.
meta
.
module
).
then
(
dsModule
=>
{
return
{
baseUrl
:
ds
.
meta
.
baseUrl
,
baseUrl
:
ds
.
meta
.
baseUrl
,
name
:
'query-ctrl-'
+
ds
.
meta
.
id
,
name
:
'query-ctrl-'
+
ds
.
meta
.
id
,
bindings
:
{
target
:
'='
,
panelCtrl
:
'='
,
datasource
:
'='
},
bindings
:
{
target
:
'='
,
panelCtrl
:
'='
,
datasource
:
'='
},
attrs
:
{
attrs
:
{
target
:
'
target'
,
target
:
'ctrl.
target'
,
'panel-ctrl'
:
'ctrl'
,
'panel-ctrl'
:
'ctrl'
,
datasource
:
'
datasource'
,
datasource
:
'ctrl.
datasource'
,
},
},
Component
:
dsModule
.
QueryCtrl
,
Component
:
ds
.
pluginExports
.
QueryCtrl
,
};
});
});
});
}
}
// Annotations
// Annotations
...
...
public/app/plugins/datasource/graphite/query_ctrl.ts
View file @
dfe1b20f
...
@@ -391,6 +391,10 @@ export class GraphiteQueryCtrl extends QueryCtrl {
...
@@ -391,6 +391,10 @@ export class GraphiteQueryCtrl extends QueryCtrl {
this
.
paused
=
false
;
this
.
paused
=
false
;
this
.
panelCtrl
.
refresh
();
this
.
panelCtrl
.
refresh
();
}
}
getCollapsedText
()
{
return
this
.
target
.
target
;
}
}
}
function
mapToDropdownOptions
(
results
)
{
function
mapToDropdownOptions
(
results
)
{
...
...
public/app/plugins/datasource/postgres/partials/query.editor.html
View file @
dfe1b20f
public/app/types/plugins.ts
View file @
dfe1b20f
...
@@ -4,6 +4,7 @@ import { PanelProps, PanelOptionsProps } from '@grafana/ui';
...
@@ -4,6 +4,7 @@ import { PanelProps, PanelOptionsProps } from '@grafana/ui';
export
interface
PluginExports
{
export
interface
PluginExports
{
Datasource
?:
any
;
Datasource
?:
any
;
QueryCtrl
?:
any
;
QueryCtrl
?:
any
;
QueryEditor
?:
any
;
ConfigCtrl
?:
any
;
ConfigCtrl
?:
any
;
AnnotationsQueryCtrl
?:
any
;
AnnotationsQueryCtrl
?:
any
;
VariableQueryEditor
?:
any
;
VariableQueryEditor
?:
any
;
...
...
public/app/types/series.ts
View file @
dfe1b20f
import
{
PluginMeta
}
from
'./plugins'
;
import
{
PluginMeta
,
PluginExports
}
from
'./plugins'
;
import
{
TimeSeries
,
TimeRange
,
RawTimeRange
}
from
'@grafana/ui'
;
import
{
TimeSeries
,
TimeRange
,
RawTimeRange
}
from
'@grafana/ui'
;
export
interface
DataQueryResponse
{
export
interface
DataQueryResponse
{
...
@@ -25,6 +25,10 @@ export interface DataQueryOptions {
...
@@ -25,6 +25,10 @@ export interface DataQueryOptions {
}
}
export
interface
DataSourceApi
{
export
interface
DataSourceApi
{
name
:
string
;
meta
:
PluginMeta
;
pluginExports
:
PluginExports
;
/**
/**
* min interval range
* min interval range
*/
*/
...
...
public/sass/components/_panel_editor.scss
View file @
dfe1b20f
...
@@ -35,6 +35,7 @@
...
@@ -35,6 +35,7 @@
flex-grow
:
1
;
flex-grow
:
1
;
background
:
$input-bg
;
background
:
$input-bg
;
margin
:
0
20px
0
84px
;
margin
:
0
20px
0
84px
;
width
:
calc
(
100%
-
84px
);
border-radius
:
3px
;
border-radius
:
3px
;
box-shadow
:
$panel-editor-shadow
;
box-shadow
:
$panel-editor-shadow
;
min-height
:
0
;
min-height
:
0
;
...
...
public/sass/components/_query_editor.scss
View file @
dfe1b20f
...
@@ -3,12 +3,6 @@
...
@@ -3,12 +3,6 @@
color
:
$blue
;
color
:
$blue
;
}
}
.gf-form-disabled
{
.query-keyword
{
color
:
darken
(
$blue
,
20%
);
}
}
.query-segment-operator
{
.query-segment-operator
{
color
:
$orange
;
color
:
$orange
;
}
}
...
@@ -18,12 +12,6 @@
...
@@ -18,12 +12,6 @@
}
}
.gf-form-query
{
.gf-form-query
{
display
:
flex
;
flex-direction
:
row
;
flex-wrap
:
nowrap
;
align-content
:
flex-start
;
align-items
:
flex-start
;
.gf-form
,
.gf-form
,
.gf-form-filler
{
.gf-form-filler
{
margin-bottom
:
2px
;
margin-bottom
:
2px
;
...
@@ -188,3 +176,98 @@ input[type='text'].tight-form-func-param {
...
@@ -188,3 +176,98 @@ input[type='text'].tight-form-func-param {
.rst-literal-block
.rst-text
{
.rst-literal-block
.rst-text
{
display
:
block
;
display
:
block
;
}
}
.query-editor-row
{
margin-bottom
:
2px
;
&
:hover
{
.query-editor-row__actions
{
display
:
flex
;
}
}
&
--disabled
{
.query-keyword
{
color
:
darken
(
$blue
,
20%
);
}
}
}
.query-editor-row__header
{
display
:
flex
;
padding
:
4px
0px
4px
8px
;
position
:
relative
;
height
:
35px
;
background
:
$page-bg
;
flex-wrap
:
nowrap
;
align-items
:
center
;
}
.query-editor-row__ref-id
{
font-weight
:
$font-weight-semi-bold
;
color
:
$blue
;
font-size
:
$font-size-md
;
cursor
:
pointer
;
display
:
flex
;
align-items
:
center
;
i
{
padding-right
:
5px
;
color
:
$text-muted
;
position
:
relative
;
}
}
.query-editor-row__collapsed-text
{
padding
:
0
10px
;
display
:
flex
;
align-items
:
center
;
flex-grow
:
1
;
overflow
:
hidden
;
>
div
{
color
:
$text-muted
;
font-style
:
italic
;
overflow
:
hidden
;
white-space
:
nowrap
;
text-overflow
:
ellipsis
;
font-size
:
$font-size-sm
;
min-width
:
0
;
}
}
.query-editor-row__actions
{
flex-shrink
:
0
;
display
:
flex
;
justify-content
:
flex-end
;
color
:
$text-muted
;
}
.query-editor-row__action
{
margin-left
:
3px
;
background
:
transparent
;
border
:
none
;
box-shadow
:
none
;
&
:hover
{
color
:
$text-color
;
}
}
.query-editor-row__body
{
margin
:
0
0
10px
40px
;
background
:
$page-bg
;
&
--collapsed
{
display
:
none
;
}
}
.query-editor-row__context-info
{
font-style
:
italic
;
font-size
:
$font-size-sm
;
color
:
$text-muted
;
padding-left
:
10px
;
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment