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
Hide whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
467 additions
and
291 deletions
+467
-291
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
+54
-85
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
+11
-17
public/app/plugins/datasource/graphite/query_ctrl.ts
+4
-0
public/app/plugins/datasource/postgres/partials/query.editor.html
+3
-3
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
_
from
'lodash'
;
import
Scrollbars
from
'react-custom-scrollbars'
;
interface
Props
{
...
...
@@ -8,6 +9,8 @@ interface Props {
autoHideDuration
?:
number
;
autoMaxHeight
?:
string
;
hideTracksWhenNotNeeded
?:
boolean
;
scrollTop
?:
number
;
setScrollTop
:
(
value
:
React
.
MouseEvent
<
HTMLElement
>
)
=>
void
;
autoHeightMin
?:
number
|
string
;
}
...
...
@@ -22,14 +25,44 @@ export class CustomScrollbar extends PureComponent<Props> {
autoHideDuration
:
200
,
autoMaxHeight
:
'100%'
,
hideTracksWhenNotNeeded
:
false
,
scrollTop
:
0
,
setScrollTop
:
()
=>
{},
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
()
{
const
{
customClassName
,
children
,
autoMaxHeight
}
=
this
.
props
;
return
(
<
Scrollbars
ref=
{
this
.
ref
}
className=
{
customClassName
}
autoHeight=
{
true
}
// 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 {
heading
:
string
;
renderToolbar
?:
()
=>
JSX
.
Element
;
toolbarItems
?:
EditorToolbarView
[];
scrollTop
?:
number
;
setScrollTop
?:
(
value
:
React
.
MouseEvent
<
HTMLElement
>
)
=>
void
;
}
export
interface
EditorToolbarView
{
...
...
@@ -103,7 +105,7 @@ export class EditorTabBody extends PureComponent<Props, State> {
}
render
()
{
const
{
children
,
renderToolbar
,
heading
,
toolbarItems
}
=
this
.
props
;
const
{
children
,
renderToolbar
,
heading
,
toolbarItems
,
scrollTop
,
setScrollTop
}
=
this
.
props
;
const
{
openView
,
fadeIn
,
isOpen
}
=
this
.
state
;
return
(
...
...
@@ -119,7 +121,7 @@ export class EditorTabBody extends PureComponent<Props, State> {
)
}
</
div
>
<
div
className=
"panel-editor__scroll"
>
<
CustomScrollbar
autoHide=
{
false
}
>
<
CustomScrollbar
autoHide=
{
false
}
scrollTop=
{
scrollTop
}
setScrollTop=
{
setScrollTop
}
>
<
div
className=
"panel-editor__content"
>
<
FadeIn
in=
{
isOpen
}
duration=
{
200
}
unmountOnExit=
{
true
}
>
{
openView
&&
this
.
renderOpenView
(
openView
)
}
...
...
public/app/features/dashboard/panel_editor/QueriesTab.tsx
View file @
dfe1b20f
...
...
@@ -3,18 +3,16 @@ import React, { PureComponent } from 'react';
import
_
from
'lodash'
;
// Components
import
'app/features/panel/metrics_tab'
;
import
{
EditorTabBody
,
EditorToolbarView
}
from
'./EditorTabBody'
;
import
{
DataSourcePicker
}
from
'app/core/components/Select/DataSourcePicker'
;
import
{
QueryInspector
}
from
'./QueryInspector'
;
import
{
QueryOptions
}
from
'./QueryOptions'
;
import
{
AngularQueryComponentScope
}
from
'app/features/panel/metrics_tab'
;
import
{
PanelOptionsGroup
}
from
'@grafana/ui'
;
import
{
QueryEditorRow
}
from
'./QueryEditorRow'
;
// Services
import
{
getDatasourceSrv
}
from
'app/features/plugins/datasource_srv'
;
import
{
BackendSrv
,
getBackendSrv
}
from
'app/core/services/backend_srv'
;
import
{
AngularComponent
,
getAngularLoader
}
from
'app/core/services/AngularLoader'
;
import
config
from
'app/core/config'
;
// Types
...
...
@@ -34,66 +32,27 @@ interface State {
isLoadingHelp
:
boolean
;
isPickerOpen
:
boolean
;
isAddingMixed
:
boolean
;
scrollTop
:
number
;
}
export
class
QueriesTab
extends
PureComponent
<
Props
,
State
>
{
element
:
HTMLElement
;
component
:
AngularComponent
;
datasources
:
DataSourceSelectItem
[]
=
getDatasourceSrv
().
getMetricSources
();
backendSrv
:
BackendSrv
=
getBackendSrv
();
constructor
(
props
)
{
super
(
props
);
this
.
state
=
{
isLoadingHelp
:
false
,
currentDS
:
this
.
findCurrentDataSource
(),
helpContent
:
null
,
isPickerOpen
:
false
,
isAddingMixed
:
false
,
};
}
state
:
State
=
{
isLoadingHelp
:
false
,
currentDS
:
this
.
findCurrentDataSource
(),
helpContent
:
null
,
isPickerOpen
:
false
,
isAddingMixed
:
false
,
scrollTop
:
0
,
};
findCurrentDataSource
():
DataSourceSelectItem
{
const
{
panel
}
=
this
.
props
;
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
=>
{
const
{
panel
}
=
this
.
props
;
const
{
currentDS
}
=
this
.
state
;
...
...
@@ -137,7 +96,7 @@ export class QueriesTab extends PureComponent<Props, State> {
onAddQuery
=
(
query
?:
Partial
<
DataQuery
>
)
=>
{
this
.
props
.
panel
.
addQuery
(
query
);
this
.
forceUpdate
(
);
this
.
setState
({
scrollTop
:
this
.
state
.
scrollTop
+
100000
}
);
};
onAddQueryClick
=
()
=>
{
...
...
@@ -146,9 +105,7 @@ export class QueriesTab extends PureComponent<Props, State> {
return
;
}
this
.
props
.
panel
.
addQuery
();
this
.
component
.
digest
();
this
.
forceUpdate
();
this
.
onAddQuery
();
};
onRemoveQuery
=
(
query
:
DataQuery
)
=>
{
...
...
@@ -171,9 +128,21 @@ export class QueriesTab extends PureComponent<Props, State> {
};
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
=
()
=>
{
...
...
@@ -190,17 +159,21 @@ export class QueriesTab extends PureComponent<Props, State> {
onAddMixedQuery
=
datasource
=>
{
this
.
onAddQuery
({
datasource
:
datasource
.
name
});
this
.
component
.
digest
();
this
.
setState
({
isAddingMixed
:
false
});
this
.
setState
({
isAddingMixed
:
false
,
scrollTop
:
this
.
state
.
scrollTop
+
10000
});
};
onMixedPickerBlur
=
()
=>
{
this
.
setState
({
isAddingMixed
:
false
});
};
setScrollTop
=
(
event
:
React
.
MouseEvent
<
HTMLElement
>
)
=>
{
const
target
=
event
.
target
as
HTMLElement
;
this
.
setState
({
scrollTop
:
target
.
scrollTop
});
};
render
()
{
const
{
panel
}
=
this
.
props
;
const
{
currentDS
,
isAddingMixed
}
=
this
.
state
;
const
{
currentDS
,
scrollTop
}
=
this
.
state
;
const
queryInspector
:
EditorToolbarView
=
{
title
:
'Query Inspector'
,
...
...
@@ -214,32 +187,28 @@ export class QueriesTab extends PureComponent<Props, State> {
};
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
ref=
{
element
=>
(
this
.
element
=
element
)
}
/>
<
div
className=
"gf-form-query"
>
<
div
className=
"gf-form gf-form-query-letter-cell"
>
<
label
className=
"gf-form-label"
>
<
span
className=
"gf-form-query-letter-cell-carret muted"
>
<
i
className=
"fa fa-caret-down"
/>
</
span
>
{
' '
}
<
span
className=
"gf-form-query-letter-cell-letter"
>
{
panel
.
getNextQueryLetter
()
}
</
span
>
</
label
>
</
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
>
</
PanelOptionsGroup
>
<
div
className=
"query-editor-rows"
>
{
panel
.
targets
.
map
((
query
,
index
)
=>
(
<
QueryEditorRow
datasourceName=
{
query
.
datasource
||
panel
.
datasource
}
key=
{
query
.
refId
}
panel=
{
panel
}
query=
{
query
}
onRemoveQuery=
{
this
.
onRemoveQuery
}
onAddQuery=
{
this
.
onAddQuery
}
onMoveQuery=
{
this
.
onMoveQuery
}
inMixedMode=
{
currentDS
.
meta
.
mixed
}
/>
))
}
</
div
>
<
PanelOptionsGroup
>
<
QueryOptions
panel=
{
panel
}
datasource=
{
currentDS
}
/>
</
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 {
interface
State
{
isVizPickerOpen
:
boolean
;
searchQuery
:
string
;
scrollTop
:
number
;
}
export
class
VisualizationTab
extends
PureComponent
<
Props
,
State
>
{
...
...
@@ -39,6 +40,7 @@ export class VisualizationTab extends PureComponent<Props, State> {
this
.
state
=
{
isVizPickerOpen
:
false
,
searchQuery
:
''
,
scrollTop
:
0
,
};
}
...
...
@@ -143,7 +145,7 @@ export class VisualizationTab extends PureComponent<Props, State> {
};
onOpenVizPicker
=
()
=>
{
this
.
setState
({
isVizPickerOpen
:
true
});
this
.
setState
({
isVizPickerOpen
:
true
,
scrollTop
:
0
});
};
onCloseVizPicker
=
()
=>
{
...
...
@@ -201,9 +203,14 @@ export class VisualizationTab extends PureComponent<Props, State> {
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
()
{
const
{
plugin
}
=
this
.
props
;
const
{
isVizPickerOpen
,
searchQuery
}
=
this
.
state
;
const
{
isVizPickerOpen
,
searchQuery
,
scrollTop
}
=
this
.
state
;
const
pluginHelp
:
EditorToolbarView
=
{
heading
:
'Help'
,
...
...
@@ -212,7 +219,8 @@ export class VisualizationTab extends PureComponent<Props, State> {
};
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
}
>
<
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-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
ng-transclude
class=
"gf-form-query-content"
></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';
const
module
=
angular
.
module
(
'grafana.directives'
);
export
class
QueryRowCtrl
{
collapsedText
:
string
;
canCollapse
:
boolean
;
getCollapsedText
:
any
;
target
:
any
;
queryCtrl
:
any
;
panelCtrl
:
any
;
panel
:
any
;
collapsed
:
any
;
hideEditorRowActions
:
boolean
;
hasTextEditMode
:
boolean
;
constructor
()
{
this
.
panelCtrl
=
this
.
queryCtrl
.
panelCtrl
;
this
.
target
=
this
.
queryCtrl
.
target
;
this
.
panel
=
this
.
panelCtrl
.
panel
;
this
.
hideEditorRowActions
=
this
.
panelCtrl
.
hideEditorRowActions
;
if
(
!
this
.
target
.
refId
)
{
this
.
target
.
refId
=
this
.
panel
.
getNextQueryLetter
();
if
(
this
.
hasTextEditMode
)
{
// expose this function to react parent component
this
.
panelCtrl
.
toggleEditorMode
=
this
.
queryCtrl
.
toggleEditorMode
.
bind
(
this
.
queryCtrl
);
}
this
.
toggleCollapse
(
true
);
if
(
this
.
target
.
isNew
)
{
delete
this
.
target
.
isNew
;
this
.
toggleCollapse
(
false
);
if
(
this
.
queryCtrl
.
getCollapsedText
)
{
// expose this function to react parent component
this
.
panelCtrl
.
getCollapsedText
=
this
.
queryCtrl
.
getCollapsedText
.
bind
(
this
.
queryCtrl
);
}
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, $
switch
(
attrs
.
type
)
{
// QueryCtrl
case
'query-ctrl'
:
{
const
datasource
=
scope
.
target
.
datasource
||
scope
.
ctrl
.
panel
.
datasource
;
return
datasourceSrv
.
get
(
datasource
).
then
(
ds
=>
{
scope
.
datasource
=
ds
;
return
importPluginModule
(
ds
.
meta
.
module
).
then
(
dsModule
=>
{
return
{
baseUrl
:
ds
.
meta
.
baseUrl
,
name
:
'query-ctrl-'
+
ds
.
meta
.
id
,
bindings
:
{
target
:
'='
,
panelCtrl
:
'='
,
datasource
:
'='
},
attrs
:
{
target
:
'target'
,
'panel-ctrl'
:
'ctrl'
,
datasource
:
'datasource'
,
},
Component
:
dsModule
.
QueryCtrl
,
};
});
const
ds
=
scope
.
ctrl
.
datasource
;
return
$q
.
when
({
baseUrl
:
ds
.
meta
.
baseUrl
,
name
:
'query-ctrl-'
+
ds
.
meta
.
id
,
bindings
:
{
target
:
'='
,
panelCtrl
:
'='
,
datasource
:
'='
},
attrs
:
{
target
:
'ctrl.target'
,
'panel-ctrl'
:
'ctrl'
,
datasource
:
'ctrl.datasource'
,
},
Component
:
ds
.
pluginExports
.
QueryCtrl
,
});
}
// Annotations
...
...
public/app/plugins/datasource/graphite/query_ctrl.ts
View file @
dfe1b20f
...
...
@@ -391,6 +391,10 @@ export class GraphiteQueryCtrl extends QueryCtrl {
this
.
paused
=
false
;
this
.
panelCtrl
.
refresh
();
}
getCollapsedText
()
{
return
this
.
target
.
target
;
}
}
function
mapToDropdownOptions
(
results
)
{
...
...
public/app/plugins/datasource/postgres/partials/query.editor.html
View file @
dfe1b20f
...
...
@@ -138,9 +138,9 @@
<pre
class=
"gf-form-pre alert alert-info"
>
Time series:
- return column named
<i>
time
</i>
(UTC in seconds or timestamp)
- return column(s) with numeric datatype as values
Optional:
- return column named
<i>
metric
</i>
to represent the series name.
- If multiple value columns are returned the metric column is used as prefix.
Optional:
- return column named
<i>
metric
</i>
to represent the series name.
- If multiple value columns are returned the metric column is used as prefix.
- If no column named metric is found the column name of the value column is used as series name
Resultsets of time series queries need to be sorted by time.
...
...
public/app/types/plugins.ts
View file @
dfe1b20f
...
...
@@ -4,6 +4,7 @@ import { PanelProps, PanelOptionsProps } from '@grafana/ui';
export
interface
PluginExports
{
Datasource
?:
any
;
QueryCtrl
?:
any
;
QueryEditor
?:
any
;
ConfigCtrl
?:
any
;
AnnotationsQueryCtrl
?:
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'
;
export
interface
DataQueryResponse
{
...
...
@@ -25,6 +25,10 @@ export interface DataQueryOptions {
}
export
interface
DataSourceApi
{
name
:
string
;
meta
:
PluginMeta
;
pluginExports
:
PluginExports
;
/**
* min interval range
*/
...
...
public/sass/components/_panel_editor.scss
View file @
dfe1b20f
...
...
@@ -35,6 +35,7 @@
flex-grow
:
1
;
background
:
$input-bg
;
margin
:
0
20px
0
84px
;
width
:
calc
(
100%
-
84px
);
border-radius
:
3px
;
box-shadow
:
$panel-editor-shadow
;
min-height
:
0
;
...
...
public/sass/components/_query_editor.scss
View file @
dfe1b20f
...
...
@@ -3,12 +3,6 @@
color
:
$blue
;
}
.gf-form-disabled
{
.query-keyword
{
color
:
darken
(
$blue
,
20%
);
}
}
.query-segment-operator
{
color
:
$orange
;
}
...
...
@@ -18,12 +12,6 @@
}
.gf-form-query
{
display
:
flex
;
flex-direction
:
row
;
flex-wrap
:
nowrap
;
align-content
:
flex-start
;
align-items
:
flex-start
;
.gf-form
,
.gf-form-filler
{
margin-bottom
:
2px
;
...
...
@@ -188,3 +176,98 @@ input[type='text'].tight-form-func-param {
.rst-literal-block
.rst-text
{
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