diff --git a/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.test.tsx b/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.test.tsx index 91da066e4cc..6f09d1faede 100644 --- a/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.test.tsx +++ b/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.test.tsx @@ -1,12 +1,13 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { AddPanelWidget, Props } from './AddPanelWidget'; +import { AddPanelWidgetUnconnected as AddPanelWidget, Props } from './AddPanelWidget'; import { DashboardModel, PanelModel } from '../../state'; const setup = (propOverrides?: object) => { const props: Props = { dashboard: {} as DashboardModel, panel: {} as PanelModel, + addPanelToDashboard: jest.fn() as any, }; Object.assign(props, propOverrides); diff --git a/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx b/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx index 36b1c3cc963..b6057096dab 100644 --- a/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx +++ b/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx @@ -3,28 +3,36 @@ import React from 'react'; import _ from 'lodash'; import { LocationUpdate } from '@grafana/runtime'; import { e2e } from '@grafana/e2e'; +import { connect, MapDispatchToProps } from 'react-redux'; // Utils import config from 'app/core/config'; import store from 'app/core/store'; // Store import { store as reduxStore } from 'app/store/store'; import { updateLocation } from 'app/core/actions'; +import { addPanelToDashboard } from 'app/features/dashboard/state/reducers'; // Types import { DashboardModel, PanelModel } from '../../state'; import { LS_PANEL_COPY_KEY } from 'app/core/constants'; export type PanelPluginInfo = { id: any; defaults: { gridPos: { w: any; h: any }; title: any } }; -export interface Props { +export interface OwnProps { panel: PanelModel; dashboard: DashboardModel; } +export interface DispatchProps { + addPanelToDashboard: typeof addPanelToDashboard; +} + +export type Props = OwnProps & DispatchProps; + export interface State { copiedPanelPlugins: any[]; } -export class AddPanelWidget extends React.Component { +export class AddPanelWidgetUnconnected extends React.Component { constructor(props: Props) { super(props); this.handleCloseAddPanel = this.handleCloseAddPanel.bind(this); @@ -188,3 +196,7 @@ export class AddPanelWidget extends React.Component { ); } } + +const mapDispatchToProps: MapDispatchToProps = { addPanelToDashboard }; + +export const AddPanelWidget = connect(null, mapDispatchToProps)(AddPanelWidgetUnconnected); diff --git a/public/app/features/dashboard/containers/DashboardPage.test.tsx b/public/app/features/dashboard/containers/DashboardPage.test.tsx index 65a27087f26..ad4b3990a38 100644 --- a/public/app/features/dashboard/containers/DashboardPage.test.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.test.tsx @@ -8,7 +8,7 @@ import { mockToolkitActionCreatorWithoutPayload, ToolkitActionCreatorWithoutPayloadMockType, } from 'test/core/redux/mocks'; -import { DashboardInitPhase, DashboardRouteInfo, MutableDashboard } from 'app/types'; +import { DashboardInitPhase, DashboardRouteInfo } from 'app/types'; import { notifyApp, updateLocation } from 'app/core/actions'; jest.mock('app/features/dashboard/components/DashboardSettings/SettingsCtrl', () => ({})); @@ -215,7 +215,7 @@ describe('DashboardPage', () => { it('should set scrollTop to 0', () => { expect(ctx.wrapper).not.toBe(null); expect(ctx.wrapper?.state()).not.toBe(null); - expect(ctx.wrapper?.state().scrollTop).toBe(0); + expect(ctx.wrapper?.state().updateScrollTop).toBe(0); }); it('should add panel widget to dashboard panels', () => { @@ -272,7 +272,7 @@ describe('DashboardPage', () => { }, }, dashboard: { - getModel: () => null as MutableDashboard, + getModel: () => null as DashboardModel, }, } as any); @@ -290,7 +290,7 @@ describe('DashboardPage', () => { }, }, dashboard: { - getModel: () => null as MutableDashboard, + getModel: () => null as DashboardModel, }, } as any); diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx index 7b882921e25..305cdf865d2 100644 --- a/public/app/features/dashboard/containers/DashboardPage.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.tsx @@ -62,7 +62,7 @@ export interface State { isFullscreen: boolean; fullscreenPanel: PanelModel | null; scrollTop: number; - updateScrollTop: number; + updateScrollTop?: number; rememberScrollTop: number; showLoadingState: boolean; } @@ -75,7 +75,6 @@ export class DashboardPage extends PureComponent { showLoadingState: false, fullscreenPanel: null, scrollTop: 0, - updateScrollTop: null, rememberScrollTop: 0, }; @@ -127,19 +126,19 @@ export class DashboardPage extends PureComponent { // Sync url state with model if (urlFullscreen !== dashboard.meta.fullscreen || urlEdit !== dashboard.meta.isEditing) { - if (!isNaN(parseInt(urlPanelId, 10))) { - this.onEnterFullscreen(); + if (urlPanelId && !isNaN(parseInt(urlPanelId, 10))) { + this.onEnterFullscreen(dashboard, urlPanelId); } else { - this.onLeaveFullscreen(); + this.onLeaveFullscreen(dashboard); } } } - onEnterFullscreen() { - const { dashboard, urlEdit, urlFullscreen, urlPanelId } = this.props; - - const panelId = parseInt(urlPanelId, 10); + onEnterFullscreen(dashboard: DashboardModel, urlPanelId: string) { + const { urlEdit, urlFullscreen } = this.props; + const panelId = parseInt(urlPanelId!, 10); + dashboard; // need to expand parent row if this panel is inside a row dashboard.expandParentRowFor(panelId); @@ -148,7 +147,7 @@ export class DashboardPage extends PureComponent { if (panel) { dashboard.setViewMode(panel, urlFullscreen, urlEdit); this.setState({ - isEditing: urlEdit && dashboard.meta.canEdit, + isEditing: urlEdit && dashboard.meta.canEdit === true, isFullscreen: urlFullscreen, fullscreenPanel: panel, rememberScrollTop: this.state.scrollTop, @@ -159,9 +158,7 @@ export class DashboardPage extends PureComponent { } } - onLeaveFullscreen() { - const { dashboard } = this.props; - + onLeaveFullscreen(dashboard: DashboardModel) { if (this.state.fullscreenPanel) { dashboard.setViewMode(this.state.fullscreenPanel, false, false); } @@ -181,7 +178,7 @@ export class DashboardPage extends PureComponent { triggerPanelsRendering() { try { - this.props.dashboard.render(); + this.props.dashboard!.render(); } catch (err) { console.error(err); this.props.notifyApp(createErrorNotification(`Panel rendering error`, err)); @@ -226,7 +223,7 @@ export class DashboardPage extends PureComponent { }); // scroll to top after adding panel - this.setState({ scrollTop: 0 }); + this.setState({ updateScrollTop: 0 }); }; renderSlowInitState() { diff --git a/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap b/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap index 4761bef87da..f7f2a72942e 100644 --- a/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap +++ b/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap @@ -113,7 +113,6 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1` autoHideTimeout={200} className="custom-scrollbar--page" hideTracksWhenNotNeeded={false} - scrollTop={null} setScrollTop={[Function]} updateAfterMountMs={500} > @@ -449,7 +448,6 @@ exports[`DashboardPage When dashboard has editview url state should render setti autoHideTimeout={200} className="custom-scrollbar--page" hideTracksWhenNotNeeded={false} - scrollTop={null} setScrollTop={[Function]} updateAfterMountMs={500} > diff --git a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx index d437124e82f..96ff9f8b17a 100644 --- a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx @@ -6,6 +6,10 @@ import classNames from 'classnames'; // @ts-ignore import sizeMe from 'react-sizeme'; +// Components +import { AddPanelWidget } from '../components/AddPanelWidget'; +import { DashboardRow } from '../components/DashboardRow'; + // Types import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT } from 'app/core/constants'; import { DashboardPanel } from './DashboardPanel'; @@ -102,6 +106,7 @@ export class DashboardGrid extends PureComponent { componentDidMount() { const { dashboard } = this.props; + dashboard.on(panelAdded, this.triggerForceUpdate); dashboard.on(panelRemoved, this.triggerForceUpdate); dashboard.on(CoreEvents.repeatsProcessed, this.triggerForceUpdate); @@ -173,7 +178,6 @@ export class DashboardGrid extends PureComponent { for (const panel of this.props.dashboard.panels) { panel.resizeDone(); } - this.forceUpdate(); }; onViewModeChanged = () => { @@ -241,10 +245,12 @@ export class DashboardGrid extends PureComponent { renderPanels() { const panelElements = []; + for (const panel of this.props.dashboard.panels) { const panelClasses = classNames({ 'react-grid-item--fullscreen': panel.fullscreen }); const id = panel.id.toString(); panel.isInView = this.isInView(panel); + panelElements.push(
{ this.panelRef[id] = elem; }} > - + {this.renderPanel(panel)}
); } @@ -268,6 +268,26 @@ export class DashboardGrid extends PureComponent { return panelElements; } + renderPanel(panel: PanelModel) { + if (panel.type === 'row') { + return ; + } + + if (panel.type === 'add-panel') { + return ; + } + + return ( + + ); + } + render() { const { dashboard, isFullscreen } = this.props; diff --git a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx index fe08799a931..a3be8d81bfd 100644 --- a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx @@ -2,23 +2,23 @@ import React, { PureComponent } from 'react'; import classNames from 'classnames'; import AutoSizer from 'react-virtualized-auto-sizer'; - -// Utils & Services -import { importPanelPlugin } from 'app/features/plugins/plugin_loader'; +import { connect, MapStateToProps, MapDispatchToProps } from 'react-redux'; // Components -import { AddPanelWidget } from '../components/AddPanelWidget'; -import { DashboardRow } from '../components/DashboardRow'; import { PanelChrome } from './PanelChrome'; import { PanelEditor } from '../panel_editor/PanelEditor'; import { PanelResizer } from './PanelResizer'; import { PanelChromeAngular } from './PanelChromeAngular'; +// Actions +import { initDashboardPanel } from '../state/actions'; + // Types import { PanelModel, DashboardModel } from '../state'; -import { PanelPluginMeta, PanelPlugin } from '@grafana/data'; +import { StoreState } from 'app/types'; +import { PanelPlugin } from '@grafana/data'; -export interface Props { +export interface OwnProps { panel: PanelModel; dashboard: DashboardModel; isEditing: boolean; @@ -27,12 +27,21 @@ export interface Props { isInView: boolean; } +export interface ConnectedProps { + plugin?: PanelPlugin; +} + +export interface DispatchProps { + initDashboardPanel: typeof initDashboardPanel; +} + +export type Props = OwnProps & ConnectedProps & DispatchProps; + export interface State { - plugin: PanelPlugin; isLazy: boolean; } -export class DashboardPanel extends PureComponent { +export class DashboardPanelUnconnected extends PureComponent { element: HTMLElement; specialPanels: { [key: string]: Function } = {}; @@ -40,56 +49,15 @@ export class DashboardPanel extends PureComponent { super(props); this.state = { - plugin: null, isLazy: !props.isInView, }; - - this.specialPanels['row'] = this.renderRow.bind(this); - this.specialPanels['add-panel'] = this.renderAddPanel.bind(this); - } - - isSpecial(pluginId: string) { - return this.specialPanels[pluginId]; - } - - renderRow() { - return ; - } - - renderAddPanel() { - return ; - } - - onPluginTypeChange = (plugin: PanelPluginMeta) => { - this.loadPlugin(plugin.id); - }; - - async loadPlugin(pluginId: string) { - if (this.isSpecial(pluginId)) { - return; - } - - const { panel } = this.props; - - // handle plugin loading & changing of plugin type - if (!this.state.plugin || this.state.plugin.meta.id !== pluginId) { - const plugin = await importPanelPlugin(pluginId); - - if (panel.type !== pluginId) { - panel.changePlugin(plugin); - } else { - panel.pluginLoaded(plugin); - } - - this.setState({ plugin }); - } } componentDidMount() { - this.loadPlugin(this.props.panel.type); + this.props.initDashboardPanel(this.props.panel); } - componentDidUpdate(prevProps: Props, prevState: State) { + componentDidUpdate() { if (this.state.isLazy && this.props.isInView) { this.setState({ isLazy: false }); } @@ -104,8 +72,7 @@ export class DashboardPanel extends PureComponent { }; renderPanel() { - const { dashboard, panel, isFullscreen, isInView, isInEditMode } = this.props; - const { plugin } = this.state; + const { dashboard, panel, isFullscreen, isInView, isInEditMode, plugin } = this.props; return ( @@ -146,12 +113,8 @@ export class DashboardPanel extends PureComponent { } render() { - const { panel, dashboard, isFullscreen, isEditing } = this.props; - const { plugin, isLazy } = this.state; - - if (this.isSpecial(panel.type)) { - return this.specialPanels[panel.type](); - } + const { panel, dashboard, isFullscreen, isEditing, plugin } = this.props; + const { isLazy } = this.state; // if we have not loaded plugin exports yet, wait if (!plugin) { @@ -190,15 +153,18 @@ export class DashboardPanel extends PureComponent { )} /> - {panel.isEditing && ( - - )} + {panel.isEditing && } ); } } + +const mapStateToProps: MapStateToProps = (state, props) => { + return { + plugin: state.plugins.panels[props.panel.type], + }; +}; + +const mapDispatchToProps: MapDispatchToProps = { initDashboardPanel }; + +export const DashboardPanel = connect(mapStateToProps, mapDispatchToProps)(DashboardPanelUnconnected); diff --git a/public/app/features/dashboard/dashgrid/PanelChromeAngular.tsx b/public/app/features/dashboard/dashgrid/PanelChromeAngular.tsx index 5ebabbb1698..7f2920f13a7 100644 --- a/public/app/features/dashboard/dashgrid/PanelChromeAngular.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChromeAngular.tsx @@ -115,8 +115,6 @@ export class PanelChromeAngular extends PureComponent { this.cleanUpAngularPanel(); this.loadAngularPanel(); } - - this.loadAngularPanel(); } loadAngularPanel() { diff --git a/public/app/features/dashboard/dashgrid/__snapshots__/DashboardGrid.test.tsx.snap b/public/app/features/dashboard/dashgrid/__snapshots__/DashboardGrid.test.tsx.snap index 3694703f37f..7414d6b3b2e 100644 --- a/public/app/features/dashboard/dashgrid/__snapshots__/DashboardGrid.test.tsx.snap +++ b/public/app/features/dashboard/dashgrid/__snapshots__/DashboardGrid.test.tsx.snap @@ -49,7 +49,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` id="panel-1" key="1" > - - - - void; activeTab: PanelEditorTabIds; tabs: PanelEditorTab[]; refreshPanelEditor: typeof refreshPanelEditor; panelEditorCleanUp: typeof panelEditorCleanUp; changePanelEditorTab: typeof changePanelEditorTab; + changePanelPlugin: typeof changePanelPlugin; } class UnConnectedPanelEditor extends PureComponent { @@ -63,9 +63,7 @@ class UnConnectedPanelEditor extends PureComponent { }; onPluginTypeChange = (newType: PanelPluginMeta) => { - const { onPluginTypeChange } = this.props; - onPluginTypeChange(newType); - + this.props.changePanelPlugin(this.props.panel, newType.id); this.refreshFromState(newType); }; @@ -108,11 +106,10 @@ class UnConnectedPanelEditor extends PureComponent { } } -export const mapStateToProps = (state: StoreState) => getActiveTabAndTabs(state.location, state.panelEditor); +const mapStateToProps = (state: StoreState) => getActiveTabAndTabs(state.location, state.panelEditor); +const mapDispatchToProps = { refreshPanelEditor, panelEditorCleanUp, changePanelEditorTab, changePanelPlugin }; -const mapDispatchToProps = { refreshPanelEditor, panelEditorCleanUp, changePanelEditorTab }; - -export const PanelEditor = hot(module)(connect(mapStateToProps, mapDispatchToProps)(UnConnectedPanelEditor)); +export const PanelEditor = connect(mapStateToProps, mapDispatchToProps)(UnConnectedPanelEditor); interface TabItemParams { tab: PanelEditorTab; diff --git a/public/app/features/dashboard/state/PanelModel.ts b/public/app/features/dashboard/state/PanelModel.ts index fbce6669995..1015881ab33 100644 --- a/public/app/features/dashboard/state/PanelModel.ts +++ b/public/app/features/dashboard/state/PanelModel.ts @@ -92,6 +92,7 @@ const defaults: any = { }; export class PanelModel { + /* persisted id, used in URL to identify a panel */ id: number; gridPos: GridPos; type: string; @@ -280,6 +281,7 @@ export class PanelModel { if (plugin.panel && plugin.onPanelMigration) { const version = getPluginVersion(plugin); + if (version !== this.pluginVersion) { this.options = plugin.onPanelMigration(this); this.pluginVersion = version; diff --git a/public/app/features/dashboard/state/actions.ts b/public/app/features/dashboard/state/actions.ts index 73b343c52fb..479ec60653c 100644 --- a/public/app/features/dashboard/state/actions.ts +++ b/public/app/features/dashboard/state/actions.ts @@ -3,10 +3,12 @@ import { getBackendSrv } from '@grafana/runtime'; import { createSuccessNotification } from 'app/core/copy/appNotification'; // Actions import { loadPluginDashboards } from '../../plugins/state/actions'; -import { loadDashboardPermissions } from './reducers'; +import { loadDashboardPermissions, dashboardPanelTypeChanged } from './reducers'; import { notifyApp } from 'app/core/actions'; +import { loadPanelPlugin } from 'app/features/plugins/state/actions'; // Types import { DashboardAcl, DashboardAclUpdateDTO, NewDashboardAclItem, PermissionLevel, ThunkResult } from 'app/types'; +import { PanelModel } from './PanelModel'; export function getDashboardPermissions(id: number): ThunkResult { return async dispatch => { @@ -108,3 +110,35 @@ export function removeDashboard(uri: string): ThunkResult { dispatch(loadPluginDashboards()); }; } + +export function initDashboardPanel(panel: PanelModel): ThunkResult { + return async (dispatch, getStore) => { + let plugin = getStore().plugins.panels[panel.type]; + + if (!plugin) { + plugin = await dispatch(loadPanelPlugin(panel.type)); + } + + if (!panel.plugin) { + panel.pluginLoaded(plugin); + } + }; +} + +export function changePanelPlugin(panel: PanelModel, pluginId: string): ThunkResult { + return async (dispatch, getStore) => { + // ignore action is no change + if (panel.type === pluginId) { + return; + } + + let plugin = getStore().plugins.panels[pluginId]; + + if (!plugin) { + plugin = await dispatch(loadPanelPlugin(pluginId)); + } + + panel.changePlugin(plugin); + dispatch(dashboardPanelTypeChanged({ panelId: panel.id, pluginId })); + }; +} diff --git a/public/app/features/dashboard/state/reducers.test.ts b/public/app/features/dashboard/state/reducers.test.ts index 75f96b83a24..b32acb97864 100644 --- a/public/app/features/dashboard/state/reducers.test.ts +++ b/public/app/features/dashboard/state/reducers.test.ts @@ -32,7 +32,15 @@ describe('dashboard reducer', () => { beforeEach(() => { state = dashboardReducer(initialState, dashboardInitFetching()); state = dashboardReducer(state, dashboardInitSlow()); - state = dashboardReducer(state, dashboardInitCompleted(new DashboardModel({ title: 'My dashboard' }))); + state = dashboardReducer( + state, + dashboardInitCompleted( + new DashboardModel({ + title: 'My dashboard', + panels: [{ id: 1 }, { id: 2 }], + }) + ) + ); }); it('should set model', async () => { @@ -42,6 +50,11 @@ describe('dashboard reducer', () => { it('should set reset isInitSlow', async () => { expect(state.isInitSlow).toBe(false); }); + + it('should create panel state', async () => { + expect(state.panels['1']).toBeDefined(); + expect(state.panels['2']).toBeDefined(); + }); }); describe('dashboardInitFailed', () => { diff --git a/public/app/features/dashboard/state/reducers.ts b/public/app/features/dashboard/state/reducers.ts index feff9496b9a..d4c090603be 100644 --- a/public/app/features/dashboard/state/reducers.ts +++ b/public/app/features/dashboard/state/reducers.ts @@ -3,13 +3,13 @@ import { DashboardInitPhase, DashboardState, DashboardAclDTO, - MutableDashboard, DashboardInitError, QueriesToUpdateOnDashboardLoad, } from 'app/types'; import { processAclItems } from 'app/core/utils/acl'; import { panelEditorReducer } from '../panel_editor/state/reducers'; import { DashboardModel } from './DashboardModel'; +import { PanelModel } from './PanelModel'; export const initialState: DashboardState = { initPhase: DashboardInitPhase.NotStarted, @@ -17,6 +17,8 @@ export const initialState: DashboardState = { getModel: () => null, permissions: [], modifiedQueries: null, + panels: {}, + initError: null, }; const dashbardSlice = createSlice({ @@ -35,10 +37,16 @@ const dashbardSlice = createSlice({ dashboardInitSlow: (state, action: PayloadAction) => { state.isInitSlow = true; }, - dashboardInitCompleted: (state, action: PayloadAction) => { + dashboardInitCompleted: (state, action: PayloadAction) => { state.getModel = () => action.payload; state.initPhase = DashboardInitPhase.Completed; state.isInitSlow = false; + + for (const panel of action.payload.panels) { + state.panels[panel.id] = { + pluginId: panel.type, + }; + } }, dashboardInitFailed: (state, action: PayloadAction) => { state.initPhase = DashboardInitPhase.Failed; @@ -49,7 +57,7 @@ const dashbardSlice = createSlice({ }, cleanUpDashboard: (state, action: PayloadAction) => { if (state.getModel()) { - state.getModel().destroy(); + state.getModel()!.destroy(); state.getModel = () => null; } @@ -63,9 +71,22 @@ const dashbardSlice = createSlice({ clearDashboardQueriesToUpdateOnLoad: (state, action: PayloadAction) => { state.modifiedQueries = null; }, + dashboardPanelTypeChanged: (state, action: PayloadAction) => { + state.panels[action.payload.panelId] = { pluginId: action.payload.pluginId }; + }, + addPanelToDashboard: (state, action: PayloadAction) => {}, }, }); +export interface DashboardPanelTypeChangedPayload { + panelId: number; + pluginId: string; +} + +export interface AddPanelPayload { + panel: PanelModel; +} + export const { loadDashboardPermissions, dashboardInitFetching, @@ -76,6 +97,8 @@ export const { cleanUpDashboard, setDashboardQueriesToUpdateOnLoad, clearDashboardQueriesToUpdateOnLoad, + dashboardPanelTypeChanged, + addPanelToDashboard, } = dashbardSlice.actions; export const dashboardReducer = dashbardSlice.reducer; diff --git a/public/app/features/plugins/plugin_loader.ts b/public/app/features/plugins/plugin_loader.ts index cba7bad9106..d3d843b319d 100644 --- a/public/app/features/plugins/plugin_loader.ts +++ b/public/app/features/plugins/plugin_loader.ts @@ -225,21 +225,24 @@ import { getPanelPluginNotFound, getPanelPluginLoadError } from '../dashboard/da import { GenericDataSourcePlugin } from '../datasources/settings/PluginSettings'; interface PanelCache { - [key: string]: PanelPlugin; + [key: string]: Promise; } const panelCache: PanelCache = {}; export function importPanelPlugin(id: string): Promise { const loaded = panelCache[id]; + if (loaded) { - return Promise.resolve(loaded); + return loaded; } + const meta = config.panels[id]; + if (!meta) { return Promise.resolve(getPanelPluginNotFound(id)); } - return importPluginModule(meta.module) + panelCache[id] = importPluginModule(meta.module) .then(pluginExports => { if (pluginExports.plugin) { return pluginExports.plugin as PanelPlugin; @@ -252,11 +255,13 @@ export function importPanelPlugin(id: string): Promise { }) .then(plugin => { plugin.meta = meta; - return (panelCache[meta.id] = plugin); + return plugin; }) .catch(err => { // TODO, maybe a different error plugin console.warn('Error loading panel plugin: ' + id, err); return getPanelPluginLoadError(meta, err); }); + + return panelCache[id]; } diff --git a/public/app/features/plugins/state/actions.ts b/public/app/features/plugins/state/actions.ts index c9dce425caa..c78aca5d5e6 100644 --- a/public/app/features/plugins/state/actions.ts +++ b/public/app/features/plugins/state/actions.ts @@ -1,7 +1,8 @@ import { getBackendSrv } from '@grafana/runtime'; - +import { PanelPlugin } from '@grafana/data'; import { ThunkResult } from 'app/types'; -import { pluginDashboardsLoad, pluginDashboardsLoaded, pluginsLoaded } from './reducers'; +import { pluginDashboardsLoad, pluginDashboardsLoaded, pluginsLoaded, panelPluginLoaded } from './reducers'; +import { importPanelPlugin } from 'app/features/plugins/plugin_loader'; export function loadPlugins(): ThunkResult { return async dispatch => { @@ -18,3 +19,20 @@ export function loadPluginDashboards(): ThunkResult { dispatch(pluginDashboardsLoaded(response)); }; } + +export function loadPanelPlugin(pluginId: string): ThunkResult> { + return async (dispatch, getStore) => { + let plugin = getStore().plugins.panels[pluginId]; + + if (!plugin) { + plugin = await importPanelPlugin(pluginId); + + // second check to protect against raise condition + if (!getStore().plugins.panels[pluginId]) { + dispatch(panelPluginLoaded(plugin)); + } + } + + return plugin; + }; +} diff --git a/public/app/features/plugins/state/reducers.ts b/public/app/features/plugins/state/reducers.ts index 4933774a22e..20dd027d8a8 100644 --- a/public/app/features/plugins/state/reducers.ts +++ b/public/app/features/plugins/state/reducers.ts @@ -1,5 +1,5 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { PluginMeta } from '@grafana/data'; +import { PluginMeta, PanelPlugin } from '@grafana/data'; import { PluginsState } from 'app/types'; import { LayoutMode, LayoutModes } from '../../../core/components/LayoutSelector/LayoutSelector'; import { PluginDashboard } from '../../../types/plugins'; @@ -11,26 +11,33 @@ export const initialState: PluginsState = { hasFetched: false, dashboards: [], isLoadingPluginDashboards: false, + panels: {}, }; const pluginsSlice = createSlice({ name: 'plugins', initialState, reducers: { - pluginsLoaded: (state, action: PayloadAction): PluginsState => { - return { ...state, hasFetched: true, plugins: action.payload }; + pluginsLoaded: (state, action: PayloadAction) => { + state.hasFetched = true; + state.plugins = action.payload; }, - setPluginsSearchQuery: (state, action: PayloadAction): PluginsState => { - return { ...state, searchQuery: action.payload }; + setPluginsSearchQuery: (state, action: PayloadAction) => { + state.searchQuery = action.payload; }, - setPluginsLayoutMode: (state, action: PayloadAction): PluginsState => { - return { ...state, layoutMode: action.payload }; + setPluginsLayoutMode: (state, action: PayloadAction) => { + state.layoutMode = action.payload; }, - pluginDashboardsLoad: (state, action: PayloadAction): PluginsState => { - return { ...state, dashboards: [], isLoadingPluginDashboards: true }; + pluginDashboardsLoad: (state, action: PayloadAction) => { + state.isLoadingPluginDashboards = true; + state.dashboards = []; }, - pluginDashboardsLoaded: (state, action: PayloadAction): PluginsState => { - return { ...state, dashboards: action.payload, isLoadingPluginDashboards: false }; + pluginDashboardsLoaded: (state, action: PayloadAction) => { + state.isLoadingPluginDashboards = false; + state.dashboards = action.payload; + }, + panelPluginLoaded: (state, action: PayloadAction) => { + state.panels[action.payload.meta!.id] = action.payload; }, }, }); @@ -41,6 +48,7 @@ export const { pluginDashboardsLoaded, setPluginsLayoutMode, setPluginsSearchQuery, + panelPluginLoaded, } = pluginsSlice.actions; export const pluginsReducer = pluginsSlice.reducer; diff --git a/public/app/types/dashboard.ts b/public/app/types/dashboard.ts index a6c8a2480bc..4f4609c2c64 100644 --- a/public/app/types/dashboard.ts +++ b/public/app/types/dashboard.ts @@ -1,11 +1,7 @@ import { DashboardAcl } from './acl'; import { DataQuery } from '@grafana/data'; - -export interface MutableDashboard { - title: string; - meta: DashboardMeta; - destroy: () => void; -} +import { AngularComponent } from '@grafana/runtime'; +import { DashboardModel } from 'app/features/dashboard/state/DashboardModel'; export interface DashboardDTO { redirectUri?: string; @@ -65,18 +61,24 @@ export interface DashboardInitError { export const KIOSK_MODE_TV = 'tv'; export type KioskUrlValue = 'tv' | '1' | true; -export type GetMutableDashboardModelFn = () => MutableDashboard | null; +export type GetMutableDashboardModelFn = () => DashboardModel | null; export interface QueriesToUpdateOnDashboardLoad { panelId: number; queries: DataQuery[]; } +export interface PanelState { + pluginId: string; + angularPanel?: AngularComponent; +} + export interface DashboardState { getModel: GetMutableDashboardModelFn; initPhase: DashboardInitPhase; isInitSlow: boolean; - initError?: DashboardInitError; + initError: DashboardInitError | null; permissions: DashboardAcl[] | null; modifiedQueries: QueriesToUpdateOnDashboardLoad | null; + panels: { [id: string]: PanelState }; } diff --git a/public/app/types/plugins.ts b/public/app/types/plugins.ts index e5c249094e6..469f6347fcc 100644 --- a/public/app/types/plugins.ts +++ b/public/app/types/plugins.ts @@ -1,4 +1,5 @@ import { PluginMeta } from '@grafana/data'; +import { PanelPlugin } from '@grafana/data'; export interface PluginDashboard { dashboardId: number; @@ -16,6 +17,10 @@ export interface PluginDashboard { title: string; } +export interface PanelPluginsIndex { + [id: string]: PanelPlugin; +} + export interface PluginsState { plugins: PluginMeta[]; searchQuery: string; @@ -23,6 +28,7 @@ export interface PluginsState { hasFetched: boolean; dashboards: PluginDashboard[]; isLoadingPluginDashboards: boolean; + panels: PanelPluginsIndex; } export interface VariableQueryProps { diff --git a/report.20200209.125304.14262.0.001.json b/report.20200209.125304.14262.0.001.json new file mode 100644 index 00000000000..dcb407a7daa --- /dev/null +++ b/report.20200209.125304.14262.0.001.json @@ -0,0 +1,442 @@ +{ + "header": { + "reportVersion": 1, + "event": "Allocation failed - JavaScript heap out of memory", + "trigger": "FatalError", + "filename": "report.20200209.125304.14262.0.001.json", + "dumpEventTime": "2020-02-09T12:53:04Z", + "dumpEventTimeStamp": "1581249184230", + "processId": 14262, + "cwd": "/home/torkelo/dev/go/src/github.com/grafana/grafana", + "commandLine": [ + "/home/torkelo/.nvm/versions/node/v12.13.1/bin/node", + "/home/torkelo/.config/coc/extensions/node_modules/coc-tsserver/bin/tsserverForkStart", + "/home/torkelo/dev/go/src/github.com/grafana/grafana/node_modules/typescript/lib/tsserver.js", + "--allowLocalPluginLoads", + "--useInferredProjectPerProjectRoot", + "--cancellationPipeName", + "/tmp/coc-nvim-tscancellation-2615e1ca6ecfc7575540.sock*", + "--npmLocation", + "\"/home/torkelo/.nvm/versions/node/v12.13.1/bin/npm\"", + "--noGetErrOnBackgroundUpdate", + "--validateDefaultNpmLocation" + ], + "nodejsVersion": "v12.13.1", + "glibcVersionRuntime": "2.27", + "glibcVersionCompiler": "2.17", + "wordSize": 64, + "arch": "x64", + "platform": "linux", + "componentVersions": { + "node": "12.13.1", + "v8": "7.7.299.13-node.16", + "uv": "1.33.1", + "zlib": "1.2.11", + "brotli": "1.0.7", + "ares": "1.15.0", + "modules": "72", + "nghttp2": "1.39.2", + "napi": "5", + "llhttp": "1.1.4", + "http_parser": "2.8.0", + "openssl": "1.1.1d", + "cldr": "35.1", + "icu": "64.2", + "tz": "2019c", + "unicode": "12.1" + }, + "release": { + "name": "node", + "lts": "Erbium", + "headersUrl": "https://nodejs.org/download/release/v12.13.1/node-v12.13.1-headers.tar.gz", + "sourceUrl": "https://nodejs.org/download/release/v12.13.1/node-v12.13.1.tar.gz" + }, + "osName": "Linux", + "osRelease": "4.15.0-76-generic", + "osVersion": "#86-Ubuntu SMP Fri Jan 17 17:24:28 UTC 2020", + "osMachine": "x86_64", + "cpus": [ + { + "model": "Intel(R) Core(TM) i7-9700K CPU @ 3.60GHz", + "speed": 4700, + "user": 23271600, + "nice": 70900, + "sys": 8208000, + "idle": 703176900, + "irq": 0 + }, + { + "model": "Intel(R) Core(TM) i7-9700K CPU @ 3.60GHz", + "speed": 4702, + "user": 25425500, + "nice": 68900, + "sys": 8484100, + "idle": 700815000, + "irq": 0 + }, + { + "model": "Intel(R) Core(TM) i7-9700K CPU @ 3.60GHz", + "speed": 4700, + "user": 22825900, + "nice": 43000, + "sys": 8405200, + "idle": 703772400, + "irq": 0 + }, + { + "model": "Intel(R) Core(TM) i7-9700K CPU @ 3.60GHz", + "speed": 4700, + "user": 23125500, + "nice": 53200, + "sys": 8344900, + "idle": 703502500, + "irq": 0 + }, + { + "model": "Intel(R) Core(TM) i7-9700K CPU @ 3.60GHz", + "speed": 4703, + "user": 22918700, + "nice": 38500, + "sys": 8401200, + "idle": 703666600, + "irq": 0 + }, + { + "model": "Intel(R) Core(TM) i7-9700K CPU @ 3.60GHz", + "speed": 4700, + "user": 22942200, + "nice": 39600, + "sys": 8651200, + "idle": 703181200, + "irq": 0 + }, + { + "model": "Intel(R) Core(TM) i7-9700K CPU @ 3.60GHz", + "speed": 4700, + "user": 23148200, + "nice": 40700, + "sys": 8578000, + "idle": 702772300, + "irq": 0 + }, + { + "model": "Intel(R) Core(TM) i7-9700K CPU @ 3.60GHz", + "speed": 4700, + "user": 23511200, + "nice": 41300, + "sys": 8625200, + "idle": 702592500, + "irq": 0 + } + ], + "networkInterfaces": [ + { + "name": "lo", + "internal": true, + "mac": "00:00:00:00:00:00", + "address": "127.0.0.1", + "netmask": "255.0.0.0", + "family": "IPv4" + }, + { + "name": "enp3s0", + "internal": false, + "mac": "04:92:26:d6:b9:7b", + "address": "192.168.1.189", + "netmask": "255.255.255.0", + "family": "IPv4" + }, + { + "name": "lo", + "internal": true, + "mac": "00:00:00:00:00:00", + "address": "::1", + "netmask": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", + "family": "IPv6", + "scopeid": 0 + }, + { + "name": "enp3s0", + "internal": false, + "mac": "04:92:26:d6:b9:7b", + "address": "fe80::e4f0:42a8:23d4:1a5a", + "netmask": "ffff:ffff:ffff:ffff::", + "family": "IPv6", + "scopeid": 2 + } + ], + "host": "tlodesk19" + }, + "javascriptStack": { + "message": "No stack.", + "stack": ["Unavailable."] + }, + "nativeStack": [ + { + "pc": "0x0000000000b04935", + "symbol": "report::TriggerNodeReport(v8::Isolate*, node::Environment*, char const*, char const*, std::string const&, v8::Local) [/home/torkelo/.nvm/versions/node/v12.13.1/bin/node]" + }, + { + "pc": "0x00000000009db9e3", + "symbol": "node::OnFatalError(char const*, char const*) [/home/torkelo/.nvm/versions/node/v12.13.1/bin/node]" + }, + { + "pc": "0x0000000000b39f1e", + "symbol": "v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [/home/torkelo/.nvm/versions/node/v12.13.1/bin/node]" + }, + { + "pc": "0x0000000000b3a299", + "symbol": "v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [/home/torkelo/.nvm/versions/node/v12.13.1/bin/node]" + }, + { + "pc": "0x0000000000ce5635", + "symbol": " [/home/torkelo/.nvm/versions/node/v12.13.1/bin/node]" + }, + { + "pc": "0x0000000000ce5cc6", + "symbol": "v8::internal::Heap::RecomputeLimits(v8::internal::GarbageCollector) [/home/torkelo/.nvm/versions/node/v12.13.1/bin/node]" + }, + { + "pc": "0x0000000000cf1b5a", + "symbol": "v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::GCCallbackFlags) [/home/torkelo/.nvm/versions/node/v12.13.1/bin/node]" + }, + { + "pc": "0x0000000000cf2a65", + "symbol": "v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [/home/torkelo/.nvm/versions/node/v12.13.1/bin/node]" + }, + { + "pc": "0x0000000000cf5478", + "symbol": "v8::internal::Heap::AllocateRawWithRetryOrFail(int, v8::internal::AllocationType, v8::internal::AllocationAlignment) [/home/torkelo/.nvm/versions/node/v12.13.1/bin/node]" + }, + { + "pc": "0x0000000000cbbda7", + "symbol": "v8::internal::Factory::NewFillerObject(int, bool, v8::internal::AllocationType) [/home/torkelo/.nvm/versions/node/v12.13.1/bin/node]" + }, + { + "pc": "0x0000000000ff1e0b", + "symbol": "v8::internal::Runtime_AllocateInYoungGeneration(int, unsigned long*, v8::internal::Isolate*) [/home/torkelo/.nvm/versions/node/v12.13.1/bin/node]" + }, + { + "pc": "0x0000000001374fd9", + "symbol": " [/home/torkelo/.nvm/versions/node/v12.13.1/bin/node]" + } + ], + "javascriptHeap": { + "totalMemory": 2174230528, + "totalCommittedMemory": 2173217000, + "usedMemory": 2095966360, + "availableMemory": 58693288, + "memoryLimit": 2197815296, + "heapSpaces": { + "read_only_space": { + "memorySize": 262144, + "committedMemory": 32568, + "capacity": 261872, + "used": 32296, + "available": 229576 + }, + "new_space": { + "memorySize": 33554432, + "committedMemory": 33552568, + "capacity": 16759808, + "used": 105024, + "available": 16654784 + }, + "old_space": { + "memorySize": 2043260928, + "committedMemory": 2043093624, + "capacity": 2001580720, + "used": 2001291056, + "available": 289664 + }, + "code_space": { + "memorySize": 9076736, + "committedMemory": 8605856, + "capacity": 8646656, + "used": 7471968, + "available": 1174688 + }, + "map_space": { + "memorySize": 1314816, + "committedMemory": 1170912, + "capacity": 793680, + "used": 793680, + "available": 0 + }, + "large_object_space": { + "memorySize": 86384640, + "committedMemory": 86384640, + "capacity": 85955568, + "used": 85955568, + "available": 0 + }, + "code_large_object_space": { + "memorySize": 376832, + "committedMemory": 376832, + "capacity": 316768, + "used": 316768, + "available": 0 + }, + "new_large_object_space": { + "memorySize": 0, + "committedMemory": 0, + "capacity": 16759808, + "used": 0, + "available": 16759808 + } + } + }, + "resourceUsage": { + "userCpuSeconds": 1662.02, + "kernelCpuSeconds": 64.5868, + "cpuConsumptionPercent": 25.4099, + "maxRss": 2328305664, + "pageFaults": { + "IORequired": 0, + "IONotRequired": 23472269 + }, + "fsActivity": { + "reads": 51752, + "writes": 260112 + } + }, + "uvthreadResourceUsage": { + "userCpuSeconds": 1149.51, + "kernelCpuSeconds": 30.8777, + "cpuConsumptionPercent": 17.3714, + "fsActivity": { + "reads": 51752, + "writes": 260112 + } + }, + "libuv": [], + "environmentVariables": { + "COLORTERM": "truecolor", + "DBUS_SESSION_BUS_ADDRESS": "unix:path=/run/user/1000/bus", + "DEFAULTS_PATH": "/usr/share/gconf/i3.default.path", + "DESKTOP_SESSION": "i3", + "DISPLAY": ":0", + "EDITOR": "nvim", + "GDMSESSION": "i3", + "GNOME_TERMINAL_SCREEN": "/org/gnome/Terminal/screen/33c3a057_5c19_4624_a686_5ec5567f5784", + "GNOME_TERMINAL_SERVICE": ":1.11", + "GOPATH": "/home/torkelo/dev/go", + "GPG_AGENT_INFO": "/run/user/1000/gnupg/S.gpg-agent:0:1", + "GTK_MODULES": "gail:atk-bridge", + "HOME": "/home/torkelo", + "LANG": "sv_SE.UTF-8", + "LC_ADDRESS": "sv_SE.UTF-8", + "LC_ALL": "sv_SE.UTF-8", + "LC_COLLATE": "sv_SE.UTF-8", + "LC_CTYPE": "sv_SE.UTF-8", + "LC_IDENTIFICATION": "sv_SE.UTF-8", + "LC_MEASUREMENT": "sv_SE.UTF-8", + "LC_MESSAGES": "sv_SE.UTF-8", + "LC_MONETARY": "sv_SE.UTF-8", + "LC_NAME": "sv_SE.UTF-8", + "LC_NUMERIC": "sv_SE.UTF-8", + "LC_PAPER": "sv_SE.UTF-8", + "LC_TELEPHONE": "sv_SE.UTF-8", + "LC_TIME": "sv_SE.UTF-8", + "LESS": "-R", + "LOGNAME": "torkelo", + "LSCOLORS": "Gxfxcxdxbxegedabagacad", + "LS_COLORS": "rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:", + "MANDATORY_PATH": "/usr/share/gconf/i3.mandatory.path", + "NVM_BIN": "/home/torkelo/.nvm/versions/node/v12.13.1/bin", + "NVM_CD_FLAGS": "-q", + "NVM_DIR": "/home/torkelo/.nvm", + "OLDPWD": "/home/torkelo", + "PAGER": "less", + "PATH": "/home/torkelo/.pyenv/plugins/pyenv-virtualenv/shims:/home/torkelo/.pyenv/shims:/home/torkelo/.pyenv/plugins/pyenv-virtualenv/shims:/home/torkelo/.pyenv/shims:/home/torkelo/bin:/home/torkelo/.dotfiles/bins:/usr/local/bin:/home/torkelo/.pyenv/shims:/home/torkelo/.pyenv/plugins/pyenv-virtualenv/shims:/home/torkelo/.pyenv/shims:/home/torkelo/.pyenv/plugins/pyenv-virtualenv/shims:/home/torkelo/.pyenv/shims:/home/torkelo/.nvm/versions/node/v12.13.1/bin:/home/torkelo/bin:/home/torkelo/.dotfiles/bins:/usr/local/bin:/home/torkelo/.pyenv/shims:/home/torkelo/.local/bin:/home/torkelo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/usr/local/go/bin:/home/torkelo/go/bin:/home/torkelo/.pyenv/bin:/home/torkelo/.rvm/bin:/home/torkelo/.local/bin:/usr/local/go/bin:/home/torkelo/.cargo/bin:/home/torkelo/dev/go/bin:/home/torkelo/.fzf/bin:/home/torkelo/.rvm/bin:/home/torkelo/.local/bin:/usr/local/go/bin:/home/torkelo/.cargo/bin:/home/torkelo/dev/go/bin", + "PROJECTS": "/home/torkelo/dev", + "PWD": "/home/torkelo/dev/go/src/github.com/grafana/grafana", + "PYENV_SHELL": "zsh", + "PYENV_VIRTUALENV_INIT": "1", + "QT_ACCESSIBILITY": "1", + "SHELL": "/usr/bin/zsh", + "SHLVL": "2", + "SSH_AGENT_PID": "1732", + "SSH_AUTH_SOCK": "/tmp/ssh-rZxvFmTCg1Ge/agent.1642", + "TERM": "xterm-256color", + "TMUX": "/tmp/tmux-1000/default,3051,0", + "TMUX_PANE": "%0", + "USER": "torkelo", + "USERNAME": "torkelo", + "VIRTUAL_ENV_DISABLE_PROMPT": "1", + "VTE_VERSION": "5202", + "WINDOWPATH": "2", + "XAUTHORITY": "/run/user/1000/gdm/Xauthority", + "XDG_CONFIG_DIRS": "/etc/xdg/xdg-i3:/etc/xdg", + "XDG_CURRENT_DESKTOP": "i3", + "XDG_DATA_DIRS": "/usr/share/i3:/usr/local/share:/usr/share:/var/lib/snapd/desktop", + "XDG_RUNTIME_DIR": "/run/user/1000", + "XDG_SEAT": "seat0", + "XDG_SESSION_DESKTOP": "i3", + "XDG_SESSION_ID": "2", + "XDG_SESSION_TYPE": "x11", + "XDG_VTNR": "2", + "_": "/usr/bin/nvim", + "is_vim": "echo \"#{pane_current_command}\" | grep -iqE \"(^|\\/)g?(view|n?vim?x?)(diff)?$\"", + "VIMRUNTIME": "/usr/share/nvim/runtime", + "NVIM_LISTEN_ADDRESS": "/tmp/nvimV1ROJn/0", + "VIM": "/usr/share/nvim", + "MYVIMRC": "/home/torkelo/.config/nvim/init.vim", + "FZF_DEFAULT_COMMAND": "ag --hidden --ignore .git -g \"\"", + "VIMCONFIG": "/home/torkelo/.config/nvim", + "TSS_LOG": "-level verbose -file /tmp/coc-nvim-tsc.log", + "NODE_PATH": "/home/torkelo/dev/go/src/github.com/grafana/grafana/node_modules" + }, + "userLimits": { + "core_file_size_blocks": { + "soft": 0, + "hard": "unlimited" + }, + "data_seg_size_kbytes": { + "soft": "unlimited", + "hard": "unlimited" + }, + "file_size_blocks": { + "soft": "unlimited", + "hard": "unlimited" + }, + "max_locked_memory_bytes": { + "soft": 16777216, + "hard": 16777216 + }, + "max_memory_size_kbytes": { + "soft": "unlimited", + "hard": "unlimited" + }, + "open_files": { + "soft": 4096, + "hard": 4096 + }, + "stack_size_bytes": { + "soft": 8388608, + "hard": "unlimited" + }, + "cpu_time_seconds": { + "soft": "unlimited", + "hard": "unlimited" + }, + "max_user_processes": { + "soft": 62634, + "hard": 62634 + }, + "virtual_memory_kbytes": { + "soft": "unlimited", + "hard": "unlimited" + } + }, + "sharedObjects": [ + "linux-vdso.so.1", + "/lib/x86_64-linux-gnu/libdl.so.2", + "/usr/lib/x86_64-linux-gnu/libstdc++.so.6", + "/lib/x86_64-linux-gnu/libm.so.6", + "/lib/x86_64-linux-gnu/libgcc_s.so.1", + "/lib/x86_64-linux-gnu/libpthread.so.0", + "/lib/x86_64-linux-gnu/libc.so.6", + "/lib64/ld-linux-x86-64.so.2" + ] +} diff --git a/scripts/ci-frontend-metrics.sh b/scripts/ci-frontend-metrics.sh index 3c872cc00ff..1cf40fb2078 100755 --- a/scripts/ci-frontend-metrics.sh +++ b/scripts/ci-frontend-metrics.sh @@ -3,7 +3,7 @@ echo -e "Collecting code stats (typescript errors & more)" -ERROR_COUNT_LIMIT=1025 +ERROR_COUNT_LIMIT=1005 DIRECTIVES_LIMIT=172 CONTROLLERS_LIMIT=139