mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
TopNav: Support for deeper nesting in section nav (#52562)
This commit is contained in:
parent
8d938175f5
commit
5b275ca3f5
@ -52,7 +52,6 @@ const getTabStyles = (theme: GrafanaTheme2) => {
|
||||
margin-right: ${theme.spacing(2)};
|
||||
position: relative;
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
`,
|
||||
link: css`
|
||||
padding: 6px 12px;
|
||||
@ -83,8 +82,8 @@ const getTabStyles = (theme: GrafanaTheme2) => {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 4px;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
bottom: 2px;
|
||||
top: 2px;
|
||||
border-radius: 2px;
|
||||
background-image: linear-gradient(0deg, #f05a28 30%, #fbca0a 99%);
|
||||
}
|
||||
|
@ -234,7 +234,21 @@ func (hs *HTTPServer) getNavTree(c *models.ReqContext, hasEditPerm bool, prefs *
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
navTree = append(navTree, appLinks...)
|
||||
|
||||
// When topnav is enabled we can test new information architecture where plugins live in Apps category
|
||||
if hs.Features.IsEnabled(featuremgmt.FlagTopnav) {
|
||||
navTree = append(navTree, &dtos.NavLink{
|
||||
Text: "Apps",
|
||||
Icon: "apps",
|
||||
Description: "App plugins",
|
||||
Id: "apps",
|
||||
Children: appLinks,
|
||||
Section: dtos.NavSectionCore,
|
||||
Url: hs.Cfg.AppSubURL + "/apps",
|
||||
})
|
||||
} else {
|
||||
navTree = append(navTree, appLinks...)
|
||||
}
|
||||
|
||||
configNodes := []*dtos.NavLink{}
|
||||
|
||||
@ -335,8 +349,11 @@ func (hs *HTTPServer) getNavTree(c *models.ReqContext, hasEditPerm bool, prefs *
|
||||
})
|
||||
}
|
||||
|
||||
var configNode *dtos.NavLink
|
||||
var serverAdminNode *dtos.NavLink
|
||||
|
||||
if len(configNodes) > 0 {
|
||||
configNode := &dtos.NavLink{
|
||||
configNode = &dtos.NavLink{
|
||||
Id: dtos.NavIDCfg,
|
||||
Text: "Configuration",
|
||||
SubTitle: "Organization: " + c.OrgName,
|
||||
@ -352,10 +369,20 @@ func (hs *HTTPServer) getNavTree(c *models.ReqContext, hasEditPerm bool, prefs *
|
||||
adminNavLinks := hs.buildAdminNavLinks(c)
|
||||
|
||||
if len(adminNavLinks) > 0 {
|
||||
serverAdminNode := navlinks.GetServerAdminNode(adminNavLinks)
|
||||
serverAdminNode = navlinks.GetServerAdminNode(adminNavLinks)
|
||||
navTree = append(navTree, serverAdminNode)
|
||||
}
|
||||
|
||||
if hs.Features.IsEnabled(featuremgmt.FlagTopnav) {
|
||||
// Move server admin into Configuration and rename to administration
|
||||
if configNode != nil && serverAdminNode != nil {
|
||||
configNode.Text = "Administration"
|
||||
configNode.Children = append(configNode.Children, serverAdminNode)
|
||||
adminNodeIndex := len(navTree) - 1
|
||||
navTree = navTree[:adminNodeIndex]
|
||||
}
|
||||
}
|
||||
|
||||
navTree = hs.addHelpLinks(navTree, c)
|
||||
|
||||
return navTree, nil
|
||||
|
@ -8,7 +8,7 @@ func GetServerAdminNode(children []*dtos.NavLink) *dtos.NavLink {
|
||||
url = children[0].Url
|
||||
}
|
||||
return &dtos.NavLink{
|
||||
Text: "Server Admin",
|
||||
Text: "Server admin",
|
||||
SubTitle: "Manage all users and orgs",
|
||||
HideFromTabs: true,
|
||||
Id: "admin",
|
||||
|
@ -88,9 +88,8 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
height: 48,
|
||||
}),
|
||||
subSection: css({
|
||||
padding: theme.spacing(3, 0, 1, 1),
|
||||
padding: theme.spacing(3, 0, 0.5, 1),
|
||||
fontWeight: 500,
|
||||
fontSize: '16px',
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
@ -12,13 +12,15 @@ export function buildInitialState(): NavIndex {
|
||||
|
||||
function buildNavIndex(navIndex: NavIndex, children: NavModelItem[], parentItem?: NavModelItem) {
|
||||
for (const node of children) {
|
||||
navIndex[node.id!] = {
|
||||
const newNode = {
|
||||
...node,
|
||||
parentItem: parentItem,
|
||||
};
|
||||
|
||||
navIndex[node.id!] = newNode;
|
||||
|
||||
if (node.children) {
|
||||
buildNavIndex(navIndex, node.children, node);
|
||||
buildNavIndex(navIndex, node.children, newNode);
|
||||
}
|
||||
}
|
||||
|
||||
|
52
public/app/core/selectors/navModel.test.ts
Normal file
52
public/app/core/selectors/navModel.test.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { config } from '@grafana/runtime';
|
||||
|
||||
import { buildInitialState } from '../reducers/navModel';
|
||||
|
||||
import { getNavModel } from './navModel';
|
||||
|
||||
describe('getNavModel', () => {
|
||||
config.bootData.navTree = [
|
||||
{
|
||||
text: 'Apps',
|
||||
id: 'apps',
|
||||
url: 'apps',
|
||||
children: [
|
||||
{ text: '', id: 'apps/child1', url: 'apps/child1' },
|
||||
{ text: '', id: 'apps/child2', url: 'apps/child2' },
|
||||
{
|
||||
text: '',
|
||||
id: 'apps/subapp',
|
||||
url: 'section/subapp',
|
||||
children: [
|
||||
{ text: '', id: 'apps/subapp/child1', url: 'apps/subapp/child1' },
|
||||
{ text: '', id: 'apps/subapp/child2', url: 'apps/subapp/child2' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const navIndex = buildInitialState();
|
||||
|
||||
test('returns the correct nav model for root node', () => {
|
||||
const navModel = getNavModel(navIndex, 'apps');
|
||||
expect(navModel.main.id).toBe('apps');
|
||||
expect(navModel.node.id).toBe('apps');
|
||||
});
|
||||
|
||||
test('returns the correct nav model a 1st-level child', () => {
|
||||
const navModel = getNavModel(navIndex, 'apps/child1');
|
||||
expect(navModel.main.id).toBe('apps');
|
||||
expect(navModel.node.id).toBe('apps/child1');
|
||||
expect(navModel.main.children![0].active).toBe(true);
|
||||
expect(navModel.node.parentItem?.id).toBe(navModel.main.id);
|
||||
});
|
||||
|
||||
test('returns the correct nav model for a 2nd-level child', () => {
|
||||
const navModel = getNavModel(navIndex, 'apps/subapp/child1');
|
||||
expect(navModel.main.id).toBe('apps');
|
||||
expect(navModel.node.id).toBe('apps/subapp/child1');
|
||||
expect(navModel.main.children![2].active).toBe(true);
|
||||
expect(navModel.main.children![2].children![0].active).toBe(true);
|
||||
});
|
||||
});
|
@ -18,22 +18,7 @@ const getNotFoundModel = (): NavModel => {
|
||||
export const getNavModel = (navIndex: NavIndex, id: string, fallback?: NavModel, onlyChild = false): NavModel => {
|
||||
if (navIndex[id]) {
|
||||
const node = navIndex[id];
|
||||
|
||||
let main: NavModelItem;
|
||||
if (!onlyChild && node.parentItem) {
|
||||
main = { ...node.parentItem };
|
||||
|
||||
main.children =
|
||||
main.children &&
|
||||
main.children.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
active: item.url === node.url,
|
||||
};
|
||||
});
|
||||
} else {
|
||||
main = node;
|
||||
}
|
||||
const main = onlyChild ? node : getSectionRoot(node);
|
||||
|
||||
return {
|
||||
node,
|
||||
@ -48,6 +33,26 @@ export const getNavModel = (navIndex: NavIndex, id: string, fallback?: NavModel,
|
||||
return getNotFoundModel();
|
||||
};
|
||||
|
||||
function getSectionRoot(node: NavModelItem): NavModelItem {
|
||||
if (!node.parentItem) {
|
||||
return node;
|
||||
}
|
||||
|
||||
const root = (node.parentItem = { ...node.parentItem });
|
||||
|
||||
if (root.children) {
|
||||
root.children = root.children.map((item) => {
|
||||
if (item.id === node.id) {
|
||||
return { ...node, active: true };
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
return getSectionRoot(root);
|
||||
}
|
||||
|
||||
export const getTitleFromNavModel = (navModel: NavModel) => {
|
||||
return `${navModel.main.text}${navModel.node.text ? ': ' + navModel.node.text : ''}`;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user