mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Nav: Add items to saved (#89908)
* App events: Add info notification type * Revert state * Use info alert * Nav: Enable saving items * Use local state * Use RTK query * Revert go.work * Revert * User-specific queries * Add memo * Fix base URL * Switch to ids * Fix memo * Add codeowners * Generate API * Separate user prefs API * Remove tag * Update export * Use feature toggle
This commit is contained in:
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@@ -448,6 +448,7 @@ playwright.config.ts @grafana/plugins-platform-frontend
|
|||||||
/public/app/features/transformers/timeSeriesTable/ @grafana/dataviz-squad @grafana/app-o11y-visualizations
|
/public/app/features/transformers/timeSeriesTable/ @grafana/dataviz-squad @grafana/app-o11y-visualizations
|
||||||
/public/app/features/users/ @grafana/access-squad
|
/public/app/features/users/ @grafana/access-squad
|
||||||
/public/app/features/variables/ @grafana/dashboards-squad
|
/public/app/features/variables/ @grafana/dashboards-squad
|
||||||
|
/public/app/features/preferences/ @grafana/grafana-frontend-platform
|
||||||
/public/app/plugins/panel/alertlist/ @grafana/alerting-frontend
|
/public/app/plugins/panel/alertlist/ @grafana/alerting-frontend
|
||||||
/public/app/plugins/panel/annolist/ @grafana/grafana-frontend-platform
|
/public/app/plugins/panel/annolist/ @grafana/grafana-frontend-platform
|
||||||
/public/app/plugins/panel/barchart/ @grafana/dataviz-squad
|
/public/app/plugins/panel/barchart/ @grafana/dataviz-squad
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { DOMAttributes } from '@react-types/shared';
|
import { DOMAttributes } from '@react-types/shared';
|
||||||
import { memo, forwardRef } from 'react';
|
import { memo, forwardRef, useCallback } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
|
import { config } from '@grafana/runtime';
|
||||||
import { CustomScrollbar, Icon, IconButton, useStyles2, Stack } from '@grafana/ui';
|
import { CustomScrollbar, Icon, IconButton, useStyles2, Stack } from '@grafana/ui';
|
||||||
import { useGrafana } from 'app/core/context/GrafanaContext';
|
import { useGrafana } from 'app/core/context/GrafanaContext';
|
||||||
import { t } from 'app/core/internationalization';
|
import { t } from 'app/core/internationalization';
|
||||||
|
import { usePatchUserPreferencesMutation } from 'app/features/preferences/api/index';
|
||||||
import { useSelector } from 'app/types';
|
import { useSelector } from 'app/types';
|
||||||
|
|
||||||
import { MegaMenuItem } from './MegaMenuItem';
|
import { MegaMenuItem } from './MegaMenuItem';
|
||||||
|
import { usePinnedItems } from './hooks';
|
||||||
import { enrichWithInteractionTracking, getActiveItem } from './utils';
|
import { enrichWithInteractionTracking, getActiveItem } from './utils';
|
||||||
|
|
||||||
export const MENU_WIDTH = '300px';
|
export const MENU_WIDTH = '300px';
|
||||||
@@ -26,6 +29,8 @@ export const MegaMenu = memo(
|
|||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { chrome } = useGrafana();
|
const { chrome } = useGrafana();
|
||||||
const state = chrome.useState();
|
const state = chrome.useState();
|
||||||
|
const [patchPreferences] = usePatchUserPreferencesMutation();
|
||||||
|
const pinnedItems = usePinnedItems();
|
||||||
|
|
||||||
// Remove profile + help from tree
|
// Remove profile + help from tree
|
||||||
const navItems = navTree
|
const navItems = navTree
|
||||||
@@ -46,6 +51,29 @@ export const MegaMenu = memo(
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isPinned = useCallback(
|
||||||
|
(id?: string) => {
|
||||||
|
if (!id || !pinnedItems?.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return pinnedItems?.includes(id);
|
||||||
|
},
|
||||||
|
[pinnedItems]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onPinItem = (id?: string) => {
|
||||||
|
if (id && config.featureToggles.pinNavItems) {
|
||||||
|
const newItems = isPinned(id) ? pinnedItems.filter((i) => id !== i) : [...pinnedItems, id];
|
||||||
|
patchPreferences({
|
||||||
|
patchPrefsCmd: {
|
||||||
|
navbar: {
|
||||||
|
savedItemIds: newItems,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-testid={selectors.components.NavMenu.Menu} ref={ref} {...restProps}>
|
<div data-testid={selectors.components.NavMenu.Menu} ref={ref} {...restProps}>
|
||||||
<div className={styles.mobileHeader}>
|
<div className={styles.mobileHeader}>
|
||||||
@@ -79,8 +107,10 @@ export const MegaMenu = memo(
|
|||||||
)}
|
)}
|
||||||
<MegaMenuItem
|
<MegaMenuItem
|
||||||
link={link}
|
link={link}
|
||||||
|
isPinned={isPinned}
|
||||||
onClick={state.megaMenuDocked ? undefined : onClose}
|
onClick={state.megaMenuDocked ? undefined : onClose}
|
||||||
activeItem={activeItem}
|
activeItem={activeItem}
|
||||||
|
onPin={onPinItem}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -19,11 +19,13 @@ interface Props {
|
|||||||
activeItem?: NavModelItem;
|
activeItem?: NavModelItem;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
level?: number;
|
level?: number;
|
||||||
|
onPin: (id?: string) => void;
|
||||||
|
isPinned: (id?: string) => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_DEPTH = 2;
|
const MAX_DEPTH = 2;
|
||||||
|
|
||||||
export function MegaMenuItem({ link, activeItem, level = 0, onClick }: Props) {
|
export function MegaMenuItem({ link, activeItem, level = 0, onClick, onPin, isPinned }: Props) {
|
||||||
const { chrome } = useGrafana();
|
const { chrome } = useGrafana();
|
||||||
const state = chrome.useState();
|
const state = chrome.useState();
|
||||||
const menuIsDocked = state.megaMenuDocked;
|
const menuIsDocked = state.megaMenuDocked;
|
||||||
@@ -102,6 +104,9 @@ export function MegaMenuItem({ link, activeItem, level = 0, onClick }: Props) {
|
|||||||
}}
|
}}
|
||||||
target={link.target}
|
target={link.target}
|
||||||
url={link.url}
|
url={link.url}
|
||||||
|
id={link.id}
|
||||||
|
onPin={onPin}
|
||||||
|
isPinned={isPinned(link.id)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={cx(styles.labelWrapper, {
|
className={cx(styles.labelWrapper, {
|
||||||
@@ -127,6 +132,8 @@ export function MegaMenuItem({ link, activeItem, level = 0, onClick }: Props) {
|
|||||||
activeItem={activeItem}
|
activeItem={activeItem}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
level={level + 1}
|
level={level + 1}
|
||||||
|
onPin={onPin}
|
||||||
|
isPinned={isPinned}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import * as React from 'react';
|
|||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
|
import { config } from '@grafana/runtime';
|
||||||
import { Icon, Link, useTheme2 } from '@grafana/ui';
|
import { Icon, Link, useTheme2 } from '@grafana/ui';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@@ -11,9 +12,12 @@ export interface Props {
|
|||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
target?: HTMLAnchorElement['target'];
|
target?: HTMLAnchorElement['target'];
|
||||||
url: string;
|
url: string;
|
||||||
|
id?: string;
|
||||||
|
onPin: (id?: string) => void;
|
||||||
|
isPinned?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MegaMenuItemText({ children, isActive, onClick, target, url }: Props) {
|
export function MegaMenuItemText({ children, isActive, onClick, target, url, id, onPin, isPinned }: Props) {
|
||||||
const theme = useTheme2();
|
const theme = useTheme2();
|
||||||
const styles = getStyles(theme, isActive);
|
const styles = getStyles(theme, isActive);
|
||||||
const LinkComponent = !target && url.startsWith('/') ? Link : 'a';
|
const LinkComponent = !target && url.startsWith('/') ? Link : 'a';
|
||||||
@@ -26,6 +30,17 @@ export function MegaMenuItemText({ children, isActive, onClick, target, url }: P
|
|||||||
// As nav links are supposed to link to internal urls this option should be used with caution
|
// As nav links are supposed to link to internal urls this option should be used with caution
|
||||||
target === '_blank' && <Icon data-testid="external-link-icon" name="external-link-alt" />
|
target === '_blank' && <Icon data-testid="external-link-icon" name="external-link-alt" />
|
||||||
}
|
}
|
||||||
|
{config.featureToggles.pinNavItems && (
|
||||||
|
<Icon
|
||||||
|
name={isPinned ? 'favorite' : 'star'}
|
||||||
|
className={'pin-icon'}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
onPin(id);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -90,5 +105,17 @@ const getStyles = (theme: GrafanaTheme2, isActive: Props['isActive']) => ({
|
|||||||
gap: '0.5rem',
|
gap: '0.5rem',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
'.pin-icon': {
|
||||||
|
display: 'none',
|
||||||
|
padding: theme.spacing(0.5),
|
||||||
|
width: theme.spacing(3),
|
||||||
|
height: theme.spacing(3),
|
||||||
|
},
|
||||||
|
'&:hover': {
|
||||||
|
'.pin-icon': {
|
||||||
|
display: 'block',
|
||||||
|
},
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
14
public/app/core/components/AppChrome/MegaMenu/hooks.ts
Normal file
14
public/app/core/components/AppChrome/MegaMenu/hooks.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
import { config } from '@grafana/runtime';
|
||||||
|
import { useGetUserPreferencesQuery } from 'app/features/preferences/api';
|
||||||
|
|
||||||
|
export const usePinnedItems = () => {
|
||||||
|
const preferences = useGetUserPreferencesQuery();
|
||||||
|
const pinnedItems = useMemo(() => preferences.data?.navbar?.savedItemIds || [], [preferences]);
|
||||||
|
|
||||||
|
if (config.featureToggles.pinNavItems) {
|
||||||
|
return pinnedItems;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
};
|
||||||
@@ -28,6 +28,7 @@ import usersReducers from 'app/features/users/state/reducers';
|
|||||||
import templatingReducers from 'app/features/variables/state/keyedVariablesReducer';
|
import templatingReducers from 'app/features/variables/state/keyedVariablesReducer';
|
||||||
|
|
||||||
import { alertingApi } from '../../features/alerting/unified/api/alertingApi';
|
import { alertingApi } from '../../features/alerting/unified/api/alertingApi';
|
||||||
|
import { userPreferencesAPI } from '../../features/preferences/api';
|
||||||
import { queryLibraryApi } from '../../features/query-library/api/factory';
|
import { queryLibraryApi } from '../../features/query-library/api/factory';
|
||||||
import { cleanUpAction } from '../actions/cleanUp';
|
import { cleanUpAction } from '../actions/cleanUp';
|
||||||
|
|
||||||
@@ -59,6 +60,7 @@ const rootReducers = {
|
|||||||
[browseDashboardsAPI.reducerPath]: browseDashboardsAPI.reducer,
|
[browseDashboardsAPI.reducerPath]: browseDashboardsAPI.reducer,
|
||||||
[cloudMigrationAPI.reducerPath]: cloudMigrationAPI.reducer,
|
[cloudMigrationAPI.reducerPath]: cloudMigrationAPI.reducer,
|
||||||
[queryLibraryApi.reducerPath]: queryLibraryApi.reducer,
|
[queryLibraryApi.reducerPath]: queryLibraryApi.reducer,
|
||||||
|
[userPreferencesAPI.reducerPath]: userPreferencesAPI.reducer,
|
||||||
};
|
};
|
||||||
|
|
||||||
const addedReducers = {};
|
const addedReducers = {};
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ export type ErrorResponseBody = {
|
|||||||
/** a human readable version of the error */
|
/** a human readable version of the error */
|
||||||
message: string;
|
message: string;
|
||||||
/** Status An optional status to denote the cause of the error.
|
/** 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. */
|
For example, a 412 Precondition Failed error may include additional information of why that error happened. */
|
||||||
status?: string;
|
status?: string;
|
||||||
};
|
};
|
||||||
|
|||||||
19
public/app/features/preferences/api/index.ts
Normal file
19
public/app/features/preferences/api/index.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { generatedAPI } from './user/endpoints.gen';
|
||||||
|
|
||||||
|
export const { useGetUserPreferencesQuery, usePatchUserPreferencesMutation, useUpdateUserPreferencesMutation } =
|
||||||
|
generatedAPI;
|
||||||
|
|
||||||
|
export const userPreferencesAPI = generatedAPI.enhanceEndpoints({
|
||||||
|
addTagTypes: ['UserPreferences'],
|
||||||
|
endpoints: {
|
||||||
|
getUserPreferences: {
|
||||||
|
providesTags: ['UserPreferences'],
|
||||||
|
},
|
||||||
|
updateUserPreferences: {
|
||||||
|
invalidatesTags: ['UserPreferences'],
|
||||||
|
},
|
||||||
|
patchUserPreferences: {
|
||||||
|
invalidatesTags: ['UserPreferences'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
36
public/app/features/preferences/api/user/baseAPI.ts
Normal file
36
public/app/features/preferences/api/user/baseAPI.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { BaseQueryFn, createApi } from '@reduxjs/toolkit/query/react';
|
||||||
|
import { lastValueFrom } from 'rxjs';
|
||||||
|
|
||||||
|
import { BackendSrvRequest, getBackendSrv } from '@grafana/runtime';
|
||||||
|
|
||||||
|
interface RequestOptions extends BackendSrvRequest {
|
||||||
|
manageError?: (err: unknown) => { error: unknown };
|
||||||
|
showErrorAlert?: boolean;
|
||||||
|
body?: BackendSrvRequest['data'];
|
||||||
|
}
|
||||||
|
|
||||||
|
function createBackendSrvBaseQuery({ baseURL }: { baseURL: string }): BaseQueryFn<RequestOptions> {
|
||||||
|
async function backendSrvBaseQuery(requestOptions: RequestOptions) {
|
||||||
|
try {
|
||||||
|
const { data: responseData, ...meta } = await lastValueFrom(
|
||||||
|
getBackendSrv().fetch({
|
||||||
|
...requestOptions,
|
||||||
|
url: baseURL + requestOptions.url,
|
||||||
|
showErrorAlert: requestOptions.showErrorAlert,
|
||||||
|
data: requestOptions.body,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return { data: responseData, meta };
|
||||||
|
} catch (error) {
|
||||||
|
return requestOptions.manageError ? requestOptions.manageError(error) : { error };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return backendSrvBaseQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const baseAPI = createApi({
|
||||||
|
reducerPath: 'userPreferencesAPI',
|
||||||
|
baseQuery: createBackendSrvBaseQuery({ baseURL: '/api' }),
|
||||||
|
endpoints: () => ({}),
|
||||||
|
});
|
||||||
102
public/app/features/preferences/api/user/endpoints.gen.ts
Normal file
102
public/app/features/preferences/api/user/endpoints.gen.ts
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import { baseAPI as api } from './baseAPI';
|
||||||
|
const injectedRtkApi = api.injectEndpoints({
|
||||||
|
endpoints: (build) => ({
|
||||||
|
getUserPreferences: build.query<GetUserPreferencesApiResponse, GetUserPreferencesApiArg>({
|
||||||
|
query: () => ({ url: `/user/preferences` }),
|
||||||
|
}),
|
||||||
|
patchUserPreferences: build.mutation<PatchUserPreferencesApiResponse, PatchUserPreferencesApiArg>({
|
||||||
|
query: (queryArg) => ({ url: `/user/preferences`, method: 'PATCH', body: queryArg.patchPrefsCmd }),
|
||||||
|
}),
|
||||||
|
updateUserPreferences: build.mutation<UpdateUserPreferencesApiResponse, UpdateUserPreferencesApiArg>({
|
||||||
|
query: (queryArg) => ({ url: `/user/preferences`, method: 'PUT', body: queryArg.updatePrefsCmd }),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
overrideExisting: false,
|
||||||
|
});
|
||||||
|
export { injectedRtkApi as generatedAPI };
|
||||||
|
export type GetUserPreferencesApiResponse = /** status 200 (empty) */ Preferences;
|
||||||
|
export type GetUserPreferencesApiArg = void;
|
||||||
|
export type PatchUserPreferencesApiResponse =
|
||||||
|
/** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody;
|
||||||
|
export type PatchUserPreferencesApiArg = {
|
||||||
|
patchPrefsCmd: PatchPrefsCmd;
|
||||||
|
};
|
||||||
|
export type UpdateUserPreferencesApiResponse =
|
||||||
|
/** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody;
|
||||||
|
export type UpdateUserPreferencesApiArg = {
|
||||||
|
updatePrefsCmd: UpdatePrefsCmd;
|
||||||
|
};
|
||||||
|
export type CookiePreferencesDefinesModelForCookiePreferences = {
|
||||||
|
analytics?: {
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
functional?: {
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
performance?: {
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export type NavbarPreferenceDefinesModelForNavbarPreference = {
|
||||||
|
savedItemIds?: string[];
|
||||||
|
};
|
||||||
|
export type QueryHistoryPreferenceDefinesModelForQueryHistoryPreference = {
|
||||||
|
/** HomeTab one of: '' | 'query' | 'starred'; */
|
||||||
|
homeTab?: string;
|
||||||
|
};
|
||||||
|
export type Preferences = {
|
||||||
|
cookiePreferences?: CookiePreferencesDefinesModelForCookiePreferences;
|
||||||
|
/** UID for the home dashboard */
|
||||||
|
homeDashboardUID?: string;
|
||||||
|
/** Selected language (beta) */
|
||||||
|
language?: string;
|
||||||
|
navbar?: NavbarPreferenceDefinesModelForNavbarPreference;
|
||||||
|
queryHistory?: QueryHistoryPreferenceDefinesModelForQueryHistoryPreference;
|
||||||
|
/** Theme light, dark, empty is default */
|
||||||
|
theme?: string;
|
||||||
|
/** The timezone selection
|
||||||
|
TODO: this should use the timezone defined in common */
|
||||||
|
timezone?: string;
|
||||||
|
/** WeekStart day of the week (sunday, monday, etc) */
|
||||||
|
weekStart?: string;
|
||||||
|
};
|
||||||
|
export type ErrorResponseBody = {
|
||||||
|
/** Error An optional detailed description of the actual error. Only included if running in developer mode. */
|
||||||
|
error?: string;
|
||||||
|
/** 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;
|
||||||
|
};
|
||||||
|
export type SuccessResponseBody = {
|
||||||
|
message?: string;
|
||||||
|
};
|
||||||
|
export type CookieType = string;
|
||||||
|
export type PatchPrefsCmd = {
|
||||||
|
cookies?: CookieType[];
|
||||||
|
/** The numerical :id of a favorited dashboard */
|
||||||
|
homeDashboardId?: number;
|
||||||
|
homeDashboardUID?: string;
|
||||||
|
language?: string;
|
||||||
|
navbar?: NavbarPreferenceDefinesModelForNavbarPreference;
|
||||||
|
queryHistory?: QueryHistoryPreferenceDefinesModelForQueryHistoryPreference;
|
||||||
|
theme?: 'light' | 'dark';
|
||||||
|
timezone?: 'utc' | 'browser';
|
||||||
|
weekStart?: string;
|
||||||
|
};
|
||||||
|
export type UpdatePrefsCmd = {
|
||||||
|
cookies?: CookieType[];
|
||||||
|
/** The numerical :id of a favorited dashboard */
|
||||||
|
homeDashboardId?: number;
|
||||||
|
homeDashboardUID?: string;
|
||||||
|
language?: string;
|
||||||
|
navbar?: NavbarPreferenceDefinesModelForNavbarPreference;
|
||||||
|
queryHistory?: QueryHistoryPreferenceDefinesModelForQueryHistoryPreference;
|
||||||
|
theme?: 'light' | 'dark' | 'system';
|
||||||
|
timezone?: 'utc' | 'browser';
|
||||||
|
weekStart?: string;
|
||||||
|
};
|
||||||
|
export const { useGetUserPreferencesQuery, usePatchUserPreferencesMutation, useUpdateUserPreferencesMutation } =
|
||||||
|
injectedRtkApi;
|
||||||
@@ -5,6 +5,7 @@ import { Middleware } from 'redux';
|
|||||||
import { browseDashboardsAPI } from 'app/features/browse-dashboards/api/browseDashboardsAPI';
|
import { browseDashboardsAPI } from 'app/features/browse-dashboards/api/browseDashboardsAPI';
|
||||||
import { publicDashboardApi } from 'app/features/dashboard/api/publicDashboardApi';
|
import { publicDashboardApi } from 'app/features/dashboard/api/publicDashboardApi';
|
||||||
import { cloudMigrationAPI } from 'app/features/migrate-to-cloud/api';
|
import { cloudMigrationAPI } from 'app/features/migrate-to-cloud/api';
|
||||||
|
import { userPreferencesAPI } from 'app/features/preferences/api';
|
||||||
import { StoreState } from 'app/types/store';
|
import { StoreState } from 'app/types/store';
|
||||||
|
|
||||||
import { buildInitialState } from '../core/reducers/navModel';
|
import { buildInitialState } from '../core/reducers/navModel';
|
||||||
@@ -39,6 +40,7 @@ export function configureStore(initialState?: Partial<StoreState>) {
|
|||||||
browseDashboardsAPI.middleware,
|
browseDashboardsAPI.middleware,
|
||||||
cloudMigrationAPI.middleware,
|
cloudMigrationAPI.middleware,
|
||||||
queryLibraryApi.middleware,
|
queryLibraryApi.middleware,
|
||||||
|
userPreferencesAPI.middleware,
|
||||||
...extraMiddleware
|
...extraMiddleware
|
||||||
),
|
),
|
||||||
devTools: process.env.NODE_ENV !== 'production',
|
devTools: process.env.NODE_ENV !== 'production',
|
||||||
|
|||||||
@@ -30,6 +30,11 @@ const config: ConfigFile = {
|
|||||||
'getDashboardByUid',
|
'getDashboardByUid',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
'../public/app/features/preferences/api/user/endpoints.gen.ts': {
|
||||||
|
apiFile: '../public/app/features/preferences/api/user/baseAPI.ts',
|
||||||
|
apiImport: 'baseAPI',
|
||||||
|
filterEndpoints: ['getUserPreferences', 'updateUserPreferences', 'patchUserPreferences'],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user