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
bedd662c
Unverified
Commit
bedd662c
authored
Jan 18, 2021
by
Dominik Prokop
Committed by
GitHub
Jan 18, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Panel options UI: Allow collapsible categories (#30301)
parent
6a2b0dde
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
214 additions
and
18 deletions
+214
-18
packages/grafana-data/src/panel/registryFactories.ts
+0
-1
packages/grafana-data/src/types/options.ts
+1
-1
packages/grafana-e2e-selectors/src/selectors/components.ts
+1
-1
public/app/features/dashboard/components/PanelEditor/DefaultFieldConfigEditor.test.tsx
+174
-0
public/app/features/dashboard/components/PanelEditor/DefaultFieldConfigEditor.tsx
+28
-11
public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx
+4
-1
public/app/features/dashboard/components/PanelEditor/PanelOptionsEditor.tsx
+6
-3
No files found.
packages/grafana-data/src/panel/registryFactories.ts
View file @
bedd662c
...
@@ -24,7 +24,6 @@ export function createFieldConfigRegistry<TFieldConfigOptions>(
...
@@ -24,7 +24,6 @@ export function createFieldConfigRegistry<TFieldConfigOptions>(
for
(
const
customProp
of
builder
.
getRegistry
().
list
())
{
for
(
const
customProp
of
builder
.
getRegistry
().
list
())
{
customProp
.
isCustom
=
true
;
customProp
.
isCustom
=
true
;
customProp
.
category
=
[
`
${
pluginName
}
options`
].
concat
(
customProp
.
category
||
[]);
// need to do something to make the custom items not conflict with standard ones
// need to do something to make the custom items not conflict with standard ones
// problem is id (registry index) is used as property path
// problem is id (registry index) is used as property path
// so sort of need a property path on the FieldPropertyEditorItem
// so sort of need a property path on the FieldPropertyEditorItem
...
...
packages/grafana-data/src/types/options.ts
View file @
bedd662c
...
@@ -39,7 +39,7 @@ export interface OptionEditorConfig<TOptions, TSettings = any, TValue = any> {
...
@@ -39,7 +39,7 @@ export interface OptionEditorConfig<TOptions, TSettings = any, TValue = any> {
/**
/**
* Array of strings representing category of the option. First element in the array will make option render as collapsible section.
* Array of strings representing category of the option. First element in the array will make option render as collapsible section.
*/
*/
category
?:
string
[]
;
category
?:
Array
<
string
|
undefined
>
;
/**
/**
* Set this value if undefined
* Set this value if undefined
...
...
packages/grafana-e2e-selectors/src/selectors/components.ts
View file @
bedd662c
...
@@ -127,7 +127,7 @@ export const Components = {
...
@@ -127,7 +127,7 @@ export const Components = {
backArrow
:
'Go Back button'
,
backArrow
:
'Go Back button'
,
},
},
OptionsGroup
:
{
OptionsGroup
:
{
toggle
:
(
title
:
string
)
=>
`Options group
${
title
}
`
,
toggle
:
(
title
?:
string
)
=>
(
title
?
`Options group
${
title
}
`
:
'Options group'
)
,
},
},
PluginVisualization
:
{
PluginVisualization
:
{
item
:
(
title
:
string
)
=>
`Plugin visualization item
${
title
}
`
,
item
:
(
title
:
string
)
=>
`Plugin visualization item
${
title
}
`
,
...
...
public/app/features/dashboard/components/PanelEditor/DefaultFieldConfigEditor.test.tsx
View file @
bedd662c
...
@@ -86,4 +86,178 @@ describe('DefaultFieldConfigEditor', () => {
...
@@ -86,4 +86,178 @@ describe('DefaultFieldConfigEditor', () => {
const
editors
=
queryAllByLabelText
(
selectors
.
components
.
PanelEditor
.
FieldOptions
.
propertyEditor
(
'Custom'
));
const
editors
=
queryAllByLabelText
(
selectors
.
components
.
PanelEditor
.
FieldOptions
.
propertyEditor
(
'Custom'
));
expect
(
editors
).
toHaveLength
(
2
);
expect
(
editors
).
toHaveLength
(
2
);
});
});
describe
(
'categories'
,
()
=>
{
it
(
'should render uncategorized options under panel category'
,
()
=>
{
const
plugin
=
new
PanelPlugin
(()
=>
null
).
useFieldConfig
({
standardOptions
:
{},
useCustomConfig
:
b
=>
{
b
.
addBooleanSwitch
({
name
:
'a'
,
path
:
'a'
,
}
as
FieldConfigEditorConfig
<
FakeFieldOptions
>
)
.
addBooleanSwitch
({
name
:
'c'
,
path
:
'c'
,
}
as
FieldConfigEditorConfig
<
FakeFieldOptions
>
)
.
addTextInput
({
name
:
'b'
,
path
:
'b'
,
}
as
FieldConfigEditorConfig
<
FakeFieldOptions
>
);
},
});
plugin
.
meta
.
name
=
'Test plugin'
;
const
{
queryAllByLabelText
}
=
render
(
<
DefaultFieldConfigEditor
data=
{
[]
}
onChange=
{
jest
.
fn
()
}
plugin=
{
plugin
}
config=
{
fieldConfigMock
}
/>
);
expect
(
queryAllByLabelText
(
selectors
.
components
.
OptionsGroup
.
toggle
(
`
${
plugin
.
meta
.
name
}
options/0`
))
).
toHaveLength
(
1
);
expect
(
queryAllByLabelText
(
selectors
.
components
.
OptionsGroup
.
toggle
(),
{
exact
:
false
})).
toHaveLength
(
1
);
});
it
(
'should render categorized options under custom category'
,
()
=>
{
const
CATEGORY_NAME
=
'Cat1'
;
const
plugin
=
new
PanelPlugin
(()
=>
null
).
useFieldConfig
({
standardOptions
:
{},
useCustomConfig
:
b
=>
{
b
.
addTextInput
({
name
:
'b'
,
path
:
'b'
,
}
as
FieldConfigEditorConfig
<
FakeFieldOptions
>
)
.
addBooleanSwitch
({
name
:
'a'
,
path
:
'a'
,
category
:
[
CATEGORY_NAME
],
}
as
FieldConfigEditorConfig
<
FakeFieldOptions
>
)
.
addBooleanSwitch
({
name
:
'c'
,
path
:
'c'
,
category
:
[
CATEGORY_NAME
],
}
as
FieldConfigEditorConfig
<
FakeFieldOptions
>
);
},
});
plugin
.
meta
.
name
=
'Test plugin'
;
const
{
queryAllByLabelText
}
=
render
(
<
DefaultFieldConfigEditor
data=
{
[]
}
onChange=
{
jest
.
fn
()
}
plugin=
{
plugin
}
config=
{
fieldConfigMock
}
/>
);
expect
(
queryAllByLabelText
(
selectors
.
components
.
OptionsGroup
.
toggle
(
`
${
plugin
.
meta
.
name
}
options/0`
))
).
toHaveLength
(
1
);
expect
(
queryAllByLabelText
(
selectors
.
components
.
OptionsGroup
.
toggle
(
`
${
CATEGORY_NAME
}
/1`
))).
toHaveLength
(
1
);
expect
(
queryAllByLabelText
(
selectors
.
components
.
OptionsGroup
.
toggle
(),
{
exact
:
false
})).
toHaveLength
(
2
);
});
it
(
'should allow subcategories in panel category'
,
()
=>
{
const
SUBCATEGORY_NAME
=
'Sub1'
;
const
plugin
=
new
PanelPlugin
(()
=>
null
).
useFieldConfig
({
standardOptions
:
{},
useCustomConfig
:
b
=>
{
b
.
addTextInput
({
name
:
'b'
,
path
:
'b'
,
category
:
[
undefined
,
SUBCATEGORY_NAME
],
}
as
FieldConfigEditorConfig
<
FakeFieldOptions
>
)
.
addBooleanSwitch
({
name
:
'a'
,
path
:
'a'
,
}
as
FieldConfigEditorConfig
<
FakeFieldOptions
>
)
.
addBooleanSwitch
({
name
:
'c'
,
path
:
'c'
,
}
as
FieldConfigEditorConfig
<
FakeFieldOptions
>
);
},
});
plugin
.
meta
.
name
=
'Test plugin'
;
const
{
queryAllByLabelText
,
queryAllByText
}
=
render
(
<
DefaultFieldConfigEditor
data=
{
[]
}
onChange=
{
jest
.
fn
()
}
plugin=
{
plugin
}
config=
{
fieldConfigMock
}
/>
);
expect
(
queryAllByLabelText
(
selectors
.
components
.
OptionsGroup
.
toggle
(
`
${
plugin
.
meta
.
name
}
options/0`
))
).
toHaveLength
(
1
);
expect
(
queryAllByText
(
SUBCATEGORY_NAME
,
{
exact
:
false
})).
toHaveLength
(
1
);
});
it
(
'should allow subcategories in custom category'
,
()
=>
{
const
CATEGORY_NAME
=
'Cat1'
;
const
SUBCATEGORY_NAME
=
'Sub1'
;
const
plugin
=
new
PanelPlugin
(()
=>
null
).
useFieldConfig
({
standardOptions
:
{},
useCustomConfig
:
b
=>
{
b
.
addBooleanSwitch
({
name
:
'a'
,
path
:
'a'
,
}
as
FieldConfigEditorConfig
<
FakeFieldOptions
>
)
.
addBooleanSwitch
({
name
:
'c'
,
path
:
'c'
,
}
as
FieldConfigEditorConfig
<
FakeFieldOptions
>
)
.
addTextInput
({
name
:
'b'
,
path
:
'b'
,
category
:
[
CATEGORY_NAME
,
SUBCATEGORY_NAME
],
}
as
FieldConfigEditorConfig
<
FakeFieldOptions
>
);
},
});
plugin
.
meta
.
name
=
'Test plugin'
;
const
{
queryAllByLabelText
,
queryAllByText
}
=
render
(
<
DefaultFieldConfigEditor
data=
{
[]
}
onChange=
{
jest
.
fn
()
}
plugin=
{
plugin
}
config=
{
fieldConfigMock
}
/>
);
expect
(
queryAllByLabelText
(
selectors
.
components
.
OptionsGroup
.
toggle
(
`
${
plugin
.
meta
.
name
}
options/0`
))
).
toHaveLength
(
1
);
expect
(
queryAllByLabelText
(
selectors
.
components
.
OptionsGroup
.
toggle
(
`
${
CATEGORY_NAME
}
/1`
))).
toHaveLength
(
1
);
expect
(
queryAllByText
(
SUBCATEGORY_NAME
,
{
exact
:
false
})).
toHaveLength
(
1
);
});
it
(
'should not render categories with hidden fields only'
,
()
=>
{
const
CATEGORY_NAME
=
'Cat1'
;
const
SUBCATEGORY_NAME
=
'Sub1'
;
const
plugin
=
new
PanelPlugin
(()
=>
null
).
useFieldConfig
({
standardOptions
:
{},
useCustomConfig
:
b
=>
{
b
.
addBooleanSwitch
({
name
:
'a'
,
path
:
'a'
,
}
as
FieldConfigEditorConfig
<
FakeFieldOptions
>
)
.
addBooleanSwitch
({
name
:
'c'
,
path
:
'c'
,
}
as
FieldConfigEditorConfig
<
FakeFieldOptions
>
)
.
addTextInput
({
name
:
'b'
,
path
:
'b'
,
hideFromDefaults
:
true
,
category
:
[
CATEGORY_NAME
,
SUBCATEGORY_NAME
],
}
as
FieldConfigEditorConfig
<
FakeFieldOptions
>
);
},
});
plugin
.
meta
.
name
=
'Test plugin'
;
const
{
queryAllByLabelText
,
queryAllByText
}
=
render
(
<
DefaultFieldConfigEditor
data=
{
[]
}
onChange=
{
jest
.
fn
()
}
plugin=
{
plugin
}
config=
{
fieldConfigMock
}
/>
);
expect
(
queryAllByLabelText
(
selectors
.
components
.
OptionsGroup
.
toggle
(
`
${
plugin
.
meta
.
name
}
options/0`
))
).
toHaveLength
(
1
);
expect
(
queryAllByLabelText
(
selectors
.
components
.
OptionsGroup
.
toggle
(
`
${
CATEGORY_NAME
}
/1`
))).
toHaveLength
(
0
);
expect
(
queryAllByText
(
SUBCATEGORY_NAME
,
{
exact
:
false
})).
toHaveLength
(
0
);
});
});
});
});
public/app/features/dashboard/components/PanelEditor/DefaultFieldConfigEditor.tsx
View file @
bedd662c
...
@@ -34,7 +34,7 @@ export const DefaultFieldConfigEditor: React.FC<Props> = ({ data, onChange, conf
...
@@ -34,7 +34,7 @@ export const DefaultFieldConfigEditor: React.FC<Props> = ({ data, onChange, conf
:
get
(
defaults
,
item
.
path
);
:
get
(
defaults
,
item
.
path
);
let
label
:
ReactNode
|
undefined
=
(
let
label
:
ReactNode
|
undefined
=
(
<
Label
description=
{
item
.
description
}
category=
{
item
.
category
?.
slice
(
1
)
}
>
<
Label
description=
{
item
.
description
}
category=
{
item
.
category
?.
slice
(
1
)
as
string
[]
}
>
{
item
.
name
}
{
item
.
name
}
</
Label
>
</
Label
>
);
);
...
@@ -67,27 +67,39 @@ export const DefaultFieldConfigEditor: React.FC<Props> = ({ data, onChange, conf
...
@@ -67,27 +67,39 @@ export const DefaultFieldConfigEditor: React.FC<Props> = ({ data, onChange, conf
[
config
]
[
config
]
);
);
const
groupedConfigs
=
groupBy
(
plugin
.
fieldConfigRegistry
.
list
(),
i
=>
i
.
category
&&
i
.
category
[
0
]);
const
GENERAL_OPTIONS_CATEGORY
=
`
${
plugin
.
meta
.
name
}
options`
;
const
groupedConfigs
=
groupBy
(
plugin
.
fieldConfigRegistry
.
list
(),
i
=>
{
if
(
!
i
.
category
)
{
return
GENERAL_OPTIONS_CATEGORY
;
}
return
i
.
category
[
0
]
?
i
.
category
[
0
]
:
GENERAL_OPTIONS_CATEGORY
;
});
return
(
return
(
<
div
aria
-
label=
{
selectors
.
components
.
FieldConfigEditor
.
content
}
>
<
div
aria
-
label=
{
selectors
.
components
.
FieldConfigEditor
.
content
}
>
{
Object
.
keys
(
groupedConfigs
).
map
((
k
,
i
)
=>
{
{
Object
.
keys
(
groupedConfigs
).
map
((
groupName
,
i
)
=>
{
const
groupItemsCounter
=
countGroupItems
(
groupedConfigs
[
k
],
config
);
const
group
=
groupedConfigs
[
groupName
];
const
groupItemsCounter
=
countGroupItems
(
group
,
config
);
if
(
!
shouldRenderGroup
(
group
))
{
return
undefined
;
}
return
(
return
(
<
OptionsGroup
<
OptionsGroup
renderTitle=
{
isExpanded
=>
{
renderTitle=
{
isExpanded
=>
{
return
(
return
(
<>
<>
{
k
}
{
!
isExpanded
&&
groupItemsCounter
&&
<
Counter
value=
{
groupItemsCounter
}
/>
}
{
groupName
}
{
!
isExpanded
&&
groupItemsCounter
&&
<
Counter
value=
{
groupItemsCounter
}
/>
}
</>
</>
);
);
}
}
}
}
id=
{
`${
k
}/${i}`
}
id=
{
`${
groupName
}/${i}`
}
key=
{
`${
k
}/${i}`
}
key=
{
`${
groupName
}/${i}`
}
>
>
{
group
edConfigs
[
k
]
.
map
(
c
=>
{
{
group
.
map
(
c
=>
{
return
renderEditor
(
c
,
group
edConfigs
[
k
]
.
length
);
return
renderEditor
(
c
,
group
.
length
);
})
}
})
}
</
OptionsGroup
>
</
OptionsGroup
>
);
);
...
@@ -96,7 +108,7 @@ export const DefaultFieldConfigEditor: React.FC<Props> = ({ data, onChange, conf
...
@@ -96,7 +108,7 @@ export const DefaultFieldConfigEditor: React.FC<Props> = ({ data, onChange, conf
);
);
};
};
const
countGroupItems
=
(
group
:
FieldConfigPropertyItem
[],
config
:
FieldConfigSource
)
=>
{
function
countGroupItems
(
group
:
FieldConfigPropertyItem
[],
config
:
FieldConfigSource
)
{
let
counter
=
0
;
let
counter
=
0
;
for
(
const
item
of
group
)
{
for
(
const
item
of
group
)
{
...
@@ -111,4 +123,9 @@ const countGroupItems = (group: FieldConfigPropertyItem[], config: FieldConfigSo
...
@@ -111,4 +123,9 @@ const countGroupItems = (group: FieldConfigPropertyItem[], config: FieldConfigSo
}
}
return
counter
===
0
?
undefined
:
counter
;
return
counter
===
0
?
undefined
:
counter
;
};
}
function
shouldRenderGroup
(
group
:
FieldConfigPropertyItem
[])
{
const
hiddenPropertiesCount
=
group
.
filter
(
i
=>
i
.
hideFromDefaults
).
length
;
return
group
.
length
-
hiddenPropertiesCount
>
0
;
}
public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx
View file @
bedd662c
...
@@ -35,7 +35,10 @@ export const DynamicConfigValueEditor: React.FC<DynamicConfigValueEditorProps> =
...
@@ -35,7 +35,10 @@ export const DynamicConfigValueEditor: React.FC<DynamicConfigValueEditorProps> =
// eslint-disable-next-line react/display-name
// eslint-disable-next-line react/display-name
const
renderLabel
=
(
includeDescription
=
true
,
includeCounter
=
false
)
=>
(
isExpanded
=
false
)
=>
(
const
renderLabel
=
(
includeDescription
=
true
,
includeCounter
=
false
)
=>
(
isExpanded
=
false
)
=>
(
<
HorizontalGroup
justify=
"space-between"
>
<
HorizontalGroup
justify=
"space-between"
>
<
Label
category=
{
item
.
category
?.
splice
(
1
)
}
description=
{
includeDescription
?
item
.
description
:
undefined
}
>
<
Label
category=
{
item
.
category
?.
filter
(
c
=>
c
!==
undefined
)
as
string
[]
}
description=
{
includeDescription
?
item
.
description
:
undefined
}
>
{
item
.
name
}
{
item
.
name
}
{
!
isExpanded
&&
includeCounter
&&
item
.
getItemsCount
&&
<
Counter
value=
{
item
.
getItemsCount
(
property
.
value
)
}
/>
}
{
!
isExpanded
&&
includeCounter
&&
item
.
getItemsCount
&&
<
Counter
value=
{
item
.
getItemsCount
(
property
.
value
)
}
/>
}
</
Label
>
</
Label
>
...
...
public/app/features/dashboard/components/PanelEditor/PanelOptionsEditor.tsx
View file @
bedd662c
...
@@ -22,7 +22,7 @@ interface PanelOptionsEditorProps<TOptions> {
...
@@ -22,7 +22,7 @@ interface PanelOptionsEditorProps<TOptions> {
options
:
TOptions
;
options
:
TOptions
;
onChange
:
(
options
:
TOptions
)
=>
void
;
onChange
:
(
options
:
TOptions
)
=>
void
;
}
}
const
DISPLAY_OPTIONS_CATEGORY
=
'Display'
;
export
const
PanelOptionsEditor
:
React
.
FC
<
PanelOptionsEditorProps
<
any
>>
=
({
export
const
PanelOptionsEditor
:
React
.
FC
<
PanelOptionsEditorProps
<
any
>>
=
({
plugin
,
plugin
,
options
,
options
,
...
@@ -33,7 +33,10 @@ export const PanelOptionsEditor: React.FC<PanelOptionsEditorProps<any>> = ({
...
@@ -33,7 +33,10 @@ export const PanelOptionsEditor: React.FC<PanelOptionsEditorProps<any>> = ({
})
=>
{
})
=>
{
const
optionEditors
=
useMemo
<
Record
<
string
,
PanelOptionsEditorItem
[]
>>
(()
=>
{
const
optionEditors
=
useMemo
<
Record
<
string
,
PanelOptionsEditorItem
[]
>>
(()
=>
{
return
groupBy
(
plugin
.
optionEditors
.
list
(),
i
=>
{
return
groupBy
(
plugin
.
optionEditors
.
list
(),
i
=>
{
return
i
.
category
?
i
.
category
[
0
]
:
'Display'
;
if
(
!
i
.
category
)
{
return
DISPLAY_OPTIONS_CATEGORY
;
}
return
i
.
category
[
0
]
?
i
.
category
[
0
]
:
DISPLAY_OPTIONS_CATEGORY
;
});
});
},
[
plugin
]);
},
[
plugin
]);
...
@@ -62,7 +65,7 @@ export const PanelOptionsEditor: React.FC<PanelOptionsEditorProps<any>> = ({
...
@@ -62,7 +65,7 @@ export const PanelOptionsEditor: React.FC<PanelOptionsEditorProps<any>> = ({
}
}
const
label
=
(
const
label
=
(
<
Label
description=
{
e
.
description
}
category=
{
e
.
category
?.
slice
(
1
)
}
>
<
Label
description=
{
e
.
description
}
category=
{
e
.
category
?.
slice
(
1
)
as
string
[]
}
>
{
e
.
name
}
{
e
.
name
}
</
Label
>
</
Label
>
);
);
...
...
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