diff --git a/.betterer.results b/.betterer.results index 6b3ee027eb3..238e002601c 100644 --- a/.betterer.results +++ b/.betterer.results @@ -200,7 +200,7 @@ exports[`no enzyme tests`] = { "public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardForm.test.tsx:1262111696": [ [1, 17, 13, "RegExp match", "2409514259"] ], - "public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx:1044891955": [ + "public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx:809006195": [ [1, 35, 13, "RegExp match", "2409514259"] ], "public/app/features/dashboard/dashgrid/DashboardGrid.test.tsx:1798654441": [ diff --git a/packages/grafana-data/src/types/config.ts b/packages/grafana-data/src/types/config.ts index 241a2b63058..b1a02c0ce95 100644 --- a/packages/grafana-data/src/types/config.ts +++ b/packages/grafana-data/src/types/config.ts @@ -5,6 +5,7 @@ import { SystemDateFormatSettings } from '../datetime'; import { GrafanaTheme2 } from '../themes'; import { MapLayerOptions } from '../geo/layer'; import { FeatureToggles } from './featureToggles.gen'; +import { NavLinkDTO, OrgRole } from '.'; /** * Describes the build information that will be available via the Grafana configuration. @@ -85,10 +86,47 @@ export type OAuth = */ export type OAuthSettings = Partial>; +/** Current user info included in bootData + * + * @internal + */ +export interface CurrentUserDTO { + isSignedIn: boolean; + id: number; + login: string; + email: string; + name: string; + lightTheme: boolean; + orgCount: number; + orgId: number; + orgName: string; + orgRole: OrgRole | ''; + isGrafanaAdmin: boolean; + gravatarUrl: string; + timezone: string; + weekStart: string; + locale: string; + permissions?: Record; +} + +/** Contains essential user and config info + * + * @internal + */ +export interface BootData { + user: CurrentUserDTO; + settings: GrafanaConfig; + navTree: NavLinkDTO[]; + themePaths: { + light: string; + dark: string; + }; +} + /** * Describes all the different Grafana configuration values available for an instance. * - * @public + * @internal */ export interface GrafanaConfig { datasources: { [str: string]: DataSourceInstanceSettings }; @@ -98,7 +136,7 @@ export interface GrafanaConfig { windowTitlePrefix: string; buildInfo: BuildInfo; newPanelTitle: string; - bootData: any; + bootData: BootData; externalUserMngLinkUrl: string; externalUserMngLinkName: string; externalUserMngInfo: string; @@ -120,9 +158,9 @@ export interface GrafanaConfig { verifyEmailEnabled: boolean; oauth: OAuthSettings; disableUserSignUp: boolean; - loginHint: any; - passwordHint: any; - loginError: any; + loginHint: string; + passwordHint: string; + loginError?: string; navTree: any; viewersCanEdit: boolean; editorsCanAdmin: boolean; diff --git a/packages/grafana-data/src/types/index.ts b/packages/grafana-data/src/types/index.ts index 48d2fdf50f4..63502faec91 100644 --- a/packages/grafana-data/src/types/index.ts +++ b/packages/grafana-data/src/types/index.ts @@ -36,7 +36,16 @@ export * from './live'; export * from './variables'; export * from './geometry'; export { isUnsignedPluginSignature } from './pluginSignature'; -export { OAuth, OAuthSettings, GrafanaConfig, BuildInfo, LicenseInfo, PreloadPlugin } from './config'; +export { + CurrentUserDTO, + BootData, + OAuth, + OAuthSettings, + GrafanaConfig, + BuildInfo, + LicenseInfo, + PreloadPlugin, +} from './config'; export { FeatureToggles } from './featureToggles.gen'; export * from './alerts'; export * from './slider'; diff --git a/packages/grafana-data/src/types/navModel.ts b/packages/grafana-data/src/types/navModel.ts index 66b1fe81737..a9cfee1a4e1 100644 --- a/packages/grafana-data/src/types/navModel.ts +++ b/packages/grafana-data/src/types/navModel.ts @@ -1,21 +1,28 @@ import { ComponentType } from 'react'; -export interface NavModelItem { +export interface NavLinkDTO { + id?: string; text: string; - url?: string; + description?: string; + section?: NavSection; subTitle?: string; icon?: string; img?: string; - id?: string; - active?: boolean; - hideFromTabs?: boolean; - hideFromMenu?: boolean; - divider?: boolean; - children?: NavModelItem[]; - breadcrumbs?: NavModelBreadcrumb[]; + url?: string; target?: string; + sortWeight?: number; + divider?: boolean; + hideFromMenu?: boolean; + hideFromTabs?: boolean; + children?: NavLinkDTO[]; + highlightText?: string; +} + +export interface NavModelItem extends NavLinkDTO { + children?: NavModelItem[]; + active?: boolean; + breadcrumbs?: NavModelBreadcrumb[]; parentItem?: NavModelItem; - section?: NavSection; showOrgSwitcher?: boolean; onClick?: () => void; menuItemType?: NavMenuItemType; diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts index 9831ae904b5..6a0e015626b 100644 --- a/packages/grafana-runtime/src/config.ts +++ b/packages/grafana-runtime/src/config.ts @@ -1,5 +1,6 @@ import { merge } from 'lodash'; import { + BootData, BuildInfo, createTheme, DataSourceInstanceSettings, @@ -28,9 +29,9 @@ export class GrafanaBootConfig implements GrafanaConfig { appUrl = ''; appSubUrl = ''; windowTitlePrefix = ''; - buildInfo: BuildInfo = {} as BuildInfo; + buildInfo: BuildInfo; newPanelTitle = ''; - bootData: any; + bootData: BootData; externalUserMngLinkUrl = ''; externalUserMngLinkName = ''; externalUserMngInfo = ''; @@ -54,9 +55,9 @@ export class GrafanaBootConfig implements GrafanaConfig { verifyEmailEnabled = false; oauth: OAuthSettings = {}; disableUserSignUp = false; - loginHint: any; - passwordHint: any; - loginError: any; + loginHint = ''; + passwordHint = ''; + loginError = undefined; navTree: any; viewersCanEdit = false; editorsCanAdmin = false; @@ -117,6 +118,8 @@ export class GrafanaBootConfig implements GrafanaConfig { const mode = options.bootData.user.lightTheme ? 'light' : 'dark'; this.theme2 = createTheme({ colors: { mode } }); this.theme = this.theme2.v1; + this.bootData = options.bootData; + this.buildInfo = options.buildInfo; const defaults = { datasources: {}, diff --git a/pkg/api/dtos/index.go b/pkg/api/dtos/index.go index 25b005aed26..141a7b2f026 100644 --- a/pkg/api/dtos/index.go +++ b/pkg/api/dtos/index.go @@ -57,7 +57,7 @@ const ( type NavLink struct { Id string `json:"id,omitempty"` - Text string `json:"text,omitempty"` + Text string `json:"text"` Description string `json:"description,omitempty"` Section string `json:"section,omitempty"` SubTitle string `json:"subTitle,omitempty"` diff --git a/public/app/angular/services/nav_model_srv.ts b/public/app/angular/services/nav_model_srv.ts index 57341325e5d..f5431849cea 100644 --- a/public/app/angular/services/nav_model_srv.ts +++ b/public/app/angular/services/nav_model_srv.ts @@ -1,46 +1,49 @@ import coreModule from 'app/angular/core_module'; import config from 'app/core/config'; -import { find, isNumber } from 'lodash'; -import { NavModel } from '@grafana/data'; +import { NavModel, NavModelItem } from '@grafana/data'; + +interface Nav { + breadcrumbs: NavModelItem[]; + node?: NavModelItem; + main?: NavModelItem; +} export class NavModelSrv { - navItems: any; + navItems: NavModelItem[]; constructor() { this.navItems = config.bootData.navTree; } getCfgNode() { - return find(this.navItems, { id: 'cfg' }); + return this.navItems.find((navItem) => navItem.id === 'cfg'); } getNav(...args: Array) { let children = this.navItems; - const nav = { + const nav: Nav = { breadcrumbs: [], - } as any; + }; for (const id of args) { // if its a number then it's the index to use for main - if (isNumber(id)) { + if (typeof id === 'number') { nav.main = nav.breadcrumbs[id]; break; } - const node: any = find(children, { id: id }); - nav.breadcrumbs.push(node); - nav.node = node; - nav.main = node; - children = node.children; + const node = children.find((child) => child.id === id); + if (node) { + nav.breadcrumbs.push(node); + nav.node = node; + nav.main = node; + children = node.children ?? []; + } } - if (nav.main.children) { + if (nav.main?.children) { for (const item of nav.main.children) { - item.active = false; - - if (item.url === nav.node.url) { - item.active = true; - } + item.active = item.url === nav.node?.url; } } diff --git a/public/app/core/services/context_srv.ts b/public/app/core/services/context_srv.ts index b9dfcaca92a..a4d838035d7 100644 --- a/public/app/core/services/context_srv.ts +++ b/public/app/core/services/context_srv.ts @@ -1,25 +1,30 @@ import config from '../../core/config'; import { extend } from 'lodash'; -import { rangeUtil, WithAccessControlMetadata } from '@grafana/data'; +import { OrgRole, rangeUtil, WithAccessControlMetadata } from '@grafana/data'; import { AccessControlAction, UserPermission } from 'app/types'; import { featureEnabled, getBackendSrv } from '@grafana/runtime'; +import { CurrentUserInternal } from 'app/types/config'; -export class User { +export class User implements CurrentUserInternal { + isSignedIn: boolean; id: number; - isGrafanaAdmin: any; - isSignedIn: any; - orgRole: any; + login: string; + email: string; + name: string; + lightTheme: boolean; + orgCount: number; orgId: number; orgName: string; - login: string; - orgCount: number; + orgRole: OrgRole | ''; + isGrafanaAdmin: boolean; + gravatarUrl: string; timezone: string; - fiscalYearStartMonth: number; + weekStart: string; + locale: string; helpFlags1: number; - lightTheme: boolean; hasEditPermissionInFolders: boolean; - email?: string; permissions?: UserPermission; + fiscalYearStartMonth: number; constructor() { this.id = 0; @@ -35,7 +40,12 @@ export class User { this.helpFlags1 = 0; this.lightTheme = false; this.hasEditPermissionInFolders = false; - this.email = undefined; + this.email = ''; + this.name = ''; + this.locale = ''; + this.weekStart = ''; + this.gravatarUrl = ''; + if (config.bootData.user) { extend(this, config.bootData.user); } @@ -55,7 +65,7 @@ export class ContextSrv { constructor() { if (!config.bootData) { - config.bootData = { user: {}, settings: {} }; + config.bootData = { user: {}, settings: {} } as any; } this.user = new User(); @@ -180,7 +190,7 @@ export { contextSrv }; export const setContextSrv = (override: ContextSrv) => { if (process.env.NODE_ENV !== 'test') { - throw new Error('contextSrv can be only overriden in test environment'); + throw new Error('contextSrv can be only overridden in test environment'); } contextSrv = override; }; diff --git a/public/app/features/dashboard/components/ShareModal/ShareEmbed.test.tsx b/public/app/features/dashboard/components/ShareModal/ShareEmbed.test.tsx index c54132fc6a8..22d9e22ad75 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareEmbed.test.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareEmbed.test.tsx @@ -53,7 +53,7 @@ describe('ShareEmbed', () => { user: { orgId: 1, }, - }; + } as any; }); afterAll(() => { diff --git a/public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx b/public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx index b93827ad01a..b8ec81aa3d3 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx @@ -110,7 +110,7 @@ describe('ShareModal', () => { user: { orgId: 1, }, - }; + } as any; ctx.mount({ panel: new PanelModel({ id: 22, options: {}, fieldConfig: { defaults: {}, overrides: [] } }), }); @@ -202,7 +202,7 @@ describe('when default_home_dashboard_path is set in the grafana config', () => user: { orgId: 1, }, - }; + } as any; }); afterAll(() => { diff --git a/public/app/features/dashboard/components/ShareModal/utils.test.ts b/public/app/features/dashboard/components/ShareModal/utils.test.ts index 3cd7d6ca236..5b359e1ea2a 100644 --- a/public/app/features/dashboard/components/ShareModal/utils.test.ts +++ b/public/app/features/dashboard/components/ShareModal/utils.test.ts @@ -36,7 +36,7 @@ describe('buildParams', () => { to: 2000, raw: { from: 'now-6h', to: 'now' }, } as unknown as TimeRange; - const orgId = '2'; + const orgId = 2; const result = buildParams({ useCurrentTimeRange, selectedTheme, panel, search, range, orgId }); expect(result.toString()).toEqual(expected); diff --git a/public/app/features/dashboard/components/ShareModal/utils.ts b/public/app/features/dashboard/components/ShareModal/utils.ts index 43d61edc992..4667ec23ae1 100644 --- a/public/app/features/dashboard/components/ShareModal/utils.ts +++ b/public/app/features/dashboard/components/ShareModal/utils.ts @@ -9,7 +9,7 @@ export interface BuildParamsArgs { panel?: PanelModel; search?: string; range?: TimeRange; - orgId?: string; + orgId?: number; } export function buildParams({ @@ -24,7 +24,7 @@ export function buildParams({ searchParams.set('from', String(range.from.valueOf())); searchParams.set('to', String(range.to.valueOf())); - searchParams.set('orgId', orgId); + searchParams.set('orgId', String(orgId)); if (!useCurrentTimeRange) { searchParams.delete('from'); diff --git a/public/app/plugins/panel/graph/specs/graph.test.ts b/public/app/plugins/panel/graph/specs/graph.test.ts index f29b64dad65..a902fc3737a 100644 --- a/public/app/plugins/panel/graph/specs/graph.test.ts +++ b/public/app/plugins/panel/graph/specs/graph.test.ts @@ -44,7 +44,7 @@ describe('grafanaGraph', () => { user: { lightTheme: false, }, - }; + } as any; Object.assign(GraphCtrl.prototype, { ...MetricsPanelCtrl.prototype, ...PanelCtrl.prototype, diff --git a/public/app/types/accessControl.ts b/public/app/types/accessControl.ts index 75ebf239edd..b2fd9b0ca2f 100644 --- a/public/app/types/accessControl.ts +++ b/public/app/types/accessControl.ts @@ -1,12 +1,10 @@ /** * UserPermission is a map storing permissions in a form of * { - * action: { scope: scope } + * action: true; * } */ -export type UserPermission = { - [key: string]: { [key: string]: string }; -}; +export type UserPermission = Record; // Permission actions export enum AccessControlAction { diff --git a/public/app/types/config.ts b/public/app/types/config.ts new file mode 100644 index 00000000000..118fbea278a --- /dev/null +++ b/public/app/types/config.ts @@ -0,0 +1,9 @@ +import { CurrentUserDTO } from '@grafana/data'; + +/** + * Extends `CurrentUserDTO` with some properties meant only for internal use. + */ +export interface CurrentUserInternal extends CurrentUserDTO { + helpFlags1: number; + hasEditPermissionInFolders: boolean; +}