Chore: Improve how mock API is enabled/disabled (#104675)

This commit is contained in:
Tom Ratcliffe
2025-05-08 20:03:51 +03:00
committed by GitHub
parent 3dc5742a75
commit 1716696175
10 changed files with 133 additions and 30 deletions
+1 -1
View File
@@ -480,7 +480,7 @@ playwright.config.ts @grafana/plugins-platform-frontend
/public/app/core/components/TimelineChart/ @grafana/dataviz-squad
/public/app/core/components/Form/ @grafana/grafana-frontend-platform
/public/app/core/components/OptionsUI/ @grafana/dashboards-squad @grafana/dataviz-squad
/public/app/mock-api-utils.ts @grafana/grafana-frontend-platform
/public/app/core/history/ @grafana/observability-traces-and-profiling
/public/app/features/admin/ @grafana/identity-access-team
-3
View File
@@ -2122,9 +2122,6 @@ alert_rules_state = "paused"
# Should UI tests fail when console log/warn/erroring?
# Does not affect the result when running on CI - only for allowing devs to choose this behaviour locally
fail_tests_on_console = true
# Whether or not to enable the MSW mock API, which intercepts requests and returns mock data
# Should only be used for local development or demo purposes
mock_api = false
# Whether to enable betterer eslint rules for local development
# Useful if you want to always see betterer rules that we're trying to fix so they're more prevalent
betterer_eslint_rules = false
+4 -5
View File
@@ -62,6 +62,7 @@ import { PluginPage } from './core/components/Page/PluginPage';
import { GrafanaContextType, useReturnToPreviousInternal } from './core/context/GrafanaContext';
import { initializeCrashDetection } from './core/crash';
import { initializeI18n } from './core/internationalization';
import { postInitTasks, preInitTasks } from './core/lifecycle-hooks';
import { setMonacoEnv } from './core/monacoEnv';
import { interceptLinkClicks } from './core/navigation/patch/interceptLinkClicks';
import { CorrelationsService } from './core/services/CorrelationsService';
@@ -72,7 +73,6 @@ import { Echo } from './core/services/echo/Echo';
import { reportPerformance } from './core/services/echo/EchoSrv';
import { KeybindingSrv } from './core/services/keybindingSrv';
import { startMeasure, stopMeasure } from './core/utils/metrics';
import { initDevFeatures } from './dev';
import { initAlerting } from './features/alerting/unified/initAlerting';
import { initAuthConfig } from './features/auth-config';
import { getTimeSrv } from './features/dashboard/services/TimeSrv';
@@ -115,15 +115,12 @@ const extensionsExports = extensionsIndex.keys().map((key) => {
return extensionsIndex(key);
});
if (process.env.NODE_ENV === 'development') {
initDevFeatures();
}
export class GrafanaApp {
context!: GrafanaContextType;
async init() {
try {
await preInitTasks();
// Let iframe container know grafana has started loading
parent.postMessage('GrafanaAppInit', '*');
@@ -268,6 +265,8 @@ export class GrafanaApp {
app: this,
})
);
await postInitTasks();
} catch (error) {
console.error('Failed to start Grafana', error);
window.__grafana_load_failed();
+40 -8
View File
@@ -2,6 +2,7 @@ import { useMemo, ReactElement } from 'react';
import { v4 as uuidv4 } from 'uuid';
import { getMessageFromError } from 'app/core/utils/errors';
import { dispatch as storeDispatch } from 'app/store/store';
import { AppNotification, AppNotificationSeverity, useDispatch } from 'app/types';
import { notifyApp } from '../actions';
@@ -86,19 +87,50 @@ export function useAppNotification() {
const dispatch = useDispatch();
return useMemo(
() => ({
success: (title: string, text = '') => {
dispatch(notifyApp(createSuccessNotification(title, text)));
[AppNotificationSeverity.Success]: (title: string, text = '') => {
dispatch(notifyApp(createNotification(title, text, AppNotificationSeverity.Success)));
},
warning: (title: string, text = '', traceId?: string) => {
dispatch(notifyApp(createWarningNotification(title, text, traceId)));
[AppNotificationSeverity.Warning]: (title: string, text = '', traceId?: string) => {
dispatch(notifyApp(createNotification(title, text, AppNotificationSeverity.Warning, traceId)));
},
error: (title: string, text = '', traceId?: string) => {
dispatch(notifyApp(createErrorNotification(title, text, traceId)));
[AppNotificationSeverity.Error]: (title: string, text = '', traceId?: string) => {
dispatch(notifyApp(createNotification(title, text, AppNotificationSeverity.Error, traceId)));
},
info: (title: string, text = '') => {
dispatch(notifyApp(createInfoNotification(title, text)));
[AppNotificationSeverity.Info]: (title: string, text = '') => {
dispatch(notifyApp(createNotification(title, text, AppNotificationSeverity.Info)));
},
}),
[dispatch]
);
}
function createNotification(
title: string,
text = '',
severity: AppNotificationSeverity = AppNotificationSeverity.Success,
traceId?: string
) {
const map = {
[AppNotificationSeverity.Success]: (title: string, text = '') => {
return createSuccessNotification(title, text);
},
[AppNotificationSeverity.Warning]: (title: string, text = '', traceId?: string) => {
return createWarningNotification(title, text, traceId);
},
[AppNotificationSeverity.Error]: (title: string, text = '', traceId?: string) => {
return createErrorNotification(title, text, traceId);
},
[AppNotificationSeverity.Info]: (title: string, text = '') => {
return createInfoNotification(title, text);
},
};
return map[severity](title, text, traceId);
}
export function sendAppNotification(
title: string,
text = '',
severity: AppNotificationSeverity = AppNotificationSeverity.Success
) {
storeDispatch(notifyApp(createNotification(title, text, severity)));
}
+18
View File
@@ -0,0 +1,18 @@
import { initDevFeatures } from 'app/dev';
import { notifyIfMockApiEnabled } from 'app/mock-api-utils';
/**
* Lifecycle tasks that need to be run prior to app initialization,
* such as setting up mock APIs or enabling dev-only features
*/
export async function preInitTasks() {
await initDevFeatures();
}
/**
* Lifecycle tasks that need to be run once the app has fully initialized,
* such as notifying if mock APIs are enabled
*/
export async function postInitTasks() {
notifyIfMockApiEnabled();
}
@@ -5,6 +5,7 @@ import { getExploreUrl } from 'app/core/utils/explore';
import { SaveDashboardDrawer } from 'app/features/dashboard/components/SaveDashboard/SaveDashboardDrawer';
import { ShareModal } from 'app/features/dashboard/components/ShareModal/ShareModal';
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
import { toggleMockApiAndReload } from 'app/mock-api-utils';
import { getTimeSrv } from '../../features/dashboard/services/TimeSrv';
import {
@@ -55,6 +56,10 @@ export class KeybindingSrv {
this.bind('c t', () => toggleTheme(false));
this.bind('c r', () => toggleTheme(true));
if (process.env.NODE_ENV === 'development') {
this.bind('c m', () => toggleMockApiAndReload());
}
}
bindGlobalEsc() {
+4
View File
@@ -1,5 +1,7 @@
import * as React from 'react';
import { potentiallySetupMockApi } from './mock-api-utils';
export async function initDevFeatures() {
// if why-render is in url enable why did you render react extension
if (window.location.search.indexOf('why-render') !== -1) {
@@ -8,4 +10,6 @@ export async function initDevFeatures() {
trackAllPureComponents: true,
});
}
await potentiallySetupMockApi();
}
@@ -5,6 +5,7 @@ import { enrichHelpItem } from 'app/core/components/AppChrome/MegaMenu/utils';
import { performInviteUserClick, shouldRenderInviteUserButton } from 'app/core/components/InviteUserButton/utils';
import { t } from 'app/core/internationalization';
import { changeTheme } from 'app/core/services/theme';
import { currentMockApiState, toggleMockApiAndReload } from 'app/mock-api-utils';
import { useSelector } from '../../../types';
import { CommandPaletteAction } from '../types';
@@ -75,7 +76,7 @@ function navTreeToActions(navTree: NavModelItem[], parents: NavModelItem[] = [])
}
function getGlobalActions(): CommandPaletteAction[] {
return [
const actions: CommandPaletteAction[] = [
{
id: 'preferences/theme',
name: t('command-palette.action.change-theme', 'Change theme'),
@@ -100,6 +101,25 @@ function getGlobalActions(): CommandPaletteAction[] {
priority: PREFERENCES_PRIORITY,
},
];
if (process.env.NODE_ENV === 'development') {
// eslint-disable @grafana/no-untranslated-strings
const section = 'Dev tooling';
const currentState = currentMockApiState();
const mockApiAction = currentState ? 'Disable' : 'Enable';
actions.push({
id: 'preferences/dev/toggle-mock-api',
section,
name: `${mockApiAction} Mock API worker and reload`,
subtitle: 'Intercepts requests and returns mock data using MSW',
keywords: 'mock api',
priority: PREFERENCES_PRIORITY,
perform: toggleMockApiAndReload,
});
// eslint-enable @grafana/no-untranslated-strings
}
return actions;
}
export function useStaticActions(): CommandPaletteAction[] {
+1 -12
View File
@@ -20,15 +20,4 @@ window.__grafana_app_bundle_loaded = true;
import app from './app';
const prepareInit = async () => {
if (process.env.frontend_dev_mock_api) {
return import('test/mock-api/worker').then((workerModule) => {
workerModule.default.start({ onUnhandledRequest: 'bypass' });
});
}
return Promise.resolve();
};
prepareInit().then(() => {
app.init();
});
app.init();
+39
View File
@@ -0,0 +1,39 @@
import store from 'app/core/store';
import { sendAppNotification } from './core/copy/appNotification';
import { AppNotificationSeverity } from './types';
export const STORAGE_MOCK_API_KEY = 'grafana.dev.mockApi';
export const currentMockApiState = () => {
return store.getBool(STORAGE_MOCK_API_KEY, false);
};
export const toggleMockApiAndReload = () => {
const currentState = currentMockApiState();
store.set(STORAGE_MOCK_API_KEY, String(!currentState));
const action = currentState ? 'Disabling' : 'Enabling';
sendAppNotification(`${action} Mock API`, 'Reloading...', AppNotificationSeverity.Info);
setTimeout(() => {
window.location.reload();
}, 200);
};
export const potentiallySetupMockApi = async () => {
const mockApiEnabled = currentMockApiState();
if (process.env.NODE_ENV === 'development' && mockApiEnabled) {
const { default: worker } = await import('test/mock-api/worker');
worker.start({ onUnhandledRequest: 'bypass' });
}
};
export const notifyIfMockApiEnabled = () => {
if (process.env.NODE_ENV === 'development' && currentMockApiState()) {
sendAppNotification(
'Mock API currently enabled',
'Some network requests will be intercepted',
AppNotificationSeverity.Info
);
}
};