Chore: Replace config.bootData.user with contextSrv (#109686)

* replace config.bootData.user with contextSrv

* fix unit tests

* fix mutating context object in test

* kick CI
This commit is contained in:
Ashley Harrison
2025-08-20 09:55:12 +01:00
committed by GitHub
parent 35ecb3330b
commit 62fbeb35c1
34 changed files with 77 additions and 59 deletions
+7 -7
View File
@@ -136,11 +136,11 @@ export class GrafanaApp {
window.parent.postMessage('GrafanaAppInit', '*');
const regionalFormat = config.featureToggles.localeFormatPreference
? config.regionalFormat
: config.bootData.user.language;
: contextSrv.user.language;
const initI18nPromise = initializeI18n(
{
language: config.bootData.user.language,
language: contextSrv.user.language,
ns: NAMESPACES,
module: loadTranslations,
},
@@ -163,7 +163,7 @@ export class GrafanaApp {
startMeasure('frontend_app_init');
setLocale(config.regionalFormat);
setWeekStart(config.bootData.user.weekStart);
setWeekStart(contextSrv.user.weekStart);
setPanelRenderer(PanelRenderer);
setPluginPage(PluginPage);
setFolderPicker(LazyFolderPicker);
@@ -171,7 +171,7 @@ export class GrafanaApp {
setLocationSrv(locationService);
setCorrelationsService(new CorrelationsService());
setEmbeddedDashboard(EmbeddedDashboardLazy);
setTimeZoneResolver(() => config.bootData.user.timezone);
setTimeZoneResolver(() => contextSrv.user.timezone);
initGrafanaLive();
setCurrentUser(contextSrv.user);
@@ -372,8 +372,8 @@ async function initEchoSrv() {
},
buildInfo: config.buildInfo,
user: {
id: String(config.bootData.user?.id),
email: config.bootData.user?.email,
id: String(contextSrv.user?.id),
email: contextSrv.user?.email,
},
ignoreUrls: rudderstackUrls,
})
@@ -405,7 +405,7 @@ async function initEchoSrv() {
new RudderstackBackend({
writeKey: config.rudderstackWriteKey,
dataPlaneUrl: config.rudderstackDataPlaneUrl,
user: config.bootData.user,
user: contextSrv.user,
sdkUrl: config.rudderstackSdkUrl,
configUrl: config.rudderstackConfigUrl,
integrationsUrl: config.rudderstackIntegrationsUrl,
@@ -3,8 +3,10 @@ import { useMemo } from 'react';
import { config } from '@grafana/runtime';
import { useGetUserPreferencesQuery } from 'app/features/preferences/api';
import { contextSrv } from '../../../services/context_srv';
export const usePinnedItems = () => {
const preferences = useGetUserPreferencesQuery(undefined, { skip: !config.bootData.user.isSignedIn });
const preferences = useGetUserPreferencesQuery(undefined, { skip: !contextSrv.user.isSignedIn });
const pinnedItems = useMemo(() => preferences.data?.navbar?.bookmarkUrls || [], [preferences]);
if (config.featureToggles.pinNavItems) {
@@ -8,6 +8,7 @@ import { SignInLink } from './SignInLink';
jest.mock('app/core/services/context_srv', () => ({
contextSrv: {
...jest.requireActual('app/core/services/context_srv').contextSrv,
setRedirectToUrl: jest.fn(),
},
}));
@@ -1,6 +1,7 @@
import { locationUtil, urlUtil } from '@grafana/data';
import { locationService, navigationLogger } from '@grafana/runtime';
import { config } from 'app/core/config';
import { contextSrv } from '../../services/context_srv';
export function interceptLinkClicks(e: MouseEvent) {
const anchor = e.target instanceof Element && getParentAnchor(e.target);
@@ -16,7 +17,7 @@ export function interceptLinkClicks(e: MouseEvent) {
if (href && !target) {
const params = urlUtil.parseKeyValue(href.split('?')[1]);
const orgIdChange = params.orgId && Number(params.orgId) !== config.bootData.user.orgId;
const orgIdChange = params.orgId && Number(params.orgId) !== contextSrv.user.orgId;
navigationLogger('utils', false, 'intercepting link click', e);
e.preventDefault();
@@ -1,4 +1,4 @@
import { BuildInfo, CurrentUserDTO } from '@grafana/data';
import { BuildInfo } from '@grafana/data';
import {
EchoBackend,
EchoEventType,
@@ -8,6 +8,7 @@ import {
PageviewEchoEvent,
} from '@grafana/runtime';
import { User } from '../../../context_srv';
import { loadScript } from '../../utils';
type Properties = Record<string, string | boolean | number>;
@@ -37,7 +38,7 @@ export interface RudderstackBackendOptions {
writeKey: string;
dataPlaneUrl: string;
buildInfo: BuildInfo;
user?: CurrentUserDTO;
user?: User;
sdkUrl?: string;
configUrl?: string;
integrationsUrl?: string;
+3 -2
View File
@@ -1,9 +1,10 @@
import { filter, isArray, isNumber, isString } from 'lodash';
import { getBackendSrv } from '@grafana/runtime';
import config from 'app/core/config';
import store from 'app/core/store';
import { contextSrv } from './context_srv';
export class ImpressionSrv {
constructor() {}
@@ -58,7 +59,7 @@ export class ImpressionSrv {
}
impressionKey() {
return 'dashboard_impressions-' + config.bootData.user.orgId;
return 'dashboard_impressions-' + contextSrv.user.orgId;
}
}
+10 -8
View File
@@ -7,14 +7,16 @@ jest.mock('@grafana/runtime', () => {
return {
...originalRuntime,
getBackendSrv: mockBackendSrv,
config: {
...originalRuntime.config,
bootData: {
...originalRuntime.config.bootData,
user: {
...originalRuntime.config.bootData.user,
orgId: 'testOrgId',
},
};
});
jest.mock('app/core/services/context_srv', () => {
return {
contextSrv: {
...jest.requireActual('app/core/services/context_srv').contextSrv,
user: {
...jest.requireActual('app/core/services/context_srv').contextSrv.user,
orgId: 'testOrgId',
},
},
};
@@ -4,7 +4,7 @@ import { byLabelText, byPlaceholderText, byRole, byTestId, byText } from 'testin
import { dateTime } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { config, locationService } from '@grafana/runtime';
import { locationService } from '@grafana/runtime';
import { mockAlertRuleApi, setupMswServer } from 'app/features/alerting/unified/mockApi';
import { waitForServerRequest } from 'app/features/alerting/unified/mocks/server/events';
import {
@@ -17,6 +17,8 @@ import { MATCHER_ALERT_RULE_UID } from 'app/features/alerting/unified/utils/cons
import { MatcherOperator, SilenceState } from 'app/plugins/datasource/alertmanager/types';
import { AccessControlAction } from 'app/types/accessControl';
import { contextSrv } from '../../../core/services/context_srv';
import NewSilencePage from './NewSilencePage';
import ExistingSilenceEditorPage from './components/silences/SilencesEditor';
import SilencesTablePage from './components/silences/SilencesTable';
@@ -89,8 +91,8 @@ const ui = {
};
const setUserLogged = (isLogged: boolean) => {
config.bootData.user.isSignedIn = isLogged;
config.bootData.user.name = isLogged ? 'admin' : '';
contextSrv.user.isSignedIn = isLogged;
contextSrv.user.name = isLogged ? 'admin' : '';
};
const enterSilenceLabel = async (index: number, name: string, matcher: MatcherOperator, value: string) => {
@@ -14,7 +14,7 @@ import {
parseDuration,
} from '@grafana/data';
import { Trans, t } from '@grafana/i18n';
import { config, isFetchError, locationService } from '@grafana/runtime';
import { isFetchError, locationService } from '@grafana/runtime';
import {
Alert,
Button,
@@ -32,6 +32,7 @@ import { MATCHER_ALERT_RULE_UID } from 'app/features/alerting/unified/utils/cons
import { GRAFANA_RULES_SOURCE_NAME, getDatasourceAPIUid } from 'app/features/alerting/unified/utils/datasource';
import { MatcherOperator, SilenceCreatePayload } from 'app/plugins/datasource/alertmanager/types';
import { contextSrv } from '../../../../../core/services/context_srv';
import { AlertmanagerAction, useAlertmanagerAbility } from '../../hooks/useAbilities';
import { useAlertmanager } from '../../state/AlertmanagerContext';
import { SilenceFormFields } from '../../types/silence-form';
@@ -216,7 +217,7 @@ export const SilencesEditor = ({
[clearErrors, duration, endsAt, prevDuration, setValue, startsAt]
);
const userLogged = Boolean(config.bootData.user.isSignedIn && config.bootData.user.name);
const userLogged = Boolean(contextSrv.user.isSignedIn && contextSrv.user.name);
return (
<FormProvider {...formAPI}>
@@ -1,11 +1,12 @@
import { DefaultTimeZone, addDurationToDate, dateTime, intervalToAbbreviatedDurationString } from '@grafana/data';
import { config } from '@grafana/runtime';
import { SilenceFormFields } from 'app/features/alerting/unified/types/silence-form';
import { matcherToMatcherField } from 'app/features/alerting/unified/utils/alertmanager';
import { MATCHER_ALERT_RULE_UID } from 'app/features/alerting/unified/utils/constants';
import { parseQueryParamMatchers } from 'app/features/alerting/unified/utils/matchers';
import { MatcherOperator, Silence } from 'app/plugins/datasource/alertmanager/types';
import { contextSrv } from '../../../../../core/services/context_srv';
/**
* Parse query params and return default silence form values
*/
@@ -68,7 +69,7 @@ export const getDefaultSilenceFormValues = (partial?: Partial<SilenceFormFields>
startsAt: now.toISOString(),
endsAt: endsAt.toISOString(),
comment: `created ${dateTime().format('YYYY-MM-DD HH:mm')}`,
createdBy: config.bootData.user.name,
createdBy: contextSrv.user.name,
duration: '2h',
isRegex: false,
matcherName: '',
@@ -9,7 +9,6 @@ import {
PluginExtensionTypes,
ReducerID,
} from '@grafana/data';
import { config } from '@grafana/runtime';
import { DataQuery, defaultDashboard } from '@grafana/schema';
import { contextSrv } from 'app/core/services/context_srv';
import { MOCK_GRAFANA_ALERT_RULE_TITLE } from 'app/features/alerting/unified/mocks/server/handlers/grafanaRuler';
@@ -338,7 +337,7 @@ export const mockSilence = (partial: Partial<Silence> = {}): Silence => {
startsAt: new Date().toISOString(),
endsAt: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
updatedAt: new Date().toISOString(),
createdBy: config.bootData.user.name || 'admin',
createdBy: contextSrv.user.name || 'admin',
comment: 'Silence noisy alerts',
status: {
state: SilenceState.Active,
+1 -1
View File
@@ -58,7 +58,7 @@ export class ScopedResourceClient<T = object, S = object, K = string> implements
.getStream<ResourceEvent<T, S, K>>({
scope: LiveChannelScope.Watch,
namespace: this.gvr.group,
path: `${this.gvr.version}/${this.gvr.resource}${query}/${config.bootData.user.uid}`,
path: `${this.gvr.version}/${this.gvr.resource}${query}/${contextSrv.user.uid}`,
})
.pipe(
filter((event) => isLiveChannelMessageEvent(event)),
@@ -15,6 +15,8 @@ import { DashboardWithAccessInfo } from 'app/features/dashboard/api/types';
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
import { DashboardDTO } from 'app/types/dashboard';
import { contextSrv } from '../../../core/services/context_srv';
export async function buildNewDashboardSaveModel(urlFolderUid?: string): Promise<DashboardDTO> {
let variablesList = defaultDashboard.templating?.list;
@@ -58,7 +60,7 @@ export async function buildNewDashboardSaveModel(urlFolderUid?: string): Promise
uid: '',
title: t('dashboard-scene.build-new-dashboard-save-model.data.title.new-dashboard', 'New dashboard'),
panels: [],
timezone: config.bootData.user?.timezone || defaultDashboard.timezone,
timezone: contextSrv.user?.timezone || defaultDashboard.timezone,
},
};
@@ -123,7 +125,7 @@ export async function buildNewDashboardSaveModelV2(
title: t('dashboard-scene.build-new-dashboard-save-model-v2.data.title.new-dashboard', 'New dashboard'),
timeSettings: {
...defaultTimeSettingsSpec(),
timezone: config.bootData.user?.timezone || defaultTimeSettingsSpec().timezone,
timezone: contextSrv.user?.timezone || defaultTimeSettingsSpec().timezone,
},
},
access: {
@@ -3,6 +3,7 @@ import { lastValueFrom } from 'rxjs';
import { config, getBackendSrv } from '@grafana/runtime';
import { getDashboardUrl } from 'app/features/dashboard-scene/utils/getDashboardUrl';
import { contextSrv } from '../../../../core/services/context_srv';
import { DashboardScene } from '../../scene/DashboardScene';
/**
@@ -50,7 +51,7 @@ export async function generateDashboardImage({
scale,
kiosk: true,
hideNav: true,
orgId: String(config.bootData.user.orgId),
orgId: String(contextSrv.user.orgId),
fullPageImage: true,
},
});
@@ -8,6 +8,7 @@ import { selectors } from '@grafana/e2e-selectors';
import { config, locationService, setPluginImportUtils } from '@grafana/runtime';
import { LocalValueVariable, SceneTimeRange, SceneVariableSet, VizPanel } from '@grafana/scenes';
import { contextSrv } from '../../../core/services/context_srv';
import { DashboardScene } from '../scene/DashboardScene';
import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager';
import { activateFullSceneTree } from '../utils/test-utils';
@@ -36,7 +37,7 @@ describe('ShareLinkTab', () => {
config.appUrl = 'http://dashboards.grafana.com/grafana/';
config.rendererAvailable = true;
config.bootData.user.orgId = 1;
contextSrv.user.orgId = 1;
config.featureToggles.dashboardSceneForViewers = true;
locationService.push('/d/dash-1?from=now-6h&to=now');
});
@@ -11,11 +11,6 @@ import { SaveDashboardDrawer } from './SaveDashboardDrawer';
const saveDashboardMutationMock = jest.fn();
jest.mock('app/core/core', () => ({
...jest.requireActual('app/core/core'),
contextSrv: {},
}));
jest.mock('app/features/browse-dashboards/api/browseDashboardsAPI', () => ({
...jest.requireActual('app/features/browse-dashboards/api/browseDashboardsAPI'),
useSaveDashboardMutation: () => [saveDashboardMutationMock],
@@ -4,6 +4,7 @@ import { BootData } from '@grafana/data';
import { setEchoSrv } from '@grafana/runtime';
import config from 'app/core/config';
import { contextSrv, User } from '../../../../core/services/context_srv';
import { Echo } from '../../../../core/services/echo/Echo';
import { createDashboardModelFixture } from '../../state/__fixtures__/dashboardFixtures';
@@ -19,8 +20,8 @@ jest.mock('app/features/dashboard/services/TimeSrv', () => ({
jest.mock('app/core/services/context_srv', () => ({
contextSrv: {
...jest.requireActual('app/core/services/context_srv').contextSrv,
sidemenu: true,
user: {},
isSignedIn: false,
isGrafanaAdmin: false,
isEditor: false,
@@ -56,11 +57,9 @@ describe('ShareEmbed', () => {
originalBootData = config.bootData;
config.appUrl = 'http://dashboards.grafana.com/';
config.bootData = {
user: {
orgId: 1,
},
} as BootData;
contextSrv.user = {
orgId: 1,
} as User;
});
afterAll(() => {
@@ -7,6 +7,7 @@ import { setEchoSrv, setTemplateSrv } from '@grafana/runtime';
import config from 'app/core/config';
import { initTemplateSrv } from '../../../../../test/helpers/initTemplateSrv';
import { contextSrv } from '../../../../core/services/context_srv';
import { Echo } from '../../../../core/services/echo/Echo';
import { variableAdapters } from '../../../variables/adapters';
import { createQueryVariableAdapter } from '../../../variables/query/adapter';
@@ -79,7 +80,7 @@ describe('ShareModal', () => {
});
mockLocationHref('http://server/#!/test');
config.rendererAvailable = true;
config.bootData.user.orgId = 1;
contextSrv.user.orgId = 1;
props = {
panel: new PanelModel({ id: 22, options: {}, fieldConfig: { defaults: {}, overrides: [] } }),
dashboard: createDashboardModelFixture({
@@ -186,7 +187,7 @@ describe('when appUrl is set in the grafana config', () => {
originalBootData = config.bootData;
config.appUrl = 'http://dashboards.grafana.com/';
config.rendererAvailable = true;
config.bootData.user.orgId = 1;
contextSrv.user.orgId = 1;
});
afterAll(() => {
@@ -3,6 +3,7 @@ import { config } from '@grafana/runtime';
import { createShortLink } from 'app/core/utils/shortLinks';
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { contextSrv } from '../../../../core/services/context_srv';
import { PanelModel } from '../../state/PanelModel';
export interface BuildParamsArgs {
@@ -22,7 +23,7 @@ export function buildParams({
timeFrom,
search = window.location.search,
range = getTimeSrv().timeRange(),
orgId = config.bootData.user.orgId,
orgId = contextSrv.user.orgId,
}: BuildParamsArgs): URLSearchParams {
const searchParams = new URLSearchParams(search);
@@ -14,8 +14,6 @@ import { PanelModel } from '../state/PanelModel';
import { DASHBOARD_SCHEMA_VERSION } from './DashboardMigrator';
jest.mock('app/core/services/context_srv', () => ({}));
const dataSources = {
prom: mockDataSource({
name: 'prom',
@@ -30,6 +30,7 @@ import {
} from 'app/types/dashboard';
import { StoreState, ThunkDispatch, ThunkResult } from 'app/types/store';
import { contextSrv } from '../../../core/services/context_srv';
import { createDashboardQueryRunner } from '../../query/state/DashboardQueryRunner/DashboardQueryRunner';
import { initVariablesTransaction } from '../../variables/state/actions';
import { getIfExistsLastKey } from '../../variables/state/selectors';
@@ -285,7 +286,7 @@ export function initDashboard(args: InitDashboardArgs): ThunkResult<void> {
if (dashboard.weekStart !== '' && dashboard.weekStart !== undefined) {
setWeekStart(dashboard.weekStart);
} else {
setWeekStart(config.bootData.user.weekStart);
setWeekStart(contextSrv.user.weekStart);
}
// Propagate an app-wide event about the dashboard being loaded
@@ -15,6 +15,7 @@ import { getPanelMenu } from './getPanelMenu';
jest.mock('app/core/services/context_srv', () => ({
contextSrv: {
...jest.requireActual('app/core/services/context_srv').contextSrv,
hasAccessToExplore: () => true,
hasPermission: jest.fn(),
},
@@ -125,6 +125,7 @@ jest.mock('@grafana/runtime', () => ({
jest.mock('app/core/core', () => ({
contextSrv: {
...jest.requireActual('app/core/core').contextSrv,
hasPermission: () => true,
getValidIntervals: (defaultIntervals: string[]) => defaultIntervals,
},
@@ -26,6 +26,7 @@ jest.mock('react-virtualized-auto-sizer', () => {
jest.mock('app/core/core', () => ({
contextSrv: {
...jest.requireActual('app/core/core').contextSrv,
hasPermission: () => true,
getValidIntervals: (defaultIntervals: string[]) => defaultIntervals,
},
@@ -17,6 +17,7 @@ jest.mock('@grafana/runtime', () => ({
jest.mock('app/core/core', () => ({
contextSrv: {
...jest.requireActual('app/core/core').contextSrv,
hasPermission: () => true,
getValidIntervals: (defaultIntervals: string[]) => defaultIntervals,
},
@@ -15,6 +15,7 @@ const testEventBus = new EventBusSrv();
jest.mock('app/core/core', () => {
return {
contextSrv: {
...jest.requireActual('app/core/core').contextSrv,
hasPermission: () => true,
getValidIntervals: (defaultIntervals: string[]) => defaultIntervals,
},
@@ -15,6 +15,7 @@ jest.mock('app/core/core', () => {
return {
...jest.requireActual('app/core/core'),
contextSrv: {
...jest.requireActual('app/core/core').contextSrv,
hasPermission: () => true,
},
};
@@ -16,6 +16,7 @@ jest.mock('@grafana/runtime', () => ({
jest.mock('app/core/services/context_srv', () => ({
contextSrv: {
...jest.requireActual('app/core/services/context_srv').contextSrv,
isEditor: true,
},
}));
@@ -15,7 +15,6 @@ jest.mock('@grafana/runtime', () => {
const original = jest.requireActual('@grafana/runtime');
const mockedRuntime = { ...original };
mockedRuntime.config.bootData.user.isGrafanaAdmin = true;
mockedRuntime.config.buildInfo.version = 'v8.1.0';
return mockedRuntime;
@@ -40,6 +40,7 @@ jest.mock('../hooks/usePluginConfig.tsx', () => ({ usePluginConfig: jest.fn(() =
jest.mock('app/core/core', () => ({
contextSrv: {
...jest.requireActual('app/core/core').contextSrv,
hasPermission: (action: string) => true,
hasPermissionInMetadata: (action: string, object: WithAccessControlMetadata) => true,
},
@@ -1,9 +1,8 @@
import { config } from 'app/core/config';
import { contextSrv } from 'app/core/services/context_srv';
import { AccessControlAction } from 'app/types/accessControl';
export function isGrafanaAdmin(): boolean {
return config.bootData.user.isGrafanaAdmin;
return contextSrv.user.isGrafanaAdmin;
}
export function isOrgAdmin() {
@@ -25,9 +25,9 @@ export interface UserState {
}
export const initialUserState: UserState = {
orgId: config.bootData.user.orgId,
timeZone: config.bootData.user.timezone,
weekStart: config.bootData.user.weekStart,
orgId: contextSrv.user.orgId,
timeZone: contextSrv.user.timezone,
weekStart: contextSrv.user.weekStart,
fiscalYearStartMonth: 0,
orgsAreLoading: false,
sessionsAreLoading: false,
@@ -10,6 +10,7 @@ import { ServiceAccountPageUnconnected, Props } from './ServiceAccountPage';
jest.mock('app/core/core', () => ({
contextSrv: {
...jest.requireActual('app/core/core').contextSrv,
licensedAccessControlEnabled: () => false,
hasPermission: () => true,
hasPermissionInMetadata: () => false,
@@ -9,6 +9,7 @@ import { Props, ServiceAccountsListPageUnconnected } from './ServiceAccountsList
jest.mock('app/core/core', () => ({
contextSrv: {
...jest.requireActual('app/core/core').contextSrv,
licensedAccessControlEnabled: () => false,
hasPermission: () => true,
hasPermissionInMetadata: () => true,