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
e846a26c
Unverified
Commit
e846a26c
authored
Feb 14, 2020
by
Dominik Prokop
Committed by
GitHub
Feb 14, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
FieldOverrides: FieldOverrides UI (#22187)
parent
f2fc7aa3
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
342 additions
and
93 deletions
+342
-93
packages/grafana-ui/src/components/ValuePicker/ValuePicker.tsx
+4
-3
public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx
+82
-0
public/app/features/dashboard/components/PanelEditor/FieldConfigEditor.tsx
+33
-87
public/app/features/dashboard/components/PanelEditor/OverrideEditor.tsx
+177
-0
public/app/features/dashboard/components/PanelEditor/OverrideHeader.tsx
+39
-0
public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx
+7
-3
No files found.
packages/grafana-ui/src/components/ValuePicker/ValuePicker.tsx
View file @
e846a26c
import
React
,
{
useState
}
from
'react'
;
import
{
IconType
}
from
'../Icon/types'
;
import
{
SelectableValue
}
from
'@grafana/data'
;
import
{
Button
}
from
'../Forms/Button'
;
import
{
Button
,
ButtonVariant
}
from
'../Forms/Button'
;
import
{
Select
}
from
'../Forms/Select/Select'
;
interface
ValuePickerProps
<
T
>
{
...
...
@@ -12,15 +12,16 @@ interface ValuePickerProps<T> {
/** ValuePicker options */
options
:
Array
<
SelectableValue
<
T
>>
;
onChange
:
(
value
:
SelectableValue
<
T
>
)
=>
void
;
variant
?:
ButtonVariant
;
}
export
function
ValuePicker
<
T
>
({
label
,
icon
,
options
,
onChange
}:
ValuePickerProps
<
T
>
)
{
export
function
ValuePicker
<
T
>
({
label
,
icon
,
options
,
onChange
,
variant
}:
ValuePickerProps
<
T
>
)
{
const
[
isPicking
,
setIsPicking
]
=
useState
(
false
);
return
(
<>
{
!
isPicking
&&
(
<
Button
icon=
{
`fa fa-${icon || 'plus'}`
}
onClick=
{
()
=>
setIsPicking
(
true
)
}
>
<
Button
onClick=
{
()
=>
setIsPicking
(
true
)
}
variant=
{
variant
}
icon=
{
`fa fa-${icon}`
}
>
{
label
}
</
Button
>
)
}
...
...
public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx
0 → 100644
View file @
e846a26c
import
React
from
'react'
;
import
{
DynamicConfigValue
,
FieldConfigEditorRegistry
,
FieldOverrideContext
,
GrafanaTheme
}
from
'@grafana/data'
;
import
{
selectThemeVariant
,
stylesFactory
,
useTheme
}
from
'@grafana/ui'
;
import
{
OverrideHeader
}
from
'./OverrideHeader'
;
import
{
css
}
from
'emotion'
;
interface
DynamicConfigValueEditorProps
{
property
:
DynamicConfigValue
;
editorsRegistry
:
FieldConfigEditorRegistry
;
onChange
:
(
value
:
DynamicConfigValue
)
=>
void
;
context
:
FieldOverrideContext
;
onRemove
:
()
=>
void
;
}
export
const
DynamicConfigValueEditor
:
React
.
FC
<
DynamicConfigValueEditorProps
>
=
({
property
,
context
,
editorsRegistry
,
onChange
,
onRemove
,
})
=>
{
const
theme
=
useTheme
();
const
styles
=
getStyles
(
theme
);
const
item
=
editorsRegistry
?.
getIfExists
(
property
.
prop
);
return
(
<
div
className=
{
styles
.
wrapper
}
>
<
OverrideHeader
onRemove=
{
onRemove
}
title=
{
item
.
name
}
description=
{
item
.
description
}
/>
<
div
className=
{
styles
.
property
}
>
<
item
.
override
value=
{
property
.
value
}
onChange=
{
value
=>
{
onChange
(
value
);
}
}
item=
{
item
}
context=
{
context
}
/>
</
div
>
</
div
>
);
};
const
getStyles
=
stylesFactory
((
theme
:
GrafanaTheme
)
=>
{
const
borderColor
=
selectThemeVariant
(
{
light
:
theme
.
colors
.
gray85
,
dark
:
theme
.
colors
.
dark9
,
},
theme
.
type
);
const
highlightColor
=
selectThemeVariant
(
{
light
:
theme
.
colors
.
blueLight
,
dark
:
theme
.
colors
.
blueShade
,
},
theme
.
type
);
return
{
wrapper
:
css
`
border-top: 1px dashed
${
borderColor
}
;
position: relative;
&:hover {
&:before {
background:
${
highlightColor
}
;
}
}
&:before {
content: '';
position: absolute;
top: 0;
z-index: 1;
left: -1px;
width: 2px;
height: 100%;
transition: background 0.5s cubic-bezier(0.19, 1, 0.22, 1);
}
`
,
property
:
css
`
padding:
${
theme
.
spacing
.
xs
}
${
theme
.
spacing
.
sm
}
${
theme
.
spacing
.
sm
}
${
theme
.
spacing
.
sm
}
;
`
,
};
});
public/app/features/dashboard/components/PanelEditor/FieldConfigEditor.tsx
View file @
e846a26c
...
...
@@ -5,13 +5,14 @@ import {
FieldConfigSource
,
DataFrame
,
FieldPropertyEditorItem
,
DynamicConfigValue
,
VariableSuggestionsScope
,
standardFieldConfigEditorRegistry
,
SelectableValue
,
}
from
'@grafana/data'
;
import
{
Forms
,
fieldMatchersUI
,
ValuePicker
}
from
'@grafana/ui'
;
import
{
getDataLinksVariableSuggestions
}
from
'../../../panel/panellinks/link_srv'
;
import
{
OptionsGroup
}
from
'./OptionsGroup'
;
import
{
OverrideEditor
}
from
'./OverrideEditor'
;
interface
Props
{
config
:
FieldConfigSource
;
...
...
@@ -53,39 +54,34 @@ export class FieldConfigEditor extends React.PureComponent<Props> {
});
};
on
MatcherConfigChange
=
(
index
:
number
,
matcherConfig
?
:
any
)
=>
{
on
OverrideChange
=
(
index
:
number
,
override
:
any
)
=>
{
const
{
config
}
=
this
.
props
;
let
overrides
=
cloneDeep
(
config
.
overrides
);
if
(
matcherConfig
===
undefined
)
{
overrides
=
overrides
.
splice
(
index
,
1
);
}
else
{
overrides
[
index
].
matcher
.
options
=
matcherConfig
;
}
overrides
[
index
]
=
override
;
this
.
props
.
onChange
({
...
config
,
overrides
});
};
on
DynamicConfigValueAdd
=
(
index
:
number
,
prop
:
string
,
custom
?:
boolean
)
=>
{
on
OverrideRemove
=
(
overrideIndex
:
number
)
=>
{
const
{
config
}
=
this
.
props
;
let
overrides
=
cloneDeep
(
config
.
overrides
);
const
propertyConfig
:
DynamicConfigValue
=
{
prop
,
custom
,
};
if
(
overrides
[
index
].
properties
)
{
overrides
[
index
].
properties
.
push
(
propertyConfig
);
}
else
{
overrides
[
index
].
properties
=
[
propertyConfig
];
}
overrides
.
splice
(
overrideIndex
,
1
);
this
.
props
.
onChange
({
...
config
,
overrides
});
};
onDynamicConfigValueChange
=
(
overrideIndex
:
number
,
propertyIndex
:
number
,
value
?:
any
)
=>
{
const
{
config
}
=
this
.
props
;
let
overrides
=
cloneDeep
(
config
.
overrides
);
overrides
[
overrideIndex
].
properties
[
propertyIndex
].
value
=
value
;
this
.
props
.
onChange
({
...
config
,
overrides
});
onOverrideAdd
=
(
value
:
SelectableValue
<
string
>
)
=>
{
const
{
onChange
,
config
}
=
this
.
props
;
onChange
({
...
config
,
overrides
:
[
...
config
.
overrides
,
{
matcher
:
{
id
:
value
.
value
!
,
},
properties
:
[],
},
],
});
};
renderEditor
(
item
:
FieldPropertyEditorItem
,
custom
:
boolean
)
{
...
...
@@ -151,55 +147,17 @@ export class FieldConfigEditor extends React.PureComponent<Props> {
return
(
<
div
>
{
config
.
overrides
.
map
((
o
,
i
)
=>
{
const
matcherUi
=
fieldMatchersUI
.
get
(
o
.
matcher
.
id
);
// TODO: apply matcher to retrieve fields
return
(
<
div
key=
{
`${o.matcher.id}/${i}`
}
style=
{
{
border
:
`2px solid red`
,
marginBottom
:
'10px'
}
}
>
<
Forms
.
Field
label=
{
matcherUi
.
name
}
description=
{
matcherUi
.
description
}
>
<>
<
matcherUi
.
component
matcher=
{
matcherUi
.
matcher
}
data=
{
data
}
options=
{
o
.
matcher
.
options
}
onChange=
{
option
=>
this
.
onMatcherConfigChange
(
i
,
option
)
}
/>
<
div
style=
{
{
border
:
`2px solid blue`
,
marginBottom
:
'5px'
}
}
>
{
o
.
properties
.
map
((
p
,
j
)
=>
{
const
reg
=
p
.
custom
?
custom
:
standardFieldConfigEditorRegistry
;
const
item
=
reg
?.
getIfExists
(
p
.
prop
);
if
(
!
item
)
{
return
<
div
>
Unknown property:
{
p
.
prop
}
</
div
>;
}
return
(
<
Forms
.
Field
label=
{
item
.
name
}
description=
{
item
.
description
}
>
<
item
.
override
value=
{
p
.
value
}
onChange=
{
value
=>
{
this
.
onDynamicConfigValueChange
(
i
,
j
,
value
);
}
}
item=
{
item
}
context=
{
{
data
,
getSuggestions
:
(
scope
?:
VariableSuggestionsScope
)
=>
getDataLinksVariableSuggestions
(
data
,
scope
),
}
}
/>
</
Forms
.
Field
>
);
})
}
<
ValuePicker
icon=
"plus"
label=
"Set config property"
options=
{
configPropertiesOptions
}
onChange=
{
o
=>
{
this
.
onDynamicConfigValueAdd
(
i
,
o
.
value
!
,
o
.
custom
);
}
}
/>
</
div
>
</>
</
Forms
.
Field
>
</
div
>
<
OverrideEditor
key=
{
`${o.matcher.id}/${i}`
}
data=
{
data
}
override=
{
o
}
onChange=
{
value
=>
this
.
onOverrideChange
(
i
,
value
)
}
onRemove=
{
()
=>
this
.
onOverrideRemove
(
i
)
}
configPropertiesOptions=
{
configPropertiesOptions
}
customPropertiesRegistry=
{
custom
}
/>
);
})
}
</
div
>
...
...
@@ -211,22 +169,10 @@ export class FieldConfigEditor extends React.PureComponent<Props> {
<
ValuePicker
icon=
"plus"
label=
"Add override"
options=
{
fieldMatchersUI
.
list
().
map
(
i
=>
({
label
:
i
.
name
,
value
:
i
.
id
,
description
:
i
.
description
}))
}
onChange=
{
value
=>
{
const
{
onChange
,
config
}
=
this
.
props
;
onChange
({
...
config
,
overrides
:
[
...
config
.
overrides
,
{
matcher
:
{
id
:
value
.
value
!
,
},
properties
:
[],
},
],
});
}
}
options=
{
fieldMatchersUI
.
list
()
.
map
<
SelectableValue
<
string
>>
(
i
=>
({
label
:
i
.
name
,
value
:
i
.
id
,
description
:
i
.
description
}))
}
onChange=
{
value
=>
this
.
onOverrideAdd
(
value
)
}
/>
);
};
...
...
public/app/features/dashboard/components/PanelEditor/OverrideEditor.tsx
0 → 100644
View file @
e846a26c
import
React
,
{
useCallback
}
from
'react'
;
import
{
ConfigOverrideRule
,
DataFrame
,
DynamicConfigValue
,
FieldConfigEditorRegistry
,
standardFieldConfigEditorRegistry
,
VariableSuggestionsScope
,
SelectableValue
,
GrafanaTheme
,
}
from
'@grafana/data'
;
import
{
fieldMatchersUI
,
stylesFactory
,
useTheme
,
ValuePicker
,
selectThemeVariant
}
from
'@grafana/ui'
;
import
{
DynamicConfigValueEditor
}
from
'./DynamicConfigValueEditor'
;
import
{
OverrideHeader
}
from
'./OverrideHeader'
;
import
{
getDataLinksVariableSuggestions
}
from
'../../../panel/panellinks/link_srv'
;
import
{
css
}
from
'emotion'
;
interface
OverrideEditorProps
{
data
:
DataFrame
[];
override
:
ConfigOverrideRule
;
onChange
:
(
config
:
ConfigOverrideRule
)
=>
void
;
onRemove
:
()
=>
void
;
customPropertiesRegistry
?:
FieldConfigEditorRegistry
;
configPropertiesOptions
:
Array
<
SelectableValue
<
string
>>
;
}
export
const
OverrideEditor
:
React
.
FC
<
OverrideEditorProps
>
=
({
data
,
override
,
onChange
,
onRemove
,
customPropertiesRegistry
,
configPropertiesOptions
,
})
=>
{
const
theme
=
useTheme
();
const
onMatcherConfigChange
=
useCallback
(
(
matcherConfig
:
any
)
=>
{
override
.
matcher
.
options
=
matcherConfig
;
onChange
(
override
);
},
[
override
,
onChange
]
);
const
onDynamicConfigValueChange
=
useCallback
(
(
index
:
number
,
value
:
DynamicConfigValue
)
=>
{
override
.
properties
[
index
].
value
=
value
;
onChange
(
override
);
},
[
override
,
onChange
]
);
const
onDynamicConfigValueRemove
=
useCallback
(
(
index
:
number
)
=>
{
override
.
properties
.
splice
(
index
,
1
);
onChange
(
override
);
},
[
override
,
onChange
]
);
const
onDynamicConfigValueAdd
=
useCallback
(
(
prop
:
string
,
custom
?:
boolean
)
=>
{
const
propertyConfig
:
DynamicConfigValue
=
{
prop
,
custom
,
};
if
(
override
.
properties
)
{
override
.
properties
.
push
(
propertyConfig
);
}
else
{
override
.
properties
=
[
propertyConfig
];
}
onChange
(
override
);
},
[
override
,
onChange
]
);
const
matcherUi
=
fieldMatchersUI
.
get
(
override
.
matcher
.
id
);
const
styles
=
getStyles
(
theme
);
return
(
<
div
className=
{
styles
.
wrapper
}
>
<
div
className=
{
styles
.
headerWrapper
}
>
<
OverrideHeader
onRemove=
{
onRemove
}
title=
{
matcherUi
.
name
}
description=
{
matcherUi
.
description
}
/>
<
div
className=
{
styles
.
matcherUi
}
>
<
matcherUi
.
component
matcher=
{
matcherUi
.
matcher
}
data=
{
data
}
options=
{
override
.
matcher
.
options
}
onChange=
{
option
=>
onMatcherConfigChange
(
option
)
}
/>
</
div
>
</
div
>
<
div
>
{
override
.
properties
.
map
((
p
,
j
)
=>
{
const
reg
=
p
.
custom
?
customPropertiesRegistry
:
standardFieldConfigEditorRegistry
;
const
item
=
reg
?.
getIfExists
(
p
.
prop
);
if
(
!
item
)
{
return
<
div
>
Unknown property:
{
p
.
prop
}
</
div
>;
}
return
(
<
div
key=
{
`${p.prop}/${j}`
}
>
<
DynamicConfigValueEditor
onChange=
{
value
=>
onDynamicConfigValueChange
(
j
,
value
)
}
onRemove=
{
()
=>
onDynamicConfigValueRemove
(
j
)
}
property=
{
p
}
editorsRegistry=
{
reg
}
context=
{
{
data
,
getSuggestions
:
(
scope
?:
VariableSuggestionsScope
)
=>
getDataLinksVariableSuggestions
(
data
,
scope
),
}
}
/>
</
div
>
);
})
}
<
div
className=
{
styles
.
propertyPickerWrapper
}
>
<
ValuePicker
label=
"Set config property"
icon=
"plus"
options=
{
configPropertiesOptions
}
variant=
{
'link'
}
onChange=
{
o
=>
{
onDynamicConfigValueAdd
(
o
.
value
,
o
.
custom
);
}
}
/>
</
div
>
</
div
>
</
div
>
);
};
const
getStyles
=
stylesFactory
((
theme
:
GrafanaTheme
)
=>
{
const
borderColor
=
selectThemeVariant
(
{
light
:
theme
.
colors
.
gray85
,
dark
:
theme
.
colors
.
dark9
,
},
theme
.
type
);
const
headerBg
=
selectThemeVariant
(
{
light
:
theme
.
colors
.
white
,
dark
:
theme
.
colors
.
dark1
,
},
theme
.
type
);
const
shadow
=
selectThemeVariant
(
{
light
:
theme
.
colors
.
gray85
,
dark
:
theme
.
colors
.
black
,
},
theme
.
type
);
return
{
wrapper
:
css
`
border: 1px dashed
${
borderColor
}
;
margin-bottom:
${
theme
.
spacing
.
md
}
;
transition: box-shadow 0.5s cubic-bezier(0.19, 1, 0.22, 1);
box-shadow: none;
&:hover {
box-shadow: 0 0 10px
${
shadow
}
;
}
`
,
headerWrapper
:
css
`
background:
${
headerBg
}
;
padding:
${
theme
.
spacing
.
xs
}
0;
`
,
matcherUi
:
css
`
padding:
${
theme
.
spacing
.
sm
}
;
`
,
propertyPickerWrapper
:
css
`
border-top: 1px solid
${
borderColor
}
;
`
,
};
});
public/app/features/dashboard/components/PanelEditor/OverrideHeader.tsx
0 → 100644
View file @
e846a26c
import
React
from
'react'
;
import
{
Forms
,
Icon
,
stylesFactory
,
useTheme
}
from
'@grafana/ui'
;
import
{
GrafanaTheme
}
from
'@grafana/data'
;
import
{
css
}
from
'emotion'
;
interface
OverrideHeaderProps
{
title
:
string
;
description
?:
string
;
onRemove
:
()
=>
void
;
}
export
const
OverrideHeader
:
React
.
FC
<
OverrideHeaderProps
>
=
({
title
,
description
,
onRemove
})
=>
{
const
theme
=
useTheme
();
const
styles
=
getOverrideHeaderStyles
(
theme
);
return
(
<
div
className=
{
styles
.
header
}
>
<
Forms
.
Label
description=
{
description
}
>
{
title
}
</
Forms
.
Label
>
<
div
className=
{
styles
.
remove
}
onClick=
{
()
=>
onRemove
()
}
>
<
Icon
name=
"trash"
/>
</
div
>
</
div
>
);
};
const
getOverrideHeaderStyles
=
stylesFactory
((
theme
:
GrafanaTheme
)
=>
{
return
{
header
:
css
`
display: flex;
justify-content: space-between;
padding:
${
theme
.
spacing
.
xs
}
${
theme
.
spacing
.
xs
}
0
${
theme
.
spacing
.
xs
}
;
`
,
remove
:
css
`
flex-grow: 0;
flex-shrink: 0;
cursor: pointer;
color:
${
theme
.
colors
.
red88
}
;
`
,
};
});
public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx
View file @
e846a26c
import
React
,
{
PureComponent
}
from
'react'
;
import
{
GrafanaTheme
,
FieldConfigSource
,
PanelData
,
PanelPlugin
,
SelectableValue
}
from
'@grafana/data'
;
import
{
stylesFactory
,
Forms
,
CustomScrollbar
,
selectThemeVariant
}
from
'@grafana/ui'
;
import
{
stylesFactory
,
Forms
,
CustomScrollbar
,
selectThemeVariant
,
Icon
}
from
'@grafana/ui'
;
import
{
css
,
cx
}
from
'emotion'
;
import
config
from
'app/core/config'
;
import
AutoSizer
from
'react-virtualized-auto-sizer'
;
...
...
@@ -219,7 +219,7 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
<
div
className=
{
styles
.
toolbar
}
>
<
div
className=
{
styles
.
toolbarLeft
}
>
<
button
className=
"navbar-edit__back-btn"
onClick=
{
this
.
onPanelExit
}
>
<
i
className=
"fa fa-arrow-left"
></
i
>
<
Icon
name=
"arrow-left"
/
>
</
button
>
<
PanelTitle
value=
{
panel
.
title
}
onChange=
{
this
.
onPanelTitleChange
}
/>
</
div
>
...
...
@@ -265,7 +265,11 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
>
{
this
.
renderHorizontalSplit
(
styles
)
}
<
div
className=
{
styles
.
panelOptionsPane
}
>
<
CustomScrollbar
>
<
CustomScrollbar
className=
{
css
`
height: 100% !important;
`
}
>
{
this
.
renderFieldOptions
()
}
<
OptionsGroup
title=
"Old settings"
>
{
this
.
renderVisSettings
()
}
</
OptionsGroup
>
</
CustomScrollbar
>
...
...
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