diff --git a/public/app/core/reducers/fn-slice.ts b/public/app/core/reducers/fn-slice.ts index d1c6b763ac14..57f9d767eec0 100644 --- a/public/app/core/reducers/fn-slice.ts +++ b/public/app/core/reducers/fn-slice.ts @@ -2,6 +2,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { WritableDraft } from 'immer/dist/internal'; import { GrafanaThemeType } from '@grafana/data'; +import { dispatch } from 'app/store/store'; import { AnyObject } from '../../fn-app/types'; @@ -16,6 +17,11 @@ export interface FnGlobalState { hiddenVariables: string[]; } +export type UpdateFNGlobalStateAction = PayloadAction<{ + type: keyof FnGlobalState; + payload: FnGlobalState[keyof FnGlobalState]; +}>; + const INITIAL_MODE = GrafanaThemeType.Light; const initialState: FnGlobalState = { @@ -33,14 +39,11 @@ const fnSlice = createSlice({ name: 'fnGlobalState', initialState, reducers: { - setInitialMountState: (state, action: PayloadAction) => { + setInitialMountState: (state, action: PayloadAction>) => { return { ...state, ...action.payload }; }, - updateFnState: ( - state: WritableDraft, - action: PayloadAction<{ type: keyof FnGlobalState; payload: FnGlobalState[keyof FnGlobalState] }> - ) => { - const { type, payload } = action; + updateFnState: (state: WritableDraft, action: UpdateFNGlobalStateAction) => { + const { type, payload } = action.payload; return { ...state, @@ -52,3 +55,15 @@ const fnSlice = createSlice({ export const { updateFnState, setInitialMountState } = fnSlice.actions; export const fnSliceReducer = fnSlice.reducer; + +export const updateFNGlobalState = ( + type: keyof FnGlobalState, + payload: UpdateFNGlobalStateAction['payload']['payload'] +): void => { + dispatch( + updateFnState({ + type, + payload, + }) + ); +}; diff --git a/public/app/fn-app/create-mfe.ts b/public/app/fn-app/create-mfe.ts index 865be73639aa..d93b754c6247 100644 --- a/public/app/fn-app/create-mfe.ts +++ b/public/app/fn-app/create-mfe.ts @@ -13,11 +13,11 @@ import { ThemeChangedEvent } from '@grafana/runtime'; import { getTheme } from '@grafana/ui'; import appEvents from 'app/core/app_events'; import config from 'app/core/config'; -import { updateFnState } from 'app/core/reducers/fn-slice'; +import { updateFNGlobalState } from 'app/core/reducers/fn-slice'; import { backendSrv } from 'app/core/services/backend_srv'; import fn_app from 'app/fn_app'; import { FnLoggerService } from 'app/fn_logger'; -import { dispatch, store } from 'app/store/store'; +import { store } from 'app/store/store'; import { createFnColors } from '../../../packages/grafana-data/src/themes/fnCreateColors'; import { GrafanaTheme2 } from '../../../packages/grafana-data/src/themes/types'; @@ -53,246 +53,245 @@ type DeepPartial = { }; class createMfe { - private static readonly containerSelector = '#grafanaRoot'; - - private static readonly logPrefix = '[FN Grafana]'; - - mode: FNDashboardProps['mode']; - static Component: ComponentType; - constructor(readonly props: FNDashboardProps) { - this.mode = props.mode; - } - - /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ - private static logger = (...args: any[]) => console.log(createMfe.logPrefix, ...args); - - static getLifeCycles(component: ComponentType) { - const lifeCycles: FrameworkLifeCycles = { - bootstrap: this.boot(), - mount: this.mountFnApp(component), - unmount: this.unMountFnApp(), - update: this.updateFnApp(), - afterMount: () => Promise.resolve(), - beforeMount: () => Promise.resolve(), - afterUnmount: () => Promise.resolve(), - beforeUnmount: () => Promise.resolve(), - beforeLoad: () => Promise.resolve(), - }; + private static readonly containerSelector = '#grafanaRoot'; + + private static readonly logPrefix = '[FN Grafana]'; - return lifeCycles; - } + mode: FNDashboardProps['mode']; + static Component: ComponentType; + constructor(readonly props: FNDashboardProps) { + this.mode = props.mode; + } + + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ + private static logger = (...args: any[]) => console.log(createMfe.logPrefix, ...args); + + static getLifeCycles(component: ComponentType) { + const lifeCycles: FrameworkLifeCycles = { + bootstrap: this.boot(), + mount: this.mountFnApp(component), + unmount: this.unMountFnApp(), + update: this.updateFnApp(), + afterMount: () => Promise.resolve(), + beforeMount: () => Promise.resolve(), + afterUnmount: () => Promise.resolve(), + beforeUnmount: () => Promise.resolve(), + beforeLoad: () => Promise.resolve(), + }; + + return lifeCycles; + } - static create(component: ComponentType) { - return createMfe.getLifeCycles(component); - } + static create(component: ComponentType) { + return createMfe.getLifeCycles(component); + } - static boot() { - return () => fn_app.init(); - } + static boot() { + return () => fn_app.init(); + } - private static toggleTheme = (mode: FNDashboardProps['mode']): GrafanaThemeType.Light | GrafanaThemeType.Dark => - mode === 'dark' ? GrafanaThemeType.Light : GrafanaThemeType.Dark; + private static toggleTheme = (mode: FNDashboardProps['mode']): GrafanaThemeType.Light | GrafanaThemeType.Dark => + mode === 'dark' ? GrafanaThemeType.Light : GrafanaThemeType.Dark; - private static get styleSheetLink() { - const stylesheetLink = document.createElement('link'); - stylesheetLink.rel = 'stylesheet'; + private static get styleSheetLink() { + const stylesheetLink = document.createElement('link'); + stylesheetLink.rel = 'stylesheet'; - return stylesheetLink; - } + return stylesheetLink; + } - private static createGrafanaTheme2(mode: FNDashboardProps['mode']) { - config.theme2 = createTheme({ - colors: { - mode, - }, - }); + private static createGrafanaTheme2(mode: FNDashboardProps['mode']) { + config.theme2 = createTheme({ + colors: { + mode, + }, + }); - config.theme2.colors = createFnColors({ mode }); + config.theme2.colors = createFnColors({ mode }); - config.theme2.v1 = getTheme(mode); + config.theme2.v1 = getTheme(mode); - config.theme2.v1.colors = config.theme.colors; + config.theme2.v1.colors = config.theme.colors; - return config.theme2; - } + return config.theme2; + } - private static getPartialBootConfigWithTheme(theme2: GrafanaTheme2) { - const partial: DeepPartial = { - theme: theme2.v1, - bootData: { user: { lightTheme: theme2.isLight } }, - }; + private static getPartialBootConfigWithTheme(theme2: GrafanaTheme2) { + const partial: DeepPartial = { + theme: theme2.v1, + bootData: { user: { lightTheme: theme2.isLight } }, + }; - return partial; - } + return partial; + } - private static mergeBootConfigs(bootConfig: GrafanaBootConfig, partialBootConfig: DeepPartial) { - return merge({}, bootConfig, partialBootConfig); - } + private static mergeBootConfigs(bootConfig: GrafanaBootConfig, partialBootConfig: DeepPartial) { + return merge({}, bootConfig, partialBootConfig); + } - private static publishTheme(theme: GrafanaTheme2) { - appEvents.publish(new ThemeChangedEvent(theme)); - } + private static publishTheme(theme: GrafanaTheme2) { + appEvents.publish(new ThemeChangedEvent(theme)); + } - // NOTE: based on grafana function: 'toggleTheme' - private static removeThemeLinks(modeToBeTurnedOff: GrafanaThemeType.Light | GrafanaThemeType.Dark, timeout?: number) { - Array.from(document.getElementsByTagName('link')).forEach(createMfe.removeThemeLink(modeToBeTurnedOff, timeout)); - } + // NOTE: based on grafana function: 'toggleTheme' + private static removeThemeLinks(modeToBeTurnedOff: GrafanaThemeType.Light | GrafanaThemeType.Dark, timeout?: number) { + Array.from(document.getElementsByTagName('link')).forEach(createMfe.removeThemeLink(modeToBeTurnedOff, timeout)); + } - private static removeThemeLink(modeToBeTurnedOff: FNDashboardProps['mode'], timeout?: number) { - return (link: HTMLLinkElement) => { - if (!link.href?.includes(`build/grafana.${modeToBeTurnedOff}`)) { - return; - } + private static removeThemeLink(modeToBeTurnedOff: FNDashboardProps['mode'], timeout?: number) { + return (link: HTMLLinkElement) => { + if (!link.href?.includes(`build/grafana.${modeToBeTurnedOff}`)) { + return; + } - if (isNull(timeout)) { - link.remove(); + if (isNull(timeout)) { + link.remove(); - return; - } + return; + } - // Remove existing link after a 500ms to allow new css to load to avoid flickering - // If we add new css at the same time we remove current one the page will be rendered without css - // As the new css file is loading - setTimeout(link.remove, timeout); - }; - } + // Remove existing link after a 500ms to allow new css to load to avoid flickering + // If we add new css at the same time we remove current one the page will be rendered without css + // As the new css file is loading + setTimeout(link.remove, timeout); + }; + } - /** - * NOTE: - * If isRuntimeOnly then the stylesheets of the turned off theme are not removed - */ - private static loadFnTheme = (mode: FNDashboardProps['mode'], isRuntimeOnly = false) => { - createMfe.logger('Trying to load theme...', mode); + /** + * NOTE: + * If isRuntimeOnly then the stylesheets of the turned off theme are not removed + */ + private static loadFnTheme = (mode: FNDashboardProps['mode'], isRuntimeOnly = false) => { + createMfe.logger('Trying to load theme...', mode); - const grafanaTheme2 = createMfe.createGrafanaTheme2(mode); + const grafanaTheme2 = createMfe.createGrafanaTheme2(mode); - const partialBootConfigWithTheme = createMfe.getPartialBootConfigWithTheme(grafanaTheme2); + const partialBootConfigWithTheme = createMfe.getPartialBootConfigWithTheme(grafanaTheme2); - const bootConfigWithTheme = createMfe.mergeBootConfigs(config, partialBootConfigWithTheme); + const bootConfigWithTheme = createMfe.mergeBootConfigs(config, partialBootConfigWithTheme); - createMfe.publishTheme(bootConfigWithTheme.theme2); + createMfe.publishTheme(bootConfigWithTheme.theme2); - if (isRuntimeOnly) { - createMfe.logger('Successfully loaded theme:', mode); + if (isRuntimeOnly) { + createMfe.logger('Successfully loaded theme:', mode); - return; - } + return; + } - createMfe.removeThemeLinks(createMfe.toggleTheme(mode)); + createMfe.removeThemeLinks(createMfe.toggleTheme(mode)); - const newCssLink = createMfe.styleSheetLink; - newCssLink.href = config.bootData.themePaths[mode]; - document.body.appendChild(newCssLink); + const newCssLink = createMfe.styleSheetLink; + newCssLink.href = config.bootData.themePaths[mode]; + document.body.appendChild(newCssLink); - createMfe.logger('Successfully loaded theme:', mode); - }; + createMfe.logger('Successfully loaded theme:', mode); + }; - private static getContainer(props: FNDashboardProps) { - const parentElement = props.container || document; + private static getContainer(props: FNDashboardProps) { + const parentElement = props.container || document; - return parentElement.querySelector(createMfe.containerSelector); - } + return parentElement.querySelector(createMfe.containerSelector); + } - static mountFnApp(Component: ComponentType) { - const lifeCycleFn: FrameworkLifeCycles['mount'] = (props: FNDashboardProps) => { - return new Promise((res, rej) => { - createMfe.logger('Trying to mount grafana...'); + static mountFnApp(Component: ComponentType) { + const lifeCycleFn: FrameworkLifeCycles['mount'] = (props: FNDashboardProps) => { + return new Promise((res, rej) => { + createMfe.logger('Trying to mount grafana...'); - try { - createMfe.loadFnTheme(props.mode); - createMfe.Component = Component; + try { + createMfe.loadFnTheme(props.mode); + createMfe.Component = Component; - createMfe.renderMfeComponent(props, () => res(true)); - } catch (err) { - const message = `[FN Grafana]: Failed to mount grafana. ${err}`; + createMfe.renderMfeComponent(props, () => res(true)); + } catch (err) { + const message = `[FN Grafana]: Failed to mount grafana. ${err}`; - FnLoggerService.log(null, message); + FnLoggerService.log(null, message); - const fnError = new Error(message); + const fnError = new Error(message); - const name: FailedToMountGrafanaErrorName = 'FailedToMountGrafana'; - fnError.name = name; + const name: FailedToMountGrafanaErrorName = 'FailedToMountGrafana'; + fnError.name = name; - return rej(fnError); - } - }); - }; + return rej(fnError); + } + }); + }; - return lifeCycleFn; - } + return lifeCycleFn; + } - static unMountFnApp() { - const lifeCycleFn: FrameworkLifeCycles['unmount'] = (props: FNDashboardProps) => { - const container = createMfe.getContainer(props); + static unMountFnApp() { + const lifeCycleFn: FrameworkLifeCycles['unmount'] = (props: FNDashboardProps) => { + const container = createMfe.getContainer(props); - if (container) { - createMfe.logger('Trying to unmount grafana...'); + if (container) { + createMfe.logger('Trying to unmount grafana...'); - ReactDOM.unmountComponentAtNode(container); + ReactDOM.unmountComponentAtNode(container); - createMfe.logger('Successfully unmounted grafana.'); - } else { - createMfe.logger('Failed to unmount grafana. Container does not exist.'); - } + createMfe.logger('Successfully unmounted grafana.'); + } else { + createMfe.logger('Failed to unmount grafana. Container does not exist.'); + } - backendSrv.cancelAllInFlightRequests(); + backendSrv.cancelAllInFlightRequests(); - return Promise.resolve(!!container); - }; + return Promise.resolve(!!container); + }; - return lifeCycleFn; - } + return lifeCycleFn; + } - static updateFnApp() { - const lifeCycleFn: FrameworkLifeCycles['update'] = (props: FNDashboardProps) => { - createMfe.logger('Trying to update grafana with theme:', props.mode); + static updateFnApp() { + const lifeCycleFn: FrameworkLifeCycles['update'] = (props: FNDashboardProps) => { + createMfe.logger('Trying to update grafana with theme:', props.mode); + + + if (props.mode) { + /** + * NOTE + * We do not use the "mode" state right now, + * but I believe that as long as we store the "mode, we should update it + */ + updateFNGlobalState("mode", props.mode); + /** + * NOTE: + * Here happens the theme change. + * + * TODO: + * We could probably made the theme change smoother + * if we use state to update the theme + */ + createMfe.loadFnTheme(props.mode); + } + + if(props.hiddenVariables){ + updateFNGlobalState("hiddenVariables", props.hiddenVariables); + } + + // NOTE: The false/true value does not change anything + return Promise.resolve(true); + }; + + return lifeCycleFn; + } - if (props.mode) { - /** - * NOTE - * We do not use the "mode" state right now, - * but I believe that as long as we store the "mode, we should update it - */ - dispatch( - updateFnState({ - type: 'mode', - payload: props.mode, - }) - ); + static renderMfeComponent(props: Partial, onSuccess = noop) { + const { fnGlobalState } = store.getState(); /** * NOTE: - * Here happens the theme change. - * - * TODO: - * We could probably made the theme change smoother - * if we use state to update the theme + * It may happen that only partial props are received in arguments. + * Then we should keep the current props that are read them from the state. */ - createMfe.loadFnTheme(props.mode); - } - - // NOTE: The false/true value does not change anything - return Promise.resolve(false); - }; - - return lifeCycleFn; - } - - static renderMfeComponent(props: Partial, onSuccess = noop) { - const { fnGlobalState } = store.getState(); + const mergedProps = merge({}, fnGlobalState, props) as FNDashboardProps; - /** - * NOTE: - * It may happen that only partial props are received in arguments. - * Then we should keep the current props that are read them from the state. - */ - const mergedProps = merge({}, fnGlobalState, props) as FNDashboardProps; - - ReactDOM.render(React.createElement(createMfe.Component, mergedProps), createMfe.getContainer(mergedProps), () => { - createMfe.logger('Successfully rendered mfe component.'); - onSuccess(); - }); - } + ReactDOM.render(React.createElement(createMfe.Component, mergedProps), createMfe.getContainer(mergedProps), () => { + createMfe.logger('Successfully rendered mfe component.'); + onSuccess(); + }); + } } export { createMfe }; diff --git a/public/app/fn-app/filter-constants/constants.ts b/public/app/fn-app/filter-constants/constants.ts deleted file mode 100644 index 9acde43c882a..000000000000 --- a/public/app/fn-app/filter-constants/constants.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { - httpDashboardFilters, - prometheusDashboardFilters, - signalDashboardFilters, -} from "./types"; - -export type ResourceType = -| "FluxMeter" -| "Classifier" -| "ConcurrencyLimiter" -| "RateLimiter" -| "Signal"; - -export const FluxMeterResource: ResourceType = "FluxMeter"; -export const ClassifierResource: ResourceType = "Classifier"; -export const ConcurrencyLimiterResource: ResourceType = "ConcurrencyLimiter"; -export const RateLimiterResource: ResourceType = "RateLimiter"; -export const SignalResource: ResourceType = "Signal"; - -export const DASHBOARDS = { - FLOW_ANALYTICS: "flow-analytics", - PROMETHEUS: "flux-meter", - SIGNAL: "signal", - }; - - -export const HIDE_FILTERS_BY_DASHBOARD_TYPE = { - FLOW_ANALYTICS: httpDashboardFilters, - PROMETHEUS: prometheusDashboardFilters, - SIGNAL: signalDashboardFilters, -}; diff --git a/public/app/fn-app/filter-constants/index.ts b/public/app/fn-app/filter-constants/index.ts deleted file mode 100644 index 8a893e45ab76..000000000000 --- a/public/app/fn-app/filter-constants/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './constants'; - diff --git a/public/app/fn-app/filter-constants/types.ts b/public/app/fn-app/filter-constants/types.ts deleted file mode 100644 index a55eacde672c..000000000000 --- a/public/app/fn-app/filter-constants/types.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { DASHBOARDS } from "./constants"; - -export type DashboardType = keyof typeof DASHBOARDS; - -/** - * NOTE: - * filters: filters enabled in the UI - * implicitQueryPArams: hidden filters derived from the context - */ -export type DashboardConfig = { - uuid: string; - slug: string; - name: string; - /** - * Explicit filters -> visible in the UI, can be applied by the user - */ - filters: Array>; - /** - * Hidden filters derived from the context - */ - hiddenVariables: Array>; - /** - * Filters passed to grafana in query-params (can be implicit or explicit) - */ - queryParams: { [F in MapDashboardTypeToDashboardFilters]?: string }; -}; - -export type MapDashboardTypeToDashboardFilters< - D extends DashboardType = DashboardType, -> = D extends "FLOW_ANALYTICS" - ? FlowAnalyticsDashboardQueryParam - : D extends "PROMETHEUS" - ? PrometheusDashboardQueryParam - : D extends "SIGNAL" - ? SignalDashboardQueryParam - : never; - -export type ResourceType = - | "FluxMeter" - | "Classifier" - | "ConcurrencyLimiter" - | "RateLimiter" - | "Signal"; - -export const FluxMeterResource: ResourceType = "FluxMeter"; -export const ClassifierResource: ResourceType = "Classifier"; -export const ConcurrencyLimiterResource: ResourceType = "ConcurrencyLimiter"; -export const RateLimiterResource: ResourceType = "RateLimiter"; -export const SignalResource: ResourceType = "Signal"; - -export type FlowAnalyticsDashboardFilter = typeof httpDashboardFilters[number]; - -export type PrometheusDashboardFilter = - typeof prometheusDashboardFilters[number]; - -export type SignalDashboardFilter = typeof signalDashboardFilters[number]; - -export type FlowAnalyticsDashboardQueryParam = - `var-${FlowAnalyticsDashboardFilter}`; -export type PrometheusDashboardQueryParam = `var-${PrometheusDashboardFilter}`; -export type SignalDashboardQueryParam = `var-${SignalDashboardFilter}`; - -export type DashboardFilter = - | FlowAnalyticsDashboardFilter - | PrometheusDashboardQueryParam - | SignalDashboardFilter; - -export type DashboardQueryParam = - | FlowAnalyticsDashboardQueryParam - | PrometheusDashboardQueryParam - | SignalDashboardQueryParam; - -/** - * NOTE: - * 1. Based on *dashboards.libsonnet - * - * 2. fn_organization_id is read from token so we do not have to provide it. - * We may have to hide it (list in implicit filters) - */ -export const httpDashboardFilters: string[] = [ - "fn_project_id", - "controller_id", - "agent_group", - "services", - "control_point", - "concurrency_limiters", - "workloads", - "rate_limiters", - "flux_meter_name", - "classifiers", - "fn_organization_id", -]; - -export const prometheusDashboardFilters: string[] = [ - "flux_meter_name", - "fn_project_id", -]; - -export const signalDashboardFilters: string[] = ["policy_name", "signal_name"]; - diff --git a/public/app/fn-app/fn-dashboard-page/fn-dashboard.tsx b/public/app/fn-app/fn-dashboard-page/fn-dashboard.tsx index 195469ea5070..a001bc33db88 100644 --- a/public/app/fn-app/fn-dashboard-page/fn-dashboard.tsx +++ b/public/app/fn-app/fn-dashboard-page/fn-dashboard.tsx @@ -1,10 +1,8 @@ -import { get, snakeCase } from 'lodash'; import { parse as parseQueryParams } from 'query-string'; import React, { FC, Suspense, useMemo } from 'react'; import { lazily } from 'react-lazily'; import { useLocation } from 'react-router-dom'; -import { HIDE_FILTERS_BY_DASHBOARD_TYPE } from '../filter-constants'; import { FNDashboardProps } from '../types'; import { RenderPortal } from '../utils'; @@ -31,15 +29,14 @@ export const DashboardPortal: FC = (props) =>{ const queryParams = parseQueryParams(search); const { dashboardUID, slug } = queryParams - console.log({queryParams}, "queryParams in FNDashboard") const newProps: FNDashboardProps = { ...props, uid: dashboardUID as string, slug: slug as string, queryParams, - hiddenVariables: get(HIDE_FILTERS_BY_DASHBOARD_TYPE, snakeCase(dashboardUID as string).toUpperCase()) || [] - } + } + return dashboardUID &&( diff --git a/public/app/fn-app/fn-dashboard-page/render-fn-dashboard.tsx b/public/app/fn-app/fn-dashboard-page/render-fn-dashboard.tsx index a8087e43b56f..db2ce4be561d 100644 --- a/public/app/fn-app/fn-dashboard-page/render-fn-dashboard.tsx +++ b/public/app/fn-app/fn-dashboard-page/render-fn-dashboard.tsx @@ -1,9 +1,7 @@ import { merge, isEmpty, isFunction } from 'lodash'; -import React, { useEffect, FC } from 'react'; +import React, { useEffect, FC, useMemo } from 'react'; import { useDispatch } from 'react-redux'; -import { ThunkDispatch } from 'redux-thunk'; -/* eslint-disable-next-line */ import { locationService as locationSrv, HistoryWrapper } from '@grafana/runtime'; import { setInitialMountState } from 'app/core/reducers/fn-slice'; import DashboardPage, { DashboardPageProps } from 'app/features/dashboard/containers/DashboardPage'; @@ -12,7 +10,6 @@ import { DashboardRoutes, StoreState, useSelector } from 'app/types'; import { FNDashboardProps } from '../types'; -/* eslint-disable-next-line */ const locationService = locationSrv as HistoryWrapper; const DEFAULT_DASHBOARD_PAGE_PROPS: Pick & { @@ -24,7 +21,7 @@ const DEFAULT_DASHBOARD_PAGE_PROPS: Pick = (props) => { mode, controlsContainer, pageTitle = '', - hiddenVariables, setErrors, fnLoader, } = props; - /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ - const dispatch = useDispatch>(); + const dispatch = useDispatch(); const firstError = useSelector((state: StoreState) => { const { appNotifications } = state; @@ -56,6 +51,8 @@ export const RenderFNDashboard: FC = (props) => { return Object.values(appNotifications.byId).find(({ severity }) => severity === 'error'); }); + const hiddenVariables = useSelector(({ fnGlobalState: { hiddenVariables } }: StoreState) => hiddenVariables); + /** * NOTE: * Grafana renders notifications in StoredNotifications component. @@ -82,7 +79,6 @@ export const RenderFNDashboard: FC = (props) => { controlsContainer, pageTitle, queryParams, - hiddenVariables, }) ); @@ -97,9 +93,9 @@ export const RenderFNDashboard: FC = (props) => { locationService.fnPathnameChange(window.location.pathname, queryParams); - }, [dispatch, uid, slug, controlsContainer, pageTitle, hiddenVariables, queryParams, mode]); + }, [dispatch, uid, slug, controlsContainer, pageTitle, queryParams, mode]); - const dashboardPageProps: DashboardPageProps = merge({}, DEFAULT_DASHBOARD_PAGE_PROPS, { + const dashboardPageProps: DashboardPageProps = useMemo(() => merge({}, DEFAULT_DASHBOARD_PAGE_PROPS, { ...DEFAULT_DASHBOARD_PAGE_PROPS, match: { params: { @@ -111,7 +107,7 @@ export const RenderFNDashboard: FC = (props) => { hiddenVariables, controlsContainer, fnLoader, - }); + }),[controlsContainer, fnLoader, hiddenVariables, props, queryParams]); return isEmpty(queryParams || {}) ? <>{fnLoader} : ; }; diff --git a/public/app/fn-app/utils.tsx b/public/app/fn-app/utils.tsx index afebf7e13f16..8ad2a580fb66 100644 --- a/public/app/fn-app/utils.tsx +++ b/public/app/fn-app/utils.tsx @@ -21,3 +21,4 @@ export const RenderPortal: FC = ({ ID, children }) => { return ReactDOM.createPortal(children, portalDiv); }; +