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
3e8c00da
Commit
3e8c00da
authored
Oct 31, 2019
by
kay delaney
Committed by
David
Oct 31, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Chore: Moves QueryField to @grafana/ui (#19678)
Closes #19626
parent
69906f73
Hide whitespace changes
Inline
Side-by-side
Showing
37 changed files
with
400 additions
and
375 deletions
+400
-375
packages/grafana-ui/src/components/QueryField/QueryField.story.tsx
+12
-0
packages/grafana-ui/src/components/QueryField/QueryField.test.tsx
+3
-3
packages/grafana-ui/src/components/QueryField/QueryField.tsx
+19
-25
packages/grafana-ui/src/components/Typeahead/Typeahead.tsx
+31
-31
packages/grafana-ui/src/components/Typeahead/TypeaheadInfo.tsx
+45
-56
packages/grafana-ui/src/components/Typeahead/TypeaheadItem.tsx
+3
-4
packages/grafana-ui/src/components/index.ts
+1
-0
packages/grafana-ui/src/slate-plugins/braces.test.tsx
+2
-2
packages/grafana-ui/src/slate-plugins/braces.ts
+10
-9
packages/grafana-ui/src/slate-plugins/clear.test.tsx
+2
-2
packages/grafana-ui/src/slate-plugins/clear.ts
+5
-4
packages/grafana-ui/src/slate-plugins/clipboard.ts
+21
-16
packages/grafana-ui/src/slate-plugins/indentation.ts
+13
-12
packages/grafana-ui/src/slate-plugins/index.ts
+8
-0
packages/grafana-ui/src/slate-plugins/newline.ts
+5
-4
packages/grafana-ui/src/slate-plugins/runner.test.tsx
+2
-2
packages/grafana-ui/src/slate-plugins/runner.ts
+9
-6
packages/grafana-ui/src/slate-plugins/selection_shortcuts.ts
+5
-4
packages/grafana-ui/src/slate-plugins/suggestions.tsx
+41
-34
packages/grafana-ui/src/themes/ThemeContext.tsx
+1
-0
packages/grafana-ui/src/types/completion.ts
+109
-0
packages/grafana-ui/src/types/datasource.ts
+16
-0
packages/grafana-ui/src/types/index.ts
+4
-3
packages/grafana-ui/src/utils/typeahead.ts
+3
-4
public/app/core/utils/explore.ts
+11
-2
public/app/features/explore/QueryRow.tsx
+2
-2
public/app/features/explore/state/actionTypes.ts
+2
-2
public/app/plugins/datasource/elasticsearch/components/ElasticsearchQueryField.tsx
+1
-2
public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx
+1
-5
public/app/plugins/datasource/loki/components/LokiQueryFieldForm.tsx
+1
-6
public/app/plugins/datasource/loki/language_provider.test.ts
+1
-1
public/app/plugins/datasource/loki/language_provider.ts
+1
-1
public/app/plugins/datasource/prometheus/components/PromQueryField.tsx
+4
-6
public/app/plugins/datasource/prometheus/language_provider.ts
+3
-4
public/app/plugins/datasource/prometheus/promql.ts
+1
-3
public/app/plugins/datasource/prometheus/specs/language_provider.test.ts
+1
-1
public/app/types/explore.ts
+1
-119
No files found.
packages/grafana-ui/src/components/QueryField/QueryField.story.tsx
0 → 100644
View file @
3e8c00da
import
React
from
'react'
;
import
{
storiesOf
}
from
'@storybook/react'
;
import
{
withCenteredStory
}
from
'../../utils/storybook/withCenteredStory'
;
import
{
QueryField
}
from
'./QueryField'
;
const
QueryFieldStories
=
storiesOf
(
'UI/QueryField'
,
module
);
QueryFieldStories
.
addDecorator
(
withCenteredStory
);
QueryFieldStories
.
add
(
'default'
,
()
=>
{
return
<
QueryField
portalOrigin=
"mock-origin"
query=
""
/>;
});
p
ublic/app/features/explore
/QueryField.test.tsx
→
p
ackages/grafana-ui/src/components/QueryField
/QueryField.test.tsx
View file @
3e8c00da
...
...
@@ -4,17 +4,17 @@ import { QueryField } from './QueryField';
describe
(
'<QueryField />'
,
()
=>
{
it
(
'should render with null initial value'
,
()
=>
{
const
wrapper
=
shallow
(<
QueryField
query=
{
null
}
/>);
const
wrapper
=
shallow
(<
QueryField
query=
{
null
}
onTypeahead=
{
jest
.
fn
()
}
portalOrigin=
"mock-origin"
/>);
expect
(
wrapper
.
find
(
'div'
).
exists
()).
toBeTruthy
();
});
it
(
'should render with empty initial value'
,
()
=>
{
const
wrapper
=
shallow
(<
QueryField
query=
""
/>);
const
wrapper
=
shallow
(<
QueryField
query=
""
onTypeahead=
{
jest
.
fn
()
}
portalOrigin=
"mock-origin"
/>);
expect
(
wrapper
.
find
(
'div'
).
exists
()).
toBeTruthy
();
});
it
(
'should render with initial value'
,
()
=>
{
const
wrapper
=
shallow
(<
QueryField
query=
"my query"
/>);
const
wrapper
=
shallow
(<
QueryField
query=
"my query"
onTypeahead=
{
jest
.
fn
()
}
portalOrigin=
"mock-origin"
/>);
expect
(
wrapper
.
find
(
'div'
).
exists
()).
toBeTruthy
();
});
});
p
ublic/app/features/explore
/QueryField.tsx
→
p
ackages/grafana-ui/src/components/QueryField
/QueryField.tsx
View file @
3e8c00da
...
...
@@ -6,16 +6,17 @@ import { Editor, Plugin } from '@grafana/slate-react';
import
Plain
from
'slate-plain-serializer'
;
import
classnames
from
'classnames'
;
import
{
CompletionItemGroup
,
TypeaheadOutput
}
from
'app/types/explore'
;
import
ClearPlugin
from
'./slate-plugins/clear'
;
import
NewlinePlugin
from
'./slate-plugins/newline'
;
import
SelectionShortcutsPlugin
from
'./slate-plugins/selection_shortcuts'
;
import
IndentationPlugin
from
'./slate-plugins/indentation'
;
import
ClipboardPlugin
from
'./slate-plugins/clipboard'
;
import
RunnerPlugin
from
'./slate-plugins/runner'
;
import
SuggestionsPlugin
,
{
SuggestionsState
}
from
'./slate-plugins/suggestions'
;
import
{
makeValue
,
SCHEMA
}
from
'@grafana/ui'
;
import
{
ClearPlugin
,
NewlinePlugin
,
SelectionShortcutsPlugin
,
IndentationPlugin
,
ClipboardPlugin
,
RunnerPlugin
,
SuggestionsPlugin
,
}
from
'../../slate-plugins'
;
import
{
makeValue
,
SCHEMA
,
CompletionItemGroup
,
TypeaheadOutput
,
TypeaheadInput
,
SuggestionsState
}
from
'../..'
;
export
interface
QueryFieldProps
{
additionalPlugins
?:
Plugin
[];
...
...
@@ -30,7 +31,7 @@ export interface QueryFieldProps {
onTypeahead
?:
(
typeahead
:
TypeaheadInput
)
=>
Promise
<
TypeaheadOutput
>
;
onWillApplySuggestion
?:
(
suggestion
:
string
,
state
:
SuggestionsState
)
=>
string
;
placeholder
?:
string
;
portalOrigin
?
:
string
;
portalOrigin
:
string
;
syntax
?:
string
;
syntaxLoaded
?:
boolean
;
}
...
...
@@ -43,15 +44,6 @@ export interface QueryFieldState {
value
:
Value
;
}
export
interface
TypeaheadInput
{
prefix
:
string
;
selection
?:
Selection
;
text
:
string
;
value
:
Value
;
wrapperClasses
:
string
[];
labelKey
?:
string
;
}
/**
* Renders an editor field.
* Pass initial value as initialQuery and listen to changes in props.onValueChanged.
...
...
@@ -60,11 +52,10 @@ export interface TypeaheadInput {
*/
export
class
QueryField
extends
React
.
PureComponent
<
QueryFieldProps
,
QueryFieldState
>
{
plugins
:
Plugin
[];
resetTimer
:
NodeJS
.
Timer
;
mounted
:
boolean
;
runOnChangeDebounced
:
Function
;
editor
:
Editor
;
lastExecutedValue
:
Value
|
null
=
null
;
mounted
=
false
;
editor
:
Editor
|
null
=
null
;
constructor
(
props
:
QueryFieldProps
,
context
:
Context
<
any
>
)
{
super
(
props
,
context
);
...
...
@@ -100,7 +91,6 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
componentWillUnmount
()
{
this
.
mounted
=
false
;
clearTimeout
(
this
.
resetTimer
);
}
componentDidUpdate
(
prevProps
:
QueryFieldProps
,
prevState
:
QueryFieldState
)
{
...
...
@@ -119,6 +109,10 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
UNSAFE_componentWillReceiveProps
(
nextProps
:
QueryFieldProps
)
{
if
(
nextProps
.
syntaxLoaded
&&
!
this
.
props
.
syntaxLoaded
)
{
if
(
!
this
.
editor
)
{
return
;
}
// Need a bogus edit to re-render the editor after syntax has fully loaded
const
editor
=
this
.
editor
.
insertText
(
' '
).
deleteBackward
(
1
);
this
.
onChange
(
editor
.
value
,
true
);
...
...
@@ -196,7 +190,7 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
<
div
className=
{
wrapperClassName
}
>
<
div
className=
"slate-query-field"
>
<
Editor
ref=
{
editor
=>
(
this
.
editor
=
editor
)
}
ref=
{
editor
=>
(
this
.
editor
=
editor
!
)
}
schema=
{
SCHEMA
}
autoCorrect=
{
false
}
readOnly=
{
this
.
props
.
disabled
}
...
...
p
ublic/app/features/explore
/Typeahead.tsx
→
p
ackages/grafana-ui/src/components/Typeahead
/Typeahead.tsx
View file @
3e8c00da
...
...
@@ -3,16 +3,15 @@ import ReactDOM from 'react-dom';
import
_
from
'lodash'
;
import
{
FixedSizeList
}
from
'react-window'
;
import
{
Themeable
,
withTheme
}
from
'@grafana/ui'
;
import
{
CompletionItem
,
CompletionItemKind
,
CompletionItemGroup
}
from
'app/types/explore'
;
import
{
TypeaheadItem
}
from
'./TypeaheadItem'
;
import
{
TypeaheadInfo
}
from
'./TypeaheadInfo'
;
import
{
flattenGroupItems
,
calculateLongestLabel
,
calculateListSizes
}
from
'./utils/typeahead'
;
import
{
TypeaheadItem
}
from
'./TypeaheadItem'
;
import
{
flattenGroupItems
,
calculateLongestLabel
,
calculateListSizes
}
from
'../../utils/typeahead'
;
import
{
ThemeContext
}
from
'../../themes/ThemeContext'
;
import
{
CompletionItem
,
CompletionItemGroup
,
CompletionItemKind
}
from
'../../types/completion'
;
const
modulo
=
(
a
:
number
,
n
:
number
)
=>
a
-
n
*
Math
.
floor
(
a
/
n
);
interface
Props
extends
Themeable
{
interface
Props
{
origin
:
string
;
groupedItems
:
CompletionItemGroup
[];
prefix
?:
string
;
...
...
@@ -26,26 +25,33 @@ interface State {
listWidth
:
number
;
listHeight
:
number
;
itemHeight
:
number
;
hoveredItem
:
number
;
hoveredItem
:
number
|
null
;
typeaheadIndex
:
number
;
}
export
class
Typeahead
extends
React
.
PureComponent
<
Props
,
State
>
{
static
contextType
=
ThemeContext
;
context
!
:
React
.
ContextType
<
typeof
ThemeContext
>
;
listRef
=
createRef
<
FixedSizeList
>
();
constructor
(
props
:
Props
)
{
super
(
props
);
const
allItems
=
flattenGroupItems
(
props
.
groupedItems
);
const
longestLabel
=
calculateLongestLabel
(
allItems
);
const
{
listWidth
,
listHeight
,
itemHeight
}
=
calculateListSizes
(
props
.
theme
,
allItems
,
longestLabel
);
this
.
state
=
{
listWidth
,
listHeight
,
itemHeight
,
hoveredItem
:
null
,
typeaheadIndex
:
1
,
allItems
};
}
state
:
State
=
{
hoveredItem
:
null
,
typeaheadIndex
:
1
,
allItems
:
[],
listWidth
:
-
1
,
listHeight
:
-
1
,
itemHeight
:
-
1
};
componentDidMount
=
()
=>
{
this
.
props
.
menuRef
(
this
);
if
(
this
.
props
.
menuRef
)
{
this
.
props
.
menuRef
(
this
);
}
document
.
addEventListener
(
'selectionchange'
,
this
.
handleSelectionChange
);
const
allItems
=
flattenGroupItems
(
this
.
props
.
groupedItems
);
const
longestLabel
=
calculateLongestLabel
(
allItems
);
const
{
listWidth
,
listHeight
,
itemHeight
}
=
calculateListSizes
(
this
.
context
,
allItems
,
longestLabel
);
this
.
setState
({
listWidth
,
listHeight
,
itemHeight
,
allItems
,
});
};
componentWillUnmount
=
()
=>
{
...
...
@@ -68,7 +74,7 @@ export class Typeahead extends React.PureComponent<Props, State> {
if
(
_
.
isEqual
(
prevProps
.
groupedItems
,
this
.
props
.
groupedItems
)
===
false
)
{
const
allItems
=
flattenGroupItems
(
this
.
props
.
groupedItems
);
const
longestLabel
=
calculateLongestLabel
(
allItems
);
const
{
listWidth
,
listHeight
,
itemHeight
}
=
calculateListSizes
(
this
.
props
.
theme
,
allItems
,
longestLabel
);
const
{
listWidth
,
listHeight
,
itemHeight
}
=
calculateListSizes
(
this
.
context
,
allItems
,
longestLabel
);
this
.
setState
({
listWidth
,
listHeight
,
itemHeight
,
allItems
});
}
};
...
...
@@ -89,7 +95,6 @@ export class Typeahead extends React.PureComponent<Props, State> {
const
itemCount
=
this
.
state
.
allItems
.
length
;
if
(
itemCount
)
{
// Select next suggestion
event
.
preventDefault
();
let
newTypeaheadIndex
=
modulo
(
this
.
state
.
typeaheadIndex
+
moveAmount
,
itemCount
);
if
(
this
.
state
.
allItems
[
newTypeaheadIndex
].
kind
===
CompletionItemKind
.
GroupTitle
)
{
...
...
@@ -105,7 +110,9 @@ export class Typeahead extends React.PureComponent<Props, State> {
};
insertSuggestion
=
()
=>
{
this
.
props
.
onSelectSuggestion
(
this
.
state
.
allItems
[
this
.
state
.
typeaheadIndex
]);
if
(
this
.
props
.
onSelectSuggestion
)
{
this
.
props
.
onSelectSuggestion
(
this
.
state
.
allItems
[
this
.
state
.
typeaheadIndex
]);
}
};
get
menuPosition
():
string
{
...
...
@@ -115,10 +122,10 @@ export class Typeahead extends React.PureComponent<Props, State> {
}
const
selection
=
window
.
getSelection
();
const
node
=
selection
.
anchorNode
;
const
node
=
selection
&&
selection
.
anchorNode
;
// Align menu overlay to editor node
if
(
node
)
{
if
(
node
&&
node
.
parentElement
)
{
// Read from DOM
const
rect
=
node
.
parentElement
.
getBoundingClientRect
();
const
scrollX
=
window
.
scrollX
;
...
...
@@ -133,7 +140,7 @@ export class Typeahead extends React.PureComponent<Props, State> {
}
render
()
{
const
{
prefix
,
theme
,
isOpen
,
origin
}
=
this
.
props
;
const
{
prefix
,
isOpen
=
false
,
origin
}
=
this
.
props
;
const
{
allItems
,
listWidth
,
listHeight
,
itemHeight
,
hoveredItem
,
typeaheadIndex
}
=
this
.
state
;
const
showDocumentation
=
hoveredItem
||
typeaheadIndex
;
...
...
@@ -161,7 +168,7 @@ export class Typeahead extends React.PureComponent<Props, State> {
return
(
<
TypeaheadItem
onClickItem=
{
()
=>
this
.
props
.
onSelectSuggestion
(
item
)
}
onClickItem=
{
()
=>
(
this
.
props
.
onSelectSuggestion
?
this
.
props
.
onSelectSuggestion
(
item
)
:
{}
)
}
isSelected=
{
allItems
[
typeaheadIndex
]
===
item
}
item=
{
item
}
prefix=
{
prefix
}
...
...
@@ -175,20 +182,13 @@ export class Typeahead extends React.PureComponent<Props, State> {
</
ul
>
{
showDocumentation
&&
(
<
TypeaheadInfo
width=
{
listWidth
}
height=
{
listHeight
}
theme=
{
theme
}
item=
{
allItems
[
hoveredItem
?
hoveredItem
:
typeaheadIndex
]
}
/>
<
TypeaheadInfo
height=
{
listHeight
}
item=
{
allItems
[
hoveredItem
?
hoveredItem
:
typeaheadIndex
]
}
/>
)
}
</
Portal
>
);
}
}
export
const
TypeaheadWithTheme
=
withTheme
(
Typeahead
);
interface
PortalProps
{
index
?:
number
;
isOpen
:
boolean
;
...
...
p
ublic/app/features/explore
/TypeaheadInfo.tsx
→
p
ackages/grafana-ui/src/components/Typeahead
/TypeaheadInfo.tsx
View file @
3e8c00da
import
React
,
{
PureComponen
t
}
from
'react'
;
import
React
,
{
useContex
t
}
from
'react'
;
import
{
css
,
cx
}
from
'emotion'
;
import
{
Themeable
,
selectThemeVariant
}
from
'@grafana/ui'
;
import
{
CompletionItem
}
from
'app/types/explore'
;
import
{
CompletionItem
,
selectThemeVariant
,
ThemeContext
,
GrafanaTheme
}
from
'../..'
;
const
getStyles
=
(
theme
:
GrafanaTheme
,
height
:
number
,
visible
:
boolean
)
=>
{
return
{
typeaheadItem
:
css
`
label: type-ahead-item;
padding:
${
theme
.
spacing
.
sm
}
${
theme
.
spacing
.
sm
}
${
theme
.
spacing
.
sm
}
${
theme
.
spacing
.
md
}
;
border-radius:
${
theme
.
border
.
radius
.
md
}
;
border:
${
selectThemeVariant
(
{
light
:
`solid 1px
${
theme
.
colors
.
gray5
}
`
,
dark
:
`solid 1px
${
theme
.
colors
.
dark1
}
`
},
theme
.
type
)}
;
overflow-y: scroll;
overflow-x: hidden;
outline: none;
background:
${
selectThemeVariant
({
light
:
theme
.
colors
.
white
,
dark
:
theme
.
colors
.
dark4
},
theme
.
type
)}
;
color:
${
theme
.
colors
.
text
}
;
box-shadow:
${
selectThemeVariant
(
{
light
:
`0 5px 10px 0
${
theme
.
colors
.
gray5
}
`
,
dark
:
`0 5px 10px 0
${
theme
.
colors
.
black
}
`
},
theme
.
type
)}
;
visibility:
${
visible
===
true
?
'visible'
:
'hidden'
}
;
width: 250px;
height:
${
height
+
parseInt
(
theme
.
spacing
.
xxs
,
10
)}
px;
position: relative;
`
,
};
};
interface
Props
extends
Themeable
{
interface
Props
{
item
:
CompletionItem
;
width
:
number
;
height
:
number
;
}
export
class
TypeaheadInfo
extends
PureComponent
<
Props
>
{
constructor
(
props
:
Props
)
{
super
(
props
);
}
getStyles
=
(
visible
:
boolean
)
=>
{
const
{
height
,
theme
}
=
this
.
props
;
return
{
typeaheadItem
:
css
`
label: type-ahead-item;
padding:
${
theme
.
spacing
.
sm
}
${
theme
.
spacing
.
sm
}
${
theme
.
spacing
.
sm
}
${
theme
.
spacing
.
md
}
;
border-radius:
${
theme
.
border
.
radius
.
md
}
;
border:
${
selectThemeVariant
(
{
light
:
`solid 1px
${
theme
.
colors
.
gray5
}
`
,
dark
:
`solid 1px
${
theme
.
colors
.
dark1
}
`
},
theme
.
type
)}
;
overflow-y: scroll;
overflow-x: hidden;
outline: none;
background:
${
selectThemeVariant
({
light
:
theme
.
colors
.
white
,
dark
:
theme
.
colors
.
dark4
},
theme
.
type
)}
;
color:
${
theme
.
colors
.
text
}
;
box-shadow:
${
selectThemeVariant
(
{
light
:
`0 5px 10px 0
${
theme
.
colors
.
gray5
}
`
,
dark
:
`0 5px 10px 0
${
theme
.
colors
.
black
}
`
},
theme
.
type
)}
;
visibility:
${
visible
===
true
?
'visible'
:
'hidden'
}
;
width: 250px;
height:
${
height
+
parseInt
(
theme
.
spacing
.
xxs
,
10
)}
px;
position: relative;
`
,
};
};
render
()
{
const
{
item
}
=
this
.
props
;
const
visible
=
item
&&
!!
item
.
documentation
;
const
label
=
item
?
item
.
label
:
''
;
const
documentation
=
item
&&
item
.
documentation
?
item
.
documentation
:
''
;
const
styles
=
this
.
getStyles
(
visible
);
return
(
<
div
className=
{
cx
([
styles
.
typeaheadItem
])
}
>
<
b
>
{
label
}
</
b
>
<
hr
/>
<
span
>
{
documentation
}
</
span
>
</
div
>
);
}
}
export
const
TypeaheadInfo
:
React
.
FC
<
Props
>
=
({
item
,
height
})
=>
{
const
visible
=
item
&&
!!
item
.
documentation
;
const
label
=
item
?
item
.
label
:
''
;
const
documentation
=
item
&&
item
.
documentation
?
item
.
documentation
:
''
;
const
theme
=
useContext
(
ThemeContext
);
const
styles
=
getStyles
(
theme
,
height
,
visible
);
return
(
<
div
className=
{
cx
([
styles
.
typeaheadItem
])
}
>
<
b
>
{
label
}
</
b
>
<
hr
/>
<
span
>
{
documentation
}
</
span
>
</
div
>
);
};
p
ublic/app/features/explore
/TypeaheadItem.tsx
→
p
ackages/grafana-ui/src/components/Typeahead
/TypeaheadItem.tsx
View file @
3e8c00da
import
React
,
{
FunctionComponent
,
useContext
}
from
'react'
;
import
React
,
{
useContext
}
from
'react'
;
// @ts-ignore
import
Highlighter
from
'react-highlight-words'
;
import
{
css
,
cx
}
from
'emotion'
;
import
{
GrafanaTheme
,
ThemeContext
,
selectThemeVariant
}
from
'@grafana/ui'
;
import
{
CompletionItem
,
CompletionItemKind
}
from
'app/types/explore
'
;
import
{
CompletionItem
,
CompletionItemKind
,
GrafanaTheme
,
ThemeContext
,
selectThemeVariant
}
from
'../..
'
;
interface
Props
{
isSelected
:
boolean
;
...
...
@@ -57,7 +56,7 @@ const getStyles = (theme: GrafanaTheme) => ({
`
,
});
export
const
TypeaheadItem
:
FunctionComponent
<
Props
>
=
(
props
:
Props
)
=>
{
export
const
TypeaheadItem
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
theme
=
useContext
(
ThemeContext
);
const
styles
=
getStyles
(
theme
);
...
...
packages/grafana-ui/src/components/index.ts
View file @
3e8c00da
...
...
@@ -38,6 +38,7 @@ export { TimeOfDayPicker } from './TimePicker/TimeOfDayPicker';
export
{
List
}
from
'./List/List'
;
export
{
TagsInput
}
from
'./TagsInput/TagsInput'
;
export
{
Modal
}
from
'./Modal/Modal'
;
export
{
QueryField
}
from
'./QueryField/QueryField'
;
// Renderless
export
{
SetInterval
}
from
'./SetInterval/SetInterval'
;
...
...
p
ublic/app/features/explore
/slate-plugins/braces.test.tsx
→
p
ackages/grafana-ui/src
/slate-plugins/braces.test.tsx
View file @
3e8c00da
...
...
@@ -2,7 +2,7 @@ import React from 'react';
import
Plain
from
'slate-plain-serializer'
;
import
{
Editor
}
from
'@grafana/slate-react'
;
import
{
shallow
}
from
'enzyme'
;
import
BracesPlugin
from
'./braces'
;
import
{
BracesPlugin
}
from
'./braces'
;
declare
global
{
interface
Window
{
...
...
@@ -11,7 +11,7 @@ declare global {
}
describe
(
'braces'
,
()
=>
{
const
handler
=
BracesPlugin
().
onKeyDown
;
const
handler
=
BracesPlugin
().
onKeyDown
!
;
const
nextMock
=
()
=>
{};
it
(
'adds closing braces around empty value'
,
()
=>
{
...
...
p
ublic/app/features/explore
/slate-plugins/braces.ts
→
p
ackages/grafana-ui/src
/slate-plugins/braces.ts
View file @
3e8c00da
...
...
@@ -7,16 +7,17 @@ const BRACES: any = {
'('
:
')'
,
};
export
default
function
BracesPlugin
():
Plugin
{
export
function
BracesPlugin
():
Plugin
{
return
{
onKeyDown
(
event
:
KeyboardEvent
,
editor
:
CoreEditor
,
next
:
Function
)
{
onKeyDown
(
event
:
Event
,
editor
:
CoreEditor
,
next
:
Function
)
{
const
keyEvent
=
event
as
KeyboardEvent
;
const
{
value
}
=
editor
;
switch
(
e
vent
.
key
)
{
switch
(
keyE
vent
.
key
)
{
case
'('
:
case
'{'
:
case
'['
:
{
e
vent
.
preventDefault
();
keyE
vent
.
preventDefault
();
const
{
start
:
{
offset
:
startOffset
,
key
:
startKey
},
end
:
{
offset
:
endOffset
,
key
:
endKey
},
...
...
@@ -27,17 +28,17 @@ export default function BracesPlugin(): Plugin {
// If text is selected, wrap selected text in parens
if
(
value
.
selection
.
isExpanded
)
{
editor
.
insertTextByKey
(
startKey
,
startOffset
,
e
vent
.
key
)
.
insertTextByKey
(
endKey
,
endOffset
+
1
,
BRACES
[
e
vent
.
key
])
.
insertTextByKey
(
startKey
,
startOffset
,
keyE
vent
.
key
)
.
insertTextByKey
(
endKey
,
endOffset
+
1
,
BRACES
[
keyE
vent
.
key
])
.
moveEndBackward
(
1
);
}
else
if
(
focusOffset
===
text
.
length
||
text
[
focusOffset
]
===
' '
||
Object
.
values
(
BRACES
).
includes
(
text
[
focusOffset
])
)
{
editor
.
insertText
(
`
${
event
.
key
}${
BRACES
[
e
vent
.
key
]}
`
).
moveBackward
(
1
);
editor
.
insertText
(
`
${
keyEvent
.
key
}${
BRACES
[
keyE
vent
.
key
]}
`
).
moveBackward
(
1
);
}
else
{
editor
.
insertText
(
e
vent
.
key
);
editor
.
insertText
(
keyE
vent
.
key
);
}
return
true
;
...
...
@@ -49,7 +50,7 @@ export default function BracesPlugin(): Plugin {
const
previousChar
=
text
[
offset
-
1
];
const
nextChar
=
text
[
offset
];
if
(
BRACES
[
previousChar
]
&&
BRACES
[
previousChar
]
===
nextChar
)
{
e
vent
.
preventDefault
();
keyE
vent
.
preventDefault
();
// Remove closing brace if directly following
editor
.
deleteBackward
(
1
)
...
...
p
ublic/app/features/explore
/slate-plugins/clear.test.tsx
→
p
ackages/grafana-ui/src
/slate-plugins/clear.test.tsx
View file @
3e8c00da
...
...
@@ -2,10 +2,10 @@ import Plain from 'slate-plain-serializer';
import
React
from
'react'
;
import
{
Editor
}
from
'@grafana/slate-react'
;
import
{
shallow
}
from
'enzyme'
;
import
ClearPlugin
from
'./clear'
;
import
{
ClearPlugin
}
from
'./clear'
;
describe
(
'clear'
,
()
=>
{
const
handler
=
ClearPlugin
().
onKeyDown
;
const
handler
=
ClearPlugin
().
onKeyDown
!
;
it
(
'does not change the empty value'
,
()
=>
{
const
value
=
Plain
.
deserialize
(
''
);
...
...
p
ublic/app/features/explore
/slate-plugins/clear.ts
→
p
ackages/grafana-ui/src
/slate-plugins/clear.ts
View file @
3e8c00da
...
...
@@ -2,17 +2,18 @@ import { Plugin } from '@grafana/slate-react';
import
{
Editor
as
CoreEditor
}
from
'slate'
;
// Clears the rest of the line after the caret
export
default
function
ClearPlugin
():
Plugin
{
export
function
ClearPlugin
():
Plugin
{
return
{
onKeyDown
(
event
:
KeyboardEvent
,
editor
:
CoreEditor
,
next
:
Function
)
{
onKeyDown
(
event
:
Event
,
editor
:
CoreEditor
,
next
:
Function
)
{
const
keyEvent
=
event
as
KeyboardEvent
;
const
value
=
editor
.
value
;
if
(
value
.
selection
.
isExpanded
)
{
return
next
();
}
if
(
event
.
key
===
'k'
&&
e
vent
.
ctrlKey
)
{
e
vent
.
preventDefault
();
if
(
keyEvent
.
key
===
'k'
&&
keyE
vent
.
ctrlKey
)
{
keyE
vent
.
preventDefault
();
const
text
=
value
.
anchorText
.
text
;
const
offset
=
value
.
selection
.
anchor
.
offset
;
const
length
=
text
.
length
;
...
...
p
ublic/app/features/explore
/slate-plugins/clipboard.ts
→
p
ackages/grafana-ui/src
/slate-plugins/clipboard.ts
View file @
3e8c00da
...
...
@@ -10,10 +10,11 @@ const getCopiedText = (textBlocks: string[], startOffset: number, endOffset: num
return
textBlocks
.
join
(
'
\
n'
).
slice
(
startOffset
,
excludingLastLineLength
+
endOffset
);
};
export
default
function
ClipboardPlugin
():
Plugin
{
const
clipboardPlugin
=
{
onCopy
(
event
:
ClipboardEvent
,
editor
:
CoreEditor
)
{
event
.
preventDefault
();
export
function
ClipboardPlugin
():
Plugin
{
const
clipboardPlugin
:
Plugin
=
{
onCopy
(
event
:
Event
,
editor
:
CoreEditor
,
next
:
()
=>
any
)
{
const
clipEvent
=
event
as
ClipboardEvent
;
clipEvent
.
preventDefault
();
const
{
document
,
selection
}
=
editor
.
value
;
const
{
...
...
@@ -26,22 +27,25 @@ export default function ClipboardPlugin(): Plugin {
.
map
(
block
=>
block
.
text
);
const
copiedText
=
getCopiedText
(
selectedBlocks
,
startOffset
,
endOffset
);
if
(
copiedText
)
{
e
vent
.
clipboardData
.
setData
(
'Text'
,
copiedText
);
if
(
copiedText
&&
clipEvent
.
clipboardData
)
{
clipE
vent
.
clipboardData
.
setData
(
'Text'
,
copiedText
);
}
return
true
;
},
onPaste
(
event
:
ClipboardEvent
,
editor
:
CoreEditor
)
{
event
.
preventDefault
();
const
pastedValue
=
event
.
clipboardData
.
getData
(
'Text'
);
const
lines
=
pastedValue
.
split
(
'
\
n'
);
onPaste
(
event
:
Event
,
editor
:
CoreEditor
,
next
:
()
=>
any
)
{
const
clipEvent
=
event
as
ClipboardEvent
;
clipEvent
.
preventDefault
();
if
(
clipEvent
.
clipboardData
)
{
const
pastedValue
=
clipEvent
.
clipboardData
.
getData
(
'Text'
);
const
lines
=
pastedValue
.
split
(
'
\
n'
);
if
(
lines
.
length
)
{
editor
.
insertText
(
lines
[
0
]);
for
(
const
line
of
lines
.
slice
(
1
))
{
editor
.
splitBlock
().
insertText
(
line
);
if
(
lines
.
length
)
{
editor
.
insertText
(
lines
[
0
]);
for
(
const
line
of
lines
.
slice
(
1
))
{
editor
.
splitBlock
().
insertText
(
line
);
}
}
}
...
...
@@ -51,8 +55,9 @@ export default function ClipboardPlugin(): Plugin {
return
{
...
clipboardPlugin
,
onCut
(
event
:
ClipboardEvent
,
editor
:
CoreEditor
)
{
clipboardPlugin
.
onCopy
(
event
,
editor
);
onCut
(
event
:
Event
,
editor
:
CoreEditor
,
next
:
()
=>
any
)
{
const
clipEvent
=
event
as
ClipboardEvent
;
clipboardPlugin
.
onCopy
!
(
clipEvent
,
editor
,
next
);
editor
.
deleteAtRange
(
editor
.
value
.
selection
);
return
true
;
...
...
p
ublic/app/features/explore
/slate-plugins/indentation.ts
→
p
ackages/grafana-ui/src
/slate-plugins/indentation.ts
View file @
3e8c00da
...
...
@@ -21,7 +21,7 @@ const handleTabKey = (event: KeyboardEvent, editor: CoreEditor, next: Function):
const
first
=
startBlock
.
getFirstText
();
const
startBlockIsSelected
=
startOffset
===
0
&&
startKey
===
first
.
key
&&
endOffset
===
first
.
text
.
length
&&
endKey
===
first
.
key
;
first
&&
startOffset
===
0
&&
startKey
===
first
.
key
&&
endOffset
===
first
.
text
.
length
&&
endKey
===
first
.
key
;
if
(
startBlockIsSelected
||
!
startBlock
.
equals
(
endBlock
))
{
handleIndent
(
editor
,
'right'
);
...
...
@@ -38,7 +38,7 @@ const handleIndent = (editor: CoreEditor, indentDirection: 'left' | 'right') =>
for
(
const
block
of
selectedBlocks
)
{
const
blockWhitespace
=
block
.
text
.
length
-
block
.
text
.
trimLeft
().
length
;
const
textKey
=
block
.
getFirstText
().
key
;
const
textKey
=
block
.
getFirstText
()
!
.
key
;
const
rangeProperties
:
RangeJSON
=
{
anchor
:
{
...
...
@@ -61,7 +61,7 @@ const handleIndent = (editor: CoreEditor, indentDirection: 'left' | 'right') =>
const
isWhiteSpace
=
/^
\s
*$/
.
test
(
textBeforeCaret
);
for
(
const
block
of
selectedBlocks
)
{
editor
.
insertTextByKey
(
block
.
getFirstText
().
key
,
0
,
SLATE_TAB
);
editor
.
insertTextByKey
(
block
.
getFirstText
()
!
.
key
,
0
,
SLATE_TAB
);
}
if
(
isWhiteSpace
)
{
...
...
@@ -71,18 +71,19 @@ const handleIndent = (editor: CoreEditor, indentDirection: 'left' | 'right') =>
};
// Clears the rest of the line after the caret
export
default
function
IndentationPlugin
():
Plugin
{
export
function
IndentationPlugin
():
Plugin
{
return
{
onKeyDown
(
event
:
KeyboardEvent
,
editor
:
CoreEditor
,
next
:
Function
)
{
if
(
isIndentLeftHotkey
(
event
)
||
isShiftTabHotkey
(
event
))
{
event
.
preventDefault
();
onKeyDown
(
event
:
Event
,
editor
:
CoreEditor
,
next
:
Function
)
{
const
keyEvent
=
event
as
KeyboardEvent
;
if
(
isIndentLeftHotkey
(
keyEvent
)
||
isShiftTabHotkey
(
keyEvent
))
{
keyEvent
.
preventDefault
();
handleIndent
(
editor
,
'left'
);
}
else
if
(
isIndentRightHotkey
(
e
vent
))
{
e
vent
.
preventDefault
();
}
else
if
(
isIndentRightHotkey
(
keyE
vent
))
{
keyE
vent
.
preventDefault
();
handleIndent
(
editor
,
'right'
);
}
else
if
(
e
vent
.
key
===
'Tab'
)
{
e
vent
.
preventDefault
();
handleTabKey
(
e
vent
,
editor
,
next
);
}
else
if
(
keyE
vent
.
key
===
'Tab'
)
{
keyE
vent
.
preventDefault
();
handleTabKey
(
keyE
vent
,
editor
,
next
);
}
else
{
return
next
();
}
...
...
packages/grafana-ui/src/slate-plugins/index.ts
View file @
3e8c00da
export
{
BracesPlugin
}
from
'./braces'
;
export
{
ClearPlugin
}
from
'./clear'
;
export
{
ClipboardPlugin
}
from
'./clipboard'
;
export
{
IndentationPlugin
}
from
'./indentation'
;
export
{
NewlinePlugin
}
from
'./newline'
;
export
{
RunnerPlugin
}
from
'./runner'
;
export
{
SelectionShortcutsPlugin
}
from
'./selection_shortcuts'
;
export
{
SlatePrism
}
from
'./slate-prism'
;
export
{
SuggestionsPlugin
}
from
'./suggestions'
;
p
ublic/app/features/explore
/slate-plugins/newline.ts
→
p
ackages/grafana-ui/src
/slate-plugins/newline.ts
View file @
3e8c00da
...
...
@@ -13,17 +13,18 @@ function getIndent(text: string) {
return
''
;
}
export
default
function
NewlinePlugin
():
Plugin
{
export
function
NewlinePlugin
():
Plugin
{
return
{
onKeyDown
(
event
:
KeyboardEvent
,
editor
:
CoreEditor
,
next
:
Function
)
{
onKeyDown
(
event
:
Event
,
editor
:
CoreEditor
,
next
:
Function
)
{
const
keyEvent
=
event
as
KeyboardEvent
;
const
value
=
editor
.
value
;
if
(
value
.
selection
.
isExpanded
)
{
return
next
();
}
if
(
event
.
key
===
'Enter'
&&
e
vent
.
shiftKey
)
{
e
vent
.
preventDefault
();
if
(
keyEvent
.
key
===
'Enter'
&&
keyE
vent
.
shiftKey
)
{
keyE
vent
.
preventDefault
();
const
{
startBlock
}
=
value
;
const
currentLineText
=
startBlock
.
text
;
...
...
p
ublic/app/features/explore
/slate-plugins/runner.test.tsx
→
p
ackages/grafana-ui/src
/slate-plugins/runner.test.tsx
View file @
3e8c00da
...
...
@@ -2,11 +2,11 @@ import Plain from 'slate-plain-serializer';
import
React
from
'react'
;
import
{
Editor
}
from
'@grafana/slate-react'
;
import
{
shallow
}
from
'enzyme'
;
import
RunnerPlugin
from
'./runner'
;
import
{
RunnerPlugin
}
from
'./runner'
;
describe
(
'runner'
,
()
=>
{
const
mockHandler
=
jest
.
fn
();
const
handler
=
RunnerPlugin
({
handler
:
mockHandler
}).
onKeyDown
;
const
handler
=
RunnerPlugin
({
handler
:
mockHandler
}).
onKeyDown
!
;
it
(
'should execute query when enter is pressed and there are no suggestions visible'
,
()
=>
{
const
value
=
Plain
.
deserialize
(
''
);
...
...
p
ublic/app/features/explore
/slate-plugins/runner.ts
→
p
ackages/grafana-ui/src
/slate-plugins/runner.ts
View file @
3e8c00da
import
{
Editor
as
SlateEditor
}
from
'slate'
;
import
{
Plugin
}
from
'@grafana/slate-react'
;
import
{
Editor
as
CoreEditor
}
from
'slate'
;
export
default
function
RunnerPlugin
({
handler
}:
any
)
{
export
function
RunnerPlugin
({
handler
}:
any
):
Plugin
{
return
{
onKeyDown
(
event
:
KeyboardEvent
,
editor
:
SlateEditor
,
next
:
Function
)
{
onKeyDown
(
event
:
Event
,
editor
:
CoreEditor
,
next
:
Function
)
{
const
keyEvent
=
event
as
KeyboardEvent
;
// Handle enter
if
(
handler
&&
event
.
key
===
'Enter'
&&
!
e
vent
.
shiftKey
)
{
if
(
handler
&&
keyEvent
.
key
===
'Enter'
&&
!
keyE
vent
.
shiftKey
)
{
// Submit on Enter
e
vent
.
preventDefault
();
handler
(
e
vent
);
keyE
vent
.
preventDefault
();
handler
(
keyE
vent
);
return
true
;
}
...
...
p
ublic/app/features/explore
/slate-plugins/selection_shortcuts.ts
→
p
ackages/grafana-ui/src
/slate-plugins/selection_shortcuts.ts
View file @
3e8c00da
...
...
@@ -6,11 +6,12 @@ import { isKeyHotkey } from 'is-hotkey';
const
isSelectLineHotkey
=
isKeyHotkey
(
'mod+l'
);
// Clears the rest of the line after the caret
export
default
function
SelectionShortcutsPlugin
():
Plugin
{
export
function
SelectionShortcutsPlugin
():
Plugin
{
return
{
onKeyDown
(
event
:
KeyboardEvent
,
editor
:
CoreEditor
,
next
:
Function
)
{
if
(
isSelectLineHotkey
(
event
))
{
event
.
preventDefault
();
onKeyDown
(
event
:
Event
,
editor
:
CoreEditor
,
next
:
()
=>
any
)
{
const
keyEvent
=
event
as
KeyboardEvent
;
if
(
isSelectLineHotkey
(
keyEvent
))
{
keyEvent
.
preventDefault
();
const
{
focusBlock
,
document
}
=
editor
.
value
;
editor
.
moveAnchorToStartOfBlock
();
...
...
p
ublic/app/features/explore
/slate-plugins/suggestions.tsx
→
p
ackages/grafana-ui/src
/slate-plugins/suggestions.tsx
View file @
3e8c00da
...
...
@@ -4,14 +4,17 @@ import sortBy from 'lodash/sortBy';
import
{
Editor
as
CoreEditor
}
from
'slate'
;
import
{
Plugin
as
SlatePlugin
}
from
'@grafana/slate-react'
;
import
{
TypeaheadOutput
,
CompletionItem
,
CompletionItemGroup
}
from
'app/types'
;
import
{
TypeaheadInput
}
from
'../QueryField'
;
import
TOKEN_MARK
from
'@grafana/ui/src/slate-plugins/slate-prism/TOKEN_MARK'
;
import
{
TypeaheadWithTheme
,
Typeahead
}
from
'../Typeahead'
;
import
{
makeFragment
}
from
'@grafana/ui'
;
import
TOKEN_MARK
from
'./slate-prism/TOKEN_MARK'
;
import
{
makeFragment
,
TypeaheadOutput
,
CompletionItem
,
TypeaheadInput
,
SuggestionsState
,
CompletionItemGroup
,
}
from
'..'
;
import
{
Typeahead
}
from
'../components/Typeahead/Typeahead'
;
export
const
TYPEAHEAD_DEBOUNCE
=
100
;
// Commands added to the editor by this plugin.
...
...
@@ -27,13 +30,13 @@ export interface SuggestionsState {
typeaheadText
:
string
;
}
export
default
function
SuggestionsPlugin
({
export
function
SuggestionsPlugin
({
onTypeahead
,
cleanText
,
onWillApplySuggestion
,
portalOrigin
,
}:
{
onTypeahead
:
(
typeahead
:
TypeaheadInput
)
=>
Promise
<
TypeaheadOutput
>
;
onTypeahead
?
:
(
typeahead
:
TypeaheadInput
)
=>
Promise
<
TypeaheadOutput
>
;
cleanText
?:
(
text
:
string
)
=>
string
;
onWillApplySuggestion
?:
(
suggestion
:
string
,
state
:
SuggestionsState
)
=>
string
;
portalOrigin
:
string
;
...
...
@@ -73,15 +76,16 @@ export default function SuggestionsPlugin({
return
next
();
},
onKeyDown
:
(
event
:
KeyboardEvent
,
editor
,
next
)
=>
{
onKeyDown
:
(
event
:
Event
,
editor
,
next
)
=>
{
const
keyEvent
=
event
as
KeyboardEvent
;
const
currentSuggestions
=
state
.
groupedItems
;
const
hasSuggestions
=
currentSuggestions
.
length
;
switch
(
e
vent
.
key
)
{
switch
(
keyE
vent
.
key
)
{
case
'Escape'
:
{
if
(
hasSuggestions
)
{
e
vent
.
preventDefault
();
keyE
vent
.
preventDefault
();
state
=
{
...
state
,
...
...
@@ -98,8 +102,8 @@ export default function SuggestionsPlugin({
case
'ArrowDown'
:
case
'ArrowUp'
:
if
(
hasSuggestions
)
{
e
vent
.
preventDefault
();
typeaheadRef
.
moveMenuIndex
(
e
vent
.
key
===
'ArrowDown'
?
1
:
-
1
);
keyE
vent
.
preventDefault
();
typeaheadRef
.
moveMenuIndex
(
keyE
vent
.
key
===
'ArrowDown'
?
1
:
-
1
);
return
;
}
...
...
@@ -108,7 +112,7 @@ export default function SuggestionsPlugin({
case
'Enter'
:
case
'Tab'
:
{
if
(
hasSuggestions
)
{
e
vent
.
preventDefault
();
keyE
vent
.
preventDefault
();
return
typeaheadRef
.
insertSuggestion
();
}
...
...
@@ -196,8 +200,8 @@ export default function SuggestionsPlugin({
return
(
<>
{
children
}
<
Typeahead
WithTheme
menuRef=
{
(
el
:
Typeahead
)
=>
(
typeaheadRef
=
el
)
}
<
Typeahead
menuRef=
{
(
menu
:
Typeahead
)
=>
(
typeaheadRef
=
menu
)
}
origin=
{
portalOrigin
}
prefix=
{
state
.
typeaheadPrefix
}
isOpen=
{
!!
state
.
groupedItems
.
length
}
...
...
@@ -217,7 +221,7 @@ const handleTypeahead = async (
cleanText
?:
(
text
:
string
)
=>
string
):
Promise
<
void
>
=>
{
if
(
!
onTypeahead
)
{
return
null
;
return
;
}
const
{
value
}
=
editor
;
...
...
@@ -226,25 +230,28 @@ const handleTypeahead = async (
// Get decorations associated with the current line
const
parentBlock
=
value
.
document
.
getClosestBlock
(
value
.
focusBlock
.
key
);
const
myOffset
=
value
.
selection
.
start
.
offset
-
1
;
const
decorations
=
parentBlock
.
getDecorations
(
editor
as
any
);
const
decorations
=
parentBlock
&&
parentBlock
.
getDecorations
(
editor
as
any
);
const
filteredDecorations
=
decorations
.
filter
(
decoration
=>
decoration
.
start
.
offset
<=
myOffset
&&
decoration
.
end
.
offset
>
myOffset
&&
decoration
.
type
===
TOKEN_MARK
)
.
toArray
();
?
decorations
.
filter
(
decoration
=>
decoration
!
.
start
.
offset
<=
myOffset
&&
decoration
!
.
end
.
offset
>
myOffset
&&
decoration
!
.
type
===
TOKEN_MARK
)
.
toArray
()
:
[];
// Find the first label key to the left of the cursor
const
labelKeyDec
=
decorations
.
filter
(
decoration
=>
{
return
(
decoration
.
end
.
offset
<=
myOffset
&&
decoration
.
type
===
TOKEN_MARK
&&
decoration
.
data
.
get
(
'className'
).
includes
(
'label-key'
)
);
})
.
last
();
const
labelKeyDec
=
decorations
&&
decorations
.
filter
(
decoration
=>
decoration
!
.
end
.
offset
<=
myOffset
&&
decoration
!
.
type
===
TOKEN_MARK
&&
decoration
!
.
data
.
get
(
'className'
).
includes
(
'label-key'
)
)
.
last
();
const
labelKey
=
labelKeyDec
&&
value
.
focusText
.
text
.
slice
(
labelKeyDec
.
start
.
offset
,
labelKeyDec
.
end
.
offset
);
...
...
@@ -276,7 +283,7 @@ const handleTypeahead = async (
text
,
value
,
wrapperClasses
,
labelKey
,
labelKey
:
labelKey
||
undefined
,
});
const
filteredSuggestions
=
suggestions
...
...
packages/grafana-ui/src/themes/ThemeContext.tsx
View file @
3e8c00da
...
...
@@ -9,6 +9,7 @@ type Subtract<T, K> = Omit<T, keyof K>;
// Use Grafana Dark theme by default
export
const
ThemeContext
=
React
.
createContext
(
getTheme
(
GrafanaThemeType
.
Dark
));
ThemeContext
.
displayName
=
'ThemeContext'
;
export
const
withTheme
=
<
P
extends
Themeable
,
S
extends
{}
=
{}
>
(Component: React.ComponentType
<
P
>
) =
>
{
const
WithTheme
:
React
.
FunctionComponent
<
Subtract
<
P
,
Themeable
>>
=
props
=>
{
...
...
packages/grafana-ui/src/types/completion.ts
0 → 100644
View file @
3e8c00da
import
{
Value
}
from
'slate'
;
import
{
Editor
}
from
'@grafana/slate-react'
;
export
interface
CompletionItemGroup
{
/**
* Label that will be displayed for all entries of this group.
*/
label
:
string
;
/**
* List of suggestions of this group.
*/
items
:
CompletionItem
[];
/**
* If true, match only by prefix (and not mid-word).
*/
prefixMatch
?:
boolean
;
/**
* If true, do not filter items in this group based on the search.
*/
skipFilter
?:
boolean
;
/**
* If true, do not sort items.
*/
skipSort
?:
boolean
;
}
export
enum
CompletionItemKind
{
GroupTitle
=
'GroupTitle'
,
}
export
interface
CompletionItem
{
/**
* The label of this completion item. By default
* this is also the text that is inserted when selecting
* this completion.
*/
label
:
string
;
/**
* The kind of this completion item. An icon is chosen
* by the editor based on the kind.
*/
kind
?:
CompletionItemKind
|
string
;
/**
* A human-readable string with additional information
* about this item, like type or symbol information.
*/
detail
?:
string
;
/**
* A human-readable string, can be Markdown, that represents a doc-comment.
*/
documentation
?:
string
;
/**
* A string that should be used when comparing this item
* with other items. When `falsy` the `label` is used.
*/
sortText
?:
string
;
/**
* A string that should be used when filtering a set of
* completion items. When `falsy` the `label` is used.
*/
filterText
?:
string
;
/**
* A string or snippet that should be inserted in a document when selecting
* this completion. When `falsy` the `label` is used.
*/
insertText
?:
string
;
/**
* Delete number of characters before the caret position,
* by default the letters from the beginning of the word.
*/
deleteBackwards
?:
number
;
/**
* Number of steps to move after the insertion, can be negative.
*/
move
?:
number
;
}
export
interface
TypeaheadOutput
{
context
?:
string
;
suggestions
:
CompletionItemGroup
[];
}
export
interface
TypeaheadInput
{
text
:
string
;
prefix
:
string
;
wrapperClasses
:
string
[];
labelKey
?:
string
;
value
?:
Value
;
editor
?:
Editor
;
}
export
interface
SuggestionsState
{
groupedItems
:
CompletionItemGroup
[];
typeaheadPrefix
:
string
;
typeaheadContext
:
string
;
typeaheadText
:
string
;
}
packages/grafana-ui/src/types/datasource.ts
View file @
3e8c00da
...
...
@@ -549,3 +549,19 @@ export interface AnnotationQueryRequest<MoreOptions = {}> {
name
:
string
;
}
&
MoreOptions
;
}
export
interface
HistoryItem
<
TQuery
extends
DataQuery
=
DataQuery
>
{
ts
:
number
;
query
:
TQuery
;
}
export
abstract
class
LanguageProvider
{
datasource
!
:
DataSourceApi
;
request
!
:
(
url
:
string
,
params
?:
any
)
=>
Promise
<
any
>
;
/**
* Returns startTask that resolves with a task list when main syntax is loaded.
* Task list consists of secondary promises that load more detailed language features.
*/
start
!
:
()
=>
Promise
<
any
[]
>
;
startTask
?:
Promise
<
any
[]
>
;
}
packages/grafana-ui/src/types/index.ts
View file @
3e8c00da
export
*
from
'./panel'
;
export
*
from
'./plugin'
;
export
*
from
'./app'
;
export
*
from
'./completion'
;
export
*
from
'./datasource'
;
export
*
from
'./theme'
;
export
*
from
'./input'
;
export
*
from
'./panel'
;
export
*
from
'./plugin'
;
export
*
from
'./theme'
;
import
*
as
PanelEvents
from
'./events'
;
export
{
PanelEvents
};
p
ublic/app/features/explore
/utils/typeahead.ts
→
p
ackages/grafana-ui/src
/utils/typeahead.ts
View file @
3e8c00da
import
{
GrafanaTheme
}
from
'@grafana/ui'
;
import
{
default
as
calculateSize
}
from
'calculate-size'
;
import
{
CompletionItemGroup
,
CompletionItem
,
CompletionItemKind
}
from
'app/types
'
;
import
{
CompletionItemGroup
,
CompletionItem
,
CompletionItemKind
}
from
'../types/completion'
;
import
{
GrafanaTheme
}
from
'..
'
;
export
const
flattenGroupItems
=
(
groupedItems
:
CompletionItemGroup
[]):
CompletionItem
[]
=>
{
return
groupedItems
.
reduce
((
all
,
current
)
=>
{
...
...
@@ -10,7 +9,7 @@ export const flattenGroupItems = (groupedItems: CompletionItemGroup[]): Completi
kind
:
CompletionItemKind
.
GroupTitle
,
};
return
all
.
concat
(
titleItem
,
current
.
items
);
},
[]
);
},
new
Array
<
CompletionItem
>
()
);
};
export
const
calculateLongestLabel
=
(
allItems
:
CompletionItem
[]):
string
=>
{
...
...
public/app/core/utils/explore.ts
View file @
3e8c00da
...
...
@@ -19,9 +19,18 @@ import { renderUrl } from 'app/core/utils/url';
import
store
from
'app/core/store'
;
import
kbn
from
'app/core/utils/kbn'
;
import
{
getNextRefIdChar
}
from
'./query'
;
// Types
import
{
DataQuery
,
DataSourceApi
,
DataQueryError
,
DataQueryRequest
,
PanelModel
,
RefreshPicker
}
from
'@grafana/ui'
;
import
{
ExploreUrlState
,
HistoryItem
,
QueryTransaction
,
QueryOptions
,
ExploreMode
}
from
'app/types/explore'
;
import
{
DataQuery
,
DataSourceApi
,
DataQueryError
,
DataQueryRequest
,
PanelModel
,
RefreshPicker
,
HistoryItem
,
}
from
'@grafana/ui'
;
import
{
ExploreUrlState
,
QueryTransaction
,
QueryOptions
,
ExploreMode
}
from
'app/types/explore'
;
import
{
config
}
from
'../config'
;
import
{
TimeSrv
}
from
'app/features/dashboard/services/TimeSrv'
;
...
...
public/app/features/explore/QueryRow.tsx
View file @
3e8c00da
...
...
@@ -13,8 +13,8 @@ import { changeQuery, modifyQueries, runQueries, addQueryRow } from './state/act
// Types
import
{
StoreState
}
from
'app/types'
;
import
{
TimeRange
,
AbsoluteTimeRange
,
LoadingState
}
from
'@grafana/data'
;
import
{
DataQuery
,
DataSourceApi
,
QueryFixAction
,
PanelData
}
from
'@grafana/ui'
;
import
{
HistoryItem
,
ExploreItemState
,
ExploreId
,
ExploreMode
}
from
'app/types/explore'
;
import
{
DataQuery
,
DataSourceApi
,
QueryFixAction
,
PanelData
,
HistoryItem
}
from
'@grafana/ui'
;
import
{
ExploreItemState
,
ExploreId
,
ExploreMode
}
from
'app/types/explore'
;
import
{
Emitter
}
from
'app/core/utils/emitter'
;
import
{
highlightLogsExpressionAction
,
removeQueryRowAction
}
from
'./state/actionTypes'
;
import
QueryStatus
from
'./QueryStatus'
;
...
...
public/app/features/explore/state/actionTypes.ts
View file @
3e8c00da
// Types
import
{
Unsubscribable
}
from
'rxjs'
;
import
{
Emitter
}
from
'app/core/core'
;
import
{
DataQuery
,
DataSourceSelectItem
,
DataSourceApi
,
QueryFixAction
,
PanelData
}
from
'@grafana/ui'
;
import
{
DataQuery
,
DataSourceSelectItem
,
DataSourceApi
,
QueryFixAction
,
PanelData
,
HistoryItem
}
from
'@grafana/ui'
;
import
{
LogLevel
,
TimeRange
,
LoadingState
,
AbsoluteTimeRange
}
from
'@grafana/data'
;
import
{
ExploreId
,
ExploreItemState
,
HistoryItem
,
ExploreUIState
,
ExploreMode
}
from
'app/types/explore'
;
import
{
ExploreId
,
ExploreItemState
,
ExploreUIState
,
ExploreMode
}
from
'app/types/explore'
;
import
{
actionCreatorFactory
,
ActionOf
}
from
'app/core/redux/actionCreatorFactory'
;
/** Higher order actions
...
...
public/app/plugins/datasource/elasticsearch/components/ElasticsearchQueryField.tsx
View file @
3e8c00da
...
...
@@ -4,8 +4,7 @@ import React from 'react';
import
{
SlatePrism
}
from
'@grafana/ui'
;
// dom also includes Element polyfills
import
QueryField
from
'app/features/explore/QueryField'
;
import
{
ExploreQueryFieldProps
}
from
'@grafana/ui'
;
import
{
QueryField
,
ExploreQueryFieldProps
}
from
'@grafana/ui'
;
import
{
ElasticDatasource
}
from
'../datasource'
;
import
{
ElasticsearchOptions
,
ElasticsearchQuery
}
from
'../types'
;
...
...
public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx
View file @
3e8c00da
import
PluginPrism
from
'app/features/explore/slate-plugins/prism'
;
import
BracesPlugin
from
'app/features/explore/slate-plugins/braces'
;
import
ClearPlugin
from
'app/features/explore/slate-plugins/clear'
;
import
NewlinePlugin
from
'app/features/explore/slate-plugins/newline'
;
import
RunnerPlugin
from
'app/features/explore/slate-plugins/runner'
;
import
{
BracesPlugin
,
ClearPlugin
,
RunnerPlugin
,
NewlinePlugin
}
from
'@grafana/ui'
;
import
Typeahead
from
'./typeahead'
;
import
{
getKeybindingSrv
,
KeybindingSrv
}
from
'app/core/services/keybindingSrv'
;
...
...
public/app/plugins/datasource/loki/components/LokiQueryFieldForm.tsx
View file @
3e8c00da
...
...
@@ -3,23 +3,18 @@ import React from 'react';
// @ts-ignore
import
Cascader
from
'rc-cascader'
;
import
{
SlatePrism
}
from
'@grafana/ui'
;
import
{
SlatePrism
,
TypeaheadOutput
,
SuggestionsState
,
QueryField
,
TypeaheadInput
,
BracesPlugin
}
from
'@grafana/ui'
;
// Components
import
QueryField
,
{
TypeaheadInput
}
from
'app/features/explore/QueryField'
;
// Utils & Services
// dom also includes Element polyfills
import
BracesPlugin
from
'app/features/explore/slate-plugins/braces'
;
import
{
Plugin
,
Node
}
from
'slate'
;
// Types
import
{
LokiQuery
}
from
'../types'
;
import
{
TypeaheadOutput
}
from
'app/types/explore'
;
import
{
ExploreQueryFieldProps
,
DOMUtil
}
from
'@grafana/ui'
;
import
{
AbsoluteTimeRange
}
from
'@grafana/data'
;
import
{
Grammar
}
from
'prismjs'
;
import
LokiLanguageProvider
,
{
LokiHistoryItem
}
from
'../language_provider'
;
import
{
SuggestionsState
}
from
'app/features/explore/slate-plugins/suggestions'
;
import
LokiDatasource
from
'../datasource'
;
function
getChooserText
(
hasSyntax
:
boolean
,
hasLogLabels
:
boolean
)
{
...
...
public/app/plugins/datasource/loki/language_provider.test.ts
View file @
3e8c00da
...
...
@@ -3,10 +3,10 @@ import { Editor as SlateEditor } from 'slate';
import
LanguageProvider
,
{
LABEL_REFRESH_INTERVAL
,
LokiHistoryItem
,
rangeToParams
}
from
'./language_provider'
;
import
{
AbsoluteTimeRange
}
from
'@grafana/data'
;
import
{
TypeaheadInput
}
from
'@grafana/ui'
;
import
{
advanceTo
,
clear
,
advanceBy
}
from
'jest-date-mock'
;
import
{
beforeEach
}
from
'test/lib/common'
;
import
{
TypeaheadInput
}
from
'../../../types'
;
import
{
makeMockLokiDatasource
}
from
'./mocks'
;
import
LokiDatasource
from
'./datasource'
;
...
...
public/app/plugins/datasource/loki/language_provider.ts
View file @
3e8c00da
...
...
@@ -6,12 +6,12 @@ import { parseSelector, labelRegexp, selectorRegexp } from 'app/plugins/datasour
import
syntax
from
'./syntax'
;
// Types
import
{
CompletionItem
,
LanguageProvider
,
TypeaheadInput
,
TypeaheadOutput
,
HistoryItem
}
from
'app/types/explore'
;
import
{
LokiQuery
}
from
'./types'
;
import
{
dateTime
,
AbsoluteTimeRange
}
from
'@grafana/data'
;
import
{
PromQuery
}
from
'../prometheus/types'
;
import
LokiDatasource
from
'./datasource'
;
import
{
CompletionItem
,
TypeaheadInput
,
TypeaheadOutput
,
LanguageProvider
,
HistoryItem
}
from
'@grafana/ui'
;
const
DEFAULT_KEYS
=
[
'job'
,
'namespace'
];
const
EMPTY_SELECTOR
=
'{}'
;
...
...
public/app/plugins/datasource/prometheus/components/PromQueryField.tsx
View file @
3e8c00da
...
...
@@ -3,21 +3,19 @@ import React from 'react';
// @ts-ignore
import
Cascader
from
'rc-cascader'
;
import
{
SlatePrism
}
from
'@grafana/ui'
;
import
{
Plugin
}
from
'slate'
;
import
{
SlatePrism
,
TypeaheadInput
,
TypeaheadOutput
,
QueryField
,
BracesPlugin
,
HistoryItem
}
from
'@grafana/ui'
;
import
Prism
from
'prismjs'
;
import
{
TypeaheadOutput
,
HistoryItem
}
from
'app/types/explore'
;
// dom also includes Element polyfills
import
BracesPlugin
from
'app/features/explore/slate-plugins/braces'
;
import
QueryField
,
{
TypeaheadInput
}
from
'app/features/explore/QueryField'
;
import
{
PromQuery
,
PromContext
,
PromOptions
}
from
'../types'
;
import
{
CancelablePromise
,
makePromiseCancelable
}
from
'app/core/utils/CancelablePromise'
;
import
{
ExploreQueryFieldProps
,
QueryHint
,
DOMUtil
}
from
'@grafana/ui'
;
import
{
isDataFrame
,
toLegacyResponseData
}
from
'@grafana/data'
;
import
{
SuggestionsState
}
from
'@grafana/ui'
;
import
{
PrometheusDatasource
}
from
'../datasource'
;
import
PromQlLanguageProvider
from
'../language_provider'
;
import
{
SuggestionsState
}
from
'app/features/explore/slate-plugins/suggestions'
;
const
HISTOGRAM_GROUP
=
'__histograms__'
;
const
METRIC_MARK
=
'metric'
;
...
...
@@ -114,7 +112,7 @@ interface PromQueryFieldState {
}
class
PromQueryField
extends
React
.
PureComponent
<
PromQueryFieldProps
,
PromQueryFieldState
>
{
plugins
:
any
[];
plugins
:
Plugin
[];
languageProvider
:
PromQlLanguageProvider
;
languageProviderInitializationPromise
:
CancelablePromise
<
any
>
;
...
...
public/app/plugins/datasource/prometheus/language_provider.ts
View file @
3e8c00da
import
_
from
'lodash'
;
import
{
dateTime
}
from
'@grafana/data'
;
import
{
CompletionItem
,
CompletionItemGroup
,
LanguageProvider
,
TypeaheadInput
,
TypeaheadOutput
,
CompletionItemGroup
,
LanguageProvider
,
HistoryItem
,
}
from
'
app/types/explore
'
;
}
from
'
@grafana/ui
'
;
import
{
parseSelector
,
processLabels
,
processHistogramLabels
}
from
'./language_utils'
;
import
PromqlSyntax
,
{
FUNCTIONS
,
RATE_RANGES
}
from
'./promql'
;
...
...
public/app/plugins/datasource/prometheus/promql.ts
View file @
3e8c00da
/* tslint:disable max-line-length */
import
{
CompletionItem
}
from
'app/types/explore'
;
import
{
CompletionItem
}
from
'@grafana/ui'
;
export
const
RATE_RANGES
:
CompletionItem
[]
=
[
{
label
:
'$__interval'
,
sortText
:
'$__interval'
},
...
...
public/app/plugins/datasource/prometheus/specs/language_provider.test.ts
View file @
3e8c00da
...
...
@@ -2,7 +2,7 @@ import Plain from 'slate-plain-serializer';
import
{
Editor
as
SlateEditor
}
from
'slate'
;
import
LanguageProvider
from
'../language_provider'
;
import
{
PrometheusDatasource
}
from
'../datasource'
;
import
{
HistoryItem
}
from
'
app/types
'
;
import
{
HistoryItem
}
from
'
@grafana/ui
'
;
import
{
PromQuery
}
from
'../types'
;
describe
(
'Language completion provider'
,
()
=>
{
...
...
public/app/types/explore.ts
View file @
3e8c00da
...
...
@@ -8,6 +8,7 @@ import {
ExploreStartPageProps
,
PanelData
,
DataQueryRequest
,
HistoryItem
,
}
from
'@grafana/ui'
;
import
{
...
...
@@ -23,100 +24,11 @@ import {
import
{
Emitter
}
from
'app/core/core'
;
import
TableModel
from
'app/core/table_model'
;
import
{
Value
}
from
'slate'
;
import
{
Editor
}
from
'@grafana/slate-react'
;
export
enum
ExploreMode
{
Metrics
=
'Metrics'
,
Logs
=
'Logs'
,
}
export
enum
CompletionItemKind
{
GroupTitle
=
'GroupTitle'
,
}
export
interface
CompletionItem
{
/**
* The label of this completion item. By default
* this is also the text that is inserted when selecting
* this completion.
*/
label
:
string
;
/**
* The kind of this completion item. An icon is chosen
* by the editor based on the kind.
*/
kind
?:
CompletionItemKind
|
string
;
/**
* A human-readable string with additional information
* about this item, like type or symbol information.
*/
detail
?:
string
;
/**
* A human-readable string, can be Markdown, that represents a doc-comment.
*/
documentation
?:
string
;
/**
* A string that should be used when comparing this item
* with other items. When `falsy` the `label` is used.
*/
sortText
?:
string
;
/**
* A string that should be used when filtering a set of
* completion items. When `falsy` the `label` is used.
*/
filterText
?:
string
;
/**
* A string or snippet that should be inserted in a document when selecting
* this completion. When `falsy` the `label` is used.
*/
insertText
?:
string
;
/**
* Delete number of characters before the caret position,
* by default the letters from the beginning of the word.
*/
deleteBackwards
?:
number
;
/**
* Number of steps to move after the insertion, can be negative.
*/
move
?:
number
;
}
export
interface
CompletionItemGroup
{
/**
* Label that will be displayed for all entries of this group.
*/
label
:
string
;
/**
* List of suggestions of this group.
*/
items
:
CompletionItem
[];
/**
* If true, match only by prefix (and not mid-word).
*/
prefixMatch
?:
boolean
;
/**
* If true, do not filter items in this group based on the search.
*/
skipFilter
?:
boolean
;
/**
* If true, do not sort items.
*/
skipSort
?:
boolean
;
}
export
enum
ExploreId
{
left
=
'left'
,
right
=
'right'
,
...
...
@@ -308,36 +220,6 @@ export interface ExploreUrlState {
context
?:
string
;
}
export
interface
HistoryItem
<
TQuery
extends
DataQuery
=
DataQuery
>
{
ts
:
number
;
query
:
TQuery
;
}
export
abstract
class
LanguageProvider
{
datasource
:
DataSourceApi
;
request
:
(
url
:
string
,
params
?:
any
)
=>
Promise
<
any
>
;
/**
* Returns startTask that resolves with a task list when main syntax is loaded.
* Task list consists of secondary promises that load more detailed language features.
*/
start
:
()
=>
Promise
<
any
[]
>
;
startTask
?:
Promise
<
any
[]
>
;
}
export
interface
TypeaheadInput
{
text
:
string
;
prefix
:
string
;
wrapperClasses
:
string
[];
labelKey
?:
string
;
value
?:
Value
;
editor
?:
Editor
;
}
export
interface
TypeaheadOutput
{
context
?:
string
;
suggestions
:
CompletionItemGroup
[];
}
export
interface
QueryIntervals
{
interval
:
string
;
intervalMs
:
number
;
...
...
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