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:
Ashley Harrison 2022-06-27 15:41:00 +01:00 committed by GitHub
parent 4a9872d108
commit ee3f4f1709
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 110 additions and 50 deletions

View File

@ -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"]

View File

@ -47,7 +47,6 @@ export interface FeatureToggles {
explore2Dashboard?: boolean;
tracing?: boolean;
commandPalette?: boolean;
savedItems?: boolean;
cloudWatchDynamicLabels?: boolean;
datasourceQueryMultiStatus?: boolean;
azureMonitorExperimentalUI?: boolean;

View File

@ -16,6 +16,7 @@ export interface NavLinkDTO {
hideFromTabs?: boolean;
children?: NavLinkDTO[];
highlightText?: string;
emptyMessageId?: string;
}
export interface NavModelItem extends NavLinkDTO {

View File

@ -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

View File

@ -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"

View File

@ -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",

View File

@ -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"

View File

@ -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)};
`,
};
}

View File

@ -88,11 +88,5 @@ function getStyles(theme: GrafanaTheme2, isFocused: boolean, isSection: boolean)
transition: none;
}
`,
upgradeBoxContainer: css`
padding: ${theme.spacing(1)};
`,
upgradeBox: css`
width: 300px;
`,
};
}

View File

@ -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({

View File

@ -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' }),

View File

@ -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));
}
}
},
},

View File

@ -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"

View File

@ -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 ""

View File

@ -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 ""

View File

@ -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 ""