mirror of
https://github.com/grafana/grafana.git
synced 2025-02-16 18:34:52 -06:00
Navigation: Display Starred
dashboards in the Navbar
(#51038)
* remove feature toggle, add empty state and fix bug with initial starring * Extract empty message into lingui * remove full stop * add empty message in the backend * remove unused import * translate starred + empty starred states * betterer changes
This commit is contained in:
parent
4a9872d108
commit
ee3f4f1709
@ -3233,9 +3233,9 @@ exports[`better eslint`] = {
|
||||
[120, 15, 41, "Do not use any type assertions.", "1904113237"],
|
||||
[137, 15, 41, "Do not use any type assertions.", "1904113237"]
|
||||
],
|
||||
"public/app/core/components/NavBar/NavBarMenu.tsx:2095049018": [
|
||||
[241, 54, 26, "Do not use any type assertions.", "1541386804"],
|
||||
[445, 23, 21, "Do not use any type assertions.", "1121249950"]
|
||||
"public/app/core/components/NavBar/NavBarMenu.tsx:2778426907": [
|
||||
[245, 54, 26, "Do not use any type assertions.", "1541386804"],
|
||||
[463, 23, 21, "Do not use any type assertions.", "1121249950"]
|
||||
],
|
||||
"public/app/core/components/OptionsUI/DashboardPicker.tsx:3166151962": [
|
||||
[11, 85, 3, "Unexpected any. Specify a different type.", "193409811"]
|
||||
|
@ -47,7 +47,6 @@ export interface FeatureToggles {
|
||||
explore2Dashboard?: boolean;
|
||||
tracing?: boolean;
|
||||
commandPalette?: boolean;
|
||||
savedItems?: boolean;
|
||||
cloudWatchDynamicLabels?: boolean;
|
||||
datasourceQueryMultiStatus?: boolean;
|
||||
azureMonitorExperimentalUI?: boolean;
|
||||
|
@ -16,6 +16,7 @@ export interface NavLinkDTO {
|
||||
hideFromTabs?: boolean;
|
||||
children?: NavLinkDTO[];
|
||||
highlightText?: string;
|
||||
emptyMessageId?: string;
|
||||
}
|
||||
|
||||
export interface NavModelItem extends NavLinkDTO {
|
||||
|
@ -74,6 +74,7 @@ type NavLink struct {
|
||||
Children []*NavLink `json:"children,omitempty"`
|
||||
HighlightText string `json:"highlightText,omitempty"`
|
||||
HighlightID string `json:"highlightId,omitempty"`
|
||||
EmptyMessageId string `json:"emptyMessageId,omitempty"`
|
||||
}
|
||||
|
||||
// NavIDCfg is the id for org configuration navigation node
|
||||
|
@ -163,22 +163,21 @@ func (hs *HTTPServer) getNavTree(c *models.ReqContext, hasEditPerm bool, prefs *
|
||||
hasAccess := ac.HasAccess(hs.AccessControl, c)
|
||||
navTree := []*dtos.NavLink{}
|
||||
|
||||
if hs.Features.IsEnabled(featuremgmt.FlagSavedItems) {
|
||||
starredItemsLinks, err := hs.buildStarredItemsNavLinks(c, prefs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
navTree = append(navTree, &dtos.NavLink{
|
||||
Text: "Starred",
|
||||
Id: "starred",
|
||||
Icon: "star",
|
||||
SortWeight: dtos.WeightSavedItems,
|
||||
Section: dtos.NavSectionCore,
|
||||
Children: starredItemsLinks,
|
||||
})
|
||||
starredItemsLinks, err := hs.buildStarredItemsNavLinks(c, prefs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
navTree = append(navTree, &dtos.NavLink{
|
||||
Text: "Starred",
|
||||
Id: "starred",
|
||||
Icon: "star",
|
||||
SortWeight: dtos.WeightSavedItems,
|
||||
Section: dtos.NavSectionCore,
|
||||
Children: starredItemsLinks,
|
||||
EmptyMessageId: "starred-empty",
|
||||
})
|
||||
|
||||
dashboardChildLinks := hs.buildDashboardNavLinks(c, hasEditPerm)
|
||||
|
||||
dashboardsUrl := "/dashboards"
|
||||
|
@ -178,11 +178,6 @@ var (
|
||||
Description: "Enable command palette",
|
||||
State: FeatureStateAlpha,
|
||||
},
|
||||
{
|
||||
Name: "savedItems",
|
||||
Description: "Enable Saved Items in the navbar.",
|
||||
State: FeatureStateAlpha,
|
||||
},
|
||||
{
|
||||
Name: "cloudWatchDynamicLabels",
|
||||
Description: "Use dynamic labels instead of alias patterns in CloudWatch datasource",
|
||||
|
@ -131,10 +131,6 @@ const (
|
||||
// Enable command palette
|
||||
FlagCommandPalette = "commandPalette"
|
||||
|
||||
// FlagSavedItems
|
||||
// Enable Saved Items in the navbar.
|
||||
FlagSavedItems = "savedItems"
|
||||
|
||||
// FlagCloudWatchDynamicLabels
|
||||
// Use dynamic labels instead of alias patterns in CloudWatch datasource
|
||||
FlagCloudWatchDynamicLabels = "cloudWatchDynamicLabels"
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { useMenu } from '@react-aria/menu';
|
||||
import { mergeProps } from '@react-aria/utils';
|
||||
import { useTreeState } from '@react-stately/tree';
|
||||
@ -11,6 +12,7 @@ import { useTheme2 } from '@grafana/ui';
|
||||
import { NavBarItemMenuItem } from './NavBarItemMenuItem';
|
||||
import { NavBarScrollContainer } from './NavBarScrollContainer';
|
||||
import { useNavBarItemMenuContext } from './context';
|
||||
import menuItemTranslations from './navBarItem-translations';
|
||||
import { getNavModelItemKey } from './utils';
|
||||
|
||||
export interface NavBarItemMenuProps extends SpectrumMenuProps<NavModelItem> {
|
||||
@ -21,6 +23,7 @@ export interface NavBarItemMenuProps extends SpectrumMenuProps<NavModelItem> {
|
||||
|
||||
export function NavBarItemMenu(props: NavBarItemMenuProps): ReactElement | null {
|
||||
const { reverseMenuDirection, adjustHeightForBorder, disabledKeys, onNavigate, ...rest } = props;
|
||||
const { i18n } = useLingui();
|
||||
const contextProps = useNavBarItemMenuContext();
|
||||
const completeProps = {
|
||||
...mergeProps(contextProps, rest),
|
||||
@ -58,6 +61,15 @@ export function NavBarItemMenu(props: NavBarItemMenuProps): ReactElement | null
|
||||
<NavBarItemMenuItem key={getNavModelItemKey(item.value)} item={item} state={state} onNavigate={onNavigate} />
|
||||
));
|
||||
|
||||
if (itemComponents.length === 0 && section.value.emptyMessageId) {
|
||||
const emptyMessageTranslated = i18n._(menuItemTranslations[section.value.emptyMessageId]);
|
||||
itemComponents.push(
|
||||
<div key="empty-message" className={styles.emptyMessage}>
|
||||
{emptyMessageTranslated}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const subTitleComponent = menuSubTitle && (
|
||||
<li key={menuSubTitle} className={styles.subtitle}>
|
||||
{menuSubTitle}
|
||||
@ -105,5 +117,9 @@ function getStyles(theme: GrafanaTheme2, reverseDirection?: boolean) {
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
`,
|
||||
emptyMessage: css`
|
||||
font-style: italic;
|
||||
padding: ${theme.spacing(0.5, 2)};
|
||||
`,
|
||||
};
|
||||
}
|
||||
|
@ -88,11 +88,5 @@ function getStyles(theme: GrafanaTheme2, isFocused: boolean, isSection: boolean)
|
||||
transition: none;
|
||||
}
|
||||
`,
|
||||
upgradeBoxContainer: css`
|
||||
padding: ${theme.spacing(1)};
|
||||
`,
|
||||
upgradeBox: css`
|
||||
width: 300px;
|
||||
`,
|
||||
};
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { useDialog } from '@react-aria/dialog';
|
||||
import { FocusScope } from '@react-aria/focus';
|
||||
import { OverlayContainer, useOverlay } from '@react-aria/overlays';
|
||||
@ -16,6 +17,7 @@ import { NavBarItemWithoutMenu } from './NavBarItemWithoutMenu';
|
||||
import { NavBarMenuItem } from './NavBarMenuItem';
|
||||
import { NavBarToggle } from './NavBarToggle';
|
||||
import { NavFeatureHighlight } from './NavFeatureHighlight';
|
||||
import menuItemTranslations from './navBarItem-translations';
|
||||
import { isMatchOrChildMatch } from './utils';
|
||||
|
||||
const MENU_WIDTH = '350px';
|
||||
@ -35,6 +37,7 @@ export function NavBarMenu({ activeItem, isOpen, navItems, onClose, setMenuAnima
|
||||
const animStyles = getAnimStyles(theme, ANIMATION_DURATION);
|
||||
const ref = useRef(null);
|
||||
const { dialogProps } = useDialog({}, ref);
|
||||
|
||||
const { overlayProps, underlayProps } = useOverlay(
|
||||
{
|
||||
isDismissable: true,
|
||||
@ -226,6 +229,7 @@ function NavItem({
|
||||
activeItem?: NavModelItem;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const { i18n } = useLingui();
|
||||
const styles = useStyles2(getNavItemStyles);
|
||||
|
||||
if (linkHasChildren(link)) {
|
||||
@ -255,6 +259,15 @@ function NavItem({
|
||||
</ul>
|
||||
</CollapsibleNavItem>
|
||||
);
|
||||
} else if (link.emptyMessageId) {
|
||||
const emptyMessageTranslated = i18n._(menuItemTranslations[link.emptyMessageId]);
|
||||
return (
|
||||
<CollapsibleNavItem onClose={onClose} link={link} isActive={isMatchOrChildMatch(link, activeItem)}>
|
||||
<ul className={styles.children}>
|
||||
<div className={styles.emptyMessage}>{emptyMessageTranslated}</div>
|
||||
</ul>
|
||||
</CollapsibleNavItem>
|
||||
);
|
||||
} else {
|
||||
const FeatureHighlightWrapper = link.highlightText ? NavFeatureHighlight : React.Fragment;
|
||||
return (
|
||||
@ -326,6 +339,11 @@ const getNavItemStyles = (theme: GrafanaTheme2) => ({
|
||||
justifySelf: 'start',
|
||||
padding: theme.spacing(0.5, 4.25, 0.5, 0.5),
|
||||
}),
|
||||
emptyMessage: css({
|
||||
color: theme.colors.text.secondary,
|
||||
fontStyle: 'italic',
|
||||
padding: theme.spacing(1, 1.5),
|
||||
}),
|
||||
});
|
||||
|
||||
function CollapsibleNavItem({
|
||||
|
@ -15,6 +15,8 @@ const TRANSLATED_MENU_ITEMS: Record<string, MessageDescriptor> = {
|
||||
import: defineMessage({ id: 'nav.create-import', message: 'Import' }),
|
||||
alert: defineMessage({ id: 'nav.create-alert', message: 'New alert rule' }),
|
||||
|
||||
starred: defineMessage({ id: 'nav.starred', message: 'Starred' }),
|
||||
'starred-empty': defineMessage({ id: 'nav.starred-empty', message: 'Your starred dashboards will appear here' }),
|
||||
dashboards: defineMessage({ id: 'nav.dashboards', message: 'Dashboards' }),
|
||||
'manage-dashboards': defineMessage({ id: 'nav.manage-dashboards', message: 'Browse' }),
|
||||
playlists: defineMessage({ id: 'nav.playlists', message: 'Playlists' }),
|
||||
|
@ -12,29 +12,36 @@ const navTreeSlice = createSlice({
|
||||
setStarred: (state, action: PayloadAction<{ id: string; title: string; url: string; isStarred: boolean }>) => {
|
||||
const starredItems = state.find((navItem) => navItem.id === 'starred');
|
||||
const { id, title, url, isStarred } = action.payload;
|
||||
if (isStarred) {
|
||||
const newStarredItem: NavModelItem = {
|
||||
id,
|
||||
text: title,
|
||||
url,
|
||||
};
|
||||
starredItems?.children?.push(newStarredItem);
|
||||
starredItems?.children?.sort((a, b) => a.text.localeCompare(b.text));
|
||||
} else {
|
||||
const index = starredItems?.children?.findIndex((item) => item.id === id) ?? -1;
|
||||
if (index > -1) {
|
||||
starredItems?.children?.splice(index, 1);
|
||||
if (starredItems) {
|
||||
if (isStarred) {
|
||||
if (!starredItems.children) {
|
||||
starredItems.children = [];
|
||||
}
|
||||
const newStarredItem: NavModelItem = {
|
||||
id,
|
||||
text: title,
|
||||
url,
|
||||
};
|
||||
starredItems.children.push(newStarredItem);
|
||||
starredItems.children.sort((a, b) => a.text.localeCompare(b.text));
|
||||
} else {
|
||||
const index = starredItems.children?.findIndex((item) => item.id === id) ?? -1;
|
||||
if (index > -1) {
|
||||
starredItems?.children?.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
updateDashboardName: (state, action: PayloadAction<{ id: string; title: string; url: string }>) => {
|
||||
const { id, title, url } = action.payload;
|
||||
const starredItems = state.find((navItem) => navItem.id === 'starred');
|
||||
const navItem = starredItems?.children?.find((navItem) => navItem.id === id);
|
||||
if (navItem) {
|
||||
navItem.text = title;
|
||||
navItem.url = url;
|
||||
starredItems?.children?.sort((a, b) => a.text.localeCompare(b.text));
|
||||
if (starredItems) {
|
||||
const navItem = starredItems.children?.find((navItem) => navItem.id === id);
|
||||
if (navItem) {
|
||||
navItem.text = title;
|
||||
navItem.url = url;
|
||||
starredItems.children?.sort((a, b) => a.text.localeCompare(b.text));
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -190,6 +190,14 @@ msgstr "Sign out"
|
||||
msgid "nav.snapshots"
|
||||
msgstr "Snapshots"
|
||||
|
||||
#: public/app/core/components/NavBar/navBarItem-translations.ts
|
||||
msgid "nav.starred"
|
||||
msgstr "Starred"
|
||||
|
||||
#: public/app/core/components/NavBar/navBarItem-translations.ts
|
||||
msgid "nav.starred-empty"
|
||||
msgstr "Your starred dashboards will appear here"
|
||||
|
||||
#: public/app/core/components/NavBar/navBarItem-translations.ts
|
||||
msgid "nav.teams"
|
||||
msgstr "Teams"
|
||||
|
@ -190,6 +190,14 @@ msgstr ""
|
||||
msgid "nav.snapshots"
|
||||
msgstr ""
|
||||
|
||||
#: public/app/core/components/NavBar/navBarItem-translations.ts
|
||||
msgid "nav.starred"
|
||||
msgstr ""
|
||||
|
||||
#: public/app/core/components/NavBar/navBarItem-translations.ts
|
||||
msgid "nav.starred-empty"
|
||||
msgstr ""
|
||||
|
||||
#: public/app/core/components/NavBar/navBarItem-translations.ts
|
||||
msgid "nav.teams"
|
||||
msgstr ""
|
||||
|
@ -190,6 +190,14 @@ msgstr ""
|
||||
msgid "nav.snapshots"
|
||||
msgstr ""
|
||||
|
||||
#: public/app/core/components/NavBar/navBarItem-translations.ts
|
||||
msgid "nav.starred"
|
||||
msgstr ""
|
||||
|
||||
#: public/app/core/components/NavBar/navBarItem-translations.ts
|
||||
msgid "nav.starred-empty"
|
||||
msgstr ""
|
||||
|
||||
#: public/app/core/components/NavBar/navBarItem-translations.ts
|
||||
msgid "nav.teams"
|
||||
msgstr ""
|
||||
|
@ -190,6 +190,14 @@ msgstr ""
|
||||
msgid "nav.snapshots"
|
||||
msgstr ""
|
||||
|
||||
#: public/app/core/components/NavBar/navBarItem-translations.ts
|
||||
msgid "nav.starred"
|
||||
msgstr ""
|
||||
|
||||
#: public/app/core/components/NavBar/navBarItem-translations.ts
|
||||
msgid "nav.starred-empty"
|
||||
msgstr ""
|
||||
|
||||
#: public/app/core/components/NavBar/navBarItem-translations.ts
|
||||
msgid "nav.teams"
|
||||
msgstr ""
|
||||
|
Loading…
Reference in New Issue
Block a user