mirror of
https://github.com/grafana/grafana.git
synced 2024-11-22 08:56:43 -06:00
Navigation: Show list of pinned items on MegaMenu (#90280)
* Navigation: Show list of pinned ites on the navigation * Rename section to 'Bookmarks' * Internationalization * Rename everything to bookmarks * Update public/app/core/reducers/navBarTree.ts Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com> * Ignore empty message as well * Dont update navigation if there is an error patching --------- Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com>
This commit is contained in:
parent
38ac0f3506
commit
546f4aa700
@ -53,7 +53,7 @@ Content-Type: application/json
|
||||
"timezone": "utc",
|
||||
"weekStart": "",
|
||||
"navbar": {
|
||||
"savedItemIds": null
|
||||
"bookmarkIds": null
|
||||
},
|
||||
"queryHistory": {
|
||||
"homeTab": ""
|
||||
@ -142,7 +142,7 @@ Content-Type: application/json
|
||||
"timezone": "",
|
||||
"weekStart": "",
|
||||
"navbar": {
|
||||
"savedItemIds": null
|
||||
"bookmarkIds": null
|
||||
},
|
||||
"queryHistory": {
|
||||
"homeTab": ""
|
||||
|
@ -49,7 +49,7 @@ lineage: schemas: [{
|
||||
} @cuetsy(kind="interface")
|
||||
|
||||
#NavbarPreference: {
|
||||
savedItemIds: [...string]
|
||||
bookmarkIds: [...string]
|
||||
} @cuetsy(kind="interface")
|
||||
}
|
||||
}]
|
||||
|
@ -22,11 +22,11 @@ export interface CookiePreferences {
|
||||
}
|
||||
|
||||
export interface NavbarPreference {
|
||||
savedItemIds: Array<string>;
|
||||
bookmarkIds: Array<string>;
|
||||
}
|
||||
|
||||
export const defaultNavbarPreference: Partial<NavbarPreference> = {
|
||||
savedItemIds: [],
|
||||
bookmarkIds: [],
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -18,7 +18,7 @@ type CookiePreferences struct {
|
||||
|
||||
// NavbarPreference defines model for NavbarPreference.
|
||||
type NavbarPreference struct {
|
||||
SavedItemIds []string `json:"savedItemIds"`
|
||||
BookmarkIds []string `json:"bookmarkIds"`
|
||||
}
|
||||
|
||||
// QueryHistoryPreference defines model for QueryHistoryPreference.
|
||||
|
@ -12,6 +12,7 @@ const (
|
||||
// any items with default weight.
|
||||
|
||||
WeightHome = (iota - 20) * 100
|
||||
WeightBookmarks
|
||||
WeightSavedItems
|
||||
WeightDashboard
|
||||
WeightExplore
|
||||
|
@ -163,6 +163,20 @@ func (s *ServiceImpl) GetNavTree(c *contextmodel.ReqContext, prefs *pref.Prefere
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagPinNavItems) {
|
||||
bookmarks := s.buildBookmarksNavLinks(prefs, treeRoot)
|
||||
|
||||
treeRoot.AddSection(&navtree.NavLink{
|
||||
Text: "Bookmarks",
|
||||
Id: "bookmarks",
|
||||
Icon: "bookmark",
|
||||
SortWeight: navtree.WeightBookmarks,
|
||||
Children: bookmarks,
|
||||
EmptyMessageId: "bookmarks-empty",
|
||||
Url: s.cfg.AppSubURL + "/bookmarks",
|
||||
})
|
||||
}
|
||||
|
||||
return treeRoot, nil
|
||||
}
|
||||
|
||||
@ -316,6 +330,38 @@ func (s *ServiceImpl) buildStarredItemsNavLinks(c *contextmodel.ReqContext) ([]*
|
||||
|
||||
return starredItemsChildNavs, nil
|
||||
}
|
||||
func (s *ServiceImpl) buildBookmarksNavLinks(prefs *pref.Preference, treeRoot *navtree.NavTreeRoot) []*navtree.NavLink {
|
||||
bookmarksChildNavs := []*navtree.NavLink{}
|
||||
|
||||
bookmarkIds := prefs.JSONData.Navbar.BookmarkIds
|
||||
|
||||
if len(bookmarkIds) > 0 {
|
||||
for _, id := range bookmarkIds {
|
||||
item := treeRoot.FindById(id)
|
||||
if item != nil {
|
||||
bookmarksChildNavs = append(bookmarksChildNavs, &navtree.NavLink{
|
||||
Id: item.Id,
|
||||
Text: item.Text,
|
||||
SubTitle: item.SubTitle,
|
||||
Icon: item.Icon,
|
||||
Img: item.Img,
|
||||
Url: item.Url,
|
||||
Target: item.Target,
|
||||
HideFromTabs: item.HideFromTabs,
|
||||
RoundIcon: item.RoundIcon,
|
||||
IsSection: item.IsSection,
|
||||
HighlightText: item.HighlightText,
|
||||
HighlightID: item.HighlightID,
|
||||
PluginID: item.PluginID,
|
||||
IsCreateAction: item.IsCreateAction,
|
||||
Keywords: item.Keywords,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bookmarksChildNavs
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) buildDashboardNavLinks(c *contextmodel.ReqContext) []*navtree.NavLink {
|
||||
hasAccess := ac.HasAccess(s.accessControl, c)
|
||||
|
@ -98,7 +98,7 @@ type QueryHistoryPreference struct {
|
||||
}
|
||||
|
||||
type NavbarPreference struct {
|
||||
SavedItemIds []string `json:"savedItemIds"`
|
||||
BookmarkIds []string `json:"bookmarkIds"`
|
||||
}
|
||||
|
||||
func (j *PreferenceJSONData) FromDB(data []byte) error {
|
||||
|
@ -97,11 +97,11 @@ func GetPreferencesFor(ctx context.Context,
|
||||
dto.Language = &preference.JSONData.Language
|
||||
}
|
||||
|
||||
if preference.JSONData.Navbar.SavedItemIds != nil {
|
||||
if preference.JSONData.Navbar.BookmarkIds != nil {
|
||||
dto.Navbar = &preferences.NavbarPreference{
|
||||
SavedItemIds: []string{},
|
||||
BookmarkIds: []string{},
|
||||
}
|
||||
dto.Navbar.SavedItemIds = preference.JSONData.Navbar.SavedItemIds
|
||||
dto.Navbar.BookmarkIds = preference.JSONData.Navbar.BookmarkIds
|
||||
}
|
||||
|
||||
if preference.JSONData.QueryHistory.HomeTab != "" {
|
||||
|
@ -71,8 +71,8 @@ func (s *Service) GetWithDefaults(ctx context.Context, query *pref.GetPreference
|
||||
res.JSONData.QueryHistory.HomeTab = p.JSONData.QueryHistory.HomeTab
|
||||
}
|
||||
|
||||
if p.JSONData.Navbar.SavedItemIds != nil {
|
||||
res.JSONData.Navbar.SavedItemIds = p.JSONData.Navbar.SavedItemIds
|
||||
if p.JSONData.Navbar.BookmarkIds != nil {
|
||||
res.JSONData.Navbar.BookmarkIds = p.JSONData.Navbar.BookmarkIds
|
||||
}
|
||||
|
||||
if p.JSONData.CookiePreferences != nil {
|
||||
@ -174,11 +174,11 @@ func (s *Service) Patch(ctx context.Context, cmd *pref.PatchPreferenceCommand) e
|
||||
preference.JSONData.Language = *cmd.Language
|
||||
}
|
||||
|
||||
if cmd.Navbar != nil && cmd.Navbar.SavedItemIds != nil {
|
||||
if cmd.Navbar != nil && cmd.Navbar.BookmarkIds != nil {
|
||||
if preference.JSONData == nil {
|
||||
preference.JSONData = &pref.PreferenceJSONData{}
|
||||
}
|
||||
preference.JSONData.Navbar.SavedItemIds = cmd.Navbar.SavedItemIds
|
||||
preference.JSONData.Navbar.BookmarkIds = cmd.Navbar.BookmarkIds
|
||||
}
|
||||
|
||||
if cmd.QueryHistory != nil {
|
||||
|
@ -17050,7 +17050,7 @@
|
||||
"type": "object",
|
||||
"title": "NavbarPreference defines model for NavbarPreference.",
|
||||
"properties": {
|
||||
"savedItemIds": {
|
||||
"bookmarkIds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
|
@ -3,14 +3,15 @@ import { DOMAttributes } from '@react-types/shared';
|
||||
import { memo, forwardRef, useCallback } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { GrafanaTheme2, NavModelItem } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { config, reportInteraction } from '@grafana/runtime';
|
||||
import { CustomScrollbar, Icon, IconButton, useStyles2, Stack } from '@grafana/ui';
|
||||
import { useGrafana } from 'app/core/context/GrafanaContext';
|
||||
import { t } from 'app/core/internationalization';
|
||||
import { setBookmark } from 'app/core/reducers/navBarTree';
|
||||
import { usePatchUserPreferencesMutation } from 'app/features/preferences/api/index';
|
||||
import { useSelector } from 'app/types';
|
||||
import { useDispatch, useSelector } from 'app/types';
|
||||
|
||||
import { MegaMenuItem } from './MegaMenuItem';
|
||||
import { usePinnedItems } from './hooks';
|
||||
@ -28,6 +29,7 @@ export const MegaMenu = memo(
|
||||
const styles = useStyles2(getStyles);
|
||||
const location = useLocation();
|
||||
const { chrome } = useGrafana();
|
||||
const dispatch = useDispatch();
|
||||
const state = chrome.useState();
|
||||
const [patchPreferences] = usePatchUserPreferencesMutation();
|
||||
const pinnedItems = usePinnedItems();
|
||||
@ -61,7 +63,8 @@ export const MegaMenu = memo(
|
||||
[pinnedItems]
|
||||
);
|
||||
|
||||
const onPinItem = (id?: string) => {
|
||||
const onPinItem = (item: NavModelItem) => {
|
||||
const id = item.id;
|
||||
if (id && config.featureToggles.pinNavItems) {
|
||||
const navItem = navTree.find((item) => item.id === id);
|
||||
const isSaved = isPinned(id);
|
||||
@ -73,9 +76,13 @@ export const MegaMenu = memo(
|
||||
patchPreferences({
|
||||
patchPrefsCmd: {
|
||||
navbar: {
|
||||
savedItemIds: newItems,
|
||||
bookmarkIds: newItems,
|
||||
},
|
||||
},
|
||||
}).then((data) => {
|
||||
if (!data.error) {
|
||||
dispatch(setBookmark({ item: item, isSaved: !isSaved }));
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -19,7 +19,7 @@ interface Props {
|
||||
activeItem?: NavModelItem;
|
||||
onClick?: () => void;
|
||||
level?: number;
|
||||
onPin: (id?: string) => void;
|
||||
onPin: (item: NavModelItem) => void;
|
||||
isPinned: (id?: string) => boolean;
|
||||
}
|
||||
|
||||
@ -105,7 +105,7 @@ export function MegaMenuItem({ link, activeItem, level = 0, onClick, onPin, isPi
|
||||
target={link.target}
|
||||
url={link.url}
|
||||
id={link.id}
|
||||
onPin={onPin}
|
||||
onPin={() => onPin(link)}
|
||||
isPinned={isPinned(link.id)}
|
||||
>
|
||||
<div
|
||||
|
@ -32,7 +32,8 @@ export function MegaMenuItemText({ children, isActive, onClick, target, url, id,
|
||||
}
|
||||
{config.featureToggles.pinNavItems && (
|
||||
<Icon
|
||||
name={isPinned ? 'favorite' : 'star'}
|
||||
name="bookmark"
|
||||
type={isPinned ? 'solid' : 'default'}
|
||||
className={'pin-icon'}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
|
@ -5,7 +5,7 @@ import { useGetUserPreferencesQuery } from 'app/features/preferences/api';
|
||||
|
||||
export const usePinnedItems = () => {
|
||||
const preferences = useGetUserPreferencesQuery();
|
||||
const pinnedItems = useMemo(() => preferences.data?.navbar?.savedItemIds || [], [preferences]);
|
||||
const pinnedItems = useMemo(() => preferences.data?.navbar?.bookmarkIds || [], [preferences]);
|
||||
|
||||
if (config.featureToggles.pinNavItems) {
|
||||
return pinnedItems;
|
||||
|
@ -51,6 +51,31 @@ const navTreeSlice = createSlice({
|
||||
}
|
||||
}
|
||||
},
|
||||
setBookmark: (state, action: PayloadAction<{ item: NavModelItem; isSaved: boolean }>) => {
|
||||
if (!config.featureToggles.pinNavItems) {
|
||||
return;
|
||||
}
|
||||
const bookmarks = state.find((navItem) => navItem.id === 'bookmarks');
|
||||
const { item, isSaved } = action.payload;
|
||||
if (bookmarks) {
|
||||
if (isSaved) {
|
||||
if (!bookmarks.children) {
|
||||
bookmarks.children = [];
|
||||
}
|
||||
const newBookmark: NavModelItem = {
|
||||
...item,
|
||||
// Clear the children, sortWeight and empty message of the item
|
||||
children: [],
|
||||
sortWeight: 0,
|
||||
emptyMessageId: '',
|
||||
emptyMessage: '',
|
||||
};
|
||||
bookmarks.children.push(newBookmark);
|
||||
} else {
|
||||
bookmarks.children = bookmarks.children?.filter((i) => i.id !== item.id) ?? [];
|
||||
}
|
||||
}
|
||||
},
|
||||
updateDashboardName: (state, action: PayloadAction<{ id: string; title: string; url: string }>) => {
|
||||
const { id, title, url } = action.payload;
|
||||
const starredItems = state.find((navItem) => navItem.id === 'starred');
|
||||
@ -73,5 +98,5 @@ const navTreeSlice = createSlice({
|
||||
},
|
||||
});
|
||||
|
||||
export const { setStarred, removePluginFromNavTree, updateDashboardName } = navTreeSlice.actions;
|
||||
export const { setStarred, removePluginFromNavTree, updateDashboardName, setBookmark } = navTreeSlice.actions;
|
||||
export const navTreeReducer = navTreeSlice.reducer;
|
||||
|
@ -22,6 +22,10 @@ export function getNavTitle(navId: string | undefined) {
|
||||
return t('nav.create-import.title', 'Import dashboard');
|
||||
case 'alert':
|
||||
return t('nav.create-alert.title', 'New alert rule');
|
||||
case 'bookmarks':
|
||||
return t('nav.bookmarks.title', 'Bookmarks');
|
||||
case 'bookmarks-empty':
|
||||
return t('nav.bookmarks-empty.title', 'Bookmark pages for them to appear here');
|
||||
case 'starred':
|
||||
return t('nav.starred.title', 'Starred');
|
||||
case 'starred-empty':
|
||||
|
@ -38,7 +38,7 @@ export type CookiePreferencesDefinesModelForCookiePreferences = {
|
||||
};
|
||||
};
|
||||
export type NavbarPreferenceDefinesModelForNavbarPreference = {
|
||||
savedItemIds?: string[];
|
||||
bookmarkIds?: string[];
|
||||
};
|
||||
export type QueryHistoryPreferenceDefinesModelForQueryHistoryPreference = {
|
||||
/** HomeTab one of: '' | 'query' | 'starred'; */
|
||||
@ -66,7 +66,7 @@ export type ErrorResponseBody = {
|
||||
/** a human readable version of the error */
|
||||
message: string;
|
||||
/** Status An optional status to denote the cause of the error.
|
||||
|
||||
|
||||
For example, a 412 Precondition Failed error may include additional information of why that error happened. */
|
||||
status?: string;
|
||||
};
|
||||
|
@ -514,6 +514,10 @@ export function getAppRoutes(): RouteDescriptor[] {
|
||||
() => import(/* webpackChunkName: "DataTrailsPage"*/ 'app/features/trails/DataTrailsPage')
|
||||
),
|
||||
},
|
||||
{
|
||||
path: '/bookmarks',
|
||||
component: () => <NavLandingPage navId="bookmarks" />,
|
||||
},
|
||||
...getPluginCatalogRoutes(),
|
||||
...getSupportBundleRoutes(),
|
||||
...getAlertingRoutes(),
|
||||
|
@ -1188,6 +1188,12 @@
|
||||
"authentication": {
|
||||
"title": "Authentication"
|
||||
},
|
||||
"bookmarks": {
|
||||
"title": "Bookmarks"
|
||||
},
|
||||
"bookmarks-empty": {
|
||||
"title": "Bookmark pages for them to appear here"
|
||||
},
|
||||
"collector": {
|
||||
"title": "Collector"
|
||||
},
|
||||
|
@ -1188,6 +1188,12 @@
|
||||
"authentication": {
|
||||
"title": "Åūŧĥęʼnŧįčäŧįőʼn"
|
||||
},
|
||||
"bookmarks": {
|
||||
"title": "ßőőĸmäřĸş"
|
||||
},
|
||||
"bookmarks-empty": {
|
||||
"title": "ßőőĸmäřĸ päģęş ƒőř ŧĥęm ŧő äppęäř ĥęřę"
|
||||
},
|
||||
"collector": {
|
||||
"title": "Cőľľęčŧőř"
|
||||
},
|
||||
|
@ -7146,7 +7146,7 @@
|
||||
},
|
||||
"NavbarPreference": {
|
||||
"properties": {
|
||||
"savedItemIds": {
|
||||
"bookmarkIds": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user