Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 13 additions & 177 deletions src/components/HelpFooter/HelpFooter.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import React, { useState } from 'react';
import { Icon, useTheme2, Modal } from '@grafana/ui';
import { config } from '@grafana/runtime';
import { t } from '@grafana/i18n';
import React from 'react';
import { Icon, useTheme2 } from '@grafana/ui';
import { getHelpFooterStyles } from '../../styles/help-footer.styles';
import { useGrafanaHelpMenu } from '../../utils/help-menu.hook';

interface HelpFooterProps {
className?: string;
Expand All @@ -11,59 +10,19 @@ interface HelpFooterProps {
export const HelpFooter: React.FC<HelpFooterProps> = ({ className }) => {
const theme = useTheme2();
const styles = getHelpFooterStyles(theme);
const [isHelpModalOpen, setIsHelpModalOpen] = useState(false);

const handleKeyboardShortcuts = () => {
setIsHelpModalOpen(true);
};
// Get help menu data from Grafana's nav state
const helpMenuData = useGrafanaHelpMenu();

const handleCloseHelpModal = () => {
setIsHelpModalOpen(false);
};

const helpButtons = [
{
key: 'documentation',
label: t('helpFooter.buttons.documentation', 'Documentation'),
icon: 'file-alt' as const,
href: 'https://grafana.com/docs/grafana/latest/?utm_source=grafana_footer',
},
{
key: 'support',
label: t('helpFooter.buttons.support', 'Support'),
icon: 'question-circle' as const,
href: 'https://grafana.com/support/?utm_source=grafana_footer',
},
{
key: 'community',
label: t('helpFooter.buttons.community', 'Community'),
icon: 'comments-alt' as const,
href: 'https://community.grafana.com/?utm_source=grafana_footer',
},
{
key: 'enterprise',
label: t('helpFooter.buttons.enterprise', 'Enterprise'),
icon: 'external-link-alt' as const,
href: 'https://grafana.com/products/enterprise/?utm_source=grafana_footer',
},
{
key: 'download',
label: t('helpFooter.buttons.download', 'Download'),
icon: 'download-alt' as const,
href: 'https://grafana.com/grafana/download?utm_source=grafana_footer',
},
{
key: 'shortcuts',
label: t('helpFooter.buttons.shortcuts', 'Shortcuts'),
icon: 'keyboard' as const,
onClick: handleKeyboardShortcuts,
},
];
// Only render if we have menu items
if (helpMenuData.items.length === 0) {
return null;
}

return (
<div className={`${styles.helpFooter} ${className || ''}`}>
<div className={styles.helpButtons}>
{helpButtons.map((button) => {
{helpMenuData.items.map((button) => {
const ButtonComponent = button.href ? 'a' : 'button';
const buttonProps = button.href
? {
Expand All @@ -87,137 +46,14 @@ export const HelpFooter: React.FC<HelpFooterProps> = ({ className }) => {
})}
</div>

{/* Version info */}
{config.buildInfo && (
{/* Version info from help node subtitle */}
{helpMenuData.subtitle && (
<div className={styles.versionInfo}>
<div className={styles.versionText}>
Grafana v{config.buildInfo.version} ({config.buildInfo.commit?.substring(0, 10) || 'unknown'})
{helpMenuData.subtitle}
</div>
</div>
)}

{/* Keyboard Shortcuts Modal */}
{isHelpModalOpen && (
<Modal
title={t('helpFooter.modal.keyboardShortcuts', 'Keyboard Shortcuts')}
isOpen={isHelpModalOpen}
onDismiss={handleCloseHelpModal}
>
<div style={{ minWidth: '500px', padding: '16px' }}>
<div style={{ marginBottom: '16px' }}>
<h3 style={{ marginBottom: '8px' }}>{t('helpFooter.modal.globalShortcuts', 'Global Shortcuts')}</h3>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 2fr', gap: '8px', fontSize: '14px' }}>
<div>
<kbd
style={{
padding: '2px 6px',
backgroundColor: theme.colors.background.secondary,
border: `1px solid ${theme.colors.border.medium}`,
borderRadius: '3px',
}}
>
?
</kbd>
</div>
<div>{t('helpFooter.modal.showAllShortcuts', 'Show all keyboard shortcuts')}</div>
<div>
<kbd
style={{
padding: '2px 6px',
backgroundColor: theme.colors.background.secondary,
border: `1px solid ${theme.colors.border.medium}`,
borderRadius: '3px',
}}
>
g h
</kbd>
</div>
<div>{t('helpFooter.modal.goToHomeDashboard', 'Go to Home Dashboard')}</div>
<div>
<kbd
style={{
padding: '2px 6px',
backgroundColor: theme.colors.background.secondary,
border: `1px solid ${theme.colors.border.medium}`,
borderRadius: '3px',
}}
>
g d
</kbd>
</div>
<div>{t('helpFooter.modal.goToDashboards', 'Go to Dashboards')}</div>
<div>
<kbd
style={{
padding: '2px 6px',
backgroundColor: theme.colors.background.secondary,
border: `1px solid ${theme.colors.border.medium}`,
borderRadius: '3px',
}}
>
esc
</kbd>
</div>
<div>{t('helpFooter.modal.exitEditViews', 'Exit edit/setting views')}</div>
</div>
</div>

<div style={{ marginBottom: '16px' }}>
<h3 style={{ marginBottom: '8px' }}>{t('helpFooter.modal.dashboardShortcuts', 'Dashboard Shortcuts')}</h3>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 2fr', gap: '8px', fontSize: '14px' }}>
<div>
<kbd
style={{
padding: '2px 6px',
backgroundColor: theme.colors.background.secondary,
border: `1px solid ${theme.colors.border.medium}`,
borderRadius: '3px',
}}
>
d r
</kbd>
</div>
<div>{t('helpFooter.modal.refreshAllPanels', 'Refresh all panels')}</div>
<div>
<kbd
style={{
padding: '2px 6px',
backgroundColor: theme.colors.background.secondary,
border: `1px solid ${theme.colors.border.medium}`,
borderRadius: '3px',
}}
>
d s
</kbd>
</div>
<div>{t('helpFooter.modal.dashboardSettings', 'Dashboard settings')}</div>
<div>
<kbd
style={{
padding: '2px 6px',
backgroundColor: theme.colors.background.secondary,
border: `1px solid ${theme.colors.border.medium}`,
borderRadius: '3px',
}}
>
d v
</kbd>
</div>
<div>{t('helpFooter.modal.toggleViewMode', 'Toggle view mode')}</div>
</div>
</div>

<div
style={{ fontSize: '12px', color: theme.colors.text.secondary, marginTop: '16px', fontStyle: 'italic' }}
>
{t(
'helpFooter.modal.simplifiedView',
"This is a simplified view. You can replace this with Grafana's full HelpModal component."
)}
</div>
</div>
</Modal>
)}
</div>
);
};
111 changes: 111 additions & 0 deletions src/utils/help-menu.hook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { useSelector } from 'react-redux';
import { cloneDeep } from 'lodash';
import { config } from '@grafana/runtime';
import { NavModelItem } from '@grafana/data';
import { t } from '@grafana/i18n';

interface HelpMenuItem {
key: string;
label: string;
icon: string;
href?: string;
onClick?: () => void;
}

interface HelpMenuData {
items: HelpMenuItem[];
subtitle?: string;
}

// Copied from https://github.com/grafana/grafana/blob/2b8c74d/public/app/core/components/Footer/Footer.tsx#L17
// Get footer links.
function getFooterLinks(): NavModelItem[] {
return [
{
text: t('nav.help/documentation', 'Documentation'),
icon: 'document-info',
url: 'https://grafana.com/docs/grafana/latest/?utm_source=grafana_pathfinder',
target: '_blank',
},
{
text: t('nav.help/support', 'Support'),
icon: 'question-circle',
url: 'https://grafana.com/products/enterprise/?utm_source=grafana_pathfinder',
target: '_blank',
},
{
text: t('nav.help/community', 'Community'),
icon: 'comments-alt',
url: 'https://community.grafana.com/?utm_source=grafana_pathfinder',
target: '_blank',
},
];
}

// Copied from https://github.com/grafana/grafana/blob/2b8c74d/public/app/core/components/AppChrome/MegaMenu/utils.ts#L130
// Get edition and update links.
export function getEditionAndUpdateLinks(): NavModelItem[] {
const { buildInfo, licenseInfo } = config;
const stateInfo = licenseInfo.stateInfo ? ` (${licenseInfo.stateInfo})` : '';
const links: NavModelItem[] = [];

links.push({
target: '_blank',
id: 'version',
text: `${buildInfo.edition}${stateInfo}`,
url: licenseInfo.licenseUrl,
icon: 'external-link-alt',
});

if (buildInfo.hasUpdate) {
links.push({
target: '_blank',
id: 'updateVersion',
text: `New version available!`,
icon: 'download-alt',
url: 'https://grafana.com/grafana/download?utm_source=grafana_pathfinder',
});
}

return links;
}

// Copied from https://github.com/grafana/grafana/blob/2b8c74d/public/app/core/components/AppChrome/MegaMenu/utils.ts#L16
// Enrich help item.
function enrichHelpItem(helpItem: NavModelItem): NavModelItem {
let menuItems = helpItem.children || [];

if (helpItem.id === 'help') {
helpItem.children = [
...menuItems,
...getFooterLinks(),
...getEditionAndUpdateLinks(),
// No keyboard shortcuts until Grafana exposes that for us.
];
}
return helpItem;
}

export function useGrafanaHelpMenu(): HelpMenuData {
const navIndex = useSelector((state: any) => state.navIndex);

if (!navIndex || !navIndex['help']) {
return { items: [] };
}

const helpNode = cloneDeep(navIndex['help']);
const enrichedHelpNode = enrichHelpItem(helpNode);

const helpMenuItems: HelpMenuItem[] = (enrichedHelpNode.children || []).map((item) => ({
key: item.id || item.text,
label: item.text,
icon: item.icon || 'question-circle',
href: item.url,
onClick: item.onClick,
}));

return {
items: helpMenuItems,
subtitle: enrichedHelpNode.subTitle,
};
}
Loading