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
02b12d3a
Unverified
Commit
02b12d3a
authored
Aug 06, 2020
by
kay delaney
Committed by
GitHub
Aug 06, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Explore: Adds query inspector drawer to explore (#26698)
* Explore: Adds query inspector drawer to explore
parent
145d2219
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
472 additions
and
158 deletions
+472
-158
packages/grafana-ui/src/components/TabbedContainer/TabbedContainer.tsx
+84
-0
packages/grafana-ui/src/components/index.ts
+1
-0
public/app/features/dashboard/components/Inspector/InspectContent.tsx
+1
-1
public/app/features/dashboard/components/Inspector/InspectStatsTab.tsx
+6
-6
public/app/features/dashboard/components/Inspector/InspectStatsTable.tsx
+4
-4
public/app/features/explore/Explore.tsx
+30
-4
public/app/features/explore/ExploreDrawer.test.tsx
+11
-0
public/app/features/explore/ExploreDrawer.tsx
+93
-0
public/app/features/explore/ExploreQueryInspector.tsx
+183
-0
public/app/features/explore/RichHistory/RichHistory.test.tsx
+6
-3
public/app/features/explore/RichHistory/RichHistory.tsx
+12
-65
public/app/features/explore/RichHistory/RichHistoryContainer.tsx
+5
-72
public/app/features/explore/SecondaryActions.test.tsx
+20
-2
public/app/features/explore/SecondaryActions.tsx
+14
-1
public/app/features/explore/__snapshots__/Explore.test.tsx.snap
+2
-0
No files found.
packages/grafana-ui/src/components/TabbedContainer/TabbedContainer.tsx
0 → 100644
View file @
02b12d3a
import
React
,
{
useState
}
from
'react'
;
import
{
css
}
from
'emotion'
;
import
{
SelectableValue
,
GrafanaTheme
}
from
'@grafana/data'
;
import
{
stylesFactory
,
useTheme
}
from
'../../themes'
;
import
{
IconName
,
TabsBar
,
Tab
,
IconButton
,
CustomScrollbar
,
TabContent
}
from
'../..'
;
export
interface
TabConfig
{
label
:
string
;
value
:
string
;
content
:
React
.
ReactNode
;
icon
:
IconName
;
}
export
interface
TabbedContainerProps
{
tabs
:
TabConfig
[];
defaultTab
?:
string
;
closeIconTooltip
?:
string
;
onClose
:
()
=>
void
;
}
const
getStyles
=
stylesFactory
((
theme
:
GrafanaTheme
)
=>
{
return
{
container
:
css
`
height: 100%;
`
,
tabContent
:
css
`
padding:
${
theme
.
spacing
.
md
}
;
background-color:
${
theme
.
colors
.
bodyBg
}
;
`
,
close
:
css
`
position: absolute;
right: 16px;
top: 5px;
cursor: pointer;
font-size:
${
theme
.
typography
.
size
.
lg
}
;
`
,
tabs
:
css
`
padding-top:
${
theme
.
spacing
.
sm
}
;
border-color:
${
theme
.
colors
.
formInputBorder
}
;
ul {
margin-left:
${
theme
.
spacing
.
md
}
;
}
`
,
scrollbar
:
css
`
min-height: 100% !important;
background-color:
${
theme
.
colors
.
panelBg
}
;
`
,
};
});
export
function
TabbedContainer
(
props
:
TabbedContainerProps
)
{
const
[
activeTab
,
setActiveTab
]
=
useState
(
props
.
tabs
.
some
(
tab
=>
tab
.
value
===
props
.
defaultTab
)
?
props
.
defaultTab
:
props
.
tabs
?.[
0
].
value
);
const
onSelectTab
=
(
item
:
SelectableValue
<
string
>
)
=>
{
setActiveTab
(
item
.
value
!
);
};
const
{
tabs
,
onClose
,
closeIconTooltip
}
=
props
;
const
theme
=
useTheme
();
const
styles
=
getStyles
(
theme
);
return
(
<
div
className=
{
styles
.
container
}
>
<
TabsBar
className=
{
styles
.
tabs
}
>
{
tabs
.
map
(
t
=>
(
<
Tab
key=
{
t
.
value
}
label=
{
t
.
label
}
active=
{
t
.
value
===
activeTab
}
onChangeTab=
{
()
=>
onSelectTab
(
t
)
}
icon=
{
t
.
icon
}
/>
))
}
<
IconButton
className=
{
styles
.
close
}
onClick=
{
onClose
}
name=
"times"
title=
{
closeIconTooltip
??
'Close'
}
/>
</
TabsBar
>
<
CustomScrollbar
className=
{
styles
.
scrollbar
}
>
<
TabContent
className=
{
styles
.
tabContent
}
>
{
tabs
.
find
(
t
=>
t
.
value
===
activeTab
)?.
content
}
</
TabContent
>
</
CustomScrollbar
>
</
div
>
);
}
packages/grafana-ui/src/components/index.ts
View file @
02b12d3a
...
...
@@ -7,6 +7,7 @@ export { PopoverController } from './Tooltip/PopoverController';
export
{
Popover
}
from
'./Tooltip/Popover'
;
export
{
Portal
}
from
'./Portal/Portal'
;
export
{
CustomScrollbar
}
from
'./CustomScrollbar/CustomScrollbar'
;
export
{
TabbedContainer
,
TabConfig
}
from
'./TabbedContainer/TabbedContainer'
;
export
{
ClipboardButton
}
from
'./ClipboardButton/ClipboardButton'
;
export
{
Cascader
,
CascaderOption
}
from
'./Cascader/Cascader'
;
...
...
public/app/features/dashboard/components/Inspector/InspectContent.tsx
View file @
02b12d3a
...
...
@@ -91,7 +91,7 @@ export const InspectContent: React.FC<Props> = ({
<
InspectJSONTab
panel=
{
panel
}
dashboard=
{
dashboard
}
data=
{
data
}
onClose=
{
onClose
}
/>
)
}
{
activeTab
===
InspectTab
.
Error
&&
<
InspectErrorTab
error=
{
error
}
/>
}
{
data
&&
activeTab
===
InspectTab
.
Stats
&&
<
InspectStatsTab
data=
{
data
}
dashboard=
{
dashboard
}
/>
}
{
data
&&
activeTab
===
InspectTab
.
Stats
&&
<
InspectStatsTab
data=
{
data
}
timeZone=
{
dashboard
.
getTimezone
()
}
/>
}
{
data
&&
activeTab
===
InspectTab
.
Query
&&
<
QueryInspector
panel=
{
panel
}
data=
{
data
.
series
}
/>
}
</
TabContent
>
</
CustomScrollbar
>
...
...
public/app/features/dashboard/components/Inspector/InspectStatsTab.tsx
View file @
02b12d3a
import
{
PanelData
,
QueryResultMetaStat
}
from
'@grafana/data'
;
import
{
PanelData
,
QueryResultMetaStat
,
TimeZone
}
from
'@grafana/data'
;
import
{
selectors
}
from
'@grafana/e2e-selectors'
;
import
{
InspectStatsTable
}
from
'./InspectStatsTable'
;
import
React
from
'react'
;
import
{
DashboardModel
}
from
'app/features/dashboard/state'
;
interface
InspectStatsTabProps
{
data
:
PanelData
;
dashboard
:
DashboardModel
;
timeZone
:
TimeZone
;
}
export
const
InspectStatsTab
:
React
.
FC
<
InspectStatsTabProps
>
=
({
data
,
dashboard
})
=>
{
export
const
InspectStatsTab
:
React
.
FC
<
InspectStatsTabProps
>
=
({
data
,
timeZone
})
=>
{
if
(
!
data
.
request
)
{
return
null
;
}
...
...
@@ -42,8 +42,8 @@ export const InspectStatsTab: React.FC<InspectStatsTabProps> = ({ data, dashboar
return
(
<
div
aria
-
label=
{
selectors
.
components
.
PanelInspector
.
Stats
.
content
}
>
<
InspectStatsTable
dashboard=
{
dashboard
}
name=
{
'Stats'
}
stats=
{
stats
}
/>
<
InspectStatsTable
dashboard=
{
dashboard
}
name=
{
'Data source stats'
}
stats=
{
dataStats
}
/>
<
InspectStatsTable
timeZone=
{
timeZone
}
name=
{
'Stats'
}
stats=
{
stats
}
/>
<
InspectStatsTable
timeZone=
{
timeZone
}
name=
{
'Data source stats'
}
stats=
{
dataStats
}
/>
</
div
>
);
};
public/app/features/dashboard/components/Inspector/InspectStatsTable.tsx
View file @
02b12d3a
...
...
@@ -7,17 +7,17 @@ import {
QueryResultMetaStat
,
TimeZone
,
}
from
'@grafana/data'
;
import
{
DashboardModel
}
from
'app/features/dashboard/state'
;
import
{
config
}
from
'app/core/config'
;
import
{
stylesFactory
,
useTheme
}
from
'@grafana/ui'
;
import
{
css
}
from
'emotion'
;
interface
InspectStatsTableProps
{
dashboard
:
DashboardModel
;
timeZone
:
TimeZone
;
name
:
string
;
stats
:
QueryResultMetaStat
[];
}
export
const
InspectStatsTable
:
React
.
FC
<
InspectStatsTableProps
>
=
({
dashboard
,
name
,
stats
})
=>
{
export
const
InspectStatsTable
:
React
.
FC
<
InspectStatsTableProps
>
=
({
timeZone
,
name
,
stats
})
=>
{
const
theme
=
useTheme
();
const
styles
=
getStyles
(
theme
);
...
...
@@ -34,7 +34,7 @@ export const InspectStatsTable: React.FC<InspectStatsTableProps> = ({ dashboard,
return
(
<
tr
key=
{
`${stat.displayName}-${index}`
}
>
<
td
>
{
stat
.
displayName
}
</
td
>
<
td
className=
{
styles
.
cell
}
>
{
formatStat
(
stat
,
dashboard
.
getTimezone
()
)
}
</
td
>
<
td
className=
{
styles
.
cell
}
>
{
formatStat
(
stat
,
timeZone
)
}
</
td
>
</
tr
>
);
})
}
...
...
public/app/features/explore/Explore.tsx
View file @
02b12d3a
...
...
@@ -28,6 +28,7 @@ import LogsContainer from './LogsContainer';
import
QueryRows
from
'./QueryRows'
;
import
TableContainer
from
'./TableContainer'
;
import
RichHistoryContainer
from
'./RichHistory/RichHistoryContainer'
;
import
ExploreQueryInspector
from
'./ExploreQueryInspector'
;
import
{
addQueryRow
,
changeSize
,
...
...
@@ -128,8 +129,13 @@ export interface ExploreProps {
showTrace
:
boolean
;
}
enum
ExploreDrawer
{
RichHistory
,
QueryInspector
,
}
interface
ExploreState
{
showRichHistory
:
boolean
;
openDrawer
?:
ExploreDrawer
;
}
/**
...
...
@@ -164,7 +170,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
super
(
props
);
this
.
exploreEvents
=
new
Emitter
();
this
.
state
=
{
showRichHistory
:
false
,
openDrawer
:
undefined
,
};
}
...
...
@@ -276,7 +282,15 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
toggleShowRichHistory
=
()
=>
{
this
.
setState
(
state
=>
{
return
{
showRichHistory
:
!
state
.
showRichHistory
,
openDrawer
:
state
.
openDrawer
===
ExploreDrawer
.
RichHistory
?
undefined
:
ExploreDrawer
.
RichHistory
,
};
});
};
toggleShowQueryInspector
=
()
=>
{
this
.
setState
(
state
=>
{
return
{
openDrawer
:
state
.
openDrawer
===
ExploreDrawer
.
QueryInspector
?
undefined
:
ExploreDrawer
.
QueryInspector
,
};
});
};
...
...
@@ -319,7 +333,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
showLogs
,
showTrace
,
}
=
this
.
props
;
const
{
showRichHistory
}
=
this
.
state
;
const
{
openDrawer
}
=
this
.
state
;
const
exploreClass
=
split
?
'explore explore-split'
:
'explore'
;
const
styles
=
getStyles
(
theme
);
const
StartPage
=
datasourceInstance
?.
components
?.
ExploreStartPage
;
...
...
@@ -329,6 +343,9 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
const
queryErrors
=
queryResponse
.
error
?
[
queryResponse
.
error
]
:
undefined
;
const
queryError
=
getFirstNonQueryRowSpecificError
(
queryErrors
);
const
showRichHistory
=
openDrawer
===
ExploreDrawer
.
RichHistory
;
const
showQueryInspector
=
openDrawer
===
ExploreDrawer
.
QueryInspector
;
return
(
<
div
className=
{
exploreClass
}
ref=
{
this
.
getRef
}
aria
-
label=
{
selectors
.
pages
.
Explore
.
General
.
container
}
>
<
ExploreToolbar
exploreId=
{
exploreId
}
onChangeTime=
{
this
.
onChangeTime
}
/>
...
...
@@ -343,8 +360,10 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
//TODO:unification
addQueryRowButtonHidden=
{
false
}
richHistoryButtonActive=
{
showRichHistory
}
queryInspectorButtonActive=
{
showQueryInspector
}
onClickAddQueryRowButton=
{
this
.
onClickAddQueryRowButton
}
onClickRichHistoryButton=
{
this
.
toggleShowRichHistory
}
onClickQueryInspectorButton=
{
this
.
toggleShowQueryInspector
}
/>
</
div
>
<
ErrorContainer
queryError=
{
queryError
}
/>
...
...
@@ -421,6 +440,13 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
onClose=
{
this
.
toggleShowRichHistory
}
/>
)
}
{
showQueryInspector
&&
(
<
ExploreQueryInspector
exploreId=
{
exploreId
}
width=
{
width
}
onClose=
{
this
.
toggleShowQueryInspector
}
/>
)
}
</
ErrorBoundaryAlert
>
</
main
>
);
...
...
public/app/features/explore/ExploreDrawer.test.tsx
0 → 100644
View file @
02b12d3a
import
React
from
'react'
;
import
{
mount
}
from
'enzyme'
;
import
{
ExploreDrawer
}
from
'./ExploreDrawer'
;
describe
(
'<ExploreDrawer />'
,
()
=>
{
it
(
'renders child element'
,
()
=>
{
const
childElement
=
<
div
>
Child element
</
div
>;
const
wrapper
=
mount
(<
ExploreDrawer
width=
{
400
}
>
{
childElement
}
</
ExploreDrawer
>);
expect
(
wrapper
.
text
()).
toBe
(
'Child element'
);
});
});
public/app/features/explore/ExploreDrawer.tsx
0 → 100644
View file @
02b12d3a
// Libraries
import
React
from
'react'
;
import
{
Resizable
,
ResizeCallback
}
from
're-resizable'
;
import
{
css
,
cx
,
keyframes
}
from
'emotion'
;
// Services & Utils
import
{
stylesFactory
,
useTheme
}
from
'@grafana/ui'
;
// Types
import
{
GrafanaTheme
}
from
'@grafana/data'
;
const
drawerSlide
=
keyframes
`
0% {
transform: translateY(400px);
}
100% {
transform: translateY(0px);
}
`
;
const
getStyles
=
stylesFactory
((
theme
:
GrafanaTheme
)
=>
{
const
shadowColor
=
theme
.
isLight
?
theme
.
palette
.
gray4
:
theme
.
palette
.
black
;
return
{
container
:
css
`
position: fixed !important;
bottom: 0;
background:
${
theme
.
colors
.
pageHeaderBg
}
;
border-top: 1px solid
${
theme
.
colors
.
formInputBorder
}
;
margin: 0px;
margin-right: -
${
theme
.
spacing
.
md
}
;
margin-left: -
${
theme
.
spacing
.
md
}
;
box-shadow: 0 0 4px
${
shadowColor
}
;
z-index:
${
theme
.
zIndex
.
sidemenu
}
;
`
,
drawerActive
:
css
`
opacity: 1;
animation: 0.5s ease-out
${
drawerSlide
}
;
`
,
rzHandle
:
css
`
background:
${
theme
.
colors
.
formInputBorder
}
;
transition: 0.3s background ease-in-out;
position: relative;
width: 200px !important;
height: 7px !important;
left: calc(50% - 100px) !important;
top: -4px !important;
cursor: grab;
border-radius: 4px;
&:hover {
background:
${
theme
.
colors
.
formInputBorderHover
}
;
}
`
,
};
});
export
interface
Props
{
width
:
number
;
children
:
React
.
ReactNode
;
onResize
?:
ResizeCallback
;
}
export
function
ExploreDrawer
(
props
:
Props
)
{
const
{
width
,
children
,
onResize
}
=
props
;
const
theme
=
useTheme
();
const
styles
=
getStyles
(
theme
);
const
drawerWidth
=
`
${
width
+
31.5
}
px`
;
return
(
<
Resizable
className=
{
cx
(
styles
.
container
,
styles
.
drawerActive
)
}
defaultSize=
{
{
width
:
drawerWidth
,
height
:
'400px'
}
}
handleClasses=
{
{
top
:
styles
.
rzHandle
}
}
enable=
{
{
top
:
true
,
right
:
false
,
bottom
:
false
,
left
:
false
,
topRight
:
false
,
bottomRight
:
false
,
bottomLeft
:
false
,
topLeft
:
false
,
}
}
maxHeight=
"100vh"
maxWidth=
{
drawerWidth
}
minWidth=
{
drawerWidth
}
onResize=
{
onResize
}
>
{
children
}
</
Resizable
>
);
}
public/app/features/explore/ExploreQueryInspector.tsx
0 → 100644
View file @
02b12d3a
import
React
,
{
useState
}
from
'react'
;
import
{
Button
,
JSONFormatter
,
LoadingPlaceholder
,
TabbedContainer
,
TabConfig
}
from
'@grafana/ui'
;
import
{
AppEvents
,
PanelData
,
TimeZone
}
from
'@grafana/data'
;
import
appEvents
from
'app/core/app_events'
;
import
{
CopyToClipboard
}
from
'app/core/components/CopyToClipboard/CopyToClipboard'
;
import
{
StoreState
,
ExploreItemState
,
ExploreId
}
from
'app/types'
;
import
{
hot
}
from
'react-hot-loader'
;
import
{
connect
}
from
'react-redux'
;
import
{
ExploreDrawer
}
from
'app/features/explore/ExploreDrawer'
;
import
{
useEffectOnce
}
from
'react-use'
;
import
{
getBackendSrv
}
from
'app/core/services/backend_srv'
;
import
{
InspectStatsTab
}
from
'../dashboard/components/Inspector/InspectStatsTab'
;
import
{
getPanelInspectorStyles
}
from
'../dashboard/components/Inspector/styles'
;
function
stripPropsFromResponse
(
response
:
any
)
{
// ignore silent requests
if
(
response
.
config
?.
silent
)
{
return
{};
}
const
clonedResponse
=
{
...
response
};
// clone - dont modify the response
if
(
clonedResponse
.
headers
)
{
delete
clonedResponse
.
headers
;
}
if
(
clonedResponse
.
config
)
{
clonedResponse
.
request
=
clonedResponse
.
config
;
delete
clonedResponse
.
config
;
delete
clonedResponse
.
request
.
transformRequest
;
delete
clonedResponse
.
request
.
transformResponse
;
delete
clonedResponse
.
request
.
paramSerializer
;
delete
clonedResponse
.
request
.
jsonpCallbackParam
;
delete
clonedResponse
.
request
.
headers
;
delete
clonedResponse
.
request
.
requestId
;
delete
clonedResponse
.
request
.
inspect
;
delete
clonedResponse
.
request
.
retry
;
delete
clonedResponse
.
request
.
timeout
;
}
if
(
clonedResponse
.
data
)
{
clonedResponse
.
response
=
clonedResponse
.
data
;
delete
clonedResponse
.
config
;
delete
clonedResponse
.
data
;
delete
clonedResponse
.
status
;
delete
clonedResponse
.
statusText
;
delete
clonedResponse
.
ok
;
delete
clonedResponse
.
url
;
delete
clonedResponse
.
redirected
;
delete
clonedResponse
.
type
;
delete
clonedResponse
.
$$config
;
}
return
clonedResponse
;
}
interface
Props
{
loading
:
boolean
;
width
:
number
;
exploreId
:
ExploreId
;
queryResponse
?:
PanelData
;
onClose
:
()
=>
void
;
}
function
ExploreQueryInspector
(
props
:
Props
)
{
const
[
formattedJSON
,
setFormattedJSON
]
=
useState
({});
const
getTextForClipboard
=
()
=>
{
return
JSON
.
stringify
(
formattedJSON
,
null
,
2
);
};
const
onClipboardSuccess
=
()
=>
{
appEvents
.
emit
(
AppEvents
.
alertSuccess
,
[
'Content copied to clipboard'
]);
};
const
[
allNodesExpanded
,
setAllNodesExpanded
]
=
useState
(
false
);
const
getOpenNodeCount
=
()
=>
{
if
(
allNodesExpanded
===
null
)
{
return
3
;
// 3 is default, ie when state is null
}
else
if
(
allNodesExpanded
)
{
return
20
;
}
return
1
;
};
const
onToggleExpand
=
()
=>
{
setAllNodesExpanded
(
!
allNodesExpanded
);
};
const
{
loading
,
width
,
onClose
,
queryResponse
}
=
props
;
const
[
response
,
setResponse
]
=
useState
<
PanelData
>
({}
as
PanelData
);
useEffectOnce
(()
=>
{
const
inspectorStreamSub
=
getBackendSrv
()
.
getInspectorStream
()
.
subscribe
(
resp
=>
{
const
strippedResponse
=
stripPropsFromResponse
(
resp
);
setResponse
(
strippedResponse
);
});
return
()
=>
{
inspectorStreamSub
?.
unsubscribe
();
};
});
const
haveData
=
response
&&
Object
.
keys
(
response
).
length
>
0
;
const
styles
=
getPanelInspectorStyles
();
const
statsTab
:
TabConfig
=
{
label
:
'Stats'
,
value
:
'stats'
,
icon
:
'chart-line'
,
content
:
<
InspectStatsTab
data=
{
queryResponse
!
}
timeZone=
{
queryResponse
?.
request
?.
timezone
as
TimeZone
}
/>,
};
const
inspectorTab
:
TabConfig
=
{
label
:
'Query Inspector'
,
value
:
'query_inspector'
,
icon
:
'info-circle'
,
content
:
(
<>
<
div
className=
{
styles
.
toolbar
}
>
{
haveData
&&
(
<>
<
Button
icon=
{
allNodesExpanded
?
'minus'
:
'plus'
}
variant=
"secondary"
className=
{
styles
.
toolbarItem
}
onClick=
{
onToggleExpand
}
>
{
allNodesExpanded
?
'Collapse'
:
'Expand'
}
all
</
Button
>
<
CopyToClipboard
text=
{
getTextForClipboard
}
onSuccess=
{
onClipboardSuccess
}
elType=
"div"
className=
{
styles
.
toolbarItem
}
>
<
Button
icon=
"copy"
variant=
"secondary"
>
Copy to clipboard
</
Button
>
</
CopyToClipboard
>
</>
)
}
<
div
className=
"flex-grow-1"
/>
</
div
>
<
div
className=
{
styles
.
contentQueryInspector
}
>
{
loading
&&
<
LoadingPlaceholder
text=
"Loading query inspector..."
/>
}
{
!
loading
&&
haveData
&&
(
<
JSONFormatter
json=
{
response
!
}
open=
{
getOpenNodeCount
()
}
onDidRender=
{
setFormattedJSON
}
/>
)
}
{
!
loading
&&
!
haveData
&&
(
<
p
className=
"muted"
>
No request
&
response collected yet. Run query to collect request
&
response.
</
p
>
)
}
</
div
>
</>
),
};
const
tabs
=
[
statsTab
,
inspectorTab
];
return
(
<
ExploreDrawer
width=
{
width
}
onResize=
{
()
=>
{}
}
>
<
TabbedContainer
tabs=
{
tabs
}
onClose=
{
onClose
}
closeIconTooltip=
"Close query inspector"
/>
</
ExploreDrawer
>
);
}
function
mapStateToProps
(
state
:
StoreState
,
{
exploreId
}:
{
exploreId
:
ExploreId
})
{
const
explore
=
state
.
explore
;
const
item
:
ExploreItemState
=
explore
[
exploreId
];
const
{
loading
,
queryResponse
}
=
item
;
return
{
loading
,
queryResponse
,
};
}
export
default
hot
(
module
)(
connect
(
mapStateToProps
)(
ExploreQueryInspector
));
public/app/features/explore/RichHistory/RichHistory.test.tsx
View file @
02b12d3a
...
...
@@ -4,7 +4,7 @@ import { GrafanaTheme } from '@grafana/data';
import
{
ExploreId
}
from
'../../../types/explore'
;
import
{
RichHistory
,
RichHistoryProps
}
from
'./RichHistory'
;
import
{
Tabs
}
from
'./RichHistory'
;
import
{
Tab
,
Slider
}
from
'@grafana/ui'
;
import
{
Tab
}
from
'@grafana/ui'
;
jest
.
mock
(
'../state/selectors'
,
()
=>
({
getExploreDatasources
:
jest
.
fn
()
}));
...
...
@@ -31,6 +31,7 @@ describe('RichHistory', () => {
const
wrapper
=
setup
();
expect
(
wrapper
.
find
(
Tab
)).
toHaveLength
(
3
);
});
it
(
'should render correct lebels of tabs in tab bar'
,
()
=>
{
const
wrapper
=
setup
();
expect
(
...
...
@@ -52,12 +53,14 @@ describe('RichHistory', () => {
.
text
()
).
toEqual
(
'Settings'
);
});
it
(
'should correctly render query history tab as active tab'
,
()
=>
{
const
wrapper
=
setup
();
expect
(
wrapper
.
find
(
Slider
)).
toHaveLength
(
1
);
expect
(
wrapper
.
find
(
'RichHistoryQueriesTab'
)).
toHaveLength
(
1
);
});
it
(
'should correctly render starred tab as active tab'
,
()
=>
{
const
wrapper
=
setup
({
firstTab
:
Tabs
.
Starred
});
expect
(
wrapper
.
find
(
Slider
)).
toHaveLength
(
0
);
expect
(
wrapper
.
find
(
'RichHistoryStarredTab'
)).
toHaveLength
(
1
);
});
});
public/app/features/explore/RichHistory/RichHistory.tsx
View file @
02b12d3a
import
React
,
{
PureComponent
}
from
'react'
;
import
{
css
}
from
'emotion'
;
//Services & Utils
import
{
SortOrder
}
from
'app/core/utils/explore'
;
import
{
RICH_HISTORY_SETTING_KEYS
}
from
'app/core/utils/richHistory'
;
import
store
from
'app/core/store'
;
import
{
stylesFactory
,
withTheme
}
from
'@grafana/ui'
;
import
{
withTheme
,
TabbedContainer
,
TabConfig
}
from
'@grafana/ui'
;
//Types
import
{
RichHistoryQuery
,
ExploreId
}
from
'app/types/explore'
;
import
{
SelectableValue
,
GrafanaTheme
}
from
'@grafana/data'
;
import
{
T
absBar
,
Tab
,
TabContent
,
Themeable
,
CustomScrollbar
,
IconName
,
IconButton
}
from
'@grafana/ui'
;
import
{
SelectableValue
}
from
'@grafana/data'
;
import
{
T
hemeable
}
from
'@grafana/ui'
;
//Components
import
{
RichHistorySettings
}
from
'./RichHistorySettings'
;
...
...
@@ -41,7 +40,6 @@ export interface RichHistoryProps extends Themeable {
}
interface
RichHistoryState
{
activeTab
:
Tabs
;
sortOrder
:
SortOrder
;
retentionPeriod
:
number
;
starredTabAsFirstTab
:
boolean
;
...
...
@@ -49,41 +47,10 @@ interface RichHistoryState {
datasourceFilters
:
SelectableValue
[]
|
null
;
}
const
getStyles
=
stylesFactory
((
theme
:
GrafanaTheme
)
=>
{
return
{
container
:
css
`
height: 100%;
`
,
tabContent
:
css
`
padding:
${
theme
.
spacing
.
md
}
;
background-color:
${
theme
.
colors
.
bodyBg
}
;
`
,
close
:
css
`
position: absolute;
right: 16px;
top: 5px;
cursor: pointer;
font-size:
${
theme
.
typography
.
size
.
lg
}
;
`
,
tabs
:
css
`
padding-top:
${
theme
.
spacing
.
sm
}
;
border-color:
${
theme
.
colors
.
formInputBorder
}
;
ul {
margin-left:
${
theme
.
spacing
.
md
}
;
}
`
,
scrollbar
:
css
`
min-height: 100% !important;
background-color:
${
theme
.
colors
.
panelBg
}
;
`
,
};
});
class
UnThemedRichHistory
extends
PureComponent
<
RichHistoryProps
,
RichHistoryState
>
{
constructor
(
props
:
RichHistoryProps
)
{
super
(
props
);
this
.
state
=
{
activeTab
:
this
.
props
.
firstTab
,
sortOrder
:
SortOrder
.
Descending
,
datasourceFilters
:
store
.
getObject
(
RICH_HISTORY_SETTING_KEYS
.
datasourceFilters
,
null
),
retentionPeriod
:
store
.
getObject
(
RICH_HISTORY_SETTING_KEYS
.
retentionPeriod
,
7
),
...
...
@@ -107,7 +74,7 @@ class UnThemedRichHistory extends PureComponent<RichHistoryProps, RichHistorySta
store
.
set
(
RICH_HISTORY_SETTING_KEYS
.
starredTabAsFirstTab
,
starredTabAsFirstTab
);
};
toggle
a
ctiveDatasourceOnly
=
()
=>
{
toggle
A
ctiveDatasourceOnly
=
()
=>
{
const
activeDatasourceOnly
=
!
this
.
state
.
activeDatasourceOnly
;
this
.
setState
({
activeDatasourceOnly
,
...
...
@@ -127,10 +94,6 @@ class UnThemedRichHistory extends PureComponent<RichHistoryProps, RichHistorySta
this
.
setState
({
datasourceFilters
:
value
});
};
onSelectTab
=
(
item
:
SelectableValue
<
Tabs
>
)
=>
{
this
.
setState
({
activeTab
:
item
.
value
!
});
};
onChangeSortOrder
=
(
sortOrder
:
SortOrder
)
=>
this
.
setState
({
sortOrder
});
/* If user selects activeDatasourceOnly === true, set datasource filter to currently active datasource.
...
...
@@ -148,6 +111,7 @@ class UnThemedRichHistory extends PureComponent<RichHistoryProps, RichHistorySta
componentDidMount
()
{
this
.
updateFilters
();
}
componentDidUpdate
(
prevProps
:
RichHistoryProps
,
prevState
:
RichHistoryState
)
{
if
(
this
.
props
.
activeDatasourceInstance
!==
prevProps
.
activeDatasourceInstance
||
...
...
@@ -158,11 +122,10 @@ class UnThemedRichHistory extends PureComponent<RichHistoryProps, RichHistorySta
}
render
()
{
const
{
datasourceFilters
,
sortOrder
,
activeTab
,
activeDatasourceOnly
,
retentionPeriod
}
=
this
.
state
;
const
{
theme
,
richHistory
,
height
,
exploreId
,
deleteRichHistory
,
onClose
}
=
this
.
props
;
const
styles
=
getStyles
(
theme
);
const
{
datasourceFilters
,
sortOrder
,
activeDatasourceOnly
,
retentionPeriod
}
=
this
.
state
;
const
{
richHistory
,
height
,
exploreId
,
deleteRichHistory
,
onClose
,
firstTab
}
=
this
.
props
;
const
QueriesTab
=
{
const
QueriesTab
:
TabConfig
=
{
label
:
'Query history'
,
value
:
Tabs
.
RichHistory
,
content
:
(
...
...
@@ -181,7 +144,7 @@ class UnThemedRichHistory extends PureComponent<RichHistoryProps, RichHistorySta
icon
:
'history'
,
};
const
StarredTab
=
{
const
StarredTab
:
TabConfig
=
{
label
:
'Starred'
,
value
:
Tabs
.
Starred
,
content
:
(
...
...
@@ -198,7 +161,7 @@ class UnThemedRichHistory extends PureComponent<RichHistoryProps, RichHistorySta
icon
:
'star'
,
};
const
SettingsTab
=
{
const
SettingsTab
:
TabConfig
=
{
label
:
'Settings'
,
value
:
Tabs
.
Settings
,
content
:
(
...
...
@@ -208,7 +171,7 @@ class UnThemedRichHistory extends PureComponent<RichHistoryProps, RichHistorySta
activeDatasourceOnly=
{
this
.
state
.
activeDatasourceOnly
}
onChangeRetentionPeriod=
{
this
.
onChangeRetentionPeriod
}
toggleStarredTabAsFirstTab=
{
this
.
toggleStarredTabAsFirstTab
}
toggleactiveDatasourceOnly=
{
this
.
toggle
a
ctiveDatasourceOnly
}
toggleactiveDatasourceOnly=
{
this
.
toggle
A
ctiveDatasourceOnly
}
deleteRichHistory=
{
deleteRichHistory
}
/>
),
...
...
@@ -217,23 +180,7 @@ class UnThemedRichHistory extends PureComponent<RichHistoryProps, RichHistorySta
let
tabs
=
[
QueriesTab
,
StarredTab
,
SettingsTab
];
return
(
<
div
className=
{
styles
.
container
}
>
<
TabsBar
className=
{
styles
.
tabs
}
>
{
tabs
.
map
(
t
=>
(
<
Tab
key=
{
t
.
value
}
label=
{
t
.
label
}
active=
{
t
.
value
===
activeTab
}
onChangeTab=
{
()
=>
this
.
onSelectTab
(
t
)
}
icon=
{
t
.
icon
as
IconName
}
/>
))
}
<
IconButton
className=
{
styles
.
close
}
onClick=
{
onClose
}
name=
"times"
title=
"Close query history"
/>
</
TabsBar
>
<
CustomScrollbar
className=
{
styles
.
scrollbar
}
>
<
TabContent
className=
{
styles
.
tabContent
}
>
{
tabs
.
find
(
t
=>
t
.
value
===
activeTab
)?.
content
}
</
TabContent
>
</
CustomScrollbar
>
</
div
>
<
TabbedContainer
tabs=
{
tabs
}
onClose=
{
onClose
}
defaultTab=
{
firstTab
}
closeIconTooltip=
"Close query history"
/>
);
}
}
...
...
public/app/features/explore/RichHistory/RichHistoryContainer.tsx
View file @
02b12d3a
// Libraries
import
React
,
{
useState
}
from
'react'
;
import
{
Resizable
}
from
're-resizable'
;
import
{
connect
}
from
'react-redux'
;
import
{
hot
}
from
'react-hot-loader'
;
import
{
css
,
cx
,
keyframes
}
from
'emotion'
;
// Services & Utils
import
store
from
'app/core/store'
;
import
{
stylesFactory
,
useTheme
}
from
'@grafana/ui'
;
import
{
RICH_HISTORY_SETTING_KEYS
}
from
'app/core/utils/richHistory'
;
// Types
import
{
StoreState
}
from
'app/types'
;
import
{
GrafanaTheme
}
from
'@grafana/data'
;
import
{
ExploreId
,
RichHistoryQuery
}
from
'app/types/explore'
;
// Components, enums
...
...
@@ -20,52 +16,7 @@ import { RichHistory, Tabs } from './RichHistory';
//Actions
import
{
deleteRichHistory
}
from
'../state/actions'
;
const
drawerSlide
=
keyframes
`
0% {
transform: translateY(400px);
}
100% {
transform: translateY(0px);
}
`
;
const
getStyles
=
stylesFactory
((
theme
:
GrafanaTheme
)
=>
{
const
shadowColor
=
theme
.
isLight
?
theme
.
palette
.
gray4
:
theme
.
palette
.
black
;
return
{
container
:
css
`
position: fixed !important;
bottom: 0;
background:
${
theme
.
colors
.
pageHeaderBg
}
;
border-top: 1px solid
${
theme
.
colors
.
formInputBorder
}
;
margin: 0px;
margin-right: -
${
theme
.
spacing
.
md
}
;
margin-left: -
${
theme
.
spacing
.
md
}
;
box-shadow: 0 0 4px
${
shadowColor
}
;
z-index:
${
theme
.
zIndex
.
sidemenu
}
;
`
,
drawerActive
:
css
`
opacity: 1;
animation: 0.5s ease-out
${
drawerSlide
}
;
`
,
rzHandle
:
css
`
background:
${
theme
.
colors
.
formInputBorder
}
;
transition: 0.3s background ease-in-out;
position: relative;
width: 200px !important;
height: 7px !important;
left: calc(50% - 100px) !important;
top: -4px !important;
cursor: grab;
border-radius: 4px;
&:hover {
background:
${
theme
.
colors
.
formInputBorderHover
}
;
}
`
,
};
});
import
{
ExploreDrawer
}
from
'../ExploreDrawer'
;
export
interface
Props
{
width
:
number
;
...
...
@@ -81,29 +32,11 @@ export function RichHistoryContainer(props: Props) {
const
[
height
,
setHeight
]
=
useState
(
400
);
const
{
richHistory
,
width
,
firstTab
,
activeDatasourceInstance
,
exploreId
,
deleteRichHistory
,
onClose
}
=
props
;
const
theme
=
useTheme
();
const
styles
=
getStyles
(
theme
);
const
drawerWidth
=
`
${
width
+
31.5
}
px`
;
return
(
<
Resizable
className=
{
cx
(
styles
.
container
,
styles
.
drawerActive
)
}
defaultSize=
{
{
width
:
drawerWidth
,
height
:
'400px'
}
}
handleClasses=
{
{
top
:
styles
.
rzHandle
}
}
enable=
{
{
top
:
true
,
right
:
false
,
bottom
:
false
,
left
:
false
,
topRight
:
false
,
bottomRight
:
false
,
bottomLeft
:
false
,
topLeft
:
false
,
}
}
maxHeight=
"100vh"
maxWidth=
{
drawerWidth
}
minWidth=
{
drawerWidth
}
onResize=
{
(
e
,
dir
,
ref
)
=>
{
<
ExploreDrawer
width=
{
width
}
onResize=
{
(
_e
,
_dir
,
ref
)
=>
{
setHeight
(
Number
(
ref
.
style
.
height
.
slice
(
0
,
-
2
)));
}
}
>
...
...
@@ -116,7 +49,7 @@ export function RichHistoryContainer(props: Props) {
onClose=
{
onClose
}
height=
{
height
}
/>
</
Resizable
>
</
ExploreDrawer
>
);
}
...
...
public/app/features/explore/SecondaryActions.test.tsx
View file @
02b12d3a
...
...
@@ -5,10 +5,17 @@ import { SecondaryActions } from './SecondaryActions';
const
addQueryRowButtonSelector
=
'[aria-label="Add row button"]'
;
const
richHistoryButtonSelector
=
'[aria-label="Rich history button"]'
;
const
queryInspectorButtonSelector
=
'[aria-label="Query inspector button"]'
;
describe
(
'SecondaryActions'
,
()
=>
{
it
(
'should render component two buttons'
,
()
=>
{
const
wrapper
=
shallow
(<
SecondaryActions
onClickAddQueryRowButton=
{
noop
}
onClickRichHistoryButton=
{
noop
}
/>);
const
wrapper
=
shallow
(
<
SecondaryActions
onClickAddQueryRowButton=
{
noop
}
onClickRichHistoryButton=
{
noop
}
onClickQueryInspectorButton=
{
noop
}
/>
);
expect
(
wrapper
.
find
(
addQueryRowButtonSelector
)).
toHaveLength
(
1
);
expect
(
wrapper
.
find
(
richHistoryButtonSelector
)).
toHaveLength
(
1
);
});
...
...
@@ -19,6 +26,7 @@ describe('SecondaryActions', () => {
addQueryRowButtonHidden=
{
true
}
onClickAddQueryRowButton=
{
noop
}
onClickRichHistoryButton=
{
noop
}
onClickQueryInspectorButton=
{
noop
}
/>
);
expect
(
wrapper
.
find
(
addQueryRowButtonSelector
)).
toHaveLength
(
0
);
...
...
@@ -31,6 +39,7 @@ describe('SecondaryActions', () => {
addQueryRowButtonDisabled=
{
true
}
onClickAddQueryRowButton=
{
noop
}
onClickRichHistoryButton=
{
noop
}
onClickQueryInspectorButton=
{
noop
}
/>
);
expect
(
wrapper
.
find
(
addQueryRowButtonSelector
).
props
().
disabled
).
toBe
(
true
);
...
...
@@ -39,13 +48,22 @@ describe('SecondaryActions', () => {
it
(
'should map click handlers correctly'
,
()
=>
{
const
onClickAddRow
=
jest
.
fn
();
const
onClickHistory
=
jest
.
fn
();
const
onClickQueryInspector
=
jest
.
fn
();
const
wrapper
=
shallow
(
<
SecondaryActions
onClickAddQueryRowButton=
{
onClickAddRow
}
onClickRichHistoryButton=
{
onClickHistory
}
/>
<
SecondaryActions
onClickAddQueryRowButton=
{
onClickAddRow
}
onClickRichHistoryButton=
{
onClickHistory
}
onClickQueryInspectorButton=
{
onClickQueryInspector
}
/>
);
wrapper
.
find
(
addQueryRowButtonSelector
).
simulate
(
'click'
);
expect
(
onClickAddRow
).
toBeCalled
();
wrapper
.
find
(
richHistoryButtonSelector
).
simulate
(
'click'
);
expect
(
onClickHistory
).
toBeCalled
();
wrapper
.
find
(
queryInspectorButtonSelector
).
simulate
(
'click'
);
expect
(
onClickQueryInspector
).
toBeCalled
();
});
});
public/app/features/explore/SecondaryActions.tsx
View file @
02b12d3a
...
...
@@ -4,10 +4,13 @@ import { stylesFactory, Icon } from '@grafana/ui';
type
Props
=
{
addQueryRowButtonDisabled
?:
boolean
;
richHistoryButtonActive
?:
boolean
;
addQueryRowButtonHidden
?:
boolean
;
richHistoryButtonActive
?:
boolean
;
queryInspectorButtonActive
?:
boolean
;
onClickAddQueryRowButton
:
()
=>
void
;
onClickRichHistoryButton
:
()
=>
void
;
onClickQueryInspectorButton
:
()
=>
void
;
};
const
getStyles
=
stylesFactory
(()
=>
{
...
...
@@ -42,6 +45,16 @@ export function SecondaryActions(props: Props) {
<
Icon
className=
"icon-margin-right"
name=
"history"
size=
"sm"
/>
<
span
className=
"btn-title"
>
{
'
\
xA0'
+
'Query history'
}
</
span
>
</
button
>
<
button
aria
-
label=
"Query inspector button"
className=
{
cx
(
`gf-form-label gf-form-label--btn ${styles.button}`
,
{
[
'explore-active-button'
]:
props
.
queryInspectorButtonActive
,
})
}
onClick=
{
props
.
onClickQueryInspectorButton
}
>
<
Icon
className=
"icon-margin-right"
name=
"info-circle"
size=
"sm"
/>
<
span
className=
"btn-title"
>
{
'
\
xA0'
+
'Query inspector'
}
</
span
>
</
button
>
</
div
>
);
}
public/app/features/explore/__snapshots__/Explore.test.tsx.snap
View file @
02b12d3a
...
...
@@ -31,7 +31,9 @@ exports[`Explore should render component 1`] = `
addQueryRowButtonDisabled={false}
addQueryRowButtonHidden={false}
onClickAddQueryRowButton={[Function]}
onClickQueryInspectorButton={[Function]}
onClickRichHistoryButton={[Function]}
queryInspectorButtonActive={false}
richHistoryButtonActive={false}
/>
</div>
...
...
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