From 2179aa0fa7a30498cac0ca7b34d369bcdd813a45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 26 Nov 2020 18:12:02 +0100 Subject: [PATCH 001/124] Queries: Extract queries from dashboard (#29349) * Moving query stuff out of dashboard folder * Moved more stuff * Moving more stuff * Update * WIP test page * Minor change * Before big changes * Fixed test --- .../features/annotations/annotations_srv.ts | 2 +- .../components/Inspector/InspectContent.tsx | 2 +- .../components/Inspector/InspectDataTab.tsx | 2 +- .../components/Inspector/PanelInspector.tsx | 2 +- .../PanelEditor/PanelEditorQueries.tsx | 16 +++++ .../PanelEditor/PanelEditorTabs.tsx | 4 +- .../PanelEditor/VisualizationTab.tsx | 2 +- .../PanelEditor/usePanelLatestData.ts | 2 +- .../VizTypePicker}/VizTypePicker.tsx | 0 .../VizTypePicker}/VizTypePickerPlugin.tsx | 2 +- .../dashboard/state/PanelModel.test.ts | 2 +- .../features/dashboard/state/PanelModel.ts | 2 +- .../dashboard/state/analyticsProcessor.ts | 64 +------------------ .../dashboard/utils/loadSnapshotData.ts | 2 +- public/app/features/explore/state/query.ts | 2 +- .../app/features/panel/metrics_panel_ctrl.ts | 2 +- .../components}/QueriesTab.tsx | 10 +-- .../components}/QueryEditorRow.test.ts | 0 .../components}/QueryEditorRow.tsx | 13 ++-- .../components}/QueryEditorRowTitle.tsx | 0 .../components}/QueryEditorRows.tsx | 6 +- .../components}/QueryOptions.tsx | 2 +- .../state/PanelQueryRunner.test.ts | 2 +- .../state/PanelQueryRunner.ts | 0 .../state/queryAnalytics.test.ts} | 4 +- .../features/query/state/queryAnalytics.ts | 56 ++++++++++++++++ .../state/runRequest.test.ts | 2 +- .../{dashboard => query}/state/runRequest.ts | 2 +- public/app/features/sandbox/TestStuffPage.tsx | 33 ++++++++++ .../variables/query/VariableQueryRunner.ts | 2 +- .../dashboard/DashboardQueryEditor.tsx | 3 +- .../datasource/dashboard/runSharedRequest.ts | 2 +- public/app/plugins/panel/graph/module.ts | 2 +- .../panel/graph/specs/data_processor.test.ts | 2 +- .../app/plugins/panel/heatmap/heatmap_ctrl.ts | 2 +- public/app/plugins/panel/singlestat/module.ts | 2 +- public/app/routes/routes.ts | 8 +++ 37 files changed, 156 insertions(+), 105 deletions(-) create mode 100644 public/app/features/dashboard/components/PanelEditor/PanelEditorQueries.tsx rename public/app/features/dashboard/{panel_editor => components/VizTypePicker}/VizTypePicker.tsx (100%) rename public/app/features/dashboard/{panel_editor => components/VizTypePicker}/VizTypePickerPlugin.tsx (99%) rename public/app/features/{dashboard/panel_editor => query/components}/QueriesTab.tsx (98%) rename public/app/features/{dashboard/panel_editor => query/components}/QueryEditorRow.test.ts (100%) rename public/app/features/{dashboard/panel_editor => query/components}/QueryEditorRow.tsx (97%) rename public/app/features/{dashboard/panel_editor => query/components}/QueryEditorRowTitle.tsx (100%) rename public/app/features/{dashboard/panel_editor => query/components}/QueryEditorRows.tsx (95%) rename public/app/features/{dashboard/panel_editor => query/components}/QueryOptions.tsx (99%) rename public/app/features/{dashboard => query}/state/PanelQueryRunner.test.ts (99%) rename public/app/features/{dashboard => query}/state/PanelQueryRunner.ts (100%) rename public/app/features/{dashboard/state/analyticsProcessor.test.ts => query/state/queryAnalytics.test.ts} (95%) create mode 100644 public/app/features/query/state/queryAnalytics.ts rename public/app/features/{dashboard => query}/state/runRequest.test.ts (99%) rename public/app/features/{dashboard => query}/state/runRequest.ts (99%) create mode 100644 public/app/features/sandbox/TestStuffPage.tsx diff --git a/public/app/features/annotations/annotations_srv.ts b/public/app/features/annotations/annotations_srv.ts index 6540a5aca93..3a8588503ac 100644 --- a/public/app/features/annotations/annotations_srv.ts +++ b/public/app/features/annotations/annotations_srv.ts @@ -25,7 +25,7 @@ import { Observable, of } from 'rxjs'; import { map, mergeMap } from 'rxjs/operators'; import { AnnotationQueryOptions, AnnotationQueryResponse } from './types'; import { standardAnnotationSupport } from './standardAnnotationSupport'; -import { runRequest } from '../dashboard/state/runRequest'; +import { runRequest } from '../query/state/runRequest'; let counter = 100; function getNextRequestId() { diff --git a/public/app/features/dashboard/components/Inspector/InspectContent.tsx b/public/app/features/dashboard/components/Inspector/InspectContent.tsx index 7e51f31d29f..5edf0e725f0 100644 --- a/public/app/features/dashboard/components/Inspector/InspectContent.tsx +++ b/public/app/features/dashboard/components/Inspector/InspectContent.tsx @@ -12,7 +12,7 @@ import { InspectStatsTab } from './InspectStatsTab'; import { QueryInspector } from './QueryInspector'; import { InspectTab } from './types'; import { DashboardModel, PanelModel } from '../../state'; -import { GetDataOptions } from '../../state/PanelQueryRunner'; +import { GetDataOptions } from '../../../query/state/PanelQueryRunner'; interface Props { dashboard: DashboardModel; diff --git a/public/app/features/dashboard/components/Inspector/InspectDataTab.tsx b/public/app/features/dashboard/components/Inspector/InspectDataTab.tsx index 7156006f499..65d90e5f985 100644 --- a/public/app/features/dashboard/components/Inspector/InspectDataTab.tsx +++ b/public/app/features/dashboard/components/Inspector/InspectDataTab.tsx @@ -19,7 +19,7 @@ import { getPanelInspectorStyles } from './styles'; import { config } from 'app/core/config'; import { saveAs } from 'file-saver'; import { css } from 'emotion'; -import { GetDataOptions } from '../../state/PanelQueryRunner'; +import { GetDataOptions } from '../../../query/state/PanelQueryRunner'; import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOperationRow'; import { PanelModel } from 'app/features/dashboard/state'; import { DetailText } from './DetailText'; diff --git a/public/app/features/dashboard/components/Inspector/PanelInspector.tsx b/public/app/features/dashboard/components/Inspector/PanelInspector.tsx index 188423fe87c..e5f63922b35 100644 --- a/public/app/features/dashboard/components/Inspector/PanelInspector.tsx +++ b/public/app/features/dashboard/components/Inspector/PanelInspector.tsx @@ -5,7 +5,7 @@ import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { PanelPlugin } from '@grafana/data'; import { StoreState } from 'app/types'; -import { GetDataOptions } from '../../state/PanelQueryRunner'; +import { GetDataOptions } from '../../../query/state/PanelQueryRunner'; import { usePanelLatestData } from '../PanelEditor/usePanelLatestData'; import { InspectContent } from './InspectContent'; import { useDatasourceMetadata, useInspectTabs } from './hooks'; diff --git a/public/app/features/dashboard/components/PanelEditor/PanelEditorQueries.tsx b/public/app/features/dashboard/components/PanelEditor/PanelEditorQueries.tsx new file mode 100644 index 00000000000..2e974c3ede6 --- /dev/null +++ b/public/app/features/dashboard/components/PanelEditor/PanelEditorQueries.tsx @@ -0,0 +1,16 @@ +import React, { PureComponent } from 'react'; +import { QueriesTab } from 'app/features/query/components/QueriesTab'; +import { DashboardModel, PanelModel } from '../../state'; + +interface Props { + panel: PanelModel; + dashboard: DashboardModel; +} + +export class PanelEditorQueries extends PureComponent { + render() { + const { panel, dashboard } = this.props; + + return ; + } +} diff --git a/public/app/features/dashboard/components/PanelEditor/PanelEditorTabs.tsx b/public/app/features/dashboard/components/PanelEditor/PanelEditorTabs.tsx index 646d5bf3fc2..944cffda809 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelEditorTabs.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelEditorTabs.tsx @@ -2,13 +2,13 @@ import React, { PureComponent } from 'react'; import { config } from 'app/core/config'; import { css } from 'emotion'; import { IconName, stylesFactory, Tab, TabContent, TabsBar } from '@grafana/ui'; -import { QueriesTab } from '../../panel_editor/QueriesTab'; import { AlertTab } from 'app/features/alerting/AlertTab'; import { TransformationsEditor } from '../TransformationsEditor/TransformationsEditor'; import { DashboardModel, PanelModel } from '../../state'; import { PanelEditorTab, PanelEditorTabId } from './types'; import { Subscription } from 'rxjs'; import { PanelQueriesChangedEvent, PanelTransformationsChangedEvent } from 'app/types/events'; +import { PanelEditorQueries } from './PanelEditorQueries'; interface PanelEditorTabsProps { panel: PanelModel; @@ -76,7 +76,7 @@ export class PanelEditorTabs extends PureComponent { })} - {activeTab.id === PanelEditorTabId.Query && } + {activeTab.id === PanelEditorTabId.Query && } {activeTab.id === PanelEditorTabId.Alert && } {activeTab.id === PanelEditorTabId.Transform && } diff --git a/public/app/features/dashboard/components/PanelEditor/VisualizationTab.tsx b/public/app/features/dashboard/components/PanelEditor/VisualizationTab.tsx index 1d40283cffa..e5b28f98ffc 100644 --- a/public/app/features/dashboard/components/PanelEditor/VisualizationTab.tsx +++ b/public/app/features/dashboard/components/PanelEditor/VisualizationTab.tsx @@ -6,7 +6,7 @@ import { changePanelPlugin } from '../../state/actions'; import { StoreState } from 'app/types'; import { PanelModel } from '../../state/PanelModel'; import { connect, MapStateToProps, MapDispatchToProps } from 'react-redux'; -import { VizTypePicker, getAllPanelPluginMeta, filterPluginList } from '../../panel_editor/VizTypePicker'; +import { VizTypePicker, getAllPanelPluginMeta, filterPluginList } from '../VizTypePicker/VizTypePicker'; import { Field } from '@grafana/ui/src/components/Forms/Field'; interface OwnProps { diff --git a/public/app/features/dashboard/components/PanelEditor/usePanelLatestData.ts b/public/app/features/dashboard/components/PanelEditor/usePanelLatestData.ts index 3ea6d16e6e1..2c66fe00302 100644 --- a/public/app/features/dashboard/components/PanelEditor/usePanelLatestData.ts +++ b/public/app/features/dashboard/components/PanelEditor/usePanelLatestData.ts @@ -2,7 +2,7 @@ import { DataQueryError, LoadingState, PanelData } from '@grafana/data'; import { useEffect, useRef, useState } from 'react'; import { PanelModel } from '../../state'; import { Unsubscribable } from 'rxjs'; -import { GetDataOptions } from '../../state/PanelQueryRunner'; +import { GetDataOptions } from '../../../query/state/PanelQueryRunner'; interface UsePanelLatestData { data?: PanelData; diff --git a/public/app/features/dashboard/panel_editor/VizTypePicker.tsx b/public/app/features/dashboard/components/VizTypePicker/VizTypePicker.tsx similarity index 100% rename from public/app/features/dashboard/panel_editor/VizTypePicker.tsx rename to public/app/features/dashboard/components/VizTypePicker/VizTypePicker.tsx diff --git a/public/app/features/dashboard/panel_editor/VizTypePickerPlugin.tsx b/public/app/features/dashboard/components/VizTypePicker/VizTypePickerPlugin.tsx similarity index 99% rename from public/app/features/dashboard/panel_editor/VizTypePickerPlugin.tsx rename to public/app/features/dashboard/components/VizTypePicker/VizTypePickerPlugin.tsx index 791aa5256ab..043cf1ee824 100644 --- a/public/app/features/dashboard/panel_editor/VizTypePickerPlugin.tsx +++ b/public/app/features/dashboard/components/VizTypePicker/VizTypePickerPlugin.tsx @@ -3,7 +3,7 @@ import { GrafanaTheme, PanelPluginMeta, PluginState } from '@grafana/data'; import { Badge, BadgeProps, styleMixins, stylesFactory, useTheme } from '@grafana/ui'; import { css, cx } from 'emotion'; import { selectors } from '@grafana/e2e-selectors'; -import { isUnsignedPluginSignature, PluginSignatureBadge } from '../../plugins/PluginSignatureBadge'; +import { isUnsignedPluginSignature, PluginSignatureBadge } from '../../../plugins/PluginSignatureBadge'; interface Props { isCurrent: boolean; diff --git a/public/app/features/dashboard/state/PanelModel.test.ts b/public/app/features/dashboard/state/PanelModel.test.ts index a3f10f210ae..3af984340c9 100644 --- a/public/app/features/dashboard/state/PanelModel.test.ts +++ b/public/app/features/dashboard/state/PanelModel.test.ts @@ -12,7 +12,7 @@ import { FieldColorConfigSettings, } from '@grafana/data'; import { ComponentClass } from 'react'; -import { PanelQueryRunner } from './PanelQueryRunner'; +import { PanelQueryRunner } from '../../query/state/PanelQueryRunner'; import { setDataSourceSrv } from '@grafana/runtime'; class TablePanelCtrl {} diff --git a/public/app/features/dashboard/state/PanelModel.ts b/public/app/features/dashboard/state/PanelModel.ts index 074fdfaac03..4496c7a44fb 100644 --- a/public/app/features/dashboard/state/PanelModel.ts +++ b/public/app/features/dashboard/state/PanelModel.ts @@ -25,7 +25,7 @@ import { } from '@grafana/data'; import { EDIT_PANEL_ID } from 'app/core/constants'; import config from 'app/core/config'; -import { PanelQueryRunner } from './PanelQueryRunner'; +import { PanelQueryRunner } from '../../query/state/PanelQueryRunner'; import { getDatasourceSrv } from '../../plugins/datasource_srv'; import { PanelOptionsChangedEvent, PanelQueriesChangedEvent, PanelTransformationsChangedEvent } from 'app/types/events'; diff --git a/public/app/features/dashboard/state/analyticsProcessor.ts b/public/app/features/dashboard/state/analyticsProcessor.ts index 9fe1caf6049..9834d86f149 100644 --- a/public/app/features/dashboard/state/analyticsProcessor.ts +++ b/public/app/features/dashboard/state/analyticsProcessor.ts @@ -1,67 +1,5 @@ -import { getDashboardSrv } from '../services/DashboardSrv'; import { DashboardModel } from './DashboardModel'; - -import { PanelData, LoadingState, DataSourceApi, CoreApp, urlUtil } from '@grafana/data'; - -import { - reportMetaAnalytics, - MetaAnalyticsEventName, - DataRequestEventPayload, - DashboardViewEventPayload, -} from '@grafana/runtime'; - -export function emitDataRequestEvent(datasource: DataSourceApi) { - let done = false; - - return (data: PanelData) => { - if (!data.request || done || data.request.app === CoreApp.Explore) { - return; - } - - const params = urlUtil.getUrlSearchParams(); - if (params.editPanel != null) { - return; - } - - if (data.state !== LoadingState.Done && data.state !== LoadingState.Error) { - return; - } - - const eventData: DataRequestEventPayload = { - eventName: MetaAnalyticsEventName.DataRequest, - datasourceName: datasource.name, - datasourceId: datasource.id, - panelId: data.request.panelId, - dashboardId: data.request.dashboardId, - dataSize: 0, - duration: data.request.endTime! - data.request.startTime, - }; - - // enrich with dashboard info - const dashboard = getDashboardSrv().getCurrent(); - if (dashboard) { - eventData.dashboardId = dashboard.id; - eventData.dashboardName = dashboard.title; - eventData.dashboardUid = dashboard.uid; - eventData.folderName = dashboard.meta.folderTitle; - } - - if (data.series && data.series.length > 0) { - // estimate size - eventData.dataSize = data.series.length; - } - - if (data.error) { - eventData.error = data.error.message; - } - - reportMetaAnalytics(eventData); - - // this done check is to make sure we do not double emit events in case - // there are multiple responses with done state - done = true; - }; -} +import { reportMetaAnalytics, MetaAnalyticsEventName, DashboardViewEventPayload } from '@grafana/runtime'; export function emitDashboardViewEvent(dashboard: DashboardModel) { const eventData: DashboardViewEventPayload = { diff --git a/public/app/features/dashboard/utils/loadSnapshotData.ts b/public/app/features/dashboard/utils/loadSnapshotData.ts index d81802272f0..534ab23c468 100644 --- a/public/app/features/dashboard/utils/loadSnapshotData.ts +++ b/public/app/features/dashboard/utils/loadSnapshotData.ts @@ -2,7 +2,7 @@ import { applyFieldOverrides, DefaultTimeRange, LoadingState, PanelData } from ' import { config } from 'app/core/config'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { DashboardModel, PanelModel } from '../state'; -import { getProcessedDataFrames } from '../state/runRequest'; +import { getProcessedDataFrames } from '../../query/state/runRequest'; export function loadSnapshotData(panel: PanelModel, dashboard: DashboardModel): PanelData { const data = getProcessedDataFrames(panel.snapshotData); diff --git a/public/app/features/explore/state/query.ts b/public/app/features/explore/state/query.ts index 896c8edb716..f7c01368006 100644 --- a/public/app/features/explore/state/query.ts +++ b/public/app/features/explore/state/query.ts @@ -27,7 +27,7 @@ import { ExploreId, QueryOptions } from 'app/types/explore'; import { getTimeZone } from 'app/features/profile/state/selectors'; import { getShiftedTimeRange } from 'app/core/utils/timePicker'; import { notifyApp } from '../../../core/actions'; -import { preProcessPanelData, runRequest } from '../../dashboard/state/runRequest'; +import { preProcessPanelData, runRequest } from '../../query/state/runRequest'; import { decorateWithGraphLogsTraceAndTable, decorateWithGraphResult, diff --git a/public/app/features/panel/metrics_panel_ctrl.ts b/public/app/features/panel/metrics_panel_ctrl.ts index 4bcf3bdf465..f5b157bdd3a 100644 --- a/public/app/features/panel/metrics_panel_ctrl.ts +++ b/public/app/features/panel/metrics_panel_ctrl.ts @@ -16,7 +16,7 @@ import { } from '@grafana/data'; import { Unsubscribable } from 'rxjs'; import { PanelModel } from 'app/features/dashboard/state'; -import { PanelQueryRunner } from '../dashboard/state/PanelQueryRunner'; +import { PanelQueryRunner } from '../query/state/PanelQueryRunner'; class MetricsPanelCtrl extends PanelCtrl { scope: any; diff --git a/public/app/features/dashboard/panel_editor/QueriesTab.tsx b/public/app/features/query/components/QueriesTab.tsx similarity index 98% rename from public/app/features/dashboard/panel_editor/QueriesTab.tsx rename to public/app/features/query/components/QueriesTab.tsx index 99434812939..a292d6419b0 100644 --- a/public/app/features/dashboard/panel_editor/QueriesTab.tsx +++ b/public/app/features/query/components/QueriesTab.tsx @@ -11,8 +11,8 @@ import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { backendSrv } from 'app/core/services/backend_srv'; import config from 'app/core/config'; // Types -import { PanelModel } from '../state/PanelModel'; -import { DashboardModel } from '../state/DashboardModel'; +import { PanelModel } from '../../dashboard/state/PanelModel'; +import { DashboardModel } from '../../dashboard/state/DashboardModel'; import { DataQuery, DataSourceSelectItem, @@ -31,7 +31,7 @@ import { selectors } from '@grafana/e2e-selectors'; interface Props { panel: PanelModel; - dashboard: DashboardModel; + dashboard?: DashboardModel; } interface State { @@ -153,10 +153,6 @@ export class QueriesTab extends PureComponent { }); }; - renderHelp = () => { - return; - }; - /** * Sets the queries for the panel */ diff --git a/public/app/features/dashboard/panel_editor/QueryEditorRow.test.ts b/public/app/features/query/components/QueryEditorRow.test.ts similarity index 100% rename from public/app/features/dashboard/panel_editor/QueryEditorRow.test.ts rename to public/app/features/query/components/QueryEditorRow.test.ts diff --git a/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx b/public/app/features/query/components/QueryEditorRow.tsx similarity index 97% rename from public/app/features/dashboard/panel_editor/QueryEditorRow.tsx rename to public/app/features/query/components/QueryEditorRow.tsx index 626cdb45eda..9465fd5b4e4 100644 --- a/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx +++ b/public/app/features/query/components/QueryEditorRow.tsx @@ -7,7 +7,7 @@ import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { AngularComponent, getAngularLoader } from '@grafana/runtime'; import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; // Types -import { PanelModel } from '../state/PanelModel'; +import { PanelModel } from '../../dashboard/state/PanelModel'; import { ErrorBoundaryAlert, HorizontalGroup } from '@grafana/ui'; import { @@ -23,14 +23,14 @@ import { import { QueryEditorRowTitle } from './QueryEditorRowTitle'; import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOperationRow'; import { QueryOperationAction } from 'app/core/components/QueryOperationRow/QueryOperationAction'; -import { DashboardModel } from '../state/DashboardModel'; +import { DashboardModel } from '../../dashboard/state/DashboardModel'; import { selectors } from '@grafana/e2e-selectors'; interface Props { panel: PanelModel; data: PanelData; query: DataQuery; - dashboard: DashboardModel; + dashboard?: DashboardModel; dataSourceValue: string | null; inMixedMode?: boolean; id: string; @@ -79,9 +79,9 @@ export class QueryEditorRow extends PureComponent { datasource: datasource, target: query, panel: panel, - dashboard: dashboard, + dashboard: dashboard!, refresh: () => panel.refresh(), - render: () => panel.render(), + render: () => () => console.log('legacy render function called, it does nothing'), events: panel.events, range: getTimeSrv().timeRange(), }; @@ -141,13 +141,16 @@ export class QueryEditorRow extends PureComponent { if (!this.element) { return; } + if (this.angularQueryEditor) { this.angularQueryEditor.destroy(); this.angularQueryEditor = null; } + const loader = getAngularLoader(); const template = ''; const scopeProps = { ctrl: this.getAngularQueryComponentScope() }; + this.angularQueryEditor = loader.load(this.element, scopeProps, template); this.angularScope = scopeProps.ctrl; }; diff --git a/public/app/features/dashboard/panel_editor/QueryEditorRowTitle.tsx b/public/app/features/query/components/QueryEditorRowTitle.tsx similarity index 100% rename from public/app/features/dashboard/panel_editor/QueryEditorRowTitle.tsx rename to public/app/features/query/components/QueryEditorRowTitle.tsx diff --git a/public/app/features/dashboard/panel_editor/QueryEditorRows.tsx b/public/app/features/query/components/QueryEditorRows.tsx similarity index 95% rename from public/app/features/dashboard/panel_editor/QueryEditorRows.tsx rename to public/app/features/query/components/QueryEditorRows.tsx index 9a45c9bcc1d..008a4e92374 100644 --- a/public/app/features/dashboard/panel_editor/QueryEditorRows.tsx +++ b/public/app/features/query/components/QueryEditorRows.tsx @@ -2,9 +2,9 @@ import React, { PureComponent } from 'react'; // Types -import { PanelModel } from '../state/PanelModel'; +import { PanelModel } from '../../dashboard/state/PanelModel'; import { DataQuery, PanelData, DataSourceSelectItem } from '@grafana/data'; -import { DashboardModel } from '../state/DashboardModel'; +import { DashboardModel } from '../../dashboard/state/DashboardModel'; import { QueryEditorRow } from './QueryEditorRow'; import { addQuery } from 'app/core/utils/query'; import { DragDropContext, Droppable, DropResult } from 'react-beautiful-dnd'; @@ -20,7 +20,7 @@ interface Props { // Dashboard Configs panel: PanelModel; - dashboard: DashboardModel; + dashboard?: DashboardModel; // Query Response Data data: PanelData; diff --git a/public/app/features/dashboard/panel_editor/QueryOptions.tsx b/public/app/features/query/components/QueryOptions.tsx similarity index 99% rename from public/app/features/dashboard/panel_editor/QueryOptions.tsx rename to public/app/features/query/components/QueryOptions.tsx index 89421689d4c..0d820a682c4 100644 --- a/public/app/features/dashboard/panel_editor/QueryOptions.tsx +++ b/public/app/features/query/components/QueryOptions.tsx @@ -8,7 +8,7 @@ import { rangeUtil, PanelData, DataSourceApi } from '@grafana/data'; import { Switch, Input, InlineField, InlineFormLabel, stylesFactory } from '@grafana/ui'; // Types -import { PanelModel } from '../state'; +import { PanelModel } from '../../dashboard/state'; import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOperationRow'; import { config } from 'app/core/config'; import { css } from 'emotion'; diff --git a/public/app/features/dashboard/state/PanelQueryRunner.test.ts b/public/app/features/query/state/PanelQueryRunner.test.ts similarity index 99% rename from public/app/features/dashboard/state/PanelQueryRunner.test.ts rename to public/app/features/query/state/PanelQueryRunner.test.ts index 86808b2d035..092b33e16a0 100644 --- a/public/app/features/dashboard/state/PanelQueryRunner.test.ts +++ b/public/app/features/query/state/PanelQueryRunner.test.ts @@ -17,7 +17,7 @@ import { PanelData, ScopedVars, } from '@grafana/data'; -import { DashboardModel } from './index'; +import { DashboardModel } from '../../dashboard/state/index'; import { setDataSourceSrv, setEchoSrv } from '@grafana/runtime'; import { Echo } from '../../../core/services/echo/Echo'; diff --git a/public/app/features/dashboard/state/PanelQueryRunner.ts b/public/app/features/query/state/PanelQueryRunner.ts similarity index 100% rename from public/app/features/dashboard/state/PanelQueryRunner.ts rename to public/app/features/query/state/PanelQueryRunner.ts diff --git a/public/app/features/dashboard/state/analyticsProcessor.test.ts b/public/app/features/query/state/queryAnalytics.test.ts similarity index 95% rename from public/app/features/dashboard/state/analyticsProcessor.test.ts rename to public/app/features/query/state/queryAnalytics.test.ts index 5cd1590d1bf..7af84e4fcb8 100644 --- a/public/app/features/dashboard/state/analyticsProcessor.test.ts +++ b/public/app/features/query/state/queryAnalytics.test.ts @@ -1,7 +1,7 @@ import { MetaAnalyticsEventName, reportMetaAnalytics } from '@grafana/runtime'; import { CoreApp, DataQueryRequest, DataSourceApi, dateTime, LoadingState, PanelData } from '@grafana/data'; -import { emitDataRequestEvent } from './analyticsProcessor'; -import { DashboardModel } from './DashboardModel'; +import { emitDataRequestEvent } from './queryAnalytics'; +import { DashboardModel } from '../../dashboard/state/DashboardModel'; beforeEach(() => { jest.clearAllMocks(); diff --git a/public/app/features/query/state/queryAnalytics.ts b/public/app/features/query/state/queryAnalytics.ts new file mode 100644 index 00000000000..90b288724eb --- /dev/null +++ b/public/app/features/query/state/queryAnalytics.ts @@ -0,0 +1,56 @@ +import { getDashboardSrv } from '../../dashboard/services/DashboardSrv'; +import { PanelData, LoadingState, DataSourceApi, CoreApp, urlUtil } from '@grafana/data'; +import { reportMetaAnalytics, MetaAnalyticsEventName, DataRequestEventPayload } from '@grafana/runtime'; + +export function emitDataRequestEvent(datasource: DataSourceApi) { + let done = false; + + return (data: PanelData) => { + if (!data.request || done || data.request.app === CoreApp.Explore) { + return; + } + + const params = urlUtil.getUrlSearchParams(); + if (params.editPanel != null) { + return; + } + + if (data.state !== LoadingState.Done && data.state !== LoadingState.Error) { + return; + } + + const eventData: DataRequestEventPayload = { + eventName: MetaAnalyticsEventName.DataRequest, + datasourceName: datasource.name, + datasourceId: datasource.id, + panelId: data.request.panelId, + dashboardId: data.request.dashboardId, + dataSize: 0, + duration: data.request.endTime! - data.request.startTime, + }; + + // enrich with dashboard info + const dashboard = getDashboardSrv().getCurrent(); + if (dashboard) { + eventData.dashboardId = dashboard.id; + eventData.dashboardName = dashboard.title; + eventData.dashboardUid = dashboard.uid; + eventData.folderName = dashboard.meta.folderTitle; + } + + if (data.series && data.series.length > 0) { + // estimate size + eventData.dataSize = data.series.length; + } + + if (data.error) { + eventData.error = data.error.message; + } + + reportMetaAnalytics(eventData); + + // this done check is to make sure we do not double emit events in case + // there are multiple responses with done state + done = true; + }; +} diff --git a/public/app/features/dashboard/state/runRequest.test.ts b/public/app/features/query/state/runRequest.test.ts similarity index 99% rename from public/app/features/dashboard/state/runRequest.test.ts rename to public/app/features/query/state/runRequest.test.ts index 76ce4aa2141..d2a25396d81 100644 --- a/public/app/features/dashboard/state/runRequest.test.ts +++ b/public/app/features/query/state/runRequest.test.ts @@ -11,7 +11,7 @@ import { import { Observable, Subscriber, Subscription } from 'rxjs'; import { runRequest } from './runRequest'; import { deepFreeze } from '../../../../test/core/redux/reducerTester'; -import { DashboardModel } from './DashboardModel'; +import { DashboardModel } from '../../dashboard/state/DashboardModel'; import { setEchoSrv } from '@grafana/runtime'; import { Echo } from '../../../core/services/echo/Echo'; diff --git a/public/app/features/dashboard/state/runRequest.ts b/public/app/features/query/state/runRequest.ts similarity index 99% rename from public/app/features/dashboard/state/runRequest.ts rename to public/app/features/query/state/runRequest.ts index 327ba76190a..605c6261334 100644 --- a/public/app/features/dashboard/state/runRequest.ts +++ b/public/app/features/query/state/runRequest.ts @@ -20,7 +20,7 @@ import { toDataFrame, } from '@grafana/data'; import { toDataQueryError } from '@grafana/runtime'; -import { emitDataRequestEvent } from './analyticsProcessor'; +import { emitDataRequestEvent } from './queryAnalytics'; import { expressionDatasource, ExpressionDatasourceID } from 'app/features/expressions/ExpressionDatasource'; import { ExpressionQuery } from 'app/features/expressions/types'; diff --git a/public/app/features/sandbox/TestStuffPage.tsx b/public/app/features/sandbox/TestStuffPage.tsx new file mode 100644 index 00000000000..11d6bc4bdbb --- /dev/null +++ b/public/app/features/sandbox/TestStuffPage.tsx @@ -0,0 +1,33 @@ +import React, { FC, useState } from 'react'; +import { PanelModel } from '../dashboard/state'; +import { QueriesTab } from '../query/components/QueriesTab'; + +interface State { + panel: PanelModel; +} + +export const TestStuffPage: FC = () => { + const [state] = useState(getDefaultState()); + + return ( +
+

Hello

+ + +
+ ); +}; + +export function getDefaultState(): State { + const panel = new PanelModel({ + datasource: 'gdev-testdata', + id: 10, + targets: [], + }); + + return { + panel, + }; +} + +export default TestStuffPage; diff --git a/public/app/features/variables/query/VariableQueryRunner.ts b/public/app/features/variables/query/VariableQueryRunner.ts index 4ab45c5be8a..58a399be439 100644 --- a/public/app/features/variables/query/VariableQueryRunner.ts +++ b/public/app/features/variables/query/VariableQueryRunner.ts @@ -19,7 +19,7 @@ import { getTemplatedRegex } from '../utils'; import { v4 as uuidv4 } from 'uuid'; import { getTimeSrv } from '../../dashboard/services/TimeSrv'; import { QueryRunners } from './queryRunners'; -import { runRequest } from '../../dashboard/state/runRequest'; +import { runRequest } from '../../query/state/runRequest'; import { runUpdateTagsRequest, toMetricFindValues, diff --git a/public/app/plugins/datasource/dashboard/DashboardQueryEditor.tsx b/public/app/plugins/datasource/dashboard/DashboardQueryEditor.tsx index 52e1df63d12..a49afcb6133 100644 --- a/public/app/plugins/datasource/dashboard/DashboardQueryEditor.tsx +++ b/public/app/plugins/datasource/dashboard/DashboardQueryEditor.tsx @@ -11,7 +11,8 @@ import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { PanelModel } from 'app/features/dashboard/state'; import { SHARED_DASHBODARD_QUERY } from './types'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; -import { filterPanelDataToQuery } from 'app/features/dashboard/panel_editor/QueryEditorRow'; +import { filterPanelDataToQuery } from 'app/features/query/components/QueryEditorRow'; + const { Select } = LegacyForms; type ResultInfo = { diff --git a/public/app/plugins/datasource/dashboard/runSharedRequest.ts b/public/app/plugins/datasource/dashboard/runSharedRequest.ts index 09f1e01dfc4..8a2addb8a2e 100644 --- a/public/app/plugins/datasource/dashboard/runSharedRequest.ts +++ b/public/app/plugins/datasource/dashboard/runSharedRequest.ts @@ -1,5 +1,5 @@ import { Observable } from 'rxjs'; -import { QueryRunnerOptions } from 'app/features/dashboard/state/PanelQueryRunner'; +import { QueryRunnerOptions } from 'app/features/query/state/PanelQueryRunner'; import { DashboardQuery, SHARED_DASHBODARD_QUERY } from './types'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; import { LoadingState, DefaultTimeRange, DataQuery, PanelData, DataSourceApi, DataQueryRequest } from '@grafana/data'; diff --git a/public/app/plugins/panel/graph/module.ts b/public/app/plugins/panel/graph/module.ts index 195c95d55cb..a05e9aba3e4 100644 --- a/public/app/plugins/panel/graph/module.ts +++ b/public/app/plugins/panel/graph/module.ts @@ -11,7 +11,7 @@ import { DataProcessor } from './data_processor'; import { axesEditorComponent } from './axes_editor'; import config from 'app/core/config'; import TimeSeries from 'app/core/time_series2'; -import { getProcessedDataFrames } from 'app/features/dashboard/state/runRequest'; +import { getProcessedDataFrames } from 'app/features/query/state/runRequest'; import { DataFrame, FieldConfigProperty, getColorForTheme, PanelEvents, PanelPlugin } from '@grafana/data'; import { GraphContextMenuCtrl } from './GraphContextMenuCtrl'; diff --git a/public/app/plugins/panel/graph/specs/data_processor.test.ts b/public/app/plugins/panel/graph/specs/data_processor.test.ts index 5eee488b673..307b6e3e8d9 100644 --- a/public/app/plugins/panel/graph/specs/data_processor.test.ts +++ b/public/app/plugins/panel/graph/specs/data_processor.test.ts @@ -1,5 +1,5 @@ import { DataProcessor } from '../data_processor'; -import { getProcessedDataFrames } from 'app/features/dashboard/state/runRequest'; +import { getProcessedDataFrames } from 'app/features/query/state/runRequest'; describe('Graph DataProcessor', () => { const panel: any = { diff --git a/public/app/plugins/panel/heatmap/heatmap_ctrl.ts b/public/app/plugins/panel/heatmap/heatmap_ctrl.ts index 6038f905106..ea93bf3ef99 100644 --- a/public/app/plugins/panel/heatmap/heatmap_ctrl.ts +++ b/public/app/plugins/panel/heatmap/heatmap_ctrl.ts @@ -13,7 +13,7 @@ import { sortSeriesByLabel, } from './heatmap_data_converter'; import { auto } from 'angular'; -import { getProcessedDataFrames } from 'app/features/dashboard/state/runRequest'; +import { getProcessedDataFrames } from 'app/features/query/state/runRequest'; import { DataProcessor } from '../graph/data_processor'; import { LegacyResponseData, PanelEvents, DataFrame, rangeUtil } from '@grafana/data'; import { CoreEvents } from 'app/types'; diff --git a/public/app/plugins/panel/singlestat/module.ts b/public/app/plugins/panel/singlestat/module.ts index 2abd1acfb96..6fe53bc743f 100644 --- a/public/app/plugins/panel/singlestat/module.ts +++ b/public/app/plugins/panel/singlestat/module.ts @@ -31,7 +31,7 @@ import { convertOldAngularValueMapping } from '@grafana/ui'; import config from 'app/core/config'; import { MetricsPanelCtrl } from 'app/plugins/sdk'; import { LinkSrv } from 'app/features/panel/panellinks/link_srv'; -import { getProcessedDataFrames } from 'app/features/dashboard/state/runRequest'; +import { getProcessedDataFrames } from 'app/features/query/state/runRequest'; const BASE_FONT_SIZE = 38; diff --git a/public/app/routes/routes.ts b/public/app/routes/routes.ts index c27b65080f0..2218cf50cb0 100644 --- a/public/app/routes/routes.ts +++ b/public/app/routes/routes.ts @@ -237,6 +237,14 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati component: () => SafeDynamicImport(import(/* webpackChunkName: "explore" */ 'app/features/explore/Wrapper')), }, }) + .when('/sandbox/test', { + template: '', + reloadOnSearch: false, + resolve: { + component: () => + SafeDynamicImport(import(/* webpackChunkName: "sandbox" */ 'app/features/sandbox/TestStuffPage')), + }, + }) .when('/a/:pluginId/', { // Someday * and will get a ReactRouter under that path! template: '', From dece0288208a5d8b7cdb31bfb2483661f0ed88f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Fri, 27 Nov 2020 10:20:57 +0100 Subject: [PATCH 002/124] Variables: Fixes issue with upgrading legacy queries (#29375) * Variables: Fixes issue with upgrading legacy queries * Tests: adds tests for upgradeLegacyQueries --- .../app/features/variables/query/actions.ts | 36 +--- .../shared/testing/optionsVariableBuilder.ts | 2 +- .../shared/testing/queryVariableBuilder.ts | 5 + .../app/features/variables/state/actions.ts | 54 +++++- .../state/upgradeLegacyQueries.test.ts | 155 ++++++++++++++++++ 5 files changed, 212 insertions(+), 40 deletions(-) create mode 100644 public/app/features/variables/state/upgradeLegacyQueries.test.ts diff --git a/public/app/features/variables/query/actions.ts b/public/app/features/variables/query/actions.ts index b07d54b345d..38bfa614185 100644 --- a/public/app/features/variables/query/actions.ts +++ b/public/app/features/variables/query/actions.ts @@ -1,4 +1,4 @@ -import { DataQuery, DataSourceApi, DataSourcePluginMeta, DataSourceSelectItem } from '@grafana/data'; +import { DataSourcePluginMeta, DataSourceSelectItem } from '@grafana/data'; import { toDataQueryError } from '@grafana/runtime'; import { updateOptions } from '../state/actions'; @@ -9,7 +9,6 @@ import { getVariable } from '../state/selectors'; import { addVariableEditorError, changeVariableEditorExtended, removeVariableEditorError } from '../editor/reducer'; import { changeVariableProp } from '../state/sharedReducer'; import { toVariableIdentifier, toVariablePayload, VariableIdentifier } from '../state/types'; -import { hasLegacyVariableSupport, hasStandardVariableSupport } from '../guard'; import { getVariableQueryEditor } from '../editor/getVariableQueryEditor'; import { Subscription } from 'rxjs'; import { getVariableQueryRunner } from './VariableQueryRunner'; @@ -26,7 +25,6 @@ export const updateQueryVariableOptions = ( dispatch(removeVariableEditorError({ errorProp: 'update' })); } const datasource = await getDatasourceSrv().get(variableInState.datasource ?? ''); - dispatch(upgradeLegacyQueries(identifier, datasource)); // we need to await the result from variableQueryRunner before moving on otherwise variables dependent on this // variable will have the wrong current value as input @@ -158,35 +156,3 @@ export function flattenQuery(query: any): any { return flattened; } - -export function upgradeLegacyQueries(identifier: VariableIdentifier, datasource: DataSourceApi): ThunkResult { - return function(dispatch, getState) { - if (hasLegacyVariableSupport(datasource)) { - return; - } - - if (!hasStandardVariableSupport(datasource)) { - return; - } - - const variable = getVariable(identifier.id, getState()); - if (isDataQuery(variable.query)) { - return; - } - - const query = { - refId: `${datasource.name}-${identifier.id}-Variable-Query`, - query: variable.query, - }; - - dispatch(changeVariableProp(toVariablePayload(identifier, { propName: 'query', propValue: query }))); - }; -} - -function isDataQuery(query: any): query is DataQuery { - if (!query) { - return false; - } - - return query.hasOwnProperty('refId') && typeof query.refId === 'string'; -} diff --git a/public/app/features/variables/shared/testing/optionsVariableBuilder.ts b/public/app/features/variables/shared/testing/optionsVariableBuilder.ts index b3ef1e14300..4d6e658c2de 100644 --- a/public/app/features/variables/shared/testing/optionsVariableBuilder.ts +++ b/public/app/features/variables/shared/testing/optionsVariableBuilder.ts @@ -28,7 +28,7 @@ export class OptionsVariableBuilder extends Varia return this; } - withQuery(query: string) { + withQuery(query: any) { this.variable.query = query; return this; } diff --git a/public/app/features/variables/shared/testing/queryVariableBuilder.ts b/public/app/features/variables/shared/testing/queryVariableBuilder.ts index 728e0a427c6..a12fb583f37 100644 --- a/public/app/features/variables/shared/testing/queryVariableBuilder.ts +++ b/public/app/features/variables/shared/testing/queryVariableBuilder.ts @@ -11,4 +11,9 @@ export class QueryVariableBuilder extends Datasour this.variable.tagsQuery = tagsQuery; return this; } + + withDatasource(datasource: string) { + this.variable.datasource = datasource; + return this; + } } diff --git a/public/app/features/variables/state/actions.ts b/public/app/features/variables/state/actions.ts index 89fcdbdf025..238beb2006f 100644 --- a/public/app/features/variables/state/actions.ts +++ b/public/app/features/variables/state/actions.ts @@ -1,6 +1,6 @@ import angular from 'angular'; import castArray from 'lodash/castArray'; -import { LoadingState, TimeRange, UrlQueryMap, UrlQueryValue } from '@grafana/data'; +import { DataQuery, LoadingState, TimeRange, UrlQueryMap, UrlQueryValue } from '@grafana/data'; import { DashboardVariableModel, @@ -39,7 +39,7 @@ import { import { contextSrv } from 'app/core/services/context_srv'; import { getTemplateSrv, TemplateSrv } from '../../templating/template_srv'; import { alignCurrentWithMulti } from '../shared/multiOptions'; -import { isMulti } from '../guard'; +import { hasLegacyVariableSupport, hasStandardVariableSupport, isMulti, isQuery } from '../guard'; import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { DashboardModel } from 'app/features/dashboard/state'; import { createErrorNotification } from '../../../core/copy/appNotification'; @@ -54,6 +54,7 @@ import { cleanVariables } from './variablesReducer'; import isEqual from 'lodash/isEqual'; import { getCurrentText, getVariableRefresh } from '../utils'; import { store } from 'app/store/store'; +import { getDatasourceSrv } from '../../plugins/datasource_srv'; // process flow queryVariable // thunk => processVariables @@ -99,8 +100,11 @@ export const initDashboardTemplating = (list: VariableModel[]): ThunkResult { + return async function(dispatch, getState) { + const variable = getVariable(identifier.id, getState()); + + if (!isQuery(variable)) { + return; + } + + const datasource = await getDatasourceSrvFunc().get(variable.datasource ?? ''); + + if (hasLegacyVariableSupport(datasource)) { + return; + } + + if (!hasStandardVariableSupport(datasource)) { + return; + } + + if (isDataQueryType(variable.query)) { + return; + } + + const query = { + refId: `${datasource.name}-${identifier.id}-Variable-Query`, + query: variable.query, + }; + + dispatch(changeVariableProp(toVariablePayload(identifier, { propName: 'query', propValue: query }))); + }; +} + +function isDataQueryType(query: any): query is DataQuery { + if (!query) { + return false; + } + + return query.hasOwnProperty('refId') && typeof query.refId === 'string'; +} diff --git a/public/app/features/variables/state/upgradeLegacyQueries.test.ts b/public/app/features/variables/state/upgradeLegacyQueries.test.ts new file mode 100644 index 00000000000..cb29d3e76ab --- /dev/null +++ b/public/app/features/variables/state/upgradeLegacyQueries.test.ts @@ -0,0 +1,155 @@ +import { customBuilder, queryBuilder } from '../shared/testing/builders'; +import { VariableSupportType } from '@grafana/data'; +import { toVariableIdentifier } from './types'; +import { upgradeLegacyQueries } from './actions'; +import { changeVariableProp } from './sharedReducer'; +import { thunkTester } from '../../../../test/core/thunk/thunkTester'; +import { VariableModel } from '../types'; + +interface Args { + query?: any; + variable?: VariableModel; + datasource?: any; +} +function getTestContext({ query = '', variable, datasource }: Args = {}) { + variable = + variable ?? + queryBuilder() + .withId('query') + .withName('query') + .withQuery(query) + .withDatasource('test-data') + .build(); + const state = { + templating: { + variables: { + [variable.id]: variable, + }, + }, + }; + datasource = datasource ?? { + name: 'TestData', + metricFindQuery: () => undefined, + variables: { getType: () => VariableSupportType.Standard, toDataQuery: () => undefined }, + }; + const get = jest.fn().mockResolvedValue(datasource); + const getDatasourceSrv = jest.fn().mockReturnValue({ get }); + const identifier = toVariableIdentifier(variable); + + return { state, get, getDatasourceSrv, identifier }; +} + +describe('upgradeLegacyQueries', () => { + describe('when called with a query variable for a standard variable supported data source that has not been upgraded', () => { + it('then it should dispatch changeVariableProp', async () => { + const { state, identifier, get, getDatasourceSrv } = getTestContext({ query: '*' }); + + const dispatchedActions = await thunkTester(state) + .givenThunk(upgradeLegacyQueries) + .whenThunkIsDispatched(identifier, getDatasourceSrv); + + expect(dispatchedActions).toEqual([ + changeVariableProp({ + type: 'query', + id: 'query', + data: { + propName: 'query', + propValue: { + refId: 'TestData-query-Variable-Query', + query: '*', + }, + }, + }), + ]); + expect(get).toHaveBeenCalledTimes(1); + expect(get).toHaveBeenCalledWith('test-data'); + }); + }); + + describe('when called with a query variable for a standard variable supported data source that has been upgraded', () => { + it('then it should not dispatch any actions', async () => { + const { state, identifier, get, getDatasourceSrv } = getTestContext({ query: { refId: 'A' } }); + + const dispatchedActions = await thunkTester(state) + .givenThunk(upgradeLegacyQueries) + .whenThunkIsDispatched(identifier, getDatasourceSrv); + + expect(dispatchedActions).toEqual([]); + expect(get).toHaveBeenCalledTimes(1); + expect(get).toHaveBeenCalledWith('test-data'); + }); + }); + + describe('when called with a query variable for a legacy variable supported data source', () => { + it('then it should not dispatch any actions', async () => { + const datasource = { + name: 'TestData', + metricFindQuery: () => undefined, + }; + const { state, identifier, get, getDatasourceSrv } = getTestContext({ datasource }); + + const dispatchedActions = await thunkTester(state) + .givenThunk(upgradeLegacyQueries) + .whenThunkIsDispatched(identifier, getDatasourceSrv); + + expect(dispatchedActions).toEqual([]); + expect(get).toHaveBeenCalledTimes(1); + expect(get).toHaveBeenCalledWith('test-data'); + }); + }); + + describe('when called with a query variable for a custom variable supported data source', () => { + it('then it should not dispatch any actions', async () => { + const datasource = { + name: 'TestData', + metricFindQuery: () => undefined, + variables: { getType: () => VariableSupportType.Custom, query: () => undefined, editor: {} }, + }; + const { state, identifier, get, getDatasourceSrv } = getTestContext({ datasource }); + + const dispatchedActions = await thunkTester(state) + .givenThunk(upgradeLegacyQueries) + .whenThunkIsDispatched(identifier, getDatasourceSrv); + + expect(dispatchedActions).toEqual([]); + expect(get).toHaveBeenCalledTimes(1); + expect(get).toHaveBeenCalledWith('test-data'); + }); + }); + + describe('when called with a query variable for a datasource variable supported data source', () => { + it('then it should not dispatch any actions', async () => { + const datasource = { + name: 'TestData', + metricFindQuery: () => undefined, + variables: { getType: () => VariableSupportType.Datasource }, + }; + const { state, identifier, get, getDatasourceSrv } = getTestContext({ datasource }); + + const dispatchedActions = await thunkTester(state) + .givenThunk(upgradeLegacyQueries) + .whenThunkIsDispatched(identifier, getDatasourceSrv); + + expect(dispatchedActions).toEqual([]); + expect(get).toHaveBeenCalledTimes(1); + expect(get).toHaveBeenCalledWith('test-data'); + }); + }); + + describe('when called with a custom variable', () => { + it('then it should not dispatch any actions', async () => { + const variable = customBuilder() + .withId('custom') + .withName('custom') + .build(); + const { state, identifier, get, getDatasourceSrv } = getTestContext({ variable }); + + const dispatchedActions = await thunkTester(state) + .givenThunk(upgradeLegacyQueries) + .whenThunkIsDispatched(identifier, getDatasourceSrv); + + expect(dispatchedActions).toEqual([]); + expect(get).toHaveBeenCalledTimes(0); + }); + }); +}); From 26a41ecc1ca8e817c4ebaf0689ecea6ec63707fb Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Fri, 27 Nov 2020 10:21:28 +0100 Subject: [PATCH 003/124] Docs: fixing frontend docs issue where enums ending up in wrong folder level. (#29429) * fixing issue with enums ending up in wrong folder. * updated package. --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 49ee8089e02..8c6df42e2c0 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "@babel/preset-react": "7.8.3", "@babel/preset-typescript": "7.8.3", "@emotion/core": "10.0.27", - "@grafana/api-documenter": "7.11.1", + "@grafana/api-documenter": "7.11.2", "@grafana/api-extractor": "7.10.1", "@grafana/eslint-config": "2.0.6", "@rtsao/plugin-proposal-class-properties": "7.0.1-patch.1", diff --git a/yarn.lock b/yarn.lock index 6fcae569dce..97c2e330812 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3536,10 +3536,10 @@ unique-filename "^1.1.1" which "^1.3.1" -"@grafana/api-documenter@7.11.1": - version "7.11.1" - resolved "https://registry.yarnpkg.com/@grafana/api-documenter/-/api-documenter-7.11.1.tgz#5980016ae28aceca16bbeca112913aca5ccda405" - integrity sha512-4AJ5m4ychpRA1bFMOm5lP3pMAdR9ZuiSlRuQIdeBrTZh+sFA+C9SiburYdAfr7gQuetlT8LM4+CzJdxQNw7Lbw== +"@grafana/api-documenter@7.11.2": + version "7.11.2" + resolved "https://registry.yarnpkg.com/@grafana/api-documenter/-/api-documenter-7.11.2.tgz#c08372e51426e3d67d2406834ddbe59f1585b297" + integrity sha512-03PvAj6fiN5EY+BsdHN61kGpUGdAhRBPTUtnzdMGPYuWlCQthMys0jB5XcrtRWJiOVHkxp597LG+7ZFjI+jaUw== dependencies: "@microsoft/api-extractor-model" "workspace:*" "@microsoft/tsdoc" "0.12.19" From c22a39ab2de65d9a721a339039a46b7d0aefbf16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20L=C3=B3pez=20de=20la=20Franca=20Beltran?= Date: Fri, 27 Nov 2020 10:48:37 +0100 Subject: [PATCH 004/124] Plugins: Add support for includes' icon (#29416) * Plugins: Add support for includes' icon * Docs: Add plugin includes' icon reference --- docs/sources/developers/plugins/metadata.md | 1 + pkg/api/index.go | 1 + pkg/plugins/models.go | 1 + 3 files changed, 3 insertions(+) diff --git a/docs/sources/developers/plugins/metadata.md b/docs/sources/developers/plugins/metadata.md index 9a40f8c732a..1a6dad06ea3 100644 --- a/docs/sources/developers/plugins/metadata.md +++ b/docs/sources/developers/plugins/metadata.md @@ -68,6 +68,7 @@ Plugin dependencies. | `name` | string | No | | | `role` | string | No | | | `type` | string | No | | +| `icon` | string | No | | ## info diff --git a/pkg/api/index.go b/pkg/api/index.go index e88d1e00318..5e66015471b 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -104,6 +104,7 @@ func getAppLinks(c *models.ReqContext) ([]*dtos.NavLink, error) { Text: include.Name, } } + link.Icon = include.Icon appLink.Children = append(appLink.Children, link) } diff --git a/pkg/plugins/models.go b/pkg/plugins/models.go index 53120f5fc01..3b2574595f1 100644 --- a/pkg/plugins/models.go +++ b/pkg/plugins/models.go @@ -133,6 +133,7 @@ type PluginInclude struct { AddToNav bool `json:"addToNav"` DefaultNav bool `json:"defaultNav"` Slug string `json:"slug"` + Icon string `json:"icon"` Id string `json:"-"` } From ee3475d9005c06936914d7ced8a6e2493fd68ef4 Mon Sep 17 00:00:00 2001 From: Tom Daly Date: Fri, 27 Nov 2020 09:50:35 +0000 Subject: [PATCH 005/124] Elasticsearch: Fix index pattern not working with multiple base sections (#28348) * Add logic to allow for multiple [] sections in pattern * Small refactor * Fix goimport error * Refactor formateDate --- .../elasticsearch/client/index_pattern.go | 120 +++++++++++------- .../client/index_pattern_test.go | 10 ++ 2 files changed, 86 insertions(+), 44 deletions(-) diff --git a/pkg/tsdb/elasticsearch/client/index_pattern.go b/pkg/tsdb/elasticsearch/client/index_pattern.go index ffd87af017c..8bbaabd2935 100644 --- a/pkg/tsdb/elasticsearch/client/index_pattern.go +++ b/pkg/tsdb/elasticsearch/client/index_pattern.go @@ -247,72 +247,97 @@ var datePatternReplacements = map[string]string{ } func formatDate(t time.Time, pattern string) string { - var datePattern string + var formattedDatePatterns []string + var bases []string base := "" - ltr := false + isBaseFirst := false - if strings.HasPrefix(pattern, "[") { - parts := strings.Split(strings.TrimLeft(pattern, "["), "]") - base = parts[0] - if len(parts) == 2 { - datePattern = parts[1] + baseStart := strings.Index(pattern, "[") + for baseStart != -1 { + var datePattern string + + baseEnd := strings.Index(pattern, "]") + base = pattern[baseStart+1 : baseEnd] + bases = append(bases, base) + if baseStart == 0 { + isBaseFirst = true } else { - datePattern = base - base = "" + datePattern = pattern[:baseStart] + formatted := t.Format(patternToLayout(datePattern)) + formattedDatePatterns = append(formattedDatePatterns, formatted) } - ltr = true - } else if strings.HasSuffix(pattern, "]") { - parts := strings.Split(strings.TrimRight(pattern, "]"), "[") - datePattern = parts[0] - if len(parts) == 2 { - base = parts[1] + + if len(pattern) <= baseEnd+1 { + break + } + pattern = pattern[baseEnd+1:] + + baseStart = strings.Index(pattern, "[") + if baseStart == -1 { + datePattern = pattern } else { - base = "" + datePattern = pattern[:baseStart] + pattern = pattern[baseStart:] + baseStart = 0 } - ltr = false + formatted := t.Format(patternToLayout(datePattern)) + formattedDatePatterns = append(formattedDatePatterns, formatted) } - formatted := t.Format(patternToLayout(datePattern)) + isoYear, isoWeek := t.ISOWeek() + isoYearShort := fmt.Sprintf("%d", isoYear)[2:4] + day := t.Weekday() + dayOfWeekIso := int(day) + if day == time.Sunday { + dayOfWeekIso = 7 + } + quarter := 4 + switch t.Month() { + case time.January, time.February, time.March: + quarter = 1 + case time.April, time.May, time.June: + quarter = 2 + case time.July, time.August, time.September: + quarter = 3 + } - if strings.Contains(formatted, "", fmt.Sprintf("%d", isoYear)) formatted = strings.ReplaceAll(formatted, "", isoYearShort) formatted = strings.ReplaceAll(formatted, "", fmt.Sprintf("%02d", isoWeek)) - formatted = strings.ReplaceAll(formatted, "", fmt.Sprintf("%d", t.Unix())) - - day := t.Weekday() - dayOfWeekIso := int(day) - if day == time.Sunday { - dayOfWeekIso = 7 - } - formatted = strings.ReplaceAll(formatted, "", fmt.Sprintf("%d", day)) formatted = strings.ReplaceAll(formatted, "", fmt.Sprintf("%d", dayOfWeekIso)) formatted = strings.ReplaceAll(formatted, "", fmt.Sprintf("%d", t.YearDay())) - - quarter := 4 - - switch t.Month() { - case time.January, time.February, time.March: - quarter = 1 - case time.April, time.May, time.June: - quarter = 2 - case time.July, time.August, time.September: - quarter = 3 - } - formatted = strings.ReplaceAll(formatted, "", fmt.Sprintf("%d", quarter)) formatted = strings.ReplaceAll(formatted, "", fmt.Sprintf("%d", t.Hour())) + + formattedDatePatterns[i] = formatted } - if ltr { - return base + formatted + var fullPattern []string + var i int + + minLen := min(len(formattedDatePatterns), len(bases)) + for i = 0; i < minLen; i++ { + if isBaseFirst { + fullPattern = append(fullPattern, bases[i], formattedDatePatterns[i]) + } else { + fullPattern = append(fullPattern, formattedDatePatterns[i], bases[i]) + } + } + formattedDatePatterns = formattedDatePatterns[i:] + bases = bases[i:] + if len(bases) == 0 { + fullPattern = append(fullPattern, formattedDatePatterns...) + } else { + fullPattern = append(fullPattern, bases...) } - return formatted + base + return strings.Join(fullPattern, "") } func patternToLayout(pattern string) string { @@ -329,3 +354,10 @@ func patternToLayout(pattern string) string { return pattern } + +func min(a, b int) int { + if a < b { + return a + } + return b +} diff --git a/pkg/tsdb/elasticsearch/client/index_pattern_test.go b/pkg/tsdb/elasticsearch/client/index_pattern_test.go index a3a056da04f..bf413fc2605 100644 --- a/pkg/tsdb/elasticsearch/client/index_pattern_test.go +++ b/pkg/tsdb/elasticsearch/client/index_pattern_test.go @@ -77,6 +77,16 @@ func TestIndexPattern(t *testing.T) { So(indices[0], ShouldEqual, "2018-data") }) + indexPatternScenario(intervalDaily, "YYYY[-data-]MM.DD", tsdb.NewTimeRange(from, to), func(indices []string) { + So(indices, ShouldHaveLength, 1) + So(indices[0], ShouldEqual, "2018-data-05.15") + }) + + indexPatternScenario(intervalDaily, "[data-]YYYY[-moredata-]MM.DD", tsdb.NewTimeRange(from, to), func(indices []string) { + So(indices, ShouldHaveLength, 1) + So(indices[0], ShouldEqual, "data-2018-moredata-05.15") + }) + Convey("Should return 01 week", func() { from = fmt.Sprintf("%d", time.Date(2018, 1, 15, 17, 50, 0, 0, time.UTC).UnixNano()/int64(time.Millisecond)) to = fmt.Sprintf("%d", time.Date(2018, 1, 15, 17, 55, 0, 0, time.UTC).UnixNano()/int64(time.Millisecond)) From b3c4db1d277d6b9ca585c17b48bb5acf78560665 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Fri, 27 Nov 2020 10:53:13 +0100 Subject: [PATCH 006/124] ReleaseNotes: Updated changelog and release notes for 7.3.4 (#29430) --- CHANGELOG.md | 2 +- docs/sources/release-notes/release-notes-7-3-4.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 619214f863f..22202d9d3a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,7 +59,7 @@ In order not to break all angular panel plugins and data sources we have some cu * **Dashboard**: Fixes kiosk state after being redirected to login page and back. [#29273](https://github.com/grafana/grafana/pull/29273), [@torkelo](https://github.com/torkelo) * **InfluxDB**: Update flux library to fix support for boolean label values. [#29310](https://github.com/grafana/grafana/pull/29310), [@ryantxu](https://github.com/ryantxu) * **Security**: Fixes minor security issue with alert notification webhooks that allowed GET & DELETE requests. [#29330](https://github.com/grafana/grafana/pull/29330), [@wbrowne](https://github.com/wbrowne) -* **Table**: Fix incorrect condition for rendering table filter. [#29165](https://github.com/grafana/grafana/pull/29165), [@dprokop](https://github.com/dprokop) +* **Table**: Fixes issues with phantom extra 0 for zero values. [#29165](https://github.com/grafana/grafana/pull/29165), [@dprokop](https://github.com/dprokop) diff --git a/docs/sources/release-notes/release-notes-7-3-4.md b/docs/sources/release-notes/release-notes-7-3-4.md index ac51f7b17a3..85ceb9dfbda 100644 --- a/docs/sources/release-notes/release-notes-7-3-4.md +++ b/docs/sources/release-notes/release-notes-7-3-4.md @@ -13,5 +13,5 @@ list = false * **Dashboard**: Fixes kiosk state after being redirected to login page and back. [#29273](https://github.com/grafana/grafana/pull/29273), [@torkelo](https://github.com/torkelo) * **InfluxDB**: Update flux library to fix support for boolean label values. [#29310](https://github.com/grafana/grafana/pull/29310), [@ryantxu](https://github.com/ryantxu) * **Security**: Fixes minor security issue with alert notification webhooks that allowed GET & DELETE requests. [#29330](https://github.com/grafana/grafana/pull/29330), [@wbrowne](https://github.com/wbrowne) -* **Table**: Fix incorrect condition for rendering table filter. [#29165](https://github.com/grafana/grafana/pull/29165), [@dprokop](https://github.com/dprokop) +* **Table**: Fixes issues with phantom extra 0 for zero values. [#29165](https://github.com/grafana/grafana/pull/29165), [@dprokop](https://github.com/dprokop) From 107f658229efc3800571f9b92e1ca4d108e81b85 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Fri, 27 Nov 2020 12:05:49 +0100 Subject: [PATCH 007/124] prometheus: Improve IsAPIError's documentation (#29432) Signed-off-by: Arve Knudsen --- pkg/tsdb/prometheus/prometheus.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/tsdb/prometheus/prometheus.go b/pkg/tsdb/prometheus/prometheus.go index a8efe60b179..1eb1da6e8cf 100644 --- a/pkg/tsdb/prometheus/prometheus.go +++ b/pkg/tsdb/prometheus/prometheus.go @@ -218,9 +218,9 @@ func parseResponse(value model.Value, query *PrometheusQuery) (*tsdb.QueryResult return queryRes, nil } +// IsAPIError returns whether err is or wraps a Prometheus error. func IsAPIError(err error) bool { - // Have to use errors.As to compare Prometheus errors, since errors.Is won't work due to Prometheus - // errors being pointers and errors.Is ends up comparing them by pointer address + // Check if the right error type is in err's chain. var e *apiv1.Error return errors.As(err, &e) } From cffc1b13ad9b4cfb547d0980e45af14248d3fb92 Mon Sep 17 00:00:00 2001 From: Carl Bergquist Date: Fri, 27 Nov 2020 14:58:45 +0100 Subject: [PATCH 008/124] adds tracing for all bus calls that passes ctx (#29434) Signed-off-by: bergquist --- pkg/api/alerting.go | 2 +- pkg/bus/bus.go | 7 +++++++ pkg/services/alerting/test_notification.go | 4 ++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/pkg/api/alerting.go b/pkg/api/alerting.go index 1d2817b74a5..155534d41d5 100644 --- a/pkg/api/alerting.go +++ b/pkg/api/alerting.go @@ -433,7 +433,7 @@ func NotificationTest(c *models.ReqContext, dto dtos.NotificationTestCommand) Re SecureSettings: dto.SecureSettings, } - if err := bus.Dispatch(cmd); err != nil { + if err := bus.DispatchCtx(c.Req.Context(), cmd); err != nil { if errors.Is(err, models.ErrSmtpNotEnabled) { return Error(412, err.Error(), err) } diff --git a/pkg/bus/bus.go b/pkg/bus/bus.go index 40849d90d3e..5f44774bb23 100644 --- a/pkg/bus/bus.go +++ b/pkg/bus/bus.go @@ -5,6 +5,8 @@ import ( "errors" "fmt" "reflect" + + "github.com/opentracing/opentracing-go" ) // HandlerFunc defines a handler function interface. @@ -84,6 +86,11 @@ func (b *InProcBus) SetTransactionManager(tm TransactionManager) { func (b *InProcBus) DispatchCtx(ctx context.Context, msg Msg) error { var msgName = reflect.TypeOf(msg).Elem().Name() + span, ctx := opentracing.StartSpanFromContext(ctx, "bus - "+msgName) + defer span.Finish() + + span.SetTag("msg", msgName) + var handler = b.handlersWithCtx[msgName] if handler == nil { return ErrHandlerNotFound diff --git a/pkg/services/alerting/test_notification.go b/pkg/services/alerting/test_notification.go index dd1d7043e1f..d9248e6a1eb 100644 --- a/pkg/services/alerting/test_notification.go +++ b/pkg/services/alerting/test_notification.go @@ -30,10 +30,10 @@ var ( ) func init() { - bus.AddHandler("alerting", handleNotificationTestCommand) + bus.AddHandlerCtx("alerting", handleNotificationTestCommand) } -func handleNotificationTestCommand(cmd *NotificationTestCommand) error { +func handleNotificationTestCommand(ctx context.Context, cmd *NotificationTestCommand) error { notifier := newNotificationService(nil) model := &models.AlertNotification{ From 643f7907539ff52de142c983cb8787327b13821b Mon Sep 17 00:00:00 2001 From: Todd Campbell Date: Fri, 27 Nov 2020 09:09:24 -0800 Subject: [PATCH 009/124] Alerting: Add support for Sensu Go notification channel (#28012) * Add support for Sensu Go notification channel Similar to current support for the older "Core" version of Sensu, this commit add support for the newer version. Closes #19908 Signed-off-by: Todd Campbell * fix linter errors Signed-off-by: Todd Campbell * Apply suggestions from code review PR review suggestions Co-authored-by: Arve Knudsen * Fix no new variables error * Replace convey testing with testify Signed-off-by: Todd Campbell * Wording suggestions Signed-off-by: Todd Campbell * Add docker compose environment for testing/maintenance Signed-off-by: Todd Campbell * Renamed and fixed docker-compose.yaml to work in devenv Signed-off-by: Todd Campbell * Change sensugo web UI port to 3080 so as not to conflict with grafana Signed-off-by: Todd Campbell * Apply suggestions from code review Set the API key as a secure value. Co-authored-by: Sofia Papagiannaki * Add Sensu Go information to notifications doc Signed-off-by: Todd Campbell * Update pkg/services/alerting/notifiers/sensugo.go Co-authored-by: Sofia Papagiannaki * change assert to require for Error/NoError tests Signed-off-by: Todd Campbell Co-authored-by: Arve Knudsen Co-authored-by: Sofia Papagiannaki --- devenv/docker/blocks/sensugo/backend.yml | 63 ++++++ .../docker/blocks/sensugo/docker-compose.yaml | 18 ++ devenv/docker/blocks/sensugo/notes.md | 39 ++++ docs/sources/administration/provisioning.md | 11 + docs/sources/alerting/notifications.md | 5 + pkg/services/alerting/notifiers/sensugo.go | 196 ++++++++++++++++++ .../alerting/notifiers/sensugo_test.go | 57 +++++ public/app/types/alerting.ts | 1 + 8 files changed, 390 insertions(+) create mode 100644 devenv/docker/blocks/sensugo/backend.yml create mode 100644 devenv/docker/blocks/sensugo/docker-compose.yaml create mode 100644 devenv/docker/blocks/sensugo/notes.md create mode 100644 pkg/services/alerting/notifiers/sensugo.go create mode 100644 pkg/services/alerting/notifiers/sensugo_test.go diff --git a/devenv/docker/blocks/sensugo/backend.yml b/devenv/docker/blocks/sensugo/backend.yml new file mode 100644 index 00000000000..1e0047daf3c --- /dev/null +++ b/devenv/docker/blocks/sensugo/backend.yml @@ -0,0 +1,63 @@ +--- +# Sensu backend configuration + +## +# backend configuration +## +state-dir: "/var/lib/sensu/sensu-backend" +#cache-dir: "/var/cache/sensu/sensu-backend" +#config-file: "/etc/sensu/backend.yml" +#debug: false +#deregistration-handler: "example_handler" +#log-level: "warn" # available log levels: panic, fatal, error, warn, info, debug + +## +# agent configuration +## +#agent-host: "[::]" # listen on all IPv4 and IPv6 addresses +#agent-port: 8081 + +## +# api configuration +## +#api-listen-address: "[::]:8080" # listen on all IPv4 and IPv6 addresses +#api-url: "http://localhost:8080" + +## +# dashboard configuration +## +#dashboard-cert-file: "/path/to/ssl/cert.pem" +#dashboard-key-file: "/path/to/ssl/key.pem" +#dashboard-host: "[::]" # listen on all IPv4 and IPv6 addresses +#dashboard-port: 3000 + +## +# ssl configuration +## +#cert-file: "/path/to/ssl/cert.pem" +#key-file: "/path/to/ssl/key.pem" +#trusted-ca-file: "/path/to/trusted-certificate-authorities.pem" +#insecure-skip-tls-verify: false + +## +# store configuration +## +#etcd-advertise-client-urls: "http://localhost:2379" +#etcd-cert-file: "/path/to/ssl/cert.pem" +#etcd-client-cert-auth: false +#etcd-initial-advertise-peer-urls: "http://127.0.0.1:2380" +#etcd-initial-cluster: "default=http://127.0.0.1:2380" +#etcd-initial-cluster-state: "new" # new or existing +#etcd-initial-cluster-token: "sensu" +#etcd-key-file: "/path/to/ssl/key.pem" +#etcd-listen-client-urls: "http://127.0.0.1:2379" +#etcd-listen-peer-urls: "http://127.0.0.1:2380" +#etcd-name: "default" +#etcd-peer-cert-file: "/path/to/ssl/cert.pem" +#etcd-peer-client-cert-auth: false +#etcd-peer-key-file: "/path/to/ssl/key.pem" +#etcd-peer-trusted-ca-file: "/path/to/ssl/key.pem" +#etcd-trusted-ca-file: "/path/to/ssl/key.pem" +#no-embed-etcd: false +#etcd-cipher-suits +# - TLS_EXAMPLE diff --git a/devenv/docker/blocks/sensugo/docker-compose.yaml b/devenv/docker/blocks/sensugo/docker-compose.yaml new file mode 100644 index 00000000000..69de571d35f --- /dev/null +++ b/devenv/docker/blocks/sensugo/docker-compose.yaml @@ -0,0 +1,18 @@ + sensu-backend: + image: sensu/sensu:latest + container_name: sensu-backend + ports: + - "3080:3000" + - "8080:8080" + - "8081:8081" + volumes: + - ./docker/blocks/sensugo/backend.yml:/etc/sensu/backend.yml + - sensu-backend-data:/var/lib/sensu/etcd + environment: + SENSU_BACKEND_CLUSTER_ADMIN_USERNAME: admin + SENSU_BACKEND_CLUSTER_ADMIN_PASSWORD: Password123 + command: "sensu-backend start --log-level info" + +volumes: + sensu-backend-data: {} + diff --git a/devenv/docker/blocks/sensugo/notes.md b/devenv/docker/blocks/sensugo/notes.md new file mode 100644 index 00000000000..f6f0b7e74b8 --- /dev/null +++ b/devenv/docker/blocks/sensugo/notes.md @@ -0,0 +1,39 @@ +# Notes on Sensu Go Docker Block + +The API Key needed to connect to Sensu Go has to be created manually. + +## Create the API Key + +`docker exec -it sensu-backend /bin/ash` + +Configure the `sensuctl` command using the pre-set username and password: + +```bash +sensuctl configure -n --url http://127.0.0.1:8080 --username admin --password 'Password123' --namespace default +``` + +Generate the API Key: + +```bash +sensuctl api-key grant admin +``` + +The output should look similar to this: + +``` +Created: /api/core/v2/apikeys/0a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d +``` + + +## Configuring the notification channel + +### Backend URL + +The Backend URL is the API port (8080) forwarded to the container, it should be +`http://localhost:8080` + +### API Key + +The `0a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d` in the output above is the API Key +to use in configuring the Sensu Go notification channel. + diff --git a/docs/sources/administration/provisioning.md b/docs/sources/administration/provisioning.md index e288425e4c6..98913aa9f60 100644 --- a/docs/sources/administration/provisioning.md +++ b/docs/sources/administration/provisioning.md @@ -479,6 +479,17 @@ The following sections detail the supported settings and secure settings for eac | username | | | password | yes | +#### Alert notification `sensugo` + +| Name | Secure setting | +| -------- | -------------- | +| url | | +| apikey | yes | +| entity | | +| check | | +| handler | | +| namespace | | + #### Alert notification `prometheus-alertmanager` | Name | Secure setting | diff --git a/docs/sources/alerting/notifications.md b/docs/sources/alerting/notifications.md index 7a38e0df53c..058b665fbf9 100644 --- a/docs/sources/alerting/notifications.md +++ b/docs/sources/alerting/notifications.md @@ -64,6 +64,7 @@ OpsGenie | `opsgenie` | yes, external only | yes Prometheus Alertmanager | `prometheus-alertmanager` | yes, external only | yes Pushover | `pushover` | yes | no Sensu | `sensu` | yes, external only | no +[Sensu Go](#sensu-go) | `sensugo` | yes, external only | no [Slack](#slack) | `slack` | yes | no Telegram | `telegram` | yes | no Threema | `threema` | yes, external only | no @@ -212,6 +213,10 @@ Alertmanager handles alerts sent by client applications such as Prometheus serve [Zenduty](https://www.zenduty.com) is an incident alerting and response orchestration platform that not alerts the right teams via SMS, Phone(Voice), Email, Slack, Microsoft Teams and Push notifications(Android/iOS) whenever a Grafana alert is triggered, but also helps you rapidly triage and remediate critical, user impacting incidents. Grafana alert are sent to Zenduty through Grafana's native webhook dispatcher. Refer the Zenduty-Grafana [integration documentation](https://docs.zenduty.com/docs/grafana) for configuring the integration. +### Sensu Go + +[Sensu](https://sensu.io) is a complete solution for monitoring and observability at scale. Sensu Go is designed to give you visibility into everything you care about: traditional server closets, containers, applications, the cloud, and more. Grafana notifications can be sent to Sensu Go as events via the API. This operation requires an API Key. Refer to the [Sensu Go documentation](https://docs.sensu.io/sensu-go/latest/operations/control-access/use-apikeys/#api-key-authentication) for information on creating this key. + ## Enable images in notifications {#external-image-store} Grafana can render the panel associated with the alert rule as a PNG image and include that in the notification. Read more about the requirements and how to configure diff --git a/pkg/services/alerting/notifiers/sensugo.go b/pkg/services/alerting/notifiers/sensugo.go new file mode 100644 index 00000000000..d6f7d6d21a9 --- /dev/null +++ b/pkg/services/alerting/notifiers/sensugo.go @@ -0,0 +1,196 @@ +package notifiers + +import ( + "fmt" + "strconv" + "strings" + + "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/alerting" +) + +func init() { + alerting.RegisterNotifier(&alerting.NotifierPlugin{ + Type: "sensugo", + Name: "Sensu Go", + Description: "Sends HTTP POST request to a Sensu Go API", + Heading: "Sensu Go Settings", + Factory: NewSensuGoNotifier, + Options: []alerting.NotifierOption{ + { + Label: "Backend URL", + Element: alerting.ElementTypeInput, + InputType: alerting.InputTypeText, + Placeholder: "http://sensu-api.local:8080", + PropertyName: "url", + Required: true, + }, + { + Label: "API Key", + Element: alerting.ElementTypeInput, + InputType: alerting.InputTypePassword, + Description: "API Key to auth to Sensu Go backend", + PropertyName: "apikey", + Required: true, + Secure: true, + }, + { + Label: "Proxy entity name", + Element: alerting.ElementTypeInput, + InputType: alerting.InputTypeText, + Description: "If empty, rule name will be used", + PropertyName: "entity", + }, + { + Label: "Check name", + Element: alerting.ElementTypeInput, + InputType: alerting.InputTypeText, + Description: "If empty, rule id will be used", + PropertyName: "check", + }, + { + Label: "Handler", + Element: alerting.ElementTypeInput, + InputType: alerting.InputTypeText, + PropertyName: "handler", + }, + { + Label: "Namespace", + Element: alerting.ElementTypeInput, + InputType: alerting.InputTypeText, + Placeholder: "default", + PropertyName: "namespace", + }, + }, + }) +} + +// NewSensuGoNotifier is the constructor for the Sensu Go Notifier. +func NewSensuGoNotifier(model *models.AlertNotification) (alerting.Notifier, error) { + url := model.Settings.Get("url").MustString() + apikey := model.DecryptedValue("apikey", model.Settings.Get("apikey").MustString()) + + if url == "" { + return nil, alerting.ValidationError{Reason: "Could not find URL property in settings"} + } + if apikey == "" { + return nil, alerting.ValidationError{Reason: "Could not find the API Key property in settings"} + } + + return &SensuGoNotifier{ + NotifierBase: NewNotifierBase(model), + URL: url, + Entity: model.Settings.Get("entity").MustString(), + Check: model.Settings.Get("check").MustString(), + Namespace: model.Settings.Get("namespace").MustString(), + Handler: model.Settings.Get("handler").MustString(), + APIKey: apikey, + log: log.New("alerting.notifier.sensugo"), + }, nil +} + +// SensuGoNotifier is responsible for sending +// alert notifications to Sensu Go. +type SensuGoNotifier struct { + NotifierBase + URL string + Entity string + Check string + Namespace string + Handler string + APIKey string + log log.Logger +} + +// Notify send alert notification to Sensu Go +func (sn *SensuGoNotifier) Notify(evalContext *alerting.EvalContext) error { + sn.log.Info("Sending Sensu Go result") + + var namespace string + + bodyJSON := simplejson.New() + // Sensu Go alerts require an entity and a check. We set it to the user-specified + // value (optional), else we fallback and use the grafana rule anme and ruleID. + if sn.Entity != "" { + bodyJSON.SetPath([]string{"entity", "metadata", "name"}, sn.Entity) + } else { + // Sensu Go alerts cannot have spaces in them + bodyJSON.SetPath([]string{"entity", "metadata", "name"}, strings.ReplaceAll(evalContext.Rule.Name, " ", "_")) + } + if sn.Check != "" { + bodyJSON.SetPath([]string{"check", "metadata", "name"}, sn.Check) + } else { + bodyJSON.SetPath([]string{"check", "metadata", "name"}, "grafana_rule_"+strconv.FormatInt(evalContext.Rule.ID, 10)) + } + // Sensu Go requires the entity in an event specify its namespace. We set it to + // the user-specified value (optional), else we fallback and use default + if sn.Namespace != "" { + bodyJSON.SetPath([]string{"entity", "metadata", "namespace"}, sn.Namespace) + namespace = sn.Namespace + } else { + bodyJSON.SetPath([]string{"entity", "metadata", "namespace"}, "default") + namespace = "default" + } + // Sensu Go needs check output + if evalContext.Rule.Message != "" { + bodyJSON.SetPath([]string{"check", "output"}, evalContext.Rule.Message) + } else { + bodyJSON.SetPath([]string{"check", "output"}, "Grafana Metric Condition Met") + } + // Sensu GO requires that the check portion of the event have an interval + bodyJSON.SetPath([]string{"check", "interval"}, 86400) + + switch evalContext.Rule.State { + case "alerting": + bodyJSON.SetPath([]string{"check", "status"}, 2) + case "no_data": + bodyJSON.SetPath([]string{"check", "status"}, 1) + default: + bodyJSON.SetPath([]string{"check", "status"}, 0) + } + + if sn.Handler != "" { + bodyJSON.SetPath([]string{"check", "handlers"}, []string{sn.Handler}) + } + + ruleURL, err := evalContext.GetRuleURL() + if err == nil { + bodyJSON.Set("ruleUrl", ruleURL) + } + + labels := map[string]string{ + "ruleName": evalContext.Rule.Name, + "ruleId": strconv.FormatInt(evalContext.Rule.ID, 10), + "ruleURL": ruleURL, + } + + if sn.NeedsImage() && evalContext.ImagePublicURL != "" { + labels["imageUrl"] = evalContext.ImagePublicURL + } + + bodyJSON.SetPath([]string{"check", "metadata", "labels"}, labels) + + body, err := bodyJSON.MarshalJSON() + if err != nil { + return err + } + + cmd := &models.SendWebhookSync{ + Url: fmt.Sprintf("%s/api/core/v2/namespaces/%s/events", strings.TrimSuffix(sn.URL, "/"), namespace), + Body: string(body), + HttpMethod: "POST", + HttpHeader: map[string]string{ + "Content-Type": "application/json", + "Authorization": fmt.Sprintf("Key %s", sn.APIKey), + }, + } + if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { + sn.log.Error("Failed to send Sensu Go event", "error", err, "sensugo", sn.Name) + return err + } + + return nil +} diff --git a/pkg/services/alerting/notifiers/sensugo_test.go b/pkg/services/alerting/notifiers/sensugo_test.go new file mode 100644 index 00000000000..fe8e473fbe4 --- /dev/null +++ b/pkg/services/alerting/notifiers/sensugo_test.go @@ -0,0 +1,57 @@ +package notifiers + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/models" +) + +func TestSensuGoNotifier(t *testing.T) { + json := `{ }` + + settingsJSON, err := simplejson.NewJson([]byte(json)) + require.NoError(t, err) + model := &models.AlertNotification{ + Name: "Sensu Go", + Type: "sensugo", + Settings: settingsJSON, + } + + _, err = NewSensuGoNotifier(model) + require.Error(t, err) + + json = ` + { + "url": "http://sensu-api.example.com:8080", + "entity": "grafana_instance_01", + "check": "grafana_rule_0", + "namespace": "default", + "handler": "myhandler", + "apikey": "abcdef0123456789abcdef" + }` + + settingsJSON, err = simplejson.NewJson([]byte(json)) + require.NoError(t, err) + model = &models.AlertNotification{ + Name: "Sensu Go", + Type: "sensugo", + Settings: settingsJSON, + } + + not, err := NewSensuGoNotifier(model) + require.NoError(t, err) + sensuGoNotifier := not.(*SensuGoNotifier) + + assert.Equal(t, "Sensu Go", sensuGoNotifier.Name) + assert.Equal(t, "sensugo", sensuGoNotifier.Type) + assert.Equal(t, "http://sensu-api.example.com:8080", sensuGoNotifier.URL) + assert.Equal(t, "grafana_instance_01", sensuGoNotifier.Entity) + assert.Equal(t, "grafana_rule_0", sensuGoNotifier.Check) + assert.Equal(t, "default", sensuGoNotifier.Namespace) + assert.Equal(t, "myhandler", sensuGoNotifier.Handler) + assert.Equal(t, "abcdef0123456789abcdef", sensuGoNotifier.APIKey) +} diff --git a/public/app/types/alerting.ts b/public/app/types/alerting.ts index 010f84f80d6..d22a13ecb7f 100644 --- a/public/app/types/alerting.ts +++ b/public/app/types/alerting.ts @@ -40,6 +40,7 @@ export type NotifierType = | 'hipchat' | 'email' | 'sensu' + | 'sensugo' | 'googlechat' | 'threema' | 'teams' From 61d6dcf9ce9ce5f15ea16b04db4ee54526ba4f33 Mon Sep 17 00:00:00 2001 From: Leon Sorokin Date: Fri, 27 Nov 2020 17:06:57 -0600 Subject: [PATCH 010/124] GraphNG: use uPlot's native ms support (#29445) --- packages/grafana-ui/package.json | 2 +- .../src/components/uPlot/config/UPlotAxisBuilder.ts | 8 +++++--- packages/grafana-ui/src/components/uPlot/hooks.ts | 3 ++- yarn.lock | 8 ++++---- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index fc805f88650..6d42eb76ff2 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -71,7 +71,7 @@ "react-transition-group": "4.4.1", "slate": "0.47.8", "tinycolor2": "1.4.1", - "uplot": "1.4.4" + "uplot": "1.4.6" }, "devDependencies": { "@rollup/plugin-commonjs": "11.0.2", diff --git a/packages/grafana-ui/src/components/uPlot/config/UPlotAxisBuilder.ts b/packages/grafana-ui/src/components/uPlot/config/UPlotAxisBuilder.ts index 8386284503f..a5cfdd52125 100644 --- a/packages/grafana-ui/src/components/uPlot/config/UPlotAxisBuilder.ts +++ b/packages/grafana-ui/src/components/uPlot/config/UPlotAxisBuilder.ts @@ -32,6 +32,7 @@ export class UPlotAxisBuilder extends PlotConfigBuilder { timeZone, theme, } = this.props; + const gridColor = theme.isDark ? theme.palette.gray25 : theme.palette.gray90; let config: Axis = { @@ -82,7 +83,7 @@ function calculateSpace(self: uPlot, axisIdx: number, scaleMin: number, scaleMax // For x-axis (bottom) we need bigger spacing between labels if (axis.side === 2) { - return 60; + return 50; } return 30; @@ -114,11 +115,12 @@ function calculateAxisSize(self: uPlot, values: string[], axisIdx: number) { function formatTime(self: uPlot, splits: number[], axisIdx: number, foundSpace: number, foundIncr: number): string[] { const timeZone = (self.axes[axisIdx] as any).timeZone; const scale = self.scales.x; - const range = (scale?.max ?? 0) - (scale?.min ?? 0) / 1000; + const range = ((scale?.max ?? 0) - (scale?.min ?? 0)) / 1e3; const oneDay = 86400; const oneYear = 31536000; - foundIncr = foundIncr / 1000; + foundIncr /= 1e3; + let format = systemDateFormats.interval.minute; if (foundIncr < 1) { diff --git a/packages/grafana-ui/src/components/uPlot/hooks.ts b/packages/grafana-ui/src/components/uPlot/hooks.ts index a4df275efbd..5a46c8f7e70 100644 --- a/packages/grafana-ui/src/components/uPlot/hooks.ts +++ b/packages/grafana-ui/src/components/uPlot/hooks.ts @@ -116,7 +116,7 @@ export const usePlotConfig = (width: number, height: number, timeZone: TimeZone, const tz = getTimeZoneInfo(timeZone, Date.now())?.ianaName; if (tz) { - fmt = (ts: number) => uPlot.tzDate(new Date(ts * 1e3), tz); + fmt = (ts: number) => uPlot.tzDate(new Date(ts), tz); } return fmt; @@ -130,6 +130,7 @@ export const usePlotConfig = (width: number, height: number, timeZone: TimeZone, ...DEFAULT_PLOT_CONFIG, width, height, + ms: 1, plugins: Object.entries(plugins).map(p => ({ hooks: p[1].hooks, })), diff --git a/yarn.lock b/yarn.lock index 97c2e330812..96873718f25 100644 --- a/yarn.lock +++ b/yarn.lock @@ -27296,10 +27296,10 @@ update-notifier@^2.5.0: semver-diff "^2.0.0" xdg-basedir "^3.0.0" -uplot@1.4.4: - version "1.4.4" - resolved "https://registry.yarnpkg.com/uplot/-/uplot-1.4.4.tgz#ab30da26b1e2d432df5bc43389e70399787f0448" - integrity sha512-vgV84+by3fGTU4bdpffSvA9FX8ide6MsmlBzOASPDdZCquXmCA+T2qodeNdnBen+7YOeqD9H91epVnF0dQgVKw== +uplot@1.4.6: + version "1.4.6" + resolved "https://registry.yarnpkg.com/uplot/-/uplot-1.4.6.tgz#52b6812c06b8d3cb89d4a8d053f45415cb5d9a13" + integrity sha512-nw3LdjLFhAqKQF/Rv7QjZICVnjJemOQVj2L3b7889gHBrnC1LwltkzTcawDcbMe6pxo++0KVev9xSC0YCw+m5w== upper-case-first@^1.1.0, upper-case-first@^1.1.2: version "1.1.2" From 3f6a2c29c910e4de3498d5150b4cd32089077697 Mon Sep 17 00:00:00 2001 From: Jack Westbrook Date: Sun, 29 Nov 2020 08:05:23 +0100 Subject: [PATCH 011/124] Chore: bump grafana-ui rollup dependencies (#29315) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(grafana-ui): bump rollup dependencies * update yarn lock Co-authored-by: Torkel Ödegaard Co-authored-by: Drone --- packages/grafana-ui/package.json | 16 +-- packages/grafana-ui/rollup.config.ts | 44 ------- yarn.lock | 184 ++++++++++++++++++++++++--- 3 files changed, 177 insertions(+), 67 deletions(-) diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 6d42eb76ff2..1d4a997a495 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -74,9 +74,9 @@ "uplot": "1.4.6" }, "devDependencies": { - "@rollup/plugin-commonjs": "11.0.2", - "@rollup/plugin-image": "2.0.4", - "@rollup/plugin-node-resolve": "7.1.1", + "@rollup/plugin-commonjs": "16.0.0", + "@rollup/plugin-image": "2.0.5", + "@rollup/plugin-node-resolve": "10.0.0", "@storybook/addon-essentials": "6.1.2", "@storybook/addon-controls": "6.1.2", "@storybook/addon-knobs": "6.1.2", @@ -102,11 +102,11 @@ "react-docgen-typescript-loader": "3.7.2", "react-test-renderer": "16.13.1", "react-is": "16.8.0", - "rollup": "2.0.6", - "rollup-plugin-sourcemaps": "0.5.0", - "rollup-plugin-terser": "5.3.0", - "rollup-plugin-typescript2": "0.26.0", - "rollup-plugin-visualizer": "3.3.1", + "rollup": "2.33.3", + "rollup-plugin-sourcemaps": "0.6.3", + "rollup-plugin-terser": "7.0.2", + "rollup-plugin-typescript2": "0.29.0", + "rollup-plugin-visualizer": "4.2.0", "storybook-dark-mode": "1.0.3", "ts-loader": "8.0.11", "typescript": "4.0.2", diff --git a/packages/grafana-ui/rollup.config.ts b/packages/grafana-ui/rollup.config.ts index bbe55cb54c5..eabac2ba334 100644 --- a/packages/grafana-ui/rollup.config.ts +++ b/packages/grafana-ui/rollup.config.ts @@ -1,7 +1,6 @@ import resolve from '@rollup/plugin-node-resolve'; import commonjs from '@rollup/plugin-commonjs'; import image from '@rollup/plugin-image'; -// import sourceMaps from 'rollup-plugin-sourcemaps'; import { terser } from 'rollup-plugin-terser'; const pkg = require('./package.json'); @@ -42,51 +41,8 @@ const buildCjsPackage = ({ env }) => { include: /node_modules/, // When 'rollup-plugin-commonjs' fails to properly convert the CommonJS modules to ES6 one has to manually name the exports // https://github.com/rollup/rollup-plugin-commonjs#custom-named-exports - namedExports: { - 'node_modules/lodash/lodash.js': [ - 'flatten', - 'find', - 'upperFirst', - 'debounce', - 'isNil', - 'isNumber', - 'flattenDeep', - 'map', - 'chunk', - 'sortBy', - 'uniqueId', - 'zip', - 'omit', - 'isString', - 'isEmpty', - 'toLower', - ], - '../../node_modules/react-color/lib/components/common': ['Saturation', 'Hue', 'Alpha'], - '../../node_modules/immutable/dist/immutable.js': [ - 'Record', - 'Set', - 'Map', - 'List', - 'OrderedSet', - 'is', - 'Stack', - ], - 'node_modules/immutable/dist/immutable.js': ['Record', 'Set', 'Map', 'List', 'OrderedSet', 'is', 'Stack'], - '../../node_modules/esrever/esrever.js': ['reverse'], - '../../node_modules/react-table/index.js': [ - 'useTable', - 'useSortBy', - 'useBlockLayout', - 'Cell', - 'useResizeColumns', - 'useAbsoluteLayout', - 'useFilters', - ], - '../../node_modules/react-is/index.js': ['isMemo'], - }, }), resolve(), - // sourceMaps(), image(), env === 'production' && terser(), ], diff --git a/yarn.lock b/yarn.lock index 96873718f25..c5cd032d165 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5263,10 +5263,23 @@ magic-string "^0.25.2" resolve "^1.11.0" -"@rollup/plugin-image@2.0.4": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@rollup/plugin-image/-/plugin-image-2.0.4.tgz#a9b22737684986d2299b7803b82bf87af47d41de" - integrity sha512-W23bfKBZw/k6MXks0PInxazHhbfiN5u2ySPMHFVPt2jgmACNewlV3kUR6MvpE9YEliETU5ygGetsj5SDTUW4HQ== +"@rollup/plugin-commonjs@16.0.0": + version "16.0.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-16.0.0.tgz#169004d56cd0f0a1d0f35915d31a036b0efe281f" + integrity sha512-LuNyypCP3msCGVQJ7ki8PqYdpjfEkE/xtFa5DqlF+7IBD0JsfMZ87C58heSwIMint58sAUZbt3ITqOmdQv/dXw== + dependencies: + "@rollup/pluginutils" "^3.1.0" + commondir "^1.0.1" + estree-walker "^2.0.1" + glob "^7.1.6" + is-reference "^1.2.1" + magic-string "^0.25.7" + resolve "^1.17.0" + +"@rollup/plugin-image@2.0.5": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@rollup/plugin-image/-/plugin-image-2.0.5.tgz#07859a69f5fe592643c2ad3ef59ae8a5c268a7ef" + integrity sha512-R+yGLJjLN1won2JlZPZmdlyZGLZwwsW8V/RYu3mTcRq8Aqd9GC2fo4Zi892bFteM5LolfbpxK8Y9QQcAhbBwSQ== dependencies: "@rollup/pluginutils" "^3.0.4" mini-svg-data-uri "^1.1.3" @@ -5278,6 +5291,18 @@ dependencies: "@rollup/pluginutils" "^3.0.8" +"@rollup/plugin-node-resolve@10.0.0": + version "10.0.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-10.0.0.tgz#44064a2b98df7530e66acf8941ff262fc9b4ead8" + integrity sha512-sNijGta8fqzwA1VwUEtTvWCx2E7qC70NMsDh4ZG13byAXYigBNZMxALhKUSycBks5gupJdq0lFrKumFrRZ8H3A== + dependencies: + "@rollup/pluginutils" "^3.1.0" + "@types/resolve" "1.17.1" + builtin-modules "^3.1.0" + deepmerge "^4.2.2" + is-module "^1.0.0" + resolve "^1.17.0" + "@rollup/plugin-node-resolve@7.1.1": version "7.1.1" resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.1.tgz#8c6e59c4b28baf9d223028d0e450e06a485bb2b7" @@ -5314,6 +5339,15 @@ estree-walker "^1.0.1" micromatch "^4.0.2" +"@rollup/pluginutils@^3.0.9", "@rollup/pluginutils@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" + integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== + dependencies: + "@types/estree" "0.0.39" + estree-walker "^1.0.1" + picomatch "^2.2.2" + "@rtsao/plugin-proposal-class-properties@7.0.1-patch.1": version "7.0.1-patch.1" resolved "https://registry.yarnpkg.com/@rtsao/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.0.1-patch.1.tgz#ac0f758a25a85b5be0e70a25f6e5b58103c58391" @@ -7338,6 +7372,13 @@ dependencies: "@types/node" "*" +"@types/resolve@1.17.1": + version "1.17.1" + resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6" + integrity sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw== + dependencies: + "@types/node" "*" + "@types/rimraf@^2.0.3": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-2.0.3.tgz#0199a46af106729ba14213fda7b981278d8c84f2" @@ -10546,6 +10587,15 @@ cliui@^6.0.0: strip-ansi "^6.0.0" wrap-ansi "^6.2.0" +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + clone-deep@^0.2.4: version "0.2.4" resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-0.2.4.tgz#4e73dd09e9fb971cc38670c5dced9c1896481cc6" @@ -13514,6 +13564,11 @@ estree-walker@^1.0.1: resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== +estree-walker@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.1.tgz#f8e030fb21cefa183b44b7ad516b747434e7a3e0" + integrity sha512-tF0hv+Yi2Ot1cwj9eYHtxC0jB9bmjacjQs6ZBTj82H8JwUywFuc+7E83NWfNMwHXZc11mjfFcVXPe9gEP4B8dg== + esutils@^2.0.0, esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -14669,7 +14724,7 @@ get-caller-file@^1.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== -get-caller-file@^2.0.1: +get-caller-file@^2.0.1, get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== @@ -16781,6 +16836,13 @@ is-reference@^1.1.2: dependencies: "@types/estree" "0.0.39" +is-reference@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7" + integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ== + dependencies: + "@types/estree" "*" + is-regex@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" @@ -19012,7 +19074,7 @@ macos-release@^2.2.0: resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.3.0.tgz#eb1930b036c0800adebccd5f17bc4c12de8bb71f" integrity sha512-OHhSbtcviqMPt7yfw5ef5aghS2jzFVKEFyCJndQt2YpSQ9qRVSEv2axSJI1paVThEu+FFGs584h/1YhxjVqajA== -magic-string@^0.25.2: +magic-string@^0.25.2, magic-string@^0.25.7: version "0.25.7" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== @@ -19863,6 +19925,11 @@ nanoid@^2.1.6: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.1.11.tgz#ec24b8a758d591561531b4176a01e3ab4f0f0280" integrity sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA== +nanoid@^3.0.1: + version "3.1.18" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.18.tgz#0680db22ab01c372e89209f5d18283d98de3e96d" + integrity sha512-rndlDjbbHbcV3xi+R2fpJ+PbGMdfBxz5v1fATIQFq0DP64FsicQdwnKLy47K4kZHdRpmQXtz24eGsxQqamzYTA== + nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" @@ -24315,6 +24382,13 @@ resolve@1.15.1, resolve@^1.1.7: dependencies: path-parse "^1.0.6" +resolve@1.17.0, resolve@^1.17.0, resolve@~1.17.0: + version "1.17.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" + integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== + dependencies: + path-parse "^1.0.6" + resolve@^1.1.6, resolve@^1.10.0, resolve@^1.11.0, resolve@^1.3.2: version "1.12.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6" @@ -24337,13 +24411,6 @@ resolve@^1.14.2: dependencies: path-parse "^1.0.6" -resolve@^1.17.0, resolve@~1.17.0: - version "1.17.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" - integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== - dependencies: - path-parse "^1.0.6" - resolve@~1.11.1: version "1.11.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.11.1.tgz#ea10d8110376982fef578df8fc30b9ac30a07a3e" @@ -24491,6 +24558,14 @@ rollup-plugin-sourcemaps@0.5.0: "@rollup/pluginutils" "^3.0.1" source-map-resolve "^0.5.3" +rollup-plugin-sourcemaps@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/rollup-plugin-sourcemaps/-/rollup-plugin-sourcemaps-0.6.3.tgz#bf93913ffe056e414419607f1d02780d7ece84ed" + integrity sha512-paFu+nT1xvuO1tPFYXGe+XnQvg4Hjqv/eIhG8i5EspfYYPBKL57X7iVbfv55aNVASg3dzWvES9dmWsL2KhfByw== + dependencies: + "@rollup/pluginutils" "^3.0.9" + source-map-resolve "^0.6.0" + rollup-plugin-terser@5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-5.3.0.tgz#9c0dd33d5771df9630cd027d6a2559187f65885e" @@ -24502,6 +24577,16 @@ rollup-plugin-terser@5.3.0: serialize-javascript "^2.1.2" terser "^4.6.2" +rollup-plugin-terser@7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz#e8fbba4869981b2dc35ae7e8a502d5c6c04d324d" + integrity sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ== + dependencies: + "@babel/code-frame" "^7.10.4" + jest-worker "^26.2.1" + serialize-javascript "^4.0.0" + terser "^5.0.0" + rollup-plugin-typescript2@0.26.0: version "0.26.0" resolved "https://registry.yarnpkg.com/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.26.0.tgz#cee2b44d51d9623686656d76dc30a73c4de91672" @@ -24513,6 +24598,17 @@ rollup-plugin-typescript2@0.26.0: rollup-pluginutils "2.8.2" tslib "1.10.0" +rollup-plugin-typescript2@0.29.0: + version "0.29.0" + resolved "https://registry.yarnpkg.com/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.29.0.tgz#b7ad83f5241dbc5bdf1e98d9c3fca005ffe39e1a" + integrity sha512-YytahBSZCIjn/elFugEGQR5qTsVhxhUwGZIsA9TmrSsC88qroGo65O5HZP/TTArH2dm0vUmYWhKchhwi2wL9bw== + dependencies: + "@rollup/pluginutils" "^3.1.0" + find-cache-dir "^3.3.1" + fs-extra "8.1.0" + resolve "1.17.0" + tslib "2.0.1" + rollup-plugin-visualizer@3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/rollup-plugin-visualizer/-/rollup-plugin-visualizer-3.3.1.tgz#fac77899559098d28b1b02d90243e683847f6994" @@ -24525,6 +24621,16 @@ rollup-plugin-visualizer@3.3.1: source-map "^0.7.3" yargs "^15.0.0" +rollup-plugin-visualizer@4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/rollup-plugin-visualizer/-/rollup-plugin-visualizer-4.2.0.tgz#2fbdd9d11d22bf231782b6b56a10b5d30a94a01c" + integrity sha512-xjfvoK4x0G7lBT3toMx8K/9tkCEWhRehnSJnn+PLY3Hjk8sNvyo407b68Cd3hdV9j42xcb8HXt0ZrmRi5NWeaQ== + dependencies: + nanoid "^3.0.1" + open "^7.0.3" + source-map "^0.7.3" + yargs "^16.0.3" + rollup-pluginutils@2.8.2, rollup-pluginutils@^2.8.2: version "2.8.2" resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e" @@ -24539,6 +24645,13 @@ rollup@2.0.6: optionalDependencies: fsevents "~2.1.2" +rollup@2.33.3: + version "2.33.3" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.33.3.tgz#ae72ce31f992b09a580072951bfea76e9df17342" + integrity sha512-RpayhPTe4Gu/uFGCmk7Gp5Z9Qic2VsqZ040G+KZZvsZYdcuWaJg678JeDJJvJeEQXminu24a2au+y92CUWVd+w== + optionalDependencies: + fsevents "~2.1.2" + rollup@^0.25.8: version "0.25.8" resolved "https://registry.yarnpkg.com/rollup/-/rollup-0.25.8.tgz#bf6ce83b87510d163446eeaa577ed6a6fc5835e0" @@ -25391,7 +25504,7 @@ source-map-support@^0.3.2: dependencies: source-map "0.1.32" -source-map-support@^0.5.16: +source-map-support@^0.5.16, source-map-support@~0.5.19: version "0.5.19" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== @@ -25441,7 +25554,7 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@^0.7.3: +source-map@^0.7.3, source-map@~0.7.2: version "0.7.3" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== @@ -26413,6 +26526,15 @@ terser@^4.6.3, terser@^4.8.0: source-map "~0.6.1" source-map-support "~0.5.12" +terser@^5.0.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.5.0.tgz#1406fcb4d4bc517add3b22a9694284c040e33448" + integrity sha512-eopt1Gf7/AQyPhpygdKePTzaet31TvQxXvrf7xYUvD/d8qkCJm4SKPDzu+GHK5ZaYTn8rvttfqaZc3swK21e5g== + dependencies: + commander "^2.20.0" + source-map "~0.7.2" + source-map-support "~0.5.19" + test-exclude@^5.2.3: version "5.2.3" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-5.2.3.tgz#c3d3e1e311eb7ee405e092dac10aefd09091eac0" @@ -28099,6 +28221,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -28256,6 +28387,11 @@ xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== +y18n@^5.0.5: + version "5.0.5" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.5.tgz#8769ec08d03b1ea2df2500acef561743bbb9ab18" + integrity sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg== + yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" @@ -28325,6 +28461,11 @@ yargs-parser@^18.1.0: camelcase "^5.0.0" decamelize "^1.2.0" +yargs-parser@^20.2.2: + version "20.2.4" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" + integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== + yargs-unparser@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz#ef25c2c769ff6bd09e4b0f9d7c605fb27846ea9f" @@ -28453,6 +28594,19 @@ yargs@^15.4.1: y18n "^4.0.0" yargs-parser "^18.1.2" +yargs@^16.0.3: + version "16.1.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.1.1.tgz#5a4a095bd1ca806b0a50d0c03611d38034d219a1" + integrity sha512-hAD1RcFP/wfgfxgMVswPE+z3tlPFtxG8/yWUrG2i17sTWGCGqWnxKcLTF4cUKDUK8fzokwsmO9H0TDkRbMHy8w== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + yargs@~3.10.0: version "3.10.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" From 98d753d36d71238b0ee598d43986e12daed07d9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sun, 29 Nov 2020 08:32:26 +0100 Subject: [PATCH 012/124] Jest: Upgrade to latest (#29450) --- package.json | 10 +- packages/grafana-data/package.json | 2 +- packages/grafana-runtime/package.json | 2 +- packages/grafana-toolkit/package.json | 10 +- packages/grafana-ui/package.json | 2 +- yarn.lock | 1639 +++++-------------------- 6 files changed, 351 insertions(+), 1314 deletions(-) diff --git a/package.json b/package.json index 8c6df42e2c0..7d4ccbbf00b 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "@types/enzyme-adapter-react-16": "1.0.6", "@types/file-saver": "2.0.1", "@types/is-hotkey": "0.1.1", - "@types/jest": "26.0.0", + "@types/jest": "26.0.15", "@types/jquery": "3.3.38", "@types/lodash": "4.14.149", "@types/lru-cache": "^5.1.0", @@ -124,7 +124,7 @@ "autoprefixer": "9.7.4", "axios": "0.19.2", "babel-core": "7.0.0-bridge.0", - "babel-jest": "26.0.1", + "babel-jest": "26.6.3", "babel-loader": "8.0.6", "babel-plugin-angularjs-annotate": "0.10.0", "clean-webpack-plugin": "3.0.0", @@ -162,8 +162,8 @@ "html-webpack-harddisk-plugin": "1.0.1", "html-webpack-plugin": "3.2.0", "husky": "4.2.1", - "jest": "26.4.2", - "jest-canvas-mock": "2.2.0", + "jest": "26.6.3", + "jest-canvas-mock": "2.3.0", "jest-date-mock": "1.0.8", "jest-matcher-utils": "26.0.0", "lerna": "^3.20.2", @@ -193,7 +193,7 @@ "sinon": "8.1.1", "style-loader": "1.1.3", "terser-webpack-plugin": "2.3.5", - "ts-jest": "26.3.0", + "ts-jest": "26.4.4", "ts-node": "8.8.1", "tslib": "2.0.1", "typescript": "4.0.2", diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json index c48d91ba395..9077511ba08 100644 --- a/packages/grafana-data/package.json +++ b/packages/grafana-data/package.json @@ -37,7 +37,7 @@ "@rollup/plugin-json": "4.0.3", "@rollup/plugin-node-resolve": "7.1.1", "@types/braintree__sanitize-url": "4.0.0", - "@types/jest": "26.0.0", + "@types/jest": "26.0.15", "@types/jquery": "3.3.38", "@types/lodash": "4.14.123", "@types/marked": "1.1.0", diff --git a/packages/grafana-runtime/package.json b/packages/grafana-runtime/package.json index cd441ceb0ce..4082933318f 100644 --- a/packages/grafana-runtime/package.json +++ b/packages/grafana-runtime/package.json @@ -31,7 +31,7 @@ "@grafana/tsconfig": "^1.0.0-rc1", "@rollup/plugin-commonjs": "11.0.2", "@rollup/plugin-node-resolve": "7.1.1", - "@types/jest": "26.0.0", + "@types/jest": "26.0.15", "@types/rollup-plugin-visualizer": "2.6.0", "@types/systemjs": "^0.20.6", "lodash": "4.17.19", diff --git a/packages/grafana-toolkit/package.json b/packages/grafana-toolkit/package.json index 05cee5f7d5f..d6aeff9d55e 100644 --- a/packages/grafana-toolkit/package.json +++ b/packages/grafana-toolkit/package.json @@ -37,7 +37,7 @@ "@types/expect-puppeteer": "3.3.1", "@types/fs-extra": "^8.1.0", "@types/inquirer": "^6.0.3", - "@types/jest": "26.0.0", + "@types/jest": "26.0.15", "@types/node": "^12.0.4", "@types/prettier": "^1.16.4", "@types/puppeteer-core": "1.9.0", @@ -49,7 +49,7 @@ "@typescript-eslint/eslint-plugin": "4.0.1", "@typescript-eslint/parser": "4.0.1", "axios": "0.19.2", - "babel-jest": "24.8.0", + "babel-jest": "26.6.3", "babel-loader": "8.1.0", "babel-plugin-angularjs-annotate": "0.10.0", "chalk": "^2.4.2", @@ -72,8 +72,8 @@ "html-loader": "0.5.5", "html-webpack-plugin": "^3.2.0", "inquirer": "^6.3.1", - "jest": "^25", - "jest-canvas-mock": "2.1.2", + "jest": "26.6.3", + "jest-canvas-mock": "2.3.0", "jest-coverage-badges": "^1.1.2", "jest-environment-jsdom-fifteen": "^1.0.2", "jest-junit": "^6.4.0", @@ -101,7 +101,7 @@ "simple-git": "^1.132.0", "style-loader": "1.1.3", "terser-webpack-plugin": "2.3.5", - "ts-jest": "26.3.0", + "ts-jest": "26.4.4", "ts-loader": "6.2.1", "ts-node": "8.8.1", "tslib": "2.0.1", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 1d4a997a495..1653dd92b0f 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -86,7 +86,7 @@ "@types/classnames": "2.2.7", "@types/common-tags": "^1.8.0", "@types/d3": "5.7.2", - "@types/jest": "26.0.0", + "@types/jest": "26.0.15", "@types/jquery": "3.3.38", "@types/lodash": "4.14.123", "@types/node": "10.14.1", diff --git a/yarn.lock b/yarn.lock index c5cd032d165..45aa0f33292 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1588,13 +1588,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-object-rest-spread@^7.0.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz#3b7a3e733510c57e820b9142a6579ac8b0dfad2e" - integrity sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-syntax-object-rest-spread@^7.7.4": version "7.7.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.7.4.tgz#47cf220d19d6d0d7b154304701f468fc1cc6ff46" @@ -3722,137 +3715,46 @@ chalk "^2.0.1" slash "^2.0.0" -"@jest/console@^25.5.0": - version "25.5.0" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-25.5.0.tgz#770800799d510f37329c508a9edd0b7b447d9abb" - integrity sha512-T48kZa6MK1Y6k4b89sexwmSF4YLeZS/Udqg3Jj3jG/cHH+N/sLFCEoXEDMOKugJQ9FxPN1osxIknvKkxt6MKyw== +"@jest/console@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-26.6.2.tgz#4e04bc464014358b03ab4937805ee36a0aeb98f2" + integrity sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g== dependencies: - "@jest/types" "^25.5.0" - chalk "^3.0.0" - jest-message-util "^25.5.0" - jest-util "^25.5.0" - slash "^3.0.0" - -"@jest/console@^26.3.0": - version "26.3.0" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-26.3.0.tgz#ed04063efb280c88ba87388b6f16427c0a85c856" - integrity sha512-/5Pn6sJev0nPUcAdpJHMVIsA8sKizL2ZkcKPE5+dJrCccks7tcM7c9wbgHudBJbxXLoTbqsHkG1Dofoem4F09w== - dependencies: - "@jest/types" "^26.3.0" + "@jest/types" "^26.6.2" "@types/node" "*" chalk "^4.0.0" - jest-message-util "^26.3.0" - jest-util "^26.3.0" + jest-message-util "^26.6.2" + jest-util "^26.6.2" slash "^3.0.0" -"@jest/console@^26.5.2": - version "26.5.2" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-26.5.2.tgz#94fc4865b1abed7c352b5e21e6c57be4b95604a6" - integrity sha512-lJELzKINpF1v74DXHbCRIkQ/+nUV1M+ntj+X1J8LxCgpmJZjfLmhFejiMSbjjD66fayxl5Z06tbs3HMyuik6rw== +"@jest/core@^26.6.3": + version "26.6.3" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-26.6.3.tgz#7639fcb3833d748a4656ada54bde193051e45fad" + integrity sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw== dependencies: - "@jest/types" "^26.5.2" - "@types/node" "*" - chalk "^4.0.0" - jest-message-util "^26.5.2" - jest-util "^26.5.2" - slash "^3.0.0" - -"@jest/core@^25.5.4": - version "25.5.4" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-25.5.4.tgz#3ef7412f7339210f003cdf36646bbca786efe7b4" - integrity sha512-3uSo7laYxF00Dg/DMgbn4xMJKmDdWvZnf89n8Xj/5/AeQ2dOQmn6b6Hkj/MleyzZWXpwv+WSdYWl4cLsy2JsoA== - dependencies: - "@jest/console" "^25.5.0" - "@jest/reporters" "^25.5.1" - "@jest/test-result" "^25.5.0" - "@jest/transform" "^25.5.1" - "@jest/types" "^25.5.0" - ansi-escapes "^4.2.1" - chalk "^3.0.0" - exit "^0.1.2" - graceful-fs "^4.2.4" - jest-changed-files "^25.5.0" - jest-config "^25.5.4" - jest-haste-map "^25.5.1" - jest-message-util "^25.5.0" - jest-regex-util "^25.2.6" - jest-resolve "^25.5.1" - jest-resolve-dependencies "^25.5.4" - jest-runner "^25.5.4" - jest-runtime "^25.5.4" - jest-snapshot "^25.5.1" - jest-util "^25.5.0" - jest-validate "^25.5.0" - jest-watcher "^25.5.0" - micromatch "^4.0.2" - p-each-series "^2.1.0" - realpath-native "^2.0.0" - rimraf "^3.0.0" - slash "^3.0.0" - strip-ansi "^6.0.0" - -"@jest/core@^26.4.2": - version "26.4.2" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-26.4.2.tgz#85d0894f31ac29b5bab07aa86806d03dd3d33edc" - integrity sha512-sDva7YkeNprxJfepOctzS8cAk9TOekldh+5FhVuXS40+94SHbiicRO1VV2tSoRtgIo+POs/Cdyf8p76vPTd6dg== - dependencies: - "@jest/console" "^26.3.0" - "@jest/reporters" "^26.4.1" - "@jest/test-result" "^26.3.0" - "@jest/transform" "^26.3.0" - "@jest/types" "^26.3.0" + "@jest/console" "^26.6.2" + "@jest/reporters" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" exit "^0.1.2" graceful-fs "^4.2.4" - jest-changed-files "^26.3.0" - jest-config "^26.4.2" - jest-haste-map "^26.3.0" - jest-message-util "^26.3.0" + jest-changed-files "^26.6.2" + jest-config "^26.6.3" + jest-haste-map "^26.6.2" + jest-message-util "^26.6.2" jest-regex-util "^26.0.0" - jest-resolve "^26.4.0" - jest-resolve-dependencies "^26.4.2" - jest-runner "^26.4.2" - jest-runtime "^26.4.2" - jest-snapshot "^26.4.2" - jest-util "^26.3.0" - jest-validate "^26.4.2" - jest-watcher "^26.3.0" - micromatch "^4.0.2" - p-each-series "^2.1.0" - rimraf "^3.0.0" - slash "^3.0.0" - strip-ansi "^6.0.0" - -"@jest/core@^26.5.2": - version "26.5.2" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-26.5.2.tgz#e39f14676f4ba4632ecabfdc374071ab22131f22" - integrity sha512-LLTo1LQMg7eJjG/+P1NYqFof2B25EV1EqzD5FonklihG4UJKiK2JBIvWonunws6W7e+DhNLoFD+g05tCY03eyA== - dependencies: - "@jest/console" "^26.5.2" - "@jest/reporters" "^26.5.2" - "@jest/test-result" "^26.5.2" - "@jest/transform" "^26.5.2" - "@jest/types" "^26.5.2" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - exit "^0.1.2" - graceful-fs "^4.2.4" - jest-changed-files "^26.5.2" - jest-config "^26.5.2" - jest-haste-map "^26.5.2" - jest-message-util "^26.5.2" - jest-regex-util "^26.0.0" - jest-resolve "^26.5.2" - jest-resolve-dependencies "^26.5.2" - jest-runner "^26.5.2" - jest-runtime "^26.5.2" - jest-snapshot "^26.5.2" - jest-util "^26.5.2" - jest-validate "^26.5.2" - jest-watcher "^26.5.2" + jest-resolve "^26.6.2" + jest-resolve-dependencies "^26.6.3" + jest-runner "^26.6.3" + jest-runtime "^26.6.3" + jest-snapshot "^26.6.2" + jest-util "^26.6.2" + jest-validate "^26.6.2" + jest-watcher "^26.6.2" micromatch "^4.0.2" p-each-series "^2.1.0" rimraf "^3.0.0" @@ -3869,24 +3771,15 @@ "@jest/types" "^24.9.0" jest-mock "^24.9.0" -"@jest/environment@^25.5.0": - version "25.5.0" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-25.5.0.tgz#aa33b0c21a716c65686638e7ef816c0e3a0c7b37" - integrity sha512-U2VXPEqL07E/V7pSZMSQCvV5Ea4lqOlT+0ZFijl/i316cRMHvZ4qC+jBdryd+lmRetjQo0YIQr6cVPNxxK87mA== +"@jest/environment@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-26.6.2.tgz#ba364cc72e221e79cc8f0a99555bf5d7577cf92c" + integrity sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA== dependencies: - "@jest/fake-timers" "^25.5.0" - "@jest/types" "^25.5.0" - jest-mock "^25.5.0" - -"@jest/environment@^26.5.2": - version "26.5.2" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-26.5.2.tgz#eba3cfc698f6e03739628f699c28e8a07f5e65fe" - integrity sha512-YjhCD/Zhkz0/1vdlS/QN6QmuUdDkpgBdK4SdiVg4Y19e29g4VQYN5Xg8+YuHjdoWGY7wJHMxc79uDTeTOy9Ngw== - dependencies: - "@jest/fake-timers" "^26.5.2" - "@jest/types" "^26.5.2" + "@jest/fake-timers" "^26.6.2" + "@jest/types" "^26.6.2" "@types/node" "*" - jest-mock "^26.5.2" + jest-mock "^26.6.2" "@jest/fake-timers@^24.3.0", "@jest/fake-timers@^24.9.0": version "24.9.0" @@ -3897,89 +3790,37 @@ jest-message-util "^24.9.0" jest-mock "^24.9.0" -"@jest/fake-timers@^25.5.0": - version "25.5.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-25.5.0.tgz#46352e00533c024c90c2bc2ad9f2959f7f114185" - integrity sha512-9y2+uGnESw/oyOI3eww9yaxdZyHq7XvprfP/eeoCsjqKYts2yRlsHS/SgjPDV8FyMfn2nbMy8YzUk6nyvdLOpQ== +"@jest/fake-timers@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.6.2.tgz#459c329bcf70cee4af4d7e3f3e67848123535aad" + integrity sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA== dependencies: - "@jest/types" "^25.5.0" - jest-message-util "^25.5.0" - jest-mock "^25.5.0" - jest-util "^25.5.0" - lolex "^5.0.0" - -"@jest/fake-timers@^26.5.2": - version "26.5.2" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.5.2.tgz#1291ac81680ceb0dc7daa1f92c059307eea6400a" - integrity sha512-09Hn5Oraqt36V1akxQeWMVL0fR9c6PnEhpgLaYvREXZJAh2H2Y+QLCsl0g7uMoJeoWJAuz4tozk1prbR1Fc1sw== - dependencies: - "@jest/types" "^26.5.2" + "@jest/types" "^26.6.2" "@sinonjs/fake-timers" "^6.0.1" "@types/node" "*" - jest-message-util "^26.5.2" - jest-mock "^26.5.2" - jest-util "^26.5.2" + jest-message-util "^26.6.2" + jest-mock "^26.6.2" + jest-util "^26.6.2" -"@jest/globals@^25.5.2": - version "25.5.2" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-25.5.2.tgz#5e45e9de8d228716af3257eeb3991cc2e162ca88" - integrity sha512-AgAS/Ny7Q2RCIj5kZ+0MuKM1wbF0WMLxbCVl/GOMoCNbODRdJ541IxJ98xnZdVSZXivKpJlNPIWa3QmY0l4CXA== +"@jest/globals@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-26.6.2.tgz#5b613b78a1aa2655ae908eba638cc96a20df720a" + integrity sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA== dependencies: - "@jest/environment" "^25.5.0" - "@jest/types" "^25.5.0" - expect "^25.5.0" + "@jest/environment" "^26.6.2" + "@jest/types" "^26.6.2" + expect "^26.6.2" -"@jest/globals@^26.5.2": - version "26.5.2" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-26.5.2.tgz#c333f82c29e19ecb609a75d1a532915a5c956c59" - integrity sha512-9PmnFsAUJxpPt1s/stq02acS1YHliVBDNfAWMe1bwdRr1iTCfhbNt3ERQXrO/ZfZSweftoA26Q/2yhSVSWQ3sw== - dependencies: - "@jest/environment" "^26.5.2" - "@jest/types" "^26.5.2" - expect "^26.5.2" - -"@jest/reporters@^25.5.1": - version "25.5.1" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-25.5.1.tgz#cb686bcc680f664c2dbaf7ed873e93aa6811538b" - integrity sha512-3jbd8pPDTuhYJ7vqiHXbSwTJQNavczPs+f1kRprRDxETeE3u6srJ+f0NPuwvOmk+lmunZzPkYWIFZDLHQPkviw== +"@jest/reporters@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-26.6.2.tgz#1f518b99637a5f18307bd3ecf9275f6882a667f6" + integrity sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^25.5.0" - "@jest/test-result" "^25.5.0" - "@jest/transform" "^25.5.1" - "@jest/types" "^25.5.0" - chalk "^3.0.0" - collect-v8-coverage "^1.0.0" - exit "^0.1.2" - glob "^7.1.2" - graceful-fs "^4.2.4" - istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^4.0.0" - istanbul-lib-report "^3.0.0" - istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.0.2" - jest-haste-map "^25.5.1" - jest-resolve "^25.5.1" - jest-util "^25.5.0" - jest-worker "^25.5.0" - slash "^3.0.0" - source-map "^0.6.0" - string-length "^3.1.0" - terminal-link "^2.0.0" - v8-to-istanbul "^4.1.3" - optionalDependencies: - node-notifier "^6.0.0" - -"@jest/reporters@^26.4.1": - version "26.4.1" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-26.4.1.tgz#3b4d6faf28650f3965f8b97bc3d114077fb71795" - integrity sha512-aROTkCLU8++yiRGVxLsuDmZsQEKO6LprlrxtAuzvtpbIFl3eIjgIf3EUxDKgomkS25R9ZzwGEdB5weCcBZlrpQ== - dependencies: - "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^26.3.0" - "@jest/test-result" "^26.3.0" - "@jest/transform" "^26.3.0" - "@jest/types" "^26.3.0" + "@jest/console" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" chalk "^4.0.0" collect-v8-coverage "^1.0.0" exit "^0.1.2" @@ -3990,47 +3831,15 @@ istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" istanbul-reports "^3.0.2" - jest-haste-map "^26.3.0" - jest-resolve "^26.4.0" - jest-util "^26.3.0" - jest-worker "^26.3.0" + jest-haste-map "^26.6.2" + jest-resolve "^26.6.2" + jest-util "^26.6.2" + jest-worker "^26.6.2" slash "^3.0.0" source-map "^0.6.0" string-length "^4.0.1" terminal-link "^2.0.0" - v8-to-istanbul "^5.0.1" - optionalDependencies: - node-notifier "^8.0.0" - -"@jest/reporters@^26.5.2": - version "26.5.2" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-26.5.2.tgz#0f1c900c6af712b46853d9d486c9c0382e4050f6" - integrity sha512-zvq6Wvy6MmJq/0QY0YfOPb49CXKSf42wkJbrBPkeypVa8I+XDxijvFuywo6TJBX/ILPrdrlE/FW9vJZh6Rf9vA== - dependencies: - "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^26.5.2" - "@jest/test-result" "^26.5.2" - "@jest/transform" "^26.5.2" - "@jest/types" "^26.5.2" - chalk "^4.0.0" - collect-v8-coverage "^1.0.0" - exit "^0.1.2" - glob "^7.1.2" - graceful-fs "^4.2.4" - istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^4.0.3" - istanbul-lib-report "^3.0.0" - istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.0.2" - jest-haste-map "^26.5.2" - jest-resolve "^26.5.2" - jest-util "^26.5.2" - jest-worker "^26.5.0" - slash "^3.0.0" - source-map "^0.6.0" - string-length "^4.0.1" - terminal-link "^2.0.0" - v8-to-istanbul "^5.0.1" + v8-to-istanbul "^7.0.0" optionalDependencies: node-notifier "^8.0.0" @@ -4043,19 +3852,10 @@ graceful-fs "^4.1.15" source-map "^0.6.0" -"@jest/source-map@^25.5.0": - version "25.5.0" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-25.5.0.tgz#df5c20d6050aa292c2c6d3f0d2c7606af315bd1b" - integrity sha512-eIGx0xN12yVpMcPaVpjXPnn3N30QGJCJQSkEDUt9x1fI1Gdvb07Ml6K5iN2hG7NmMP6FDmtPEssE3z6doOYUwQ== - dependencies: - callsites "^3.0.0" - graceful-fs "^4.2.4" - source-map "^0.6.0" - -"@jest/source-map@^26.5.0": - version "26.5.0" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-26.5.0.tgz#98792457c85bdd902365cd2847b58fff05d96367" - integrity sha512-jWAw9ZwYHJMe9eZq/WrsHlwF8E3hM9gynlcDpOyCb9bR8wEd9ZNBZCi7/jZyzHxC7t3thZ10gO2IDhu0bPKS5g== +"@jest/source-map@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-26.6.2.tgz#29af5e1e2e324cafccc936f218309f54ab69d535" + integrity sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA== dependencies: callsites "^3.0.0" graceful-fs "^4.2.4" @@ -4070,59 +3870,28 @@ "@jest/types" "^24.9.0" "@types/istanbul-lib-coverage" "^2.0.0" -"@jest/test-result@^25.5.0": - version "25.5.0" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-25.5.0.tgz#139a043230cdeffe9ba2d8341b27f2efc77ce87c" - integrity sha512-oV+hPJgXN7IQf/fHWkcS99y0smKLU2czLBJ9WA0jHITLst58HpQMtzSYxzaBvYc6U5U6jfoMthqsUlUlbRXs0A== +"@jest/test-result@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-26.6.2.tgz#55da58b62df134576cc95476efa5f7949e3f5f18" + integrity sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ== dependencies: - "@jest/console" "^25.5.0" - "@jest/types" "^25.5.0" + "@jest/console" "^26.6.2" + "@jest/types" "^26.6.2" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-result@^26.3.0": - version "26.3.0" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-26.3.0.tgz#46cde01fa10c0aaeb7431bf71e4a20d885bc7fdb" - integrity sha512-a8rbLqzW/q7HWheFVMtghXV79Xk+GWwOK1FrtimpI5n1la2SY0qHri3/b0/1F0Ve0/yJmV8pEhxDfVwiUBGtgg== +"@jest/test-sequencer@^26.6.3": + version "26.6.3" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz#98e8a45100863886d074205e8ffdc5a7eb582b17" + integrity sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw== dependencies: - "@jest/console" "^26.3.0" - "@jest/types" "^26.3.0" - "@types/istanbul-lib-coverage" "^2.0.0" - collect-v8-coverage "^1.0.0" - -"@jest/test-result@^26.5.2": - version "26.5.2" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-26.5.2.tgz#cc1a44cfd4db2ecee3fb0bc4e9fe087aa54b5230" - integrity sha512-E/Zp6LURJEGSCWpoMGmCFuuEI1OWuI3hmZwmULV0GsgJBh7u0rwqioxhRU95euUuviqBDN8ruX/vP/4bwYolXw== - dependencies: - "@jest/console" "^26.5.2" - "@jest/types" "^26.5.2" - "@types/istanbul-lib-coverage" "^2.0.0" - collect-v8-coverage "^1.0.0" - -"@jest/test-sequencer@^25.5.4": - version "25.5.4" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-25.5.4.tgz#9b4e685b36954c38d0f052e596d28161bdc8b737" - integrity sha512-pTJGEkSeg1EkCO2YWq6hbFvKNXk8ejqlxiOg1jBNLnWrgXOkdY6UmqZpwGFXNnRt9B8nO1uWMzLLZ4eCmhkPNA== - dependencies: - "@jest/test-result" "^25.5.0" + "@jest/test-result" "^26.6.2" graceful-fs "^4.2.4" - jest-haste-map "^25.5.1" - jest-runner "^25.5.4" - jest-runtime "^25.5.4" + jest-haste-map "^26.6.2" + jest-runner "^26.6.3" + jest-runtime "^26.6.3" -"@jest/test-sequencer@^26.5.2": - version "26.5.2" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-26.5.2.tgz#c4559c7e134b27b020317303ee5399bf62917a4b" - integrity sha512-XmGEh7hh07H2B8mHLFCIgr7gA5Y6Hw1ZATIsbz2fOhpnQ5AnQtZk0gmP0Q5/+mVB2xygO64tVFQxOajzoptkNA== - dependencies: - "@jest/test-result" "^26.5.2" - graceful-fs "^4.2.4" - jest-haste-map "^26.5.2" - jest-runner "^26.5.2" - jest-runtime "^26.5.2" - -"@jest/transform@^24.8.0", "@jest/transform@^24.9.0": +"@jest/transform@^24.9.0": version "24.9.0" resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-24.9.0.tgz#4ae2768b296553fadab09e9ec119543c90b16c56" integrity sha512-TcQUmyNRxV94S0QpMOnZl0++6RMiqpbH/ZMccFB/amku6Uwvyb1cjYX7xkp5nGNkbX4QPH/FcB6q1HBTHynLmQ== @@ -4144,29 +3913,7 @@ source-map "^0.6.1" write-file-atomic "2.4.1" -"@jest/transform@^25.5.1": - version "25.5.1" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-25.5.1.tgz#0469ddc17699dd2bf985db55fa0fb9309f5c2db3" - integrity sha512-Y8CEoVwXb4QwA6Y/9uDkn0Xfz0finGkieuV0xkdF9UtZGJeLukD5nLkaVrVsODB1ojRWlaoD0AJZpVHCSnJEvg== - dependencies: - "@babel/core" "^7.1.0" - "@jest/types" "^25.5.0" - babel-plugin-istanbul "^6.0.0" - chalk "^3.0.0" - convert-source-map "^1.4.0" - fast-json-stable-stringify "^2.0.0" - graceful-fs "^4.2.4" - jest-haste-map "^25.5.1" - jest-regex-util "^25.2.6" - jest-util "^25.5.0" - micromatch "^4.0.2" - pirates "^4.0.1" - realpath-native "^2.0.0" - slash "^3.0.0" - source-map "^0.6.1" - write-file-atomic "^3.0.0" - -"@jest/transform@^26.0.0": +"@jest/transform@^26.0.0", "@jest/transform@^26.6.2": version "26.6.2" resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-26.6.2.tgz#5ac57c5fa1ad17b2aae83e73e45813894dcf2e4b" integrity sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA== @@ -4187,70 +3934,7 @@ source-map "^0.6.1" write-file-atomic "^3.0.0" -"@jest/transform@^26.0.1": - version "26.0.1" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-26.0.1.tgz#0e3ecbb34a11cd4b2080ed0a9c4856cf0ceb0639" - integrity sha512-pPRkVkAQ91drKGbzCfDOoHN838+FSbYaEAvBXvKuWeeRRUD8FjwXkqfUNUZL6Ke48aA/1cqq/Ni7kVMCoqagWA== - dependencies: - "@babel/core" "^7.1.0" - "@jest/types" "^26.0.1" - babel-plugin-istanbul "^6.0.0" - chalk "^4.0.0" - convert-source-map "^1.4.0" - fast-json-stable-stringify "^2.0.0" - graceful-fs "^4.2.4" - jest-haste-map "^26.0.1" - jest-regex-util "^26.0.0" - jest-util "^26.0.1" - micromatch "^4.0.2" - pirates "^4.0.1" - slash "^3.0.0" - source-map "^0.6.1" - write-file-atomic "^3.0.0" - -"@jest/transform@^26.3.0": - version "26.3.0" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-26.3.0.tgz#c393e0e01459da8a8bfc6d2a7c2ece1a13e8ba55" - integrity sha512-Isj6NB68QorGoFWvcOjlUhpkT56PqNIsXKR7XfvoDlCANn/IANlh8DrKAA2l2JKC3yWSMH5wS0GwuQM20w3b2A== - dependencies: - "@babel/core" "^7.1.0" - "@jest/types" "^26.3.0" - babel-plugin-istanbul "^6.0.0" - chalk "^4.0.0" - convert-source-map "^1.4.0" - fast-json-stable-stringify "^2.0.0" - graceful-fs "^4.2.4" - jest-haste-map "^26.3.0" - jest-regex-util "^26.0.0" - jest-util "^26.3.0" - micromatch "^4.0.2" - pirates "^4.0.1" - slash "^3.0.0" - source-map "^0.6.1" - write-file-atomic "^3.0.0" - -"@jest/transform@^26.5.2": - version "26.5.2" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-26.5.2.tgz#6a0033a1d24316a1c75184d010d864f2c681bef5" - integrity sha512-AUNjvexh+APhhmS8S+KboPz+D3pCxPvEAGduffaAJYxIFxGi/ytZQkrqcKDUU0ERBAo5R7087fyOYr2oms1seg== - dependencies: - "@babel/core" "^7.1.0" - "@jest/types" "^26.5.2" - babel-plugin-istanbul "^6.0.0" - chalk "^4.0.0" - convert-source-map "^1.4.0" - fast-json-stable-stringify "^2.0.0" - graceful-fs "^4.2.4" - jest-haste-map "^26.5.2" - jest-regex-util "^26.0.0" - jest-util "^26.5.2" - micromatch "^4.0.2" - pirates "^4.0.1" - slash "^3.0.0" - source-map "^0.6.1" - write-file-atomic "^3.0.0" - -"@jest/types@^24.3.0", "@jest/types@^24.8.0", "@jest/types@^24.9.0": +"@jest/types@^24.3.0", "@jest/types@^24.9.0": version "24.9.0" resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.9.0.tgz#63cb26cb7500d069e5a389441a7c6ab5e909fc59" integrity sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw== @@ -4279,38 +3963,6 @@ "@types/yargs" "^15.0.0" chalk "^3.0.0" -"@jest/types@^26.0.1": - version "26.0.1" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.0.1.tgz#b78333fbd113fa7aec8d39de24f88de8686dac67" - integrity sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^1.1.1" - "@types/yargs" "^15.0.0" - chalk "^4.0.0" - -"@jest/types@^26.3.0": - version "26.3.0" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.3.0.tgz#97627bf4bdb72c55346eef98e3b3f7ddc4941f71" - integrity sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^15.0.0" - chalk "^4.0.0" - -"@jest/types@^26.5.2": - version "26.5.2" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.5.2.tgz#44c24f30c8ee6c7f492ead9ec3f3c62a5289756d" - integrity sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^15.0.0" - chalk "^4.0.0" - "@jest/types@^26.6.2": version "26.6.2" resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" @@ -6301,17 +5953,6 @@ "@types/babel__template" "*" "@types/babel__traverse" "*" -"@types/babel__core@^7.1.0": - version "7.1.3" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.3.tgz#e441ea7df63cd080dfcd02ab199e6d16a735fc30" - integrity sha512-8fBo0UR2CcwWxeX7WIIgJ7lXjasFxoYgRnFHUj+hRvKkpiBJbxhdAPTCY6/ZKM0uxANFVzt4yObSLuTiTnazDA== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - "@types/babel__generator" "*" - "@types/babel__template" "*" - "@types/babel__traverse" "*" - "@types/babel__core@^7.1.7": version "7.1.8" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.8.tgz#057f725aca3641f49fc11c7a87a9de5ec588a5d7" @@ -6880,7 +6521,7 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@*": +"@types/jest@*", "@types/jest@26.0.15": version "26.0.15" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.15.tgz#12e02c0372ad0548e07b9f4e19132b834cb1effe" integrity sha512-s2VMReFXRg9XXxV+CW9e5Nz8fH2K1aEhwgjUqPPbQd7g95T0laAcvLv032EhFHIa5GHsZ8W7iJEQVaJq6k3Gog== @@ -6888,14 +6529,6 @@ jest-diff "^26.0.0" pretty-format "^26.0.0" -"@types/jest@26.0.0": - version "26.0.0" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.0.tgz#a6d7573dffa9c68cbbdf38f2e0de26f159e11134" - integrity sha512-/yeMsH9HQ1RLORlXAwoLXe8S98xxvhNtUz3yrgrwbaxYjT+6SFPZZRksmRKRA6L5vsUtSHeN71viDOTTyYAD+g== - dependencies: - jest-diff "^25.2.1" - pretty-format "^25.2.1" - "@types/jest@26.x": version "26.0.12" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.12.tgz#0f20fef9e74f55a312530284e6178f3b3254f501" @@ -7093,11 +6726,6 @@ resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.18.3.tgz#64ff53329ce16139f17c3db9d3e0487199972cd8" integrity sha512-48rnerQdcZ26odp+HOvDGX8IcUkYOCuMc2BodWYTe956MqkHlOGAG4oFQ83cjZ0a4GAgj7mb4GUClxYd2Hlodg== -"@types/prettier@^1.19.0": - version "1.19.1" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.19.1.tgz#33509849f8e679e4add158959fdb086440e9553f" - integrity sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ== - "@types/prettier@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.0.1.tgz#b6e98083f13faa1e5231bfa3bdb1b0feff536b6d" @@ -8984,57 +8612,16 @@ babel-helper-to-multiple-sequence-expressions@^0.5.0: resolved "https://registry.yarnpkg.com/babel-helper-to-multiple-sequence-expressions/-/babel-helper-to-multiple-sequence-expressions-0.5.0.tgz#a3f924e3561882d42fcf48907aa98f7979a4588d" integrity sha512-m2CvfDW4+1qfDdsrtf4dwOslQC3yhbgyBFptncp4wvtdrDHqueW7slsYv4gArie056phvQFhT2nRcGS4bnm6mA== -babel-jest@24.8.0: - version "24.8.0" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.8.0.tgz#5c15ff2b28e20b0f45df43fe6b7f2aae93dba589" - integrity sha512-+5/kaZt4I9efoXzPlZASyK/lN9qdRKmmUav9smVc0ruPQD7IsfucQ87gpOE8mn2jbDuS6M/YOW6n3v9ZoIfgnw== +babel-jest@26.6.3, babel-jest@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.6.3.tgz#d87d25cb0037577a0c89f82e5755c5d293c01056" + integrity sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA== dependencies: - "@jest/transform" "^24.8.0" - "@jest/types" "^24.8.0" - "@types/babel__core" "^7.1.0" - babel-plugin-istanbul "^5.1.0" - babel-preset-jest "^24.6.0" - chalk "^2.4.2" - slash "^2.0.0" - -babel-jest@26.0.1: - version "26.0.1" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.0.1.tgz#450139ce4b6c17174b136425bda91885c397bc46" - integrity sha512-Z4GGmSNQ8pX3WS1O+6v3fo41YItJJZsVxG5gIQ+HuB/iuAQBJxMTHTwz292vuYws1LnHfwSRgoqI+nxdy/pcvw== - dependencies: - "@jest/transform" "^26.0.1" - "@jest/types" "^26.0.1" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" "@types/babel__core" "^7.1.7" babel-plugin-istanbul "^6.0.0" - babel-preset-jest "^26.0.0" - chalk "^4.0.0" - graceful-fs "^4.2.4" - slash "^3.0.0" - -babel-jest@^25.5.1: - version "25.5.1" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-25.5.1.tgz#bc2e6101f849d6f6aec09720ffc7bc5332e62853" - integrity sha512-9dA9+GmMjIzgPnYtkhBg73gOo/RHqPmLruP3BaGL4KEX3Dwz6pI8auSN8G8+iuEG90+GSswyKvslN+JYSaacaQ== - dependencies: - "@jest/transform" "^25.5.1" - "@jest/types" "^25.5.0" - "@types/babel__core" "^7.1.7" - babel-plugin-istanbul "^6.0.0" - babel-preset-jest "^25.5.0" - chalk "^3.0.0" - graceful-fs "^4.2.4" - slash "^3.0.0" - -babel-jest@^26.5.2: - version "26.5.2" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.5.2.tgz#164f367a35946c6cf54eaccde8762dec50422250" - integrity sha512-U3KvymF3SczA3vOL/cgiUFOznfMET+XDIXiWnoJV45siAp2pLMG8i2+/MGZlAC3f/F6Q40LR4M4qDrWZ9wkK8A== - dependencies: - "@jest/transform" "^26.5.2" - "@jest/types" "^26.5.2" - "@types/babel__core" "^7.1.7" - babel-plugin-istanbul "^6.0.0" - babel-preset-jest "^26.5.0" + babel-preset-jest "^26.6.2" chalk "^4.0.0" graceful-fs "^4.2.4" slash "^3.0.0" @@ -9172,35 +8759,10 @@ babel-plugin-istanbul@^6.0.0: istanbul-lib-instrument "^4.0.0" test-exclude "^6.0.0" -babel-plugin-jest-hoist@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.9.0.tgz#4f837091eb407e01447c8843cbec546d0002d756" - integrity sha512-2EMA2P8Vp7lG0RAzr4HXqtYwacfMErOuv1U3wrvxHX6rD1sV6xS3WXG3r8TRQ2r6w8OhvSdWt+z41hQNwNm3Xw== - dependencies: - "@types/babel__traverse" "^7.0.6" - -babel-plugin-jest-hoist@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-25.5.0.tgz#129c80ba5c7fc75baf3a45b93e2e372d57ca2677" - integrity sha512-u+/W+WAjMlvoocYGTwthAiQSxDcJAyHpQ6oWlHdFZaaN+Rlk8Q7iiwDPg2lN/FyJtAYnKjFxbn7xus4HCFkg5g== - dependencies: - "@babel/template" "^7.3.3" - "@babel/types" "^7.3.3" - "@types/babel__traverse" "^7.0.6" - -babel-plugin-jest-hoist@^26.0.0: - version "26.0.0" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.0.0.tgz#fd1d35f95cf8849fc65cb01b5e58aedd710b34a8" - integrity sha512-+AuoehOrjt9irZL7DOt2+4ZaTM6dlu1s5TTS46JBa0/qem4dy7VNW3tMb96qeEqcIh20LD73TVNtmVEeymTG7w== - dependencies: - "@babel/template" "^7.3.3" - "@babel/types" "^7.3.3" - "@types/babel__traverse" "^7.0.6" - -babel-plugin-jest-hoist@^26.5.0: - version "26.5.0" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.5.0.tgz#3916b3a28129c29528de91e5784a44680db46385" - integrity sha512-ck17uZFD3CDfuwCLATWZxkkuGGFhMij8quP8CNhwj8ek1mqFgbFzRJ30xwC04LLscj/aKsVFfRST+b5PT7rSuw== +babel-plugin-jest-hoist@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz#8185bd030348d254c6d7dd974355e6a28b21e62d" + integrity sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw== dependencies: "@babel/template" "^7.3.3" "@babel/types" "^7.3.3" @@ -9379,26 +8941,10 @@ babel-plugin-transform-undefined-to-void@^6.9.4: resolved "https://registry.yarnpkg.com/babel-plugin-transform-undefined-to-void/-/babel-plugin-transform-undefined-to-void-6.9.4.tgz#be241ca81404030678b748717322b89d0c8fe280" integrity sha1-viQcqBQEAwZ4t0hxcyK4nQyP4oA= -babel-preset-current-node-syntax@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.2.tgz#fb4a4c51fe38ca60fede1dc74ab35eb843cb41d6" - integrity sha512-u/8cS+dEiK1SFILbOC8/rUI3ml9lboKuuMvZ/4aQnQmhecQAgPw5ew066C1ObnEAUmlx7dv/s2z52psWEtLNiw== - dependencies: - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-syntax-bigint" "^7.8.3" - "@babel/plugin-syntax-class-properties" "^7.8.3" - "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.8.3" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - -babel-preset-current-node-syntax@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.3.tgz#b4b547acddbf963cba555ba9f9cbbb70bfd044da" - integrity sha512-uyexu1sVwcdFnyq9o8UQYsXwXflIh8LvrF5+cKrYam93ned1CStffB3+BEcsxGSgagoA3GEyjDqO4a/58hyPYQ== +babel-preset-current-node-syntax@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.0.tgz#cf5feef29551253471cfa82fc8e0f5063df07a77" + integrity sha512-mGkvkpocWJes1CmMKtgGUwCeeq0pOhALyymozzDWYomHTbDLwueDYG6p4TK1YOeYHCzBzYPsWkgTto10JubI1Q== dependencies: "@babel/plugin-syntax-async-generators" "^7.8.4" "@babel/plugin-syntax-bigint" "^7.8.3" @@ -9411,38 +8957,15 @@ babel-preset-current-node-syntax@^0.1.3: "@babel/plugin-syntax-object-rest-spread" "^7.8.3" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-top-level-await" "^7.8.3" -babel-preset-jest@^24.6.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-24.9.0.tgz#192b521e2217fb1d1f67cf73f70c336650ad3cdc" - integrity sha512-izTUuhE4TMfTRPF92fFwD2QfdXaZW08qvWTFCI51V8rW5x00UuPgc3ajRoWofXOuxjfcOM5zzSYsQS3H8KGCAg== +babel-preset-jest@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz#747872b1171df032252426586881d62d31798fee" + integrity sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ== dependencies: - "@babel/plugin-syntax-object-rest-spread" "^7.0.0" - babel-plugin-jest-hoist "^24.9.0" - -babel-preset-jest@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-25.5.0.tgz#c1d7f191829487a907764c65307faa0e66590b49" - integrity sha512-8ZczygctQkBU+63DtSOKGh7tFL0CeCuz+1ieud9lJ1WPQ9O6A1a/r+LGn6Y705PA6whHQ3T1XuB/PmpfNYf8Fw== - dependencies: - babel-plugin-jest-hoist "^25.5.0" - babel-preset-current-node-syntax "^0.1.2" - -babel-preset-jest@^26.0.0: - version "26.0.0" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-26.0.0.tgz#1eac82f513ad36c4db2e9263d7c485c825b1faa6" - integrity sha512-9ce+DatAa31DpR4Uir8g4Ahxs5K4W4L8refzt+qHWQANb6LhGcAEfIFgLUwk67oya2cCUd6t4eUMtO/z64ocNw== - dependencies: - babel-plugin-jest-hoist "^26.0.0" - babel-preset-current-node-syntax "^0.1.2" - -babel-preset-jest@^26.5.0: - version "26.5.0" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-26.5.0.tgz#f1b166045cd21437d1188d29f7fba470d5bdb0e7" - integrity sha512-F2vTluljhqkiGSJGBg/jOruA8vIIIL11YrxRcO7nviNTMbbofPSHwnm8mgP7d/wS7wRSexRoI6X1A6T74d4LQA== - dependencies: - babel-plugin-jest-hoist "^26.5.0" - babel-preset-current-node-syntax "^0.1.3" + babel-plugin-jest-hoist "^26.6.2" + babel-preset-current-node-syntax "^1.0.0" "babel-preset-minify@^0.5.0 || 0.6.0-alpha.5": version "0.5.1" @@ -9726,13 +9249,6 @@ browser-process-hrtime@^1.0.0: resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== -browser-resolve@^1.11.3: - version "1.11.3" - resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6" - integrity sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ== - dependencies: - resolve "1.1.7" - browser-stdout@1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" @@ -10418,6 +9934,11 @@ circular-json@^0.5.0: resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.5.9.tgz#932763ae88f4f7dead7a0d09c8a51a4743a53b1d" integrity sha512-4ivwqHpIFJZBuhN3g/pEcdbnGUywkBblloGbkglyloVjjR3uT6tieI89MVOfbP2tHX5sgb01FuLgAOzebNlJNQ== +cjs-module-lexer@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz#4186fcca0eae175970aee870b9fe2d6cf8d5655f" + integrity sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw== + class-utils@^0.3.5: version "0.3.6" resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" @@ -10682,17 +10203,12 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" -color-convert@~0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-0.5.3.tgz#bdb6c69ce660fadffe0b0007cc447e1b9f7282bd" - integrity sha1-vbbGnOZg+t/+CwAHzER+G59ygr0= - color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= -color-name@^1.0.0, color-name@~1.1.4: +color-name@^1.0.0, color-name@^1.1.4, color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== @@ -12398,11 +11914,6 @@ diff-sequences@^25.2.6: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd" integrity sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg== -diff-sequences@^26.5.0: - version "26.5.0" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.5.0.tgz#ef766cf09d43ed40406611f11c6d8d9dd8b2fefd" - integrity sha512-ZXx86srb/iYy6jG71k++wBN9P9J05UNQ5hQHQd9MtMPvcqXPx/vKU69jfHV637D00Q2gSgPk2D+jSx3l1lDW/Q== - diff-sequences@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" @@ -13689,7 +13200,7 @@ execa@^1.0.0: signal-exit "^3.0.0" strip-eof "^1.0.0" -execa@^3.2.0, execa@^3.4.0: +execa@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/execa/-/execa-3.4.0.tgz#c08ed4550ef65d858fac269ffc8572446f37eb89" integrity sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g== @@ -13767,28 +13278,16 @@ expect.js@0.3.1: resolved "https://registry.yarnpkg.com/expect.js/-/expect.js-0.3.1.tgz#b0a59a0d2eff5437544ebf0ceaa6015841d09b5b" integrity sha1-sKWaDS7/VDdUTr8M6qYBWEHQm1s= -expect@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-25.5.0.tgz#f07f848712a2813bb59167da3fb828ca21f58bba" - integrity sha512-w7KAXo0+6qqZZhovCaBVPSIqQp7/UTcx4M9uKt2m6pd2VB1voyC8JizLRqeEqud3AAVP02g+hbErDu5gu64tlA== +expect@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/expect/-/expect-26.6.2.tgz#c6b996bf26bf3fe18b67b2d0f51fc981ba934417" + integrity sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA== dependencies: - "@jest/types" "^25.5.0" - ansi-styles "^4.0.0" - jest-get-type "^25.2.6" - jest-matcher-utils "^25.5.0" - jest-message-util "^25.5.0" - jest-regex-util "^25.2.6" - -expect@^26.5.2: - version "26.5.2" - resolved "https://registry.yarnpkg.com/expect/-/expect-26.5.2.tgz#3e0631c4a657a83dbec769ad246a2998953a55a6" - integrity sha512-ccTGrXZd8DZCcvCz4htGXTkd/LOoy6OEtiDS38x3/VVf6E4AQL0QoeksBiw7BtGR5xDNiRYPB8GN6pfbuTOi7w== - dependencies: - "@jest/types" "^26.5.2" + "@jest/types" "^26.6.2" ansi-styles "^4.0.0" jest-get-type "^26.3.0" - jest-matcher-utils "^26.5.2" - jest-message-util "^26.5.2" + jest-matcher-utils "^26.6.2" + jest-message-util "^26.6.2" jest-regex-util "^26.0.0" expose-loader@0.7.5: @@ -17149,127 +16648,65 @@ jake@^10.6.1: filelist "^1.0.1" minimatch "^3.0.4" -jest-canvas-mock@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/jest-canvas-mock/-/jest-canvas-mock-2.1.2.tgz#0d16c9f91534f773fd132fc289f2e6b6db8faa28" - integrity sha512-1VI4PK4/X70yrSjYScYVkYJYbXYlZLKJkUrAlyHjQsfolv64aoFyIrmMDtqCjpYrpVvWYEcAGUaYv5DVJj00oQ== +jest-canvas-mock@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/jest-canvas-mock/-/jest-canvas-mock-2.3.0.tgz#50f4cc178ae52c4c0e2ce4fd3a3ad2a41ad4eb36" + integrity sha512-3TMyR66VG2MzAW8Negzec03bbcIjVJMfGNvKzrEnbws1CYKqMNkvIJ8LbkoGYfp42tKqDmhIpQq3v+MNLW2A2w== dependencies: cssfontparser "^1.2.1" - parse-color "^1.0.0" + moo-color "^1.0.2" -jest-canvas-mock@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/jest-canvas-mock/-/jest-canvas-mock-2.2.0.tgz#45fbc58589c6ce9df50dc90bd8adce747cbdada7" - integrity sha512-DcJdchb7eWFZkt6pvyceWWnu3lsp5QWbUeXiKgEMhwB3sMm5qHM1GQhDajvJgBeiYpgKcojbzZ53d/nz6tXvJw== +jest-changed-files@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.6.2.tgz#f6198479e1cc66f22f9ae1e22acaa0b429c042d0" + integrity sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ== dependencies: - cssfontparser "^1.2.1" - parse-color "^1.0.0" - -jest-changed-files@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-25.5.0.tgz#141cc23567ceb3f534526f8614ba39421383634c" - integrity sha512-EOw9QEqapsDT7mKF162m8HFzRPbmP8qJQny6ldVOdOVBz3ACgPm/1nAn5fPQ/NDaYhX/AHkrGwwkCncpAVSXcw== - dependencies: - "@jest/types" "^25.5.0" - execa "^3.2.0" - throat "^5.0.0" - -jest-changed-files@^26.3.0, jest-changed-files@^26.5.2: - version "26.5.2" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.5.2.tgz#330232c6a5c09a7f040a5870e8f0a9c6abcdbed5" - integrity sha512-qSmssmiIdvM5BWVtyK/nqVpN3spR5YyvkvPqz1x3BR1bwIxsWmU/MGwLoCrPNLbkG2ASAKfvmJpOduEApBPh2w== - dependencies: - "@jest/types" "^26.5.2" + "@jest/types" "^26.6.2" execa "^4.0.0" throat "^5.0.0" -jest-cli@^25.5.4: - version "25.5.4" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-25.5.4.tgz#b9f1a84d1301a92c5c217684cb79840831db9f0d" - integrity sha512-rG8uJkIiOUpnREh1768/N3n27Cm+xPFkSNFO91tgg+8o2rXeVLStz+vkXkGr4UtzH6t1SNbjwoiswd7p4AhHTw== +jest-cli@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-26.6.3.tgz#43117cfef24bc4cd691a174a8796a532e135e92a" + integrity sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg== dependencies: - "@jest/core" "^25.5.4" - "@jest/test-result" "^25.5.0" - "@jest/types" "^25.5.0" - chalk "^3.0.0" - exit "^0.1.2" - graceful-fs "^4.2.4" - import-local "^3.0.2" - is-ci "^2.0.0" - jest-config "^25.5.4" - jest-util "^25.5.0" - jest-validate "^25.5.0" - prompts "^2.0.1" - realpath-native "^2.0.0" - yargs "^15.3.1" - -jest-cli@^26.4.2: - version "26.5.2" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-26.5.2.tgz#0df114399b4036a3f046f0a9f25c50372c76b3a2" - integrity sha512-usm48COuUvRp8YEG5OWOaxbSM0my7eHn3QeBWxiGUuFhvkGVBvl1fic4UjC02EAEQtDv8KrNQUXdQTV6ZZBsoA== - dependencies: - "@jest/core" "^26.5.2" - "@jest/test-result" "^26.5.2" - "@jest/types" "^26.5.2" + "@jest/core" "^26.6.3" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" chalk "^4.0.0" exit "^0.1.2" graceful-fs "^4.2.4" import-local "^3.0.2" is-ci "^2.0.0" - jest-config "^26.5.2" - jest-util "^26.5.2" - jest-validate "^26.5.2" + jest-config "^26.6.3" + jest-util "^26.6.2" + jest-validate "^26.6.2" prompts "^2.0.1" yargs "^15.4.1" -jest-config@^25.5.4: - version "25.5.4" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-25.5.4.tgz#38e2057b3f976ef7309b2b2c8dcd2a708a67f02c" - integrity sha512-SZwR91SwcdK6bz7Gco8qL7YY2sx8tFJYzvg216DLihTWf+LKY/DoJXpM9nTzYakSyfblbqeU48p/p7Jzy05Atg== +jest-config@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-26.6.3.tgz#64f41444eef9eb03dc51d5c53b75c8c71f645349" + integrity sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg== dependencies: "@babel/core" "^7.1.0" - "@jest/test-sequencer" "^25.5.4" - "@jest/types" "^25.5.0" - babel-jest "^25.5.1" - chalk "^3.0.0" - deepmerge "^4.2.2" - glob "^7.1.1" - graceful-fs "^4.2.4" - jest-environment-jsdom "^25.5.0" - jest-environment-node "^25.5.0" - jest-get-type "^25.2.6" - jest-jasmine2 "^25.5.4" - jest-regex-util "^25.2.6" - jest-resolve "^25.5.1" - jest-util "^25.5.0" - jest-validate "^25.5.0" - micromatch "^4.0.2" - pretty-format "^25.5.0" - realpath-native "^2.0.0" - -jest-config@^26.4.2, jest-config@^26.5.2: - version "26.5.2" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-26.5.2.tgz#6e828e25f10124433dd008fbd83348636de0972a" - integrity sha512-dqJOnSegNdE5yDiuGHsjTM5gec7Z4AcAMHiW+YscbOYJAlb3LEtDSobXCq0or9EmGQI5SFmKy4T7P1FxetJOfg== - dependencies: - "@babel/core" "^7.1.0" - "@jest/test-sequencer" "^26.5.2" - "@jest/types" "^26.5.2" - babel-jest "^26.5.2" + "@jest/test-sequencer" "^26.6.3" + "@jest/types" "^26.6.2" + babel-jest "^26.6.3" chalk "^4.0.0" deepmerge "^4.2.2" glob "^7.1.1" graceful-fs "^4.2.4" - jest-environment-jsdom "^26.5.2" - jest-environment-node "^26.5.2" + jest-environment-jsdom "^26.6.2" + jest-environment-node "^26.6.2" jest-get-type "^26.3.0" - jest-jasmine2 "^26.5.2" + jest-jasmine2 "^26.6.3" jest-regex-util "^26.0.0" - jest-resolve "^26.5.2" - jest-util "^26.5.2" - jest-validate "^26.5.2" + jest-resolve "^26.6.2" + jest-util "^26.6.2" + jest-validate "^26.6.2" micromatch "^4.0.2" - pretty-format "^26.5.2" + pretty-format "^26.6.2" jest-coverage-badges@^1.1.2: version "1.1.2" @@ -17283,7 +16720,7 @@ jest-date-mock@1.0.8: resolved "https://registry.yarnpkg.com/jest-date-mock/-/jest-date-mock-1.0.8.tgz#13468c0352c5a3614c6b356dbc6b88eb37d9e0b3" integrity sha512-0Lyp+z9xvuNmLbK+5N6FOhSiBeux05Lp5bbveFBmYo40Aggl2wwxFoIrZ+rOWC8nDNcLeBoDd2miQdEDSf3iQw== -jest-diff@^25.2.1, jest-diff@^25.5.0: +jest-diff@^25.2.1: version "25.5.0" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.5.0.tgz#1dd26ed64f96667c068cef026b677dfa01afcfa9" integrity sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A== @@ -17293,7 +16730,7 @@ jest-diff@^25.2.1, jest-diff@^25.5.0: jest-get-type "^25.2.6" pretty-format "^25.5.0" -jest-diff@^26.0.0: +jest-diff@^26.0.0, jest-diff@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394" integrity sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA== @@ -17303,23 +16740,6 @@ jest-diff@^26.0.0: jest-get-type "^26.3.0" pretty-format "^26.6.2" -jest-diff@^26.5.2: - version "26.5.2" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.5.2.tgz#8e26cb32dc598e8b8a1b9deff55316f8313c8053" - integrity sha512-HCSWDUGwsov5oTlGzrRM+UPJI/Dpqi9jzeV0fdRNi3Ch5bnoXhnyJMmVg2juv9081zLIy3HGPI5mcuGgXM2xRA== - dependencies: - chalk "^4.0.0" - diff-sequences "^26.5.0" - jest-get-type "^26.3.0" - pretty-format "^26.5.2" - -jest-docblock@^25.3.0: - version "25.3.0" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-25.3.0.tgz#8b777a27e3477cd77a168c05290c471a575623ef" - integrity sha512-aktF0kCar8+zxRHxQZwxMy70stc9R1mOmrLsT5VO3pIT0uzGRSDAXxSlz4NqQWpuLjPpuMhPRl7H+5FRsvIQAg== - dependencies: - detect-newline "^3.0.0" - jest-docblock@^26.0.0: version "26.0.0" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-26.0.0.tgz#3e2fa20899fc928cb13bd0ff68bd3711a36889b5" @@ -17327,27 +16747,16 @@ jest-docblock@^26.0.0: dependencies: detect-newline "^3.0.0" -jest-each@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-25.5.0.tgz#0c3c2797e8225cb7bec7e4d249dcd96b934be516" - integrity sha512-QBogUxna3D8vtiItvn54xXde7+vuzqRrEeaw8r1s+1TG9eZLVJE5ZkKoSUlqFwRjnlaA4hyKGiu9OlkFIuKnjA== +jest-each@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-26.6.2.tgz#02526438a77a67401c8a6382dfe5999952c167cb" + integrity sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A== dependencies: - "@jest/types" "^25.5.0" - chalk "^3.0.0" - jest-get-type "^25.2.6" - jest-util "^25.5.0" - pretty-format "^25.5.0" - -jest-each@^26.5.2: - version "26.5.2" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-26.5.2.tgz#35e68d6906a7f826d3ca5803cfe91d17a5a34c31" - integrity sha512-w7D9FNe0m2D3yZ0Drj9CLkyF/mGhmBSULMQTypzAKR746xXnjUrK8GUJdlLTWUF6dd0ks3MtvGP7/xNFr9Aphg== - dependencies: - "@jest/types" "^26.5.2" + "@jest/types" "^26.6.2" chalk "^4.0.0" jest-get-type "^26.3.0" - jest-util "^26.5.2" - pretty-format "^26.5.2" + jest-util "^26.6.2" + pretty-format "^26.6.2" jest-environment-jsdom-fifteen@^1.0.2: version "1.0.2" @@ -17361,54 +16770,30 @@ jest-environment-jsdom-fifteen@^1.0.2: jest-util "^24.0.0" jsdom "^15.2.1" -jest-environment-jsdom@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-25.5.0.tgz#dcbe4da2ea997707997040ecf6e2560aec4e9834" - integrity sha512-7Jr02ydaq4jaWMZLY+Skn8wL5nVIYpWvmeatOHL3tOcV3Zw8sjnPpx+ZdeBfc457p8jCR9J6YCc+Lga0oIy62A== +jest-environment-jsdom@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz#78d09fe9cf019a357009b9b7e1f101d23bd1da3e" + integrity sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q== dependencies: - "@jest/environment" "^25.5.0" - "@jest/fake-timers" "^25.5.0" - "@jest/types" "^25.5.0" - jest-mock "^25.5.0" - jest-util "^25.5.0" - jsdom "^15.2.1" - -jest-environment-jsdom@^26.5.2: - version "26.5.2" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-26.5.2.tgz#5feab05b828fd3e4b96bee5e0493464ddd2bb4bc" - integrity sha512-fWZPx0bluJaTQ36+PmRpvUtUlUFlGGBNyGX1SN3dLUHHMcQ4WseNEzcGGKOw4U5towXgxI4qDoI3vwR18H0RTw== - dependencies: - "@jest/environment" "^26.5.2" - "@jest/fake-timers" "^26.5.2" - "@jest/types" "^26.5.2" + "@jest/environment" "^26.6.2" + "@jest/fake-timers" "^26.6.2" + "@jest/types" "^26.6.2" "@types/node" "*" - jest-mock "^26.5.2" - jest-util "^26.5.2" + jest-mock "^26.6.2" + jest-util "^26.6.2" jsdom "^16.4.0" -jest-environment-node@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-25.5.0.tgz#0f55270d94804902988e64adca37c6ce0f7d07a1" - integrity sha512-iuxK6rQR2En9EID+2k+IBs5fCFd919gVVK5BeND82fYeLWPqvRcFNPKu9+gxTwfB5XwBGBvZ0HFQa+cHtIoslA== +jest-environment-node@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-26.6.2.tgz#824e4c7fb4944646356f11ac75b229b0035f2b0c" + integrity sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag== dependencies: - "@jest/environment" "^25.5.0" - "@jest/fake-timers" "^25.5.0" - "@jest/types" "^25.5.0" - jest-mock "^25.5.0" - jest-util "^25.5.0" - semver "^6.3.0" - -jest-environment-node@^26.5.2: - version "26.5.2" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-26.5.2.tgz#275a0f01b5e47447056f1541a15ed4da14acca03" - integrity sha512-YHjnDsf/GKFCYMGF1V+6HF7jhY1fcLfLNBDjhAOvFGvt6d8vXvNdJGVM7uTZ2VO/TuIyEFhPGaXMX5j3h7fsrA== - dependencies: - "@jest/environment" "^26.5.2" - "@jest/fake-timers" "^26.5.2" - "@jest/types" "^26.5.2" + "@jest/environment" "^26.6.2" + "@jest/fake-timers" "^26.6.2" + "@jest/types" "^26.6.2" "@types/node" "*" - jest-mock "^26.5.2" - jest-util "^26.5.2" + jest-mock "^26.6.2" + jest-util "^26.6.2" jest-get-type@^24.9.0: version "24.9.0" @@ -17444,47 +16829,6 @@ jest-haste-map@^24.9.0: optionalDependencies: fsevents "^1.2.7" -jest-haste-map@^25.5.1: - version "25.5.1" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-25.5.1.tgz#1df10f716c1d94e60a1ebf7798c9fb3da2620943" - integrity sha512-dddgh9UZjV7SCDQUrQ+5t9yy8iEgKc1AKqZR9YDww8xsVOtzPQSMVLDChc21+g29oTRexb9/B0bIlZL+sWmvAQ== - dependencies: - "@jest/types" "^25.5.0" - "@types/graceful-fs" "^4.1.2" - anymatch "^3.0.3" - fb-watchman "^2.0.0" - graceful-fs "^4.2.4" - jest-serializer "^25.5.0" - jest-util "^25.5.0" - jest-worker "^25.5.0" - micromatch "^4.0.2" - sane "^4.0.3" - walker "^1.0.7" - which "^2.0.2" - optionalDependencies: - fsevents "^2.1.2" - -jest-haste-map@^26.0.1, jest-haste-map@^26.3.0, jest-haste-map@^26.5.2: - version "26.5.2" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.5.2.tgz#a15008abfc502c18aa56e4919ed8c96304ceb23d" - integrity sha512-lJIAVJN3gtO3k4xy+7i2Xjtwh8CfPcH08WYjZpe9xzveDaqGw9fVNCpkYu6M525wKFVkLmyi7ku+DxCAP1lyMA== - dependencies: - "@jest/types" "^26.5.2" - "@types/graceful-fs" "^4.1.2" - "@types/node" "*" - anymatch "^3.0.3" - fb-watchman "^2.0.0" - graceful-fs "^4.2.4" - jest-regex-util "^26.0.0" - jest-serializer "^26.5.0" - jest-util "^26.5.2" - jest-worker "^26.5.0" - micromatch "^4.0.2" - sane "^4.0.3" - walker "^1.0.7" - optionalDependencies: - fsevents "^2.1.2" - jest-haste-map@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.6.2.tgz#dd7e60fe7dc0e9f911a23d79c5ff7fb5c2cafeaa" @@ -17506,51 +16850,28 @@ jest-haste-map@^26.6.2: optionalDependencies: fsevents "^2.1.2" -jest-jasmine2@^25.5.4: - version "25.5.4" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-25.5.4.tgz#66ca8b328fb1a3c5364816f8958f6970a8526968" - integrity sha512-9acbWEfbmS8UpdcfqnDO+uBUgKa/9hcRh983IHdM+pKmJPL77G0sWAAK0V0kr5LK3a8cSBfkFSoncXwQlRZfkQ== +jest-jasmine2@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz#adc3cf915deacb5212c93b9f3547cd12958f2edd" + integrity sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg== dependencies: "@babel/traverse" "^7.1.0" - "@jest/environment" "^25.5.0" - "@jest/source-map" "^25.5.0" - "@jest/test-result" "^25.5.0" - "@jest/types" "^25.5.0" - chalk "^3.0.0" - co "^4.6.0" - expect "^25.5.0" - is-generator-fn "^2.0.0" - jest-each "^25.5.0" - jest-matcher-utils "^25.5.0" - jest-message-util "^25.5.0" - jest-runtime "^25.5.4" - jest-snapshot "^25.5.1" - jest-util "^25.5.0" - pretty-format "^25.5.0" - throat "^5.0.0" - -jest-jasmine2@^26.5.2: - version "26.5.2" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-26.5.2.tgz#0e33819d31b1f2aab5efd1e02ce502209c0e64a2" - integrity sha512-2J+GYcgLVPTkpmvHEj0/IDTIAuyblGNGlyGe4fLfDT2aktEPBYvoxUwFiOmDDxxzuuEAD2uxcYXr0+1Yw4tjFA== - dependencies: - "@babel/traverse" "^7.1.0" - "@jest/environment" "^26.5.2" - "@jest/source-map" "^26.5.0" - "@jest/test-result" "^26.5.2" - "@jest/types" "^26.5.2" + "@jest/environment" "^26.6.2" + "@jest/source-map" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" - expect "^26.5.2" + expect "^26.6.2" is-generator-fn "^2.0.0" - jest-each "^26.5.2" - jest-matcher-utils "^26.5.2" - jest-message-util "^26.5.2" - jest-runtime "^26.5.2" - jest-snapshot "^26.5.2" - jest-util "^26.5.2" - pretty-format "^26.5.2" + jest-each "^26.6.2" + jest-matcher-utils "^26.6.2" + jest-message-util "^26.6.2" + jest-runtime "^26.6.3" + jest-snapshot "^26.6.2" + jest-util "^26.6.2" + pretty-format "^26.6.2" throat "^5.0.0" jest-junit@^6.4.0: @@ -17563,21 +16884,13 @@ jest-junit@^6.4.0: strip-ansi "^4.0.0" xml "^1.0.1" -jest-leak-detector@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-25.5.0.tgz#2291c6294b0ce404241bb56fe60e2d0c3e34f0bb" - integrity sha512-rV7JdLsanS8OkdDpZtgBf61L5xZ4NnYLBq72r6ldxahJWWczZjXawRsoHyXzibM5ed7C2QRjpp6ypgwGdKyoVA== - dependencies: - jest-get-type "^25.2.6" - pretty-format "^25.5.0" - -jest-leak-detector@^26.5.2: - version "26.5.2" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-26.5.2.tgz#83fcf9a4a6ef157549552cb4f32ca1d6221eea69" - integrity sha512-h7ia3dLzBFItmYERaLPEtEKxy3YlcbcRSjj0XRNJgBEyODuu+3DM2o62kvIFvs3PsaYoIIv+e+nLRI61Dj1CNw== +jest-leak-detector@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz#7717cf118b92238f2eba65054c8a0c9c653a91af" + integrity sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg== dependencies: jest-get-type "^26.3.0" - pretty-format "^26.5.2" + pretty-format "^26.6.2" jest-matcher-utils@26.0.0: version "26.0.0" @@ -17589,25 +16902,15 @@ jest-matcher-utils@26.0.0: jest-get-type "^26.0.0" pretty-format "^26.0.0" -jest-matcher-utils@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-25.5.0.tgz#fbc98a12d730e5d2453d7f1ed4a4d948e34b7867" - integrity sha512-VWI269+9JS5cpndnpCwm7dy7JtGQT30UHfrnM3mXl22gHGt/b7NkjBqXfbhZ8V4B7ANUsjK18PlSBmG0YH7gjw== - dependencies: - chalk "^3.0.0" - jest-diff "^25.5.0" - jest-get-type "^25.2.6" - pretty-format "^25.5.0" - -jest-matcher-utils@^26.5.2: - version "26.5.2" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-26.5.2.tgz#6aa2c76ce8b9c33e66f8856ff3a52bab59e6c85a" - integrity sha512-W9GO9KBIC4gIArsNqDUKsLnhivaqf8MSs6ujO/JDcPIQrmY+aasewweXVET8KdrJ6ADQaUne5UzysvF/RR7JYA== +jest-matcher-utils@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz#8e6fd6e863c8b2d31ac6472eeb237bc595e53e7a" + integrity sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw== dependencies: chalk "^4.0.0" - jest-diff "^26.5.2" + jest-diff "^26.6.2" jest-get-type "^26.3.0" - pretty-format "^26.5.2" + pretty-format "^26.6.2" jest-message-util@^24.9.0: version "24.9.0" @@ -17623,31 +16926,18 @@ jest-message-util@^24.9.0: slash "^2.0.0" stack-utils "^1.0.1" -jest-message-util@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-25.5.0.tgz#ea11d93204cc7ae97456e1d8716251185b8880ea" - integrity sha512-ezddz3YCT/LT0SKAmylVyWWIGYoKHOFOFXx3/nA4m794lfVUskMcwhip6vTgdVrOtYdjeQeis2ypzes9mZb4EA== +jest-message-util@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.2.tgz#58173744ad6fc0506b5d21150b9be56ef001ca07" + integrity sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA== dependencies: "@babel/code-frame" "^7.0.0" - "@jest/types" "^25.5.0" - "@types/stack-utils" "^1.0.1" - chalk "^3.0.0" - graceful-fs "^4.2.4" - micromatch "^4.0.2" - slash "^3.0.0" - stack-utils "^1.0.1" - -jest-message-util@^26.3.0, jest-message-util@^26.5.2: - version "26.5.2" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.5.2.tgz#6c4c4c46dcfbabb47cd1ba2f6351559729bc11bb" - integrity sha512-Ocp9UYZ5Jl15C5PNsoDiGEk14A4NG0zZKknpWdZGoMzJuGAkVt10e97tnEVMYpk7LnQHZOfuK2j/izLBMcuCZw== - dependencies: - "@babel/code-frame" "^7.0.0" - "@jest/types" "^26.5.2" + "@jest/types" "^26.6.2" "@types/stack-utils" "^2.0.0" chalk "^4.0.0" graceful-fs "^4.2.4" micromatch "^4.0.2" + pretty-format "^26.6.2" slash "^3.0.0" stack-utils "^2.0.2" @@ -17658,22 +16948,15 @@ jest-mock@^24.0.0, jest-mock@^24.9.0: dependencies: "@jest/types" "^24.9.0" -jest-mock@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-25.5.0.tgz#a91a54dabd14e37ecd61665d6b6e06360a55387a" - integrity sha512-eXWuTV8mKzp/ovHc5+3USJMYsTBhyQ+5A1Mak35dey/RG8GlM4YWVylZuGgVXinaW6tpvk/RSecmF37FKUlpXA== +jest-mock@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.6.2.tgz#d6cb712b041ed47fe0d9b6fc3474bc6543feb302" + integrity sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew== dependencies: - "@jest/types" "^25.5.0" - -jest-mock@^26.5.2: - version "26.5.2" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.5.2.tgz#c9302e8ef807f2bfc749ee52e65ad11166a1b6a1" - integrity sha512-9SiU4b5PtO51v0MtJwVRqeGEroH66Bnwtq4ARdNP7jNXbpT7+ByeWNAk4NeT/uHfNSVDXEXgQo1XRuwEqS6Rdw== - dependencies: - "@jest/types" "^26.5.2" + "@jest/types" "^26.6.2" "@types/node" "*" -jest-pnp-resolver@^1.2.1, jest-pnp-resolver@^1.2.2: +jest-pnp-resolver@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== @@ -17683,174 +16966,89 @@ jest-regex-util@^24.9.0: resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-24.9.0.tgz#c13fb3380bde22bf6575432c493ea8fe37965636" integrity sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA== -jest-regex-util@^25.2.6: - version "25.2.6" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-25.2.6.tgz#d847d38ba15d2118d3b06390056028d0f2fd3964" - integrity sha512-KQqf7a0NrtCkYmZZzodPftn7fL1cq3GQAFVMn5Hg8uKx/fIenLEobNanUxb7abQ1sjADHBseG/2FGpsv/wr+Qw== - jest-regex-util@^26.0.0: version "26.0.0" resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28" integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A== -jest-resolve-dependencies@^25.5.4: - version "25.5.4" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-25.5.4.tgz#85501f53957c8e3be446e863a74777b5a17397a7" - integrity sha512-yFmbPd+DAQjJQg88HveObcGBA32nqNZ02fjYmtL16t1xw9bAttSn5UGRRhzMHIQbsep7znWvAvnD4kDqOFM0Uw== +jest-resolve-dependencies@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz#6680859ee5d22ee5dcd961fe4871f59f4c784fb6" + integrity sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg== dependencies: - "@jest/types" "^25.5.0" - jest-regex-util "^25.2.6" - jest-snapshot "^25.5.1" - -jest-resolve-dependencies@^26.4.2, jest-resolve-dependencies@^26.5.2: - version "26.5.2" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-26.5.2.tgz#ee30b7cfea81c81bf5e195a9287d7ec07f893170" - integrity sha512-LLkc8LuRtxqOx0AtX/Npa2C4I23WcIrwUgNtHYXg4owYF/ZDQShcwBAHjYZIFR06+HpQcZ43+kCTMlQ3aDCYTg== - dependencies: - "@jest/types" "^26.5.2" + "@jest/types" "^26.6.2" jest-regex-util "^26.0.0" - jest-snapshot "^26.5.2" + jest-snapshot "^26.6.2" -jest-resolve@^25.5.1: - version "25.5.1" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-25.5.1.tgz#0e6fbcfa7c26d2a5fe8f456088dc332a79266829" - integrity sha512-Hc09hYch5aWdtejsUZhA+vSzcotf7fajSlPA6EZPE1RmPBAD39XtJhvHWFStid58iit4IPDLI/Da4cwdDmAHiQ== +jest-resolve@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-26.6.2.tgz#a3ab1517217f469b504f1b56603c5bb541fbb507" + integrity sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ== dependencies: - "@jest/types" "^25.5.0" - browser-resolve "^1.11.3" - chalk "^3.0.0" - graceful-fs "^4.2.4" - jest-pnp-resolver "^1.2.1" - read-pkg-up "^7.0.1" - realpath-native "^2.0.0" - resolve "^1.17.0" - slash "^3.0.0" - -jest-resolve@^26.4.0, jest-resolve@^26.5.2: - version "26.5.2" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-26.5.2.tgz#0d719144f61944a428657b755a0e5c6af4fc8602" - integrity sha512-XsPxojXGRA0CoDD7Vis59ucz2p3cQFU5C+19tz3tLEAlhYKkK77IL0cjYjikY9wXnOaBeEdm1rOgSJjbZWpcZg== - dependencies: - "@jest/types" "^26.5.2" + "@jest/types" "^26.6.2" chalk "^4.0.0" graceful-fs "^4.2.4" jest-pnp-resolver "^1.2.2" - jest-util "^26.5.2" + jest-util "^26.6.2" read-pkg-up "^7.0.1" - resolve "^1.17.0" + resolve "^1.18.1" slash "^3.0.0" -jest-runner@^25.5.4: - version "25.5.4" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-25.5.4.tgz#ffec5df3875da5f5c878ae6d0a17b8e4ecd7c71d" - integrity sha512-V/2R7fKZo6blP8E9BL9vJ8aTU4TH2beuqGNxHbxi6t14XzTb+x90B3FRgdvuHm41GY8ch4xxvf0ATH4hdpjTqg== +jest-runner@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-26.6.3.tgz#2d1fed3d46e10f233fd1dbd3bfaa3fe8924be159" + integrity sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ== dependencies: - "@jest/console" "^25.5.0" - "@jest/environment" "^25.5.0" - "@jest/test-result" "^25.5.0" - "@jest/types" "^25.5.0" - chalk "^3.0.0" - exit "^0.1.2" - graceful-fs "^4.2.4" - jest-config "^25.5.4" - jest-docblock "^25.3.0" - jest-haste-map "^25.5.1" - jest-jasmine2 "^25.5.4" - jest-leak-detector "^25.5.0" - jest-message-util "^25.5.0" - jest-resolve "^25.5.1" - jest-runtime "^25.5.4" - jest-util "^25.5.0" - jest-worker "^25.5.0" - source-map-support "^0.5.6" - throat "^5.0.0" - -jest-runner@^26.4.2, jest-runner@^26.5.2: - version "26.5.2" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-26.5.2.tgz#4f9e6b0bb7eb4710c209a9e145b8a10894f4c19f" - integrity sha512-GKhYxtSX5+tXZsd2QwfkDqPIj5C2HqOdXLRc2x2qYqWE26OJh17xo58/fN/mLhRkO4y6o60ZVloan7Kk5YA6hg== - dependencies: - "@jest/console" "^26.5.2" - "@jest/environment" "^26.5.2" - "@jest/test-result" "^26.5.2" - "@jest/types" "^26.5.2" + "@jest/console" "^26.6.2" + "@jest/environment" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" "@types/node" "*" chalk "^4.0.0" emittery "^0.7.1" exit "^0.1.2" graceful-fs "^4.2.4" - jest-config "^26.5.2" + jest-config "^26.6.3" jest-docblock "^26.0.0" - jest-haste-map "^26.5.2" - jest-leak-detector "^26.5.2" - jest-message-util "^26.5.2" - jest-resolve "^26.5.2" - jest-runtime "^26.5.2" - jest-util "^26.5.2" - jest-worker "^26.5.0" + jest-haste-map "^26.6.2" + jest-leak-detector "^26.6.2" + jest-message-util "^26.6.2" + jest-resolve "^26.6.2" + jest-runtime "^26.6.3" + jest-util "^26.6.2" + jest-worker "^26.6.2" source-map-support "^0.5.6" throat "^5.0.0" -jest-runtime@^25.5.4: - version "25.5.4" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-25.5.4.tgz#dc981fe2cb2137abcd319e74ccae7f7eeffbfaab" - integrity sha512-RWTt8LeWh3GvjYtASH2eezkc8AehVoWKK20udV6n3/gC87wlTbE1kIA+opCvNWyyPeBs6ptYsc6nyHUb1GlUVQ== +jest-runtime@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-26.6.3.tgz#4f64efbcfac398331b74b4b3c82d27d401b8fa2b" + integrity sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw== dependencies: - "@jest/console" "^25.5.0" - "@jest/environment" "^25.5.0" - "@jest/globals" "^25.5.2" - "@jest/source-map" "^25.5.0" - "@jest/test-result" "^25.5.0" - "@jest/transform" "^25.5.1" - "@jest/types" "^25.5.0" - "@types/yargs" "^15.0.0" - chalk "^3.0.0" - collect-v8-coverage "^1.0.0" - exit "^0.1.2" - glob "^7.1.3" - graceful-fs "^4.2.4" - jest-config "^25.5.4" - jest-haste-map "^25.5.1" - jest-message-util "^25.5.0" - jest-mock "^25.5.0" - jest-regex-util "^25.2.6" - jest-resolve "^25.5.1" - jest-snapshot "^25.5.1" - jest-util "^25.5.0" - jest-validate "^25.5.0" - realpath-native "^2.0.0" - slash "^3.0.0" - strip-bom "^4.0.0" - yargs "^15.3.1" - -jest-runtime@^26.4.2, jest-runtime@^26.5.2: - version "26.5.2" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-26.5.2.tgz#b72f5f79eb2fe0c46bfef4cdb9c1e01d1c69ba41" - integrity sha512-zArr4DatX/Sn0wswX/AnAuJgmwgAR5rNtrUz36HR8BfMuysHYNq5sDbYHuLC4ICyRdy5ae/KQ+sczxyS9G6Qvw== - dependencies: - "@jest/console" "^26.5.2" - "@jest/environment" "^26.5.2" - "@jest/fake-timers" "^26.5.2" - "@jest/globals" "^26.5.2" - "@jest/source-map" "^26.5.0" - "@jest/test-result" "^26.5.2" - "@jest/transform" "^26.5.2" - "@jest/types" "^26.5.2" + "@jest/console" "^26.6.2" + "@jest/environment" "^26.6.2" + "@jest/fake-timers" "^26.6.2" + "@jest/globals" "^26.6.2" + "@jest/source-map" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" "@types/yargs" "^15.0.0" chalk "^4.0.0" + cjs-module-lexer "^0.6.0" collect-v8-coverage "^1.0.0" exit "^0.1.2" glob "^7.1.3" graceful-fs "^4.2.4" - jest-config "^26.5.2" - jest-haste-map "^26.5.2" - jest-message-util "^26.5.2" - jest-mock "^26.5.2" + jest-config "^26.6.3" + jest-haste-map "^26.6.2" + jest-message-util "^26.6.2" + jest-mock "^26.6.2" jest-regex-util "^26.0.0" - jest-resolve "^26.5.2" - jest-snapshot "^26.5.2" - jest-util "^26.5.2" - jest-validate "^26.5.2" + jest-resolve "^26.6.2" + jest-snapshot "^26.6.2" + jest-util "^26.6.2" + jest-validate "^26.6.2" slash "^3.0.0" strip-bom "^4.0.0" yargs "^15.4.1" @@ -17860,21 +17058,6 @@ jest-serializer@^24.9.0: resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-24.9.0.tgz#e6d7d7ef96d31e8b9079a714754c5d5c58288e73" integrity sha512-DxYipDr8OvfrKH3Kel6NdED3OXxjvxXZ1uIY2I9OFbGg+vUkkg7AGvi65qbhbWNPvDckXmzMPbK3u3HaDO49bQ== -jest-serializer@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-25.5.0.tgz#a993f484e769b4ed54e70e0efdb74007f503072b" - integrity sha512-LxD8fY1lByomEPflwur9o4e2a5twSQ7TaVNLlFUuToIdoJuBt8tzHfCsZ42Ok6LkKXWzFWf3AGmheuLAA7LcCA== - dependencies: - graceful-fs "^4.2.4" - -jest-serializer@^26.5.0: - version "26.5.0" - resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-26.5.0.tgz#f5425cc4c5f6b4b355f854b5f0f23ec6b962bc13" - integrity sha512-+h3Gf5CDRlSLdgTv7y0vPIAoLgX/SI7T4v6hy+TEXMgYbv+ztzbg5PSN6mUXAT/hXYHvZRWm+MaObVfqkhCGxA== - dependencies: - "@types/node" "*" - graceful-fs "^4.2.4" - jest-serializer@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-26.6.2.tgz#d139aafd46957d3a448f3a6cdabe2919ba0742d1" @@ -17883,61 +17066,28 @@ jest-serializer@^26.6.2: "@types/node" "*" graceful-fs "^4.2.4" -jest-snapshot@^25.5.1: - version "25.5.1" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-25.5.1.tgz#1a2a576491f9961eb8d00c2e5fd479bc28e5ff7f" - integrity sha512-C02JE1TUe64p2v1auUJ2ze5vcuv32tkv9PyhEb318e8XOKF7MOyXdJ7kdjbvrp3ChPLU2usI7Rjxs97Dj5P0uQ== +jest-snapshot@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-26.6.2.tgz#f3b0af1acb223316850bd14e1beea9837fb39c84" + integrity sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og== dependencies: "@babel/types" "^7.0.0" - "@jest/types" "^25.5.0" - "@types/prettier" "^1.19.0" - chalk "^3.0.0" - expect "^25.5.0" - graceful-fs "^4.2.4" - jest-diff "^25.5.0" - jest-get-type "^25.2.6" - jest-matcher-utils "^25.5.0" - jest-message-util "^25.5.0" - jest-resolve "^25.5.1" - make-dir "^3.0.0" - natural-compare "^1.4.0" - pretty-format "^25.5.0" - semver "^6.3.0" - -jest-snapshot@^26.4.2, jest-snapshot@^26.5.2: - version "26.5.2" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-26.5.2.tgz#0cf7642eaf8e8d2736bd443f619959bf237f9ccf" - integrity sha512-MkXIDvEefzDubI/WaDVSRH4xnkuirP/Pz8LhAIDXcVQTmcEfwxywj5LGwBmhz+kAAIldA7XM4l96vbpzltSjqg== - dependencies: - "@babel/types" "^7.0.0" - "@jest/types" "^26.5.2" + "@jest/types" "^26.6.2" "@types/babel__traverse" "^7.0.4" "@types/prettier" "^2.0.0" chalk "^4.0.0" - expect "^26.5.2" + expect "^26.6.2" graceful-fs "^4.2.4" - jest-diff "^26.5.2" + jest-diff "^26.6.2" jest-get-type "^26.3.0" - jest-haste-map "^26.5.2" - jest-matcher-utils "^26.5.2" - jest-message-util "^26.5.2" - jest-resolve "^26.5.2" + jest-haste-map "^26.6.2" + jest-matcher-utils "^26.6.2" + jest-message-util "^26.6.2" + jest-resolve "^26.6.2" natural-compare "^1.4.0" - pretty-format "^26.5.2" + pretty-format "^26.6.2" semver "^7.3.2" -jest-util@26.x, jest-util@^26.0.1, jest-util@^26.3.0, jest-util@^26.5.2: - version "26.5.2" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.5.2.tgz#8403f75677902cc52a1b2140f568e91f8ed4f4d7" - integrity sha512-WTL675bK+GSSAYgS8z9FWdCT2nccO1yTIplNLPlP0OD8tUk/H5IrWKMMRudIQQ0qp8bb4k+1Qa8CxGKq9qnYdg== - dependencies: - "@jest/types" "^26.5.2" - "@types/node" "*" - chalk "^4.0.0" - graceful-fs "^4.2.4" - is-ci "^2.0.0" - micromatch "^4.0.2" - jest-util@^24.0.0, jest-util@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.9.0.tgz#7396814e48536d2e85a37de3e4c431d7cb140162" @@ -17956,18 +17106,7 @@ jest-util@^24.0.0, jest-util@^24.9.0: slash "^2.0.0" source-map "^0.6.0" -jest-util@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-25.5.0.tgz#31c63b5d6e901274d264a4fec849230aa3fa35b0" - integrity sha512-KVlX+WWg1zUTB9ktvhsg2PXZVdkI1NBevOJSkTKYAyXyH4QSvh+Lay/e/v+bmaFfrkfx43xD8QTfgobzlEXdIA== - dependencies: - "@jest/types" "^25.5.0" - chalk "^3.0.0" - graceful-fs "^4.2.4" - is-ci "^2.0.0" - make-dir "^3.0.0" - -jest-util@^26.6.2: +jest-util@^26.1.0, jest-util@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.6.2.tgz#907535dbe4d5a6cb4c47ac9b926f6af29576cbc1" integrity sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q== @@ -17991,53 +17130,29 @@ jest-validate@^24.0.0: leven "^3.1.0" pretty-format "^24.9.0" -jest-validate@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-25.5.0.tgz#fb4c93f332c2e4cf70151a628e58a35e459a413a" - integrity sha512-okUFKqhZIpo3jDdtUXUZ2LxGUZJIlfdYBvZb1aczzxrlyMlqdnnws9MOxezoLGhSaFc2XYaHNReNQfj5zPIWyQ== +jest-validate@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.6.2.tgz#23d380971587150467342911c3d7b4ac57ab20ec" + integrity sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ== dependencies: - "@jest/types" "^25.5.0" - camelcase "^5.3.1" - chalk "^3.0.0" - jest-get-type "^25.2.6" - leven "^3.1.0" - pretty-format "^25.5.0" - -jest-validate@^26.4.2, jest-validate@^26.5.2: - version "26.5.2" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.5.2.tgz#7ea266700b64234cd1c0cee982490c5a80e9b0f0" - integrity sha512-FmJks0zY36mp6Af/5sqO6CTL9bNMU45yKCJk3hrz8d2aIqQIlN1pr9HPIwZE8blLaewOla134nt5+xAmWsx3SQ== - dependencies: - "@jest/types" "^26.5.2" + "@jest/types" "^26.6.2" camelcase "^6.0.0" chalk "^4.0.0" jest-get-type "^26.3.0" leven "^3.1.0" - pretty-format "^26.5.2" + pretty-format "^26.6.2" -jest-watcher@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-25.5.0.tgz#d6110d101df98badebe435003956fd4a465e8456" - integrity sha512-XrSfJnVASEl+5+bb51V0Q7WQx65dTSk7NL4yDdVjPnRNpM0hG+ncFmDYJo9O8jaSRcAitVbuVawyXCRoxGrT5Q== +jest-watcher@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-26.6.2.tgz#a5b683b8f9d68dbcb1d7dae32172d2cca0592975" + integrity sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ== dependencies: - "@jest/test-result" "^25.5.0" - "@jest/types" "^25.5.0" - ansi-escapes "^4.2.1" - chalk "^3.0.0" - jest-util "^25.5.0" - string-length "^3.1.0" - -jest-watcher@^26.3.0, jest-watcher@^26.5.2: - version "26.5.2" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-26.5.2.tgz#2957f4461007e0769d74b537379ecf6b7c696916" - integrity sha512-i3m1NtWzF+FXfJ3ljLBB/WQEp4uaNhX7QcQUWMokcifFTUQBDFyUMEwk0JkJ1kopHbx7Een3KX0Q7+9koGM/Pw== - dependencies: - "@jest/test-result" "^26.5.2" - "@jest/types" "^26.5.2" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" - jest-util "^26.5.2" + jest-util "^26.6.2" string-length "^4.0.1" jest-worker@^24.9.0: @@ -18048,7 +17163,7 @@ jest-worker@^24.9.0: merge-stream "^2.0.0" supports-color "^6.1.0" -jest-worker@^25.1.0, jest-worker@^25.5.0: +jest-worker@^25.1.0: version "25.5.0" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-25.5.0.tgz#2611d071b79cea0f43ee57a3d118593ac1547db1" integrity sha512-/dsSmUkIy5EBGfv/IjjqmFxrNAUpBERfGs1oHROyD7yxjG/w+t0GOJDX8O1k32ySmd7+a5IhnJU2qQFcJ4n1vw== @@ -18065,32 +17180,14 @@ jest-worker@^26.2.1, jest-worker@^26.6.2: merge-stream "^2.0.0" supports-color "^7.0.0" -jest-worker@^26.3.0, jest-worker@^26.5.0: - version "26.5.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.5.0.tgz#87deee86dbbc5f98d9919e0dadf2c40e3152fa30" - integrity sha512-kTw66Dn4ZX7WpjZ7T/SUDgRhapFRKWmisVAF0Rv4Fu8SLFD7eLbqpLvbxVqYhSgaWa7I+bW7pHnbyfNsH6stug== +jest@26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest/-/jest-26.6.3.tgz#40e8fdbe48f00dfa1f0ce8121ca74b88ac9148ef" + integrity sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q== dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^7.0.0" - -jest@26.4.2: - version "26.4.2" - resolved "https://registry.yarnpkg.com/jest/-/jest-26.4.2.tgz#7e8bfb348ec33f5459adeaffc1a25d5752d9d312" - integrity sha512-LLCjPrUh98Ik8CzW8LLVnSCfLaiY+wbK53U7VxnFSX7Q+kWC4noVeDvGWIFw0Amfq1lq2VfGm7YHWSLBV62MJw== - dependencies: - "@jest/core" "^26.4.2" + "@jest/core" "^26.6.3" import-local "^3.0.2" - jest-cli "^26.4.2" - -jest@^25: - version "25.5.4" - resolved "https://registry.yarnpkg.com/jest/-/jest-25.5.4.tgz#f21107b6489cfe32b076ce2adcadee3587acb9db" - integrity sha512-hHFJROBTqZahnO+X+PMtT6G2/ztqAZJveGqz//FnWWHurizkD05PQGzRZOhF3XP6z7SJmL+5tCfW8qV06JypwQ== - dependencies: - "@jest/core" "^25.5.4" - import-local "^3.0.2" - jest-cli "^25.5.4" + jest-cli "^26.6.3" jju@~1.4.0: version "1.4.0" @@ -18973,7 +18070,7 @@ loglevelnext@^1.0.1: es6-symbol "^3.1.1" object.assign "^4.1.0" -lolex@^5.0.0, lolex@^5.0.1, lolex@^5.1.2: +lolex@^5.0.1, lolex@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/lolex/-/lolex-5.1.2.tgz#953694d098ce7c07bc5ed6d0e42bc6c0c6d5a367" integrity sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A== @@ -19778,6 +18875,13 @@ monaco-editor@*, monaco-editor@0.20.0: resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.20.0.tgz#5d5009343a550124426cb4d965a4d27a348b4dea" integrity sha512-hkvf4EtPJRMQlPC3UbMoRs0vTAFAYdzFQ+gpMb8A+9znae1c43q8Mab9iVsgTcg/4PNiLGGn3SlDIa8uvK1FIQ== +moo-color@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/moo-color/-/moo-color-1.0.2.tgz#837c40758d2d58763825d1359a84e330531eca64" + integrity sha512-5iXz5n9LWQzx/C2WesGFfpE6RLamzdHwsn3KpfzShwbfIqs7stnoEpaNErf/7+3mbxwZ4s8Foq7I0tPxw7BWHg== + dependencies: + color-name "^1.1.4" + moo@^0.4.3: version "0.4.3" resolved "https://registry.yarnpkg.com/moo/-/moo-0.4.3.tgz#3f847a26f31cf625a956a87f2b10fbc013bfd10e" @@ -20149,17 +19253,6 @@ node-modules-regexp@^1.0.0: resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= -node-notifier@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-6.0.0.tgz#cea319e06baa16deec8ce5cd7f133c4a46b68e12" - integrity sha512-SVfQ/wMw+DesunOm5cKqr6yDcvUTDl/yc97ybGHMrteNEY6oekXpNpS3lZwgLlwz0FLgHoiW28ZpmBHUDg37cw== - dependencies: - growly "^1.3.0" - is-wsl "^2.1.1" - semver "^6.3.0" - shellwords "^0.1.1" - which "^1.3.1" - node-notifier@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-8.0.0.tgz#a7eee2d51da6d0f7ff5094bc7108c911240c1620" @@ -21025,13 +20118,6 @@ parse-asn1@^5.0.0: pbkdf2 "^3.0.3" safe-buffer "^5.1.1" -parse-color@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/parse-color/-/parse-color-1.0.0.tgz#7b748b95a83f03f16a94f535e52d7f3d94658619" - integrity sha1-e3SLlag/A/FqlPU15S1/PZRlhhk= - dependencies: - color-convert "~0.5.0" - parse-entities@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8" @@ -22343,16 +21429,6 @@ pretty-format@^26.0.0, pretty-format@^26.6.2: ansi-styles "^4.0.0" react-is "^17.0.1" -pretty-format@^26.5.2: - version "26.5.2" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.5.2.tgz#5d896acfdaa09210683d34b6dc0e6e21423cd3e1" - integrity sha512-VizyV669eqESlkOikKJI8Ryxl/kPpbdLwNdPs2GrbQs18MpySB5S0Yo0N7zkg2xTRiFq4CFw8ct5Vg4a0xP0og== - dependencies: - "@jest/types" "^26.5.2" - ansi-regex "^5.0.0" - ansi-styles "^4.0.0" - react-is "^16.12.0" - pretty-hrtime@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" @@ -23751,11 +22827,6 @@ realpath-native@^1.1.0: dependencies: util.promisify "^1.0.0" -realpath-native@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-2.0.0.tgz#7377ac429b6e1fd599dc38d08ed942d0d7beb866" - integrity sha512-v1SEYUOXXdbBZK8ZuNgO4TBjamPsiSgcFr0aP+tEKpQZK8vooEUqV6nm6Cv502mX4NF2EfsnVqtNAHG+/6Ur1Q== - recast@~0.11.12: version "0.11.23" resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.23.tgz#451fd3004ab1e4df9b4e4b66376b2a21912462d3" @@ -24370,11 +23441,6 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@1.1.7, resolve@~1.1.0: - version "1.1.7" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" - integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= - resolve@1.15.1, resolve@^1.1.7: version "1.15.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" @@ -24411,6 +23477,11 @@ resolve@^1.14.2: dependencies: path-parse "^1.0.6" +resolve@~1.1.0: + version "1.1.7" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" + integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= + resolve@~1.11.1: version "1.11.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.11.1.tgz#ea10d8110376982fef578df8fc30b9ac30a07a3e" @@ -25856,14 +24927,6 @@ string-argv@0.3.1, string-argv@~0.3.1: resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== -string-length@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-3.1.0.tgz#107ef8c23456e187a8abd4a61162ff4ac6e25837" - integrity sha512-Ttp5YvkGm5v9Ijagtaz1BnN+k9ObpvS0eIBblPMp2YWL8FBmi9qblQ9fexc2k/CXFgrTIteU3jAw3payCnwSTA== - dependencies: - astral-regex "^1.0.0" - strip-ansi "^5.2.0" - string-length@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.1.tgz#4a973bf31ef77c4edbceadd6af2611996985f8a1" @@ -26908,22 +25971,22 @@ ts-essentials@^2.0.3: resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-2.0.12.tgz#c9303f3d74f75fa7528c3d49b80e089ab09d8745" integrity sha512-3IVX4nI6B5cc31/GFFE+i8ey/N2eA0CZDbo6n0yrz0zDX8ZJ8djmU1p+XRz7G3is0F3bB3pu2pAroFdAWQKU3w== -ts-jest@26.3.0: - version "26.3.0" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.3.0.tgz#6b2845045347dce394f069bb59358253bc1338a9" - integrity sha512-Jq2uKfx6bPd9+JDpZNMBJMdMQUC3sJ08acISj8NXlVgR2d5OqslEHOR2KHMgwymu8h50+lKIm0m0xj/ioYdW2Q== +ts-jest@26.4.4: + version "26.4.4" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.4.4.tgz#61f13fb21ab400853c532270e52cc0ed7e502c49" + integrity sha512-3lFWKbLxJm34QxyVNNCgXX1u4o/RV0myvA2y2Bxm46iGIjKlaY0own9gIckbjZJPn+WaJEnfPPJ20HHGpoq4yg== dependencies: "@types/jest" "26.x" bs-logger "0.x" buffer-from "1.x" fast-json-stable-stringify "2.x" - jest-util "26.x" + jest-util "^26.1.0" json5 "2.x" lodash.memoize "4.x" make-error "1.x" mkdirp "1.x" semver "7.x" - yargs-parser "18.x" + yargs-parser "20.x" ts-loader@6.2.1: version "6.2.1" @@ -27600,19 +26663,10 @@ v8-compile-cache@^2.0.3: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e" integrity sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g== -v8-to-istanbul@^4.1.3: - version "4.1.4" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-4.1.4.tgz#b97936f21c0e2d9996d4985e5c5156e9d4e49cd6" - integrity sha512-Rw6vJHj1mbdK8edjR7+zuJrpDtKIgNdAvTSAcpYfgMIw+u2dPDntD3dgN4XQFLU2/fvFQdzj+EeSGfd/jnY5fQ== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.1" - convert-source-map "^1.6.0" - source-map "^0.7.3" - -v8-to-istanbul@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-5.0.1.tgz#0608f5b49a481458625edb058488607f25498ba5" - integrity sha512-mbDNjuDajqYe3TXFk5qxcQy8L1msXNE37WTlLoqqpBfRsimbNcrlhQlDPntmECEcUvdC+AQ8CyMMf6EUx1r74Q== +v8-to-istanbul@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-7.0.0.tgz#b4fe00e35649ef7785a9b7fcebcea05f37c332fc" + integrity sha512-fLL2rFuQpMtm9r8hrAV2apXX/WqHJ6+IC4/eQVdMDGBUgH/YMV4Gv3duk3kjmyg6uiQWBAA9nJwue4iJUOkHeA== dependencies: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^1.6.0" @@ -28429,13 +27483,10 @@ yargs-parser@13.1.1, yargs-parser@^13.1.0, yargs-parser@^13.1.1: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@18.x, yargs-parser@^18.1.1, yargs-parser@^18.1.2: - version "18.1.3" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" - integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" +yargs-parser@20.x, yargs-parser@^20.2.2: + version "20.2.4" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" + integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== yargs-parser@^11.1.1: version "11.1.1" @@ -28461,10 +27512,13 @@ yargs-parser@^18.1.0: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^20.2.2: - version "20.2.4" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" - integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== +yargs-parser@^18.1.2: + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" yargs-unparser@1.6.0: version "1.6.0" @@ -28560,23 +27614,6 @@ yargs@^15.0.0: y18n "^4.0.0" yargs-parser "^18.1.0" -yargs@^15.3.1: - version "15.3.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.3.1.tgz#9505b472763963e54afe60148ad27a330818e98b" - integrity sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA== - dependencies: - cliui "^6.0.0" - decamelize "^1.2.0" - find-up "^4.1.0" - get-caller-file "^2.0.1" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^4.2.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^18.1.1" - yargs@^15.4.1: version "15.4.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" From ea64dda1e305a5ed87a4c708f747b37c35e2bc62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sun, 29 Nov 2020 18:10:51 +0100 Subject: [PATCH 013/124] ReactGridLayout: Update dependency to 1.2 (#29455) --- package.json | 4 ++-- yarn.lock | 26 +++++++++++++------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 7d4ccbbf00b..416c233be34 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "@types/react": "16.9.9", "@types/react-beautiful-dnd": "12.1.2", "@types/react-dom": "16.9.9", - "@types/react-grid-layout": "0.16.7", + "@types/react-grid-layout": "1.1.1", "@types/react-redux": "7.1.7", "@types/react-select": "3.0.8", "@types/react-test-renderer": "16.9.2", @@ -267,7 +267,7 @@ "react": "17.0.1", "react-beautiful-dnd": "13.0.0", "react-dom": "17.0.1", - "react-grid-layout": "0.17.1", + "react-grid-layout": "1.2.0", "react-highlight-words": "0.16.0", "react-loadable": "5.5.0", "react-popper": "2.2.4", diff --git a/yarn.lock b/yarn.lock index 45aa0f33292..b85a645da7d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6832,10 +6832,10 @@ dependencies: "@types/react" "*" -"@types/react-grid-layout@0.16.7": - version "0.16.7" - resolved "https://registry.yarnpkg.com/@types/react-grid-layout/-/react-grid-layout-0.16.7.tgz#53d5f5034deb0c60e25a0fa578141e9a0982011f" - integrity sha512-A3tW9xySd03KGONkp8gP4+QRLuT1Mcx++m0hO0nZIM4H/Qwz8GsiDv+9okbmHk5HcsHwY5Jdsn6Cv50hwoNG+A== +"@types/react-grid-layout@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/react-grid-layout/-/react-grid-layout-1.1.1.tgz#1eb322fa88cd1475bdc60ecca9676f6853e18887" + integrity sha512-bvPkITzwGGOZKjp01nVSgPrdfGm/uTa5t8Odd8vQRXJsLj7uZLZXSXgWr+TiXBAkUsmHPxhsyswXQCiFeDuZnQ== dependencies: "@types/react" "*" @@ -22238,16 +22238,16 @@ react-fast-compare@^3.0.1, react-fast-compare@^3.2.0: resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== -react-grid-layout@0.17.1: - version "0.17.1" - resolved "https://registry.yarnpkg.com/react-grid-layout/-/react-grid-layout-0.17.1.tgz#0b75c72e5b7d7085f0f0054e160b31298971c5e2" - integrity sha512-L+wHFevK+klKvoAHuHn4Q5qHtrW+4zCj0F3QnpR7wZbkZPmrdaWC7cztFLXwINq6WnOWGE22BCTCDCHJi7dVDw== +react-grid-layout@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/react-grid-layout/-/react-grid-layout-1.2.0.tgz#87124d549c86c8df8841666618c8c3e3cb205c26" + integrity sha512-fJMGQFguphkAs0NsLNf8hz9cUv9B642JYei2yddiPby/X/kJ4HFIaMUhhqg1ArVfn/vHet1+h+LE4n85cFPh+Q== dependencies: classnames "2.x" lodash.isequal "^4.0.0" prop-types "^15.0.0" react-draggable "^4.0.0" - react-resizable "^1.9.0" + react-resizable "^1.10.0" react-helmet-async@^1.0.2: version "1.0.7" @@ -22415,10 +22415,10 @@ react-refresh@^0.8.3: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f" integrity sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg== -react-resizable@^1.9.0: - version "1.10.1" - resolved "https://registry.yarnpkg.com/react-resizable/-/react-resizable-1.10.1.tgz#f0c2cf1d83b3470b87676ce6d6b02bbe3f4d8cd4" - integrity sha512-Jd/bKOKx6+19NwC4/aMLRu/J9/krfxlDnElP41Oc+oLiUWs/zwV1S9yBfBZRnqAwQb6vQ/HRSk3bsSWGSgVbpw== +react-resizable@^1.10.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/react-resizable/-/react-resizable-1.11.0.tgz#0b237c4aff16937b7663de1045861749683227ad" + integrity sha512-VoGz2ddxUFvildS8r8/29UZJeyiM3QJnlmRZSuXm+FpTqq/eIrMPc796Y9XQLg291n2hFZJtIoP1xC3hSTw/jg== dependencies: prop-types "15.x" react-draggable "^4.0.3" From 097dcc456a617bc67c3d5134e22adc00ad5b79c5 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Sun, 29 Nov 2020 09:22:16 -0800 Subject: [PATCH 014/124] MinMax: keep global min/main in field state (#29406) --- .../grafana-data/src/field/fieldDisplay.ts | 7 +++-- .../src/field/fieldOverrides.test.ts | 8 ++--- .../grafana-data/src/field/fieldOverrides.ts | 31 +++++++------------ .../field/getFieldDisplayValuesProxy.test.tsx | 1 - packages/grafana-data/src/field/scale.ts | 24 ++++++++------ packages/grafana-data/src/types/dataFrame.ts | 13 ++++++++ .../grafana-data/src/types/fieldOverrides.ts | 1 - .../src/components/Table/BarGaugeCell.tsx | 3 +- .../uPlot/config/UPlotConfigBuilder.test.ts | 4 +-- .../uPlot/config/UPlotScaleBuilder.ts | 20 ++++++++---- .../dashboard/utils/loadSnapshotData.ts | 1 - .../panel/panellinks/linkSuppliers.test.ts | 1 - .../features/query/state/PanelQueryRunner.ts | 1 - .../plugins/panel/bargauge/BarGaugePanel.tsx | 1 - public/app/plugins/panel/gauge/GaugePanel.tsx | 1 - public/app/plugins/panel/stat/StatPanel.tsx | 1 - 16 files changed, 65 insertions(+), 53 deletions(-) diff --git a/packages/grafana-data/src/field/fieldDisplay.ts b/packages/grafana-data/src/field/fieldDisplay.ts index 9fc5b325fc5..5d24df79091 100644 --- a/packages/grafana-data/src/field/fieldDisplay.ts +++ b/packages/grafana-data/src/field/fieldDisplay.ts @@ -77,7 +77,6 @@ export interface GetFieldDisplayValuesOptions { replaceVariables: InterpolateFunction; sparkline?: boolean; // Calculate the sparkline theme: GrafanaTheme; - autoMinMax?: boolean; timeZone?: TimeZone; } @@ -122,7 +121,11 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi continue; } - const config = field.config; // already set by the prepare task + let config = field.config; // already set by the prepare task + if (field.state?.range) { + // Us the global min/max values + config = { ...config, ...field.state?.range }; + } const displayName = field.config.displayName ?? defaultDisplayName; const display = diff --git a/packages/grafana-data/src/field/fieldOverrides.test.ts b/packages/grafana-data/src/field/fieldOverrides.test.ts index 6551b922acf..d8ebd19e783 100644 --- a/packages/grafana-data/src/field/fieldOverrides.test.ts +++ b/packages/grafana-data/src/field/fieldOverrides.test.ts @@ -293,16 +293,15 @@ describe('applyFieldOverrides', () => { replaceVariables: (undefined as any) as InterpolateFunction, getDataSourceSettingsByUid: undefined as any, theme: getTestTheme(), - autoMinMax: true, })[0]; const valueColumn = data.fields[1]; - const config = valueColumn.config; + const range = valueColumn.state!.range!; // Keep max from the original setting - expect(config.max).toEqual(0); + expect(range.max).toEqual(0); // Don't Automatically pick the min value - expect(config.min).toEqual(-20); + expect(range.min).toEqual(-20); }); it('getLinks should use applied field config', () => { @@ -317,7 +316,6 @@ describe('applyFieldOverrides', () => { }) as InterpolateFunction, getDataSourceSettingsByUid: undefined as any, theme: getTestTheme(), - autoMinMax: true, fieldConfigRegistry: customFieldRegistry, })[0]; diff --git a/packages/grafana-data/src/field/fieldOverrides.ts b/packages/grafana-data/src/field/fieldOverrides.ts index 839aaa28aba..a1f141a80a1 100644 --- a/packages/grafana-data/src/field/fieldOverrides.ts +++ b/packages/grafana-data/src/field/fieldOverrides.ts @@ -13,6 +13,7 @@ import { GrafanaTheme, InterpolateFunction, LinkModel, + NumericRange, ScopedVars, TimeZone, ValueLinkConfig, @@ -40,12 +41,7 @@ interface OverrideProps { properties: DynamicConfigValue[]; } -interface GlobalMinMax { - min?: number | null; - max?: number | null; -} - -export function findNumericFieldMinMax(data: DataFrame[]): GlobalMinMax { +export function findNumericFieldMinMax(data: DataFrame[]): NumericRange { let min: number | null = null; let max: number | null = null; @@ -69,7 +65,7 @@ export function findNumericFieldMinMax(data: DataFrame[]): GlobalMinMax { } } - return { min, max }; + return { min, max, delta: (max ?? 0) - (min ?? 0) }; } /** @@ -88,7 +84,7 @@ export function applyFieldOverrides(options: ApplyFieldOverrideOptions): DataFra const fieldConfigRegistry = options.fieldConfigRegistry ?? standardFieldConfigEditorRegistry; let seriesIndex = 0; - let range: GlobalMinMax | undefined = undefined; + let globalRange: NumericRange | undefined = undefined; // Prepare the Matchers const override: OverrideProps[] = []; @@ -178,18 +174,14 @@ export function applyFieldOverrides(options: ApplyFieldOverrideOptions): DataFra } // Set the Min/Max value automatically - if (options.autoMinMax && field.type === FieldType.number) { - if (!isNumber(config.min) || !isNumber(config.max)) { - if (!range) { - range = findNumericFieldMinMax(options.data!); // Global value - } - if (!isNumber(config.min)) { - config.min = range.min; - } - if (!isNumber(config.max)) { - config.max = range.max; - } + let range: NumericRange | undefined = undefined; + if (field.type === FieldType.number) { + if (!globalRange && (!isNumber(config.min) || !isNumber(config.max))) { + globalRange = findNumericFieldMinMax(options.data!); } + const min = config.min ?? globalRange!.min; + const max = config.max ?? globalRange!.max; + range = { min, max, delta: max! - min! }; } // Some color modes needs series index to assign field color so we count @@ -207,6 +199,7 @@ export function applyFieldOverrides(options: ApplyFieldOverrideOptions): DataFra ...field.state, displayName: null, seriesIndex, + range, }, }; diff --git a/packages/grafana-data/src/field/getFieldDisplayValuesProxy.test.tsx b/packages/grafana-data/src/field/getFieldDisplayValuesProxy.test.tsx index e1dc2a5aad9..26aa02f9261 100644 --- a/packages/grafana-data/src/field/getFieldDisplayValuesProxy.test.tsx +++ b/packages/grafana-data/src/field/getFieldDisplayValuesProxy.test.tsx @@ -32,7 +32,6 @@ describe('getFieldDisplayValuesProxy', () => { getDataSourceSettingsByUid: (val: string) => ({} as any), timeZone: 'utc', theme: getTestTheme(), - autoMinMax: true, })[0]; it('should define all display functions', () => { diff --git a/packages/grafana-data/src/field/scale.ts b/packages/grafana-data/src/field/scale.ts index 32933c5dc26..35d76d35095 100644 --- a/packages/grafana-data/src/field/scale.ts +++ b/packages/grafana-data/src/field/scale.ts @@ -1,6 +1,6 @@ import { isNumber } from 'lodash'; import { reduceField, ReducerID } from '../transformations/fieldReducer'; -import { Field, FieldType, GrafanaTheme, Threshold } from '../types'; +import { Field, FieldConfig, FieldType, GrafanaTheme, NumericRange, Threshold } from '../types'; import { getFieldColorModeForField } from './fieldColor'; import { getActiveThresholdForValue } from './thresholds'; @@ -15,7 +15,7 @@ export type ScaleCalculator = (value: number) => ScaledValue; export function getScaleCalculator(field: Field, theme: GrafanaTheme): ScaleCalculator { const mode = getFieldColorModeForField(field); const getColor = mode.getCalculator(field, theme); - const info = getMinMaxAndDelta(field); + const info = field.state?.range ?? getMinMaxAndDelta(field); return (value: number) => { let percent = 0; @@ -34,13 +34,7 @@ export function getScaleCalculator(field: Field, theme: GrafanaTheme): ScaleCalc }; } -interface FieldMinMaxInfo { - min?: number | null; - max?: number | null; - delta: number; -} - -function getMinMaxAndDelta(field: Field): FieldMinMaxInfo { +function getMinMaxAndDelta(field: Field): NumericRange { if (field.type !== FieldType.number) { return { min: 0, max: 100, delta: 100 }; } @@ -70,3 +64,15 @@ function getMinMaxAndDelta(field: Field): FieldMinMaxInfo { delta: max! - min!, }; } + +export function getFieldConfigWithMinMax(field: Field, local?: boolean): FieldConfig { + const { config } = field; + let { min, max } = config; + if (isNumber(min) && !isNumber(max)) { + return config; // noop + } + if (local || !field.state?.range) { + return { ...config, ...getMinMaxAndDelta(field) }; + } + return { ...config, ...field.state.range }; +} diff --git a/packages/grafana-data/src/types/dataFrame.ts b/packages/grafana-data/src/types/dataFrame.ts index 3fdd660a80b..d634ab11e6c 100644 --- a/packages/grafana-data/src/types/dataFrame.ts +++ b/packages/grafana-data/src/types/dataFrame.ts @@ -126,6 +126,13 @@ export interface FieldState { */ calcs?: FieldCalcs; + /** + * The numeric range for values in this field. This value will respect the min/max + * set in field config, or when set to `auto` this will have the min/max for all data + * in the response + */ + range?: NumericRange; + /** * Appropriate values for templating */ @@ -138,6 +145,12 @@ export interface FieldState { seriesIndex?: number; } +export interface NumericRange { + min?: number | null; + max?: number | null; + delta: number; +} + export interface DataFrame extends QueryResultBase { name?: string; fields: Field[]; // All fields of equal length diff --git a/packages/grafana-data/src/types/fieldOverrides.ts b/packages/grafana-data/src/types/fieldOverrides.ts index 335b3e2d5d9..cedce20cbd2 100644 --- a/packages/grafana-data/src/types/fieldOverrides.ts +++ b/packages/grafana-data/src/types/fieldOverrides.ts @@ -117,7 +117,6 @@ export interface ApplyFieldOverrideOptions { getDataSourceSettingsByUid: (uid: string) => DataSourceInstanceSettings | undefined; theme: GrafanaTheme; timeZone?: TimeZone; - autoMinMax?: boolean; fieldConfigRegistry?: FieldConfigOptionsRegistry; } diff --git a/packages/grafana-ui/src/components/Table/BarGaugeCell.tsx b/packages/grafana-ui/src/components/Table/BarGaugeCell.tsx index 99ba69f3759..e4fd2f2491f 100644 --- a/packages/grafana-ui/src/components/Table/BarGaugeCell.tsx +++ b/packages/grafana-ui/src/components/Table/BarGaugeCell.tsx @@ -2,6 +2,7 @@ import React, { FC } from 'react'; import { ThresholdsConfig, ThresholdsMode, VizOrientation } from '@grafana/data'; import { BarGauge, BarGaugeDisplayMode } from '../BarGauge/BarGauge'; import { TableCellProps, TableCellDisplayMode } from './types'; +import { getFieldConfigWithMinMax } from '@grafana/data/src/field/scale'; const defaultScale: ThresholdsConfig = { mode: ThresholdsMode.Absolute, @@ -20,7 +21,7 @@ const defaultScale: ThresholdsConfig = { export const BarGaugeCell: FC = props => { const { field, column, tableStyles, cell, cellProps } = props; - let { config } = field; + let config = getFieldConfigWithMinMax(field, false); if (!config.thresholds) { config = { ...config, diff --git a/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.test.ts b/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.test.ts index a02d84d6a3f..9d5e6b2c19c 100644 --- a/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.test.ts +++ b/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.test.ts @@ -22,12 +22,10 @@ describe('UPlotConfigBuilder', () => { "axes": Array [], "scales": Object { "scale-x": Object { - "range": undefined, "time": true, }, "scale-y": Object { - "range": undefined, - "time": false, + "range": [Function], }, }, "series": Array [ diff --git a/packages/grafana-ui/src/components/uPlot/config/UPlotScaleBuilder.ts b/packages/grafana-ui/src/components/uPlot/config/UPlotScaleBuilder.ts index 796f7c432aa..e3f822b422b 100644 --- a/packages/grafana-ui/src/components/uPlot/config/UPlotScaleBuilder.ts +++ b/packages/grafana-ui/src/components/uPlot/config/UPlotScaleBuilder.ts @@ -1,5 +1,4 @@ -import isNumber from 'lodash/isNumber'; -import { Scale } from 'uplot'; +import uPlot, { Scale } from 'uplot'; import { PlotConfigBuilder } from '../types'; export interface ScaleProps { @@ -11,12 +10,21 @@ export interface ScaleProps { export class UPlotScaleBuilder extends PlotConfigBuilder { getConfig() { - const { isTime, scaleKey, min, max } = this.props; - const range = isNumber(min) && isNumber(max) ? [min, max] : undefined; + const { isTime, scaleKey } = this.props; + if (isTime) { + return { + [scaleKey]: { + time: true, // TODO? this should be based on the query range, not the data + }, + }; + } return { [scaleKey]: { - time: !!isTime, - range, + range: (u: uPlot, dataMin: number, dataMax: number) => { + const { min, max } = this.props; + const [smin, smax] = uPlot.rangeNum(min ?? dataMin, max ?? dataMax, 0.1 as any, true); + return [min ?? smin, max ?? smax]; + }, }, }; } diff --git a/public/app/features/dashboard/utils/loadSnapshotData.ts b/public/app/features/dashboard/utils/loadSnapshotData.ts index 534ab23c468..64b1e40c5cb 100644 --- a/public/app/features/dashboard/utils/loadSnapshotData.ts +++ b/public/app/features/dashboard/utils/loadSnapshotData.ts @@ -16,7 +16,6 @@ export function loadSnapshotData(panel: PanelModel, dashboard: DashboardModel): defaults: {}, overrides: [], }, - autoMinMax: true, replaceVariables: panel.replaceVariables, getDataSourceSettingsByUid: getDatasourceSrv().getDataSourceSettingsByUid.bind(getDatasourceSrv()), fieldConfigRegistry: panel.plugin!.fieldConfigRegistry, diff --git a/public/app/features/panel/panellinks/linkSuppliers.test.ts b/public/app/features/panel/panellinks/linkSuppliers.test.ts index 941b83b320a..d75a097d137 100644 --- a/public/app/features/panel/panellinks/linkSuppliers.test.ts +++ b/public/app/features/panel/panellinks/linkSuppliers.test.ts @@ -93,7 +93,6 @@ describe('getFieldLinksSupplier', () => { getDataSourceSettingsByUid: (val: string) => ({} as any), timeZone: 'utc', theme: getTheme(), - autoMinMax: true, })[0]; const rowIndex = 0; diff --git a/public/app/features/query/state/PanelQueryRunner.ts b/public/app/features/query/state/PanelQueryRunner.ts index cb94ce09b8e..7aa8daac174 100644 --- a/public/app/features/query/state/PanelQueryRunner.ts +++ b/public/app/features/query/state/PanelQueryRunner.ts @@ -89,7 +89,6 @@ export class PanelQueryRunner { ...processedData, series: applyFieldOverrides({ timeZone: timeZone, - autoMinMax: true, data: processedData.series, ...fieldConfig, }), diff --git a/public/app/plugins/panel/bargauge/BarGaugePanel.tsx b/public/app/plugins/panel/bargauge/BarGaugePanel.tsx index 7f9e02de58e..503c255692c 100644 --- a/public/app/plugins/panel/bargauge/BarGaugePanel.tsx +++ b/public/app/plugins/panel/bargauge/BarGaugePanel.tsx @@ -75,7 +75,6 @@ export class BarGaugePanel extends PureComponent> { replaceVariables, theme: config.theme, data: data.series, - autoMinMax: true, timeZone, }); }; diff --git a/public/app/plugins/panel/gauge/GaugePanel.tsx b/public/app/plugins/panel/gauge/GaugePanel.tsx index 99dd84cf8be..47f3f07c1ef 100644 --- a/public/app/plugins/panel/gauge/GaugePanel.tsx +++ b/public/app/plugins/panel/gauge/GaugePanel.tsx @@ -57,7 +57,6 @@ export class GaugePanel extends PureComponent> { replaceVariables, theme: config.theme, data: data.series, - autoMinMax: true, timeZone, }); }; diff --git a/public/app/plugins/panel/stat/StatPanel.tsx b/public/app/plugins/panel/stat/StatPanel.tsx index 6eb9470e1dc..e9d5049498c 100644 --- a/public/app/plugins/panel/stat/StatPanel.tsx +++ b/public/app/plugins/panel/stat/StatPanel.tsx @@ -103,7 +103,6 @@ export class StatPanel extends PureComponent> { theme: config.theme, data: data.series, sparkline: options.graphMode !== BigValueGraphMode.None, - autoMinMax: true, timeZone, }); }; From 3c229c6bee68149003fd2683364f93a3d0427b4a Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Tue, 1 Dec 2020 09:28:18 +0100 Subject: [PATCH 015/124] Log: Use os.Open to open file for reading (#29483) Signed-off-by: Arve Knudsen --- pkg/infra/log/file.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/infra/log/file.go b/pkg/infra/log/file.go index 8810d97e959..59ccf52f063 100644 --- a/pkg/infra/log/file.go +++ b/pkg/infra/log/file.go @@ -123,13 +123,13 @@ func (w *FileLogWriter) createLogFile() (*os.File, error) { } func (w *FileLogWriter) lineCounter() (int, error) { - r, err := os.OpenFile(w.Filename, os.O_RDONLY, 0644) + r, err := os.Open(w.Filename) if err != nil { - return 0, fmt.Errorf("lineCounter Open File : %s", err) + return 0, fmt.Errorf("failed to open file %q: %w", w.Filename, err) } + buf := make([]byte, 32*1024) count := 0 - for { c, err := r.Read(buf) count += bytes.Count(buf[:c], []byte{'\n'}) From cbb2c0a5d8e5172bb8bc0ad2ba6fffa77d990e6d Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Tue, 1 Dec 2020 09:38:35 +0100 Subject: [PATCH 016/124] Field overrides: added matcher to match all fields within frame/query. (#28872) --- .../src/transformations/matchers/ids.ts | 1 + .../matchers/nameMatcher.test.ts | 28 +++++++++ .../transformations/matchers/nameMatcher.ts | 30 ++++++++- .../MatchersUI/FieldsByFrameRefIdMatcher.tsx | 63 +++++++++++++++++++ .../components/MatchersUI/fieldMatchersUI.ts | 3 +- 5 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 packages/grafana-ui/src/components/MatchersUI/FieldsByFrameRefIdMatcher.tsx diff --git a/packages/grafana-data/src/transformations/matchers/ids.ts b/packages/grafana-data/src/transformations/matchers/ids.ts index cd3976627a0..cb6f0184a38 100644 --- a/packages/grafana-data/src/transformations/matchers/ids.ts +++ b/packages/grafana-data/src/transformations/matchers/ids.ts @@ -23,6 +23,7 @@ export enum FieldMatcherID { byNames = 'byNames', byRegexp = 'byRegexp', byRegexpOrNames = 'byRegexpOrNames', + byFrameRefID = 'byFrameRefID', // byIndex = 'byIndex', // byLabel = 'byLabel', } diff --git a/packages/grafana-data/src/transformations/matchers/nameMatcher.test.ts b/packages/grafana-data/src/transformations/matchers/nameMatcher.test.ts index fefca6bd406..5459cd5fffc 100644 --- a/packages/grafana-data/src/transformations/matchers/nameMatcher.test.ts +++ b/packages/grafana-data/src/transformations/matchers/nameMatcher.test.ts @@ -306,3 +306,31 @@ describe('Field Regexp or Names Matcher', () => { } }); }); + +describe('Fields returned by query with refId', () => { + it('Match all fields in frame with refId: A', () => { + const data = [ + toDataFrame({ + refId: 'A', + fields: [{ name: 'field_1' }, { name: 'field_2' }], + }), + toDataFrame({ + refId: 'B', + fields: [{ name: 'field_1' }, { name: 'field_2' }], + }), + ]; + + const matcher = getFieldMatcher({ + id: FieldMatcherID.byFrameRefID, + options: 'A', + }); + + const frameA = data[0]; + expect(matcher(frameA.fields[0], frameA, data)).toBe(true); + expect(matcher(frameA.fields[1], frameA, data)).toBe(true); + + const frameB = data[1]; + expect(matcher(frameB.fields[0], frameB, data)).toBe(false); + expect(matcher(frameB.fields[1], frameB, data)).toBe(false); + }); +}); diff --git a/packages/grafana-data/src/transformations/matchers/nameMatcher.ts b/packages/grafana-data/src/transformations/matchers/nameMatcher.ts index 16534ba4638..efaa7d0a4d1 100644 --- a/packages/grafana-data/src/transformations/matchers/nameMatcher.ts +++ b/packages/grafana-data/src/transformations/matchers/nameMatcher.ts @@ -66,6 +66,28 @@ const regexpFieldNameMatcher: FieldMatcherInfo = { }, }; +/** + * Field matcher that will match all fields that exists in a + * data frame with configured refId. + * @public + */ +const fieldsInFrameMatcher: FieldMatcherInfo = { + id: FieldMatcherID.byFrameRefID, + name: 'Fields by frame refId', + description: 'match all fields returned in data frame with refId.', + defaultOptions: '', + + get: (refId: string): FieldMatcher => { + return (field: Field, frame: DataFrame, allFrames: DataFrame[]) => { + return frame.refId === refId; + }; + }, + + getOptionsDisplayText: (refId: string): string => { + return `Math all fields returned by query with reference ID: ${refId}`; + }, +}; + const regexpOrMultipleNamesMatcher: FieldMatcherInfo = { id: FieldMatcherID.byRegexpOrNames, name: 'Field Name by Regexp or Names', @@ -127,7 +149,13 @@ const frameNameMatcher: FrameMatcherInfo = { * Registry Initialization */ export function getFieldNameMatchers(): FieldMatcherInfo[] { - return [fieldNameMatcher, regexpFieldNameMatcher, multipleFieldNamesMatcher, regexpOrMultipleNamesMatcher]; + return [ + fieldNameMatcher, + regexpFieldNameMatcher, + multipleFieldNamesMatcher, + regexpOrMultipleNamesMatcher, + fieldsInFrameMatcher, + ]; } export function getFrameNameMatchers(): FrameMatcherInfo[] { diff --git a/packages/grafana-ui/src/components/MatchersUI/FieldsByFrameRefIdMatcher.tsx b/packages/grafana-ui/src/components/MatchersUI/FieldsByFrameRefIdMatcher.tsx new file mode 100644 index 00000000000..c80c742411c --- /dev/null +++ b/packages/grafana-ui/src/components/MatchersUI/FieldsByFrameRefIdMatcher.tsx @@ -0,0 +1,63 @@ +import React, { memo, useMemo, useCallback } from 'react'; +import { MatcherUIProps, FieldMatcherUIRegistryItem } from './types'; +import { FieldMatcherID, fieldMatchers, SelectableValue, DataFrame } from '@grafana/data'; +import { Select } from '../Select/Select'; + +/** + * UI to configure "fields by frame refId"-matcher. + * @public + */ +export const FieldsByFrameRefIdMatcher = memo>(props => { + const { data, options, onChange: onChangeFromProps } = props; + const referenceIDs = useFrameRefIds(data); + const selectOptions = useSelectOptions(referenceIDs); + + const onChange = useCallback( + (selection: SelectableValue) => { + if (!selection.value || !referenceIDs.has(selection.value)) { + return; + } + return onChangeFromProps(selection.value); + }, + [referenceIDs, onChangeFromProps] + ); + + const selectedOption = selectOptions.find(v => v.value === options); + return + +
+
Match
+ + ); + }; +} + +export const getBasicValueMatchersUI = (): Array> => { + return [ + { + name: 'Is greater', + id: ValueMatcherID.greater, + component: basicMatcherEditor({ + validator: value => !isNaN(value), + }), + }, + { + name: 'Is greater or equal', + id: ValueMatcherID.greaterOrEqual, + component: basicMatcherEditor({ + validator: value => !isNaN(value), + }), + }, + { + name: 'Is lower', + id: ValueMatcherID.lower, + component: basicMatcherEditor({ + validator: value => !isNaN(value), + }), + }, + { + name: 'Is lower or equal', + id: ValueMatcherID.lowerOrEqual, + component: basicMatcherEditor({ + validator: value => !isNaN(value), + }), + }, + { + name: 'Is equal', + id: ValueMatcherID.equal, + component: basicMatcherEditor({ + validator: () => true, + }), + }, + { + name: 'Is not equal', + id: ValueMatcherID.notEqual, + component: basicMatcherEditor({ + validator: () => true, + }), + }, + { + name: 'Regex', + id: ValueMatcherID.regex, + component: basicMatcherEditor({ + validator: () => true, + converter: (value: any) => String(value), + }), + }, + ]; +}; diff --git a/public/app/core/components/TransformersUI/FilterByValueTransformer/ValueMatchers/NoopMatcherEditor.tsx b/public/app/core/components/TransformersUI/FilterByValueTransformer/ValueMatchers/NoopMatcherEditor.tsx new file mode 100644 index 00000000000..84119754b0b --- /dev/null +++ b/public/app/core/components/TransformersUI/FilterByValueTransformer/ValueMatchers/NoopMatcherEditor.tsx @@ -0,0 +1,22 @@ +import { ValueMatcherID } from '@grafana/data'; +import React from 'react'; +import { ValueMatcherUIProps, ValueMatcherUIRegistryItem } from './types'; + +export const NoopMatcherEditor: React.FC> = () => { + return null; +}; + +export const getNoopValueMatchersUI = (): Array> => { + return [ + { + name: 'Is null', + id: ValueMatcherID.isNull, + component: NoopMatcherEditor, + }, + { + name: 'Is not null', + id: ValueMatcherID.isNotNull, + component: NoopMatcherEditor, + }, + ]; +}; diff --git a/public/app/core/components/TransformersUI/FilterByValueTransformer/ValueMatchers/RangeMatcherEditor.tsx b/public/app/core/components/TransformersUI/FilterByValueTransformer/ValueMatchers/RangeMatcherEditor.tsx new file mode 100644 index 00000000000..55bc8284a1d --- /dev/null +++ b/public/app/core/components/TransformersUI/FilterByValueTransformer/ValueMatchers/RangeMatcherEditor.tsx @@ -0,0 +1,81 @@ +import React, { useCallback, useState } from 'react'; +import { Input } from '@grafana/ui'; +import { ValueMatcherID, RangeValueMatcherOptions } from '@grafana/data'; +import { ValueMatcherEditorConfig, ValueMatcherUIProps, ValueMatcherUIRegistryItem } from './types'; +import { convertToType } from './utils'; + +type PropNames = 'from' | 'to'; + +export function rangeMatcherEditor( + config: ValueMatcherEditorConfig +): React.FC>> { + return ({ options, onChange, field }) => { + const { validator } = config; + const [isInvalid, setInvalid] = useState({ + from: !validator(options.from), + to: !validator(options.to), + }); + + const onChangeValue = useCallback( + (event: React.FormEvent, prop: PropNames) => { + setInvalid({ + ...isInvalid, + [prop]: !validator(event.currentTarget.value), + }); + }, + [setInvalid, validator, isInvalid] + ); + + const onChangeOptions = useCallback( + (event: React.FocusEvent, prop: PropNames) => { + if (isInvalid[prop]) { + return; + } + + const { value } = event.currentTarget; + + onChange({ + ...options, + [prop]: convertToType(value, field), + }); + }, + [options, onChange, isInvalid, field] + ); + + return ( + <> + onChangeValue(event, 'from')} + onBlur={event => onChangeOptions(event, 'from')} + /> +
and
+ onChangeValue(event, 'to')} + onBlur={event => onChangeOptions(event, 'to')} + /> + + ); + }; +} + +export const getRangeValueMatchersUI = (): Array> => { + return [ + { + name: 'Is between', + id: ValueMatcherID.between, + component: rangeMatcherEditor({ + validator: value => { + return !isNaN(value); + }, + }), + }, + ]; +}; diff --git a/public/app/core/components/TransformersUI/FilterByValueTransformer/ValueMatchers/types.ts b/public/app/core/components/TransformersUI/FilterByValueTransformer/ValueMatchers/types.ts new file mode 100644 index 00000000000..56a7b799a9d --- /dev/null +++ b/public/app/core/components/TransformersUI/FilterByValueTransformer/ValueMatchers/types.ts @@ -0,0 +1,14 @@ +import { Field, RegistryItem } from '@grafana/data'; +export interface ValueMatcherUIRegistryItem extends RegistryItem { + component: React.ComponentType>; +} + +export interface ValueMatcherUIProps { + options: TOptions; + onChange: (options: TOptions) => void; + field: Field; +} +export interface ValueMatcherEditorConfig { + validator: (value: any) => boolean; + converter?: (value: any, field: Field) => any; +} diff --git a/public/app/core/components/TransformersUI/FilterByValueTransformer/ValueMatchers/utils.ts b/public/app/core/components/TransformersUI/FilterByValueTransformer/ValueMatchers/utils.ts new file mode 100644 index 00000000000..f30f590a476 --- /dev/null +++ b/public/app/core/components/TransformersUI/FilterByValueTransformer/ValueMatchers/utils.ts @@ -0,0 +1,34 @@ +import { Field, FieldType } from '@grafana/data'; +import { isString, isUndefined } from 'lodash'; + +export function convertToType(value: any, field: Field): any { + switch (field.type) { + case FieldType.boolean: + if (isUndefined(value)) { + return false; + } + return convertToBool(value); + + case FieldType.number: + if (isNaN(value)) { + return 0; + } + return parseFloat(value); + + case FieldType.string: + if (!value) { + return ''; + } + return String(value); + + default: + return value; + } +} + +const convertToBool = (value: any): boolean => { + if (isString(value)) { + return !(value[0] === 'F' || value[0] === 'f' || value[0] === '0'); + } + return !!value; +}; diff --git a/public/app/core/components/TransformersUI/FilterByValueTransformer/ValueMatchers/valueMatchersUI.ts b/public/app/core/components/TransformersUI/FilterByValueTransformer/ValueMatchers/valueMatchersUI.ts new file mode 100644 index 00000000000..0517333aca6 --- /dev/null +++ b/public/app/core/components/TransformersUI/FilterByValueTransformer/ValueMatchers/valueMatchersUI.ts @@ -0,0 +1,9 @@ +import { Registry } from '@grafana/data'; +import { getBasicValueMatchersUI } from './BasicMatcherEditor'; +import { getNoopValueMatchersUI } from './NoopMatcherEditor'; +import { getRangeValueMatchersUI } from './RangeMatcherEditor'; +import { ValueMatcherUIRegistryItem } from './types'; + +export const valueMatchersUI = new Registry>(() => { + return [...getBasicValueMatchersUI(), ...getNoopValueMatchersUI(), ...getRangeValueMatchersUI()]; +}); diff --git a/public/app/core/utils/standardTransformers.ts b/public/app/core/utils/standardTransformers.ts index 63773a37ab6..25d81984164 100644 --- a/public/app/core/utils/standardTransformers.ts +++ b/public/app/core/utils/standardTransformers.ts @@ -2,6 +2,7 @@ import { TransformerRegistyItem } from '@grafana/data'; import { reduceTransformRegistryItem } from '../components/TransformersUI/ReduceTransformerEditor'; import { filterFieldsByNameTransformRegistryItem } from '../components/TransformersUI/FilterByNameTransformerEditor'; import { filterFramesByRefIdTransformRegistryItem } from '../components/TransformersUI/FilterByRefIdTransformerEditor'; +import { filterByValueTransformRegistryItem } from '../components/TransformersUI/FilterByValueTransformer/FilterByValueTransformerEditor'; import { organizeFieldsTransformRegistryItem } from '../components/TransformersUI/OrganizeFieldsTransformerEditor'; import { seriesToFieldsTransformerRegistryItem } from '../components/TransformersUI/SeriesToFieldsTransformerEditor'; import { calculateFieldTransformRegistryItem } from '../components/TransformersUI/CalculateFieldTransformerEditor'; @@ -16,6 +17,7 @@ export const getStandardTransformers = (): Array> => reduceTransformRegistryItem, filterFieldsByNameTransformRegistryItem, filterFramesByRefIdTransformRegistryItem, + filterByValueTransformRegistryItem, organizeFieldsTransformRegistryItem, seriesToFieldsTransformerRegistryItem, seriesToRowsTransformerRegistryItem, From dc937492877c2a0919b5e8ba17186036b9d1e073 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 1 Dec 2020 10:32:12 +0100 Subject: [PATCH 019/124] Grunt: Removes grunt dependency and replaces some of its usage (#29461) * Grunt: Removes grunt dependency and replaces some of it usage with alternatives * Removed precommit toolkit task * Fixed toolkit import * Some cleanup of unused / not working stuff in build.go * Removed sass errors --- .eslintrc | 8 +- Gruntfile.js | 66 -- build.go | 259 +----- package.json | 29 +- packages/grafana-toolkit/src/cli/index.ts | 8 +- .../src/cli/tasks/nodeVersionChecker.ts | 2 +- .../src/cli/tasks/precommit.ts | 67 -- scripts/grunt/build_task.js | 6 - scripts/grunt/default_task.js | 56 -- scripts/grunt/options/clean.js | 10 - scripts/grunt/options/compress.js | 32 - scripts/grunt/options/exec.js | 16 - scripts/grunt/options/sasslint.js | 9 - scripts/grunt/options/webpack.js | 13 - scripts/grunt/release_task.js | 40 - scripts/grunt/style_guide_task.js | 46 - yarn.lock | 819 +++--------------- 17 files changed, 172 insertions(+), 1314 deletions(-) delete mode 100644 Gruntfile.js delete mode 100644 packages/grafana-toolkit/src/cli/tasks/precommit.ts delete mode 100644 scripts/grunt/build_task.js delete mode 100644 scripts/grunt/default_task.js delete mode 100644 scripts/grunt/options/clean.js delete mode 100644 scripts/grunt/options/compress.js delete mode 100644 scripts/grunt/options/exec.js delete mode 100644 scripts/grunt/options/sasslint.js delete mode 100644 scripts/grunt/options/webpack.js delete mode 100644 scripts/grunt/release_task.js delete mode 100644 scripts/grunt/style_guide_task.js diff --git a/.eslintrc b/.eslintrc index 6df70e8521f..6688e1bc107 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,6 +1,9 @@ { "extends": ["@grafana/eslint-config"], "root": true, + "plugins": [ + "no-only-tests" + ], "overrides": [ { "files": [ @@ -15,10 +18,11 @@ "react/no-unescaped-entities": "off", "react/display-name": "off", "react/no-deprecated": "off", - "react/no-unknown-property": "off", + "react/no-unknown-property": "off", "react/no-children-prop": "off", "react/no-find-dom-node": "off", - "react/no-render-return-value": "off" + "react/no-render-return-value": "off", + "no-only-tests/no-only-tests": "error" } } ] diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index b07bca92a13..00000000000 --- a/Gruntfile.js +++ /dev/null @@ -1,66 +0,0 @@ -'use strict'; -module.exports = function(grunt) { - var os = require('os'); - var config = { - pkg: grunt.file.readJSON('package.json'), - baseDir: '.', - srcDir: 'public', - genDir: 'public_gen', - destDir: 'dist', - tempDir: 'tmp', - platform: process.platform.replace('win32', 'windows'), - enterprise: false, - libc: null, - }; - - if (grunt.option('platform')) { - config.platform = grunt.option('platform'); - } - - if (grunt.option('enterprise')) { - config.enterprise = true; - } - - if (grunt.option('arch')) { - config.arch = grunt.option('arch'); - } else { - config.arch = os.arch(); - - if (process.platform.match(/^win/)) { - config.arch = process.env.hasOwnProperty('ProgramFiles(x86)') ? 'x64' : 'x86'; - } - } - - if (grunt.option('libc')) { - config.libc = grunt.option('libc'); - } - - config.pkg.version = grunt.option('pkgVer') || config.pkg.version; - - console.log('Version', config.pkg.version); - - // load plugins - require('load-grunt-tasks')(grunt); - - // load task definitions - grunt.loadTasks('./scripts/grunt'); - - // Utility function to load plugin settings into config - function loadConfig(config, path) { - require('glob') - .sync('*', { cwd: path }) - .forEach(function(option) { - var key = option.replace(/\.js$/, ''); - // If key already exists, extend it. It is your responsibility to avoid naming collisions - config[key] = config[key] || {}; - grunt.util._.extend(config[key], require(path + option)(config, grunt)); - }); - // technically not required - return config; - } - - // Merge that object with what with whatever we have here - loadConfig(config, './scripts/grunt/options/'); - // pass the config to grunt - grunt.initConfig(config); -}; diff --git a/build.go b/build.go index 906678fd485..4fcb08e6d8b 100644 --- a/build.go +++ b/build.go @@ -30,13 +30,13 @@ const ( var ( //versionRe = regexp.MustCompile(`-[0-9]{1,3}-g[0-9a-f]{5,10}`) - goarch string - goos string - gocc string - cgo bool - libc string - pkgArch string - version string = "v1" + goarch string + goos string + gocc string + cgo bool + libc string + pkgArch string + version string = "v1" buildTags []string // deb & rpm does not support semver so have to handle their version a little differently linuxPackageVersion string = "v1" @@ -125,34 +125,7 @@ func main() { } case "build-frontend": - grunt(gruntBuildArg("build")...) - - case "test": - test("./pkg/...") - grunt("test") - - case "package": - grunt(gruntBuildArg("build")...) - grunt(gruntBuildArg("package")...) - if goos == linux { - createLinuxPackages() - } - - case "package-only": - grunt(gruntBuildArg("package")...) - if goos == linux { - createLinuxPackages() - } - case "pkg-archive": - grunt(gruntBuildArg("package")...) - - case "pkg-rpm": - grunt(gruntBuildArg("release")...) - createRpmPackages() - - case "pkg-deb": - grunt(gruntBuildArg("release")...) - createDebPackages() + yarn("build") case "sha-dist": shaFilesInDist() @@ -233,202 +206,8 @@ func readVersionFromPackageJson() { } } -type linuxPackageOptions struct { - packageType string - packageArch string - homeDir string - homeBinDir string - binPath string - serverBinPath string - cliBinPath string - configDir string - ldapFilePath string - etcDefaultPath string - etcDefaultFilePath string - initdScriptFilePath string - systemdServiceFilePath string - - postinstSrc string - initdScriptSrc string - defaultFileSrc string - systemdFileSrc string - cliBinaryWrapperSrc string - - depends []string -} - -func createDebPackages() { - debPkgArch := pkgArch - if pkgArch == "armv7" || pkgArch == "armv6" { - debPkgArch = "armhf" - } - - createPackage(linuxPackageOptions{ - packageType: "deb", - packageArch: debPkgArch, - homeDir: "/usr/share/grafana", - homeBinDir: "/usr/share/grafana/bin", - binPath: "/usr/sbin", - configDir: "/etc/grafana", - etcDefaultPath: "/etc/default", - etcDefaultFilePath: "/etc/default/grafana-server", - initdScriptFilePath: "/etc/init.d/grafana-server", - systemdServiceFilePath: "/usr/lib/systemd/system/grafana-server.service", - - postinstSrc: "packaging/deb/control/postinst", - initdScriptSrc: "packaging/deb/init.d/grafana-server", - defaultFileSrc: "packaging/deb/default/grafana-server", - systemdFileSrc: "packaging/deb/systemd/grafana-server.service", - cliBinaryWrapperSrc: "packaging/wrappers/grafana-cli", - - depends: []string{"adduser", "libfontconfig1"}, - }) -} - -func createRpmPackages() { - rpmPkgArch := pkgArch - switch { - case pkgArch == "armv7": - rpmPkgArch = "armhfp" - case pkgArch == "arm64": - rpmPkgArch = "aarch64" - } - createPackage(linuxPackageOptions{ - packageType: "rpm", - packageArch: rpmPkgArch, - homeDir: "/usr/share/grafana", - homeBinDir: "/usr/share/grafana/bin", - binPath: "/usr/sbin", - configDir: "/etc/grafana", - etcDefaultPath: "/etc/sysconfig", - etcDefaultFilePath: "/etc/sysconfig/grafana-server", - initdScriptFilePath: "/etc/init.d/grafana-server", - systemdServiceFilePath: "/usr/lib/systemd/system/grafana-server.service", - - postinstSrc: "packaging/rpm/control/postinst", - initdScriptSrc: "packaging/rpm/init.d/grafana-server", - defaultFileSrc: "packaging/rpm/sysconfig/grafana-server", - systemdFileSrc: "packaging/rpm/systemd/grafana-server.service", - cliBinaryWrapperSrc: "packaging/wrappers/grafana-cli", - - depends: []string{"/sbin/service", "fontconfig", "freetype", "urw-fonts"}, - }) -} - -func createLinuxPackages() { - if !skipDebGen { - createDebPackages() - } - - if !skipRpmGen { - createRpmPackages() - } -} - -func createPackage(options linuxPackageOptions) { - packageRoot, _ := ioutil.TempDir("", "grafana-linux-pack") - - // create directories - runPrint("mkdir", "-p", filepath.Join(packageRoot, options.homeDir)) - runPrint("mkdir", "-p", filepath.Join(packageRoot, options.configDir)) - runPrint("mkdir", "-p", filepath.Join(packageRoot, "/etc/init.d")) - runPrint("mkdir", "-p", filepath.Join(packageRoot, options.etcDefaultPath)) - runPrint("mkdir", "-p", filepath.Join(packageRoot, "/usr/lib/systemd/system")) - runPrint("mkdir", "-p", filepath.Join(packageRoot, "/usr/sbin")) - - // copy grafana-cli wrapper - runPrint("cp", "-p", options.cliBinaryWrapperSrc, filepath.Join(packageRoot, "/usr/sbin/"+cliBinary)) - - // copy grafana-server binary - runPrint("cp", "-p", filepath.Join(workingDir, "tmp/bin/"+serverBinary), filepath.Join(packageRoot, "/usr/sbin/"+serverBinary)) - - // copy init.d script - runPrint("cp", "-p", options.initdScriptSrc, filepath.Join(packageRoot, options.initdScriptFilePath)) - // copy environment var file - runPrint("cp", "-p", options.defaultFileSrc, filepath.Join(packageRoot, options.etcDefaultFilePath)) - // copy systemd file - runPrint("cp", "-p", options.systemdFileSrc, filepath.Join(packageRoot, options.systemdServiceFilePath)) - // copy release files - runPrint("cp", "-a", filepath.Join(workingDir, "tmp")+"/.", filepath.Join(packageRoot, options.homeDir)) - // remove bin path - runPrint("rm", "-rf", filepath.Join(packageRoot, options.homeDir, "bin")) - - // create /bin within home - runPrint("mkdir", "-p", filepath.Join(packageRoot, options.homeBinDir)) - // The grafana-cli binary is exposed through a wrapper to ensure a proper - // configuration is in place. To enable that, we need to store the original - // binary in a separate location to avoid conflicts. - runPrint("cp", "-p", filepath.Join(workingDir, "tmp/bin/"+cliBinary), filepath.Join(packageRoot, options.homeBinDir, cliBinary)) - - args := []string{ - "-s", "dir", - "--description", "Grafana", - "-C", packageRoot, - "--url", "https://grafana.com", - "--maintainer", "contact@grafana.com", - "--config-files", options.initdScriptFilePath, - "--config-files", options.etcDefaultFilePath, - "--config-files", options.systemdServiceFilePath, - "--after-install", options.postinstSrc, - - "--version", linuxPackageVersion, - "-p", "./dist", - } - - name := "grafana" - if enterprise { - name += "-enterprise" - args = append(args, "--replaces", "grafana") - } - fmt.Printf("pkgArch is set to '%s', generated arch is '%s'\n", pkgArch, options.packageArch) - if pkgArch == "armv6" { - name += "-rpi" - args = append(args, "--replaces", "grafana") - } - args = append(args, "--name", name) - - description := "Grafana" - if enterprise { - description += " Enterprise" - } - - if !enterprise { - args = append(args, "--license", "\"Apache 2.0\"") - } - - if options.packageType == "rpm" { - args = append(args, "--rpm-posttrans", "packaging/rpm/control/posttrans") - } - - if options.packageType == "deb" { - args = append(args, "--deb-no-default-config-files") - } - - if options.packageArch != "" { - args = append(args, "-a", options.packageArch) - } - - if linuxPackageIteration != "" { - args = append(args, "--iteration", linuxPackageIteration) - } - - // add dependencies - for _, dep := range options.depends { - args = append(args, "--depends", dep) - } - - args = append(args, ".") - - fmt.Println("Creating package: ", options.packageType) - runPrint("fpm", append([]string{"-t", options.packageType}, args...)...) -} - -func grunt(params ...string) { - if runtime.GOOS == windows { - runPrint(`.\node_modules\.bin\grunt`, params...) - } else { - runPrint("./node_modules/.bin/grunt", params...) - } +func yarn(params ...string) { + runPrint(`yarn run`, params...) } func genPackageVersion() string { @@ -439,24 +218,6 @@ func genPackageVersion() string { } } -func gruntBuildArg(task string) []string { - args := []string{task} - args = append(args, fmt.Sprintf("--pkgVer=%v", genPackageVersion())) - if pkgArch != "" { - args = append(args, fmt.Sprintf("--arch=%v", pkgArch)) - } - if libc != "" { - args = append(args, fmt.Sprintf("--libc=%s", libc)) - } - if enterprise { - args = append(args, "--enterprise") - } - - args = append(args, fmt.Sprintf("--platform=%v", goos)) - - return args -} - func setup() { args := []string{"install", "-v"} if goos == windows { diff --git a/package.json b/package.json index 416c233be34..9f7fa18d1da 100644 --- a/package.json +++ b/package.json @@ -7,13 +7,15 @@ "repository": "github:grafana/grafana", "scripts": { "api-tests": "jest --notify --watch --config=devenv/e2e-api-tests/jest.js", - "build": "grunt build", + "build": "node ./node_modules/webpack/bin/webpack.js --config scripts/webpack/webpack.prod.js", "dev": "webpack --progress --colors --config scripts/webpack/webpack.dev.js", "e2e": "./e2e/start-and-run-suite", "e2e:debug": "./e2e/start-and-run-suite debug", "e2e:dev": "./e2e/start-and-run-suite dev", "test": "jest --notify --watch", - "lint": "eslint . --ext .js,.tsx,.ts --cache", + "lint": "yarn run lint:ts && yarn run lint:sass", + "lint:ts": "eslint . --ext .js,.tsx,.ts --cache", + "lint:sass": "yarn run sass-lint -c public/sass/.sass-lint.yml 'public/sass/**/*.scss, packages/**/*.scss' -v -i '**/node_modules/**/*.scss'", "test:ci": "mkdir -p reports/junit && export JEST_JUNIT_OUTPUT_DIR=reports/junit && jest --ci --reporters=default --reporters=jest-junit -w ${TEST_MAX_WORKERS:-100%}", "lint:fix": "yarn lint --fix", "packages:build": "lerna run clean && lerna run build --ignore @grafana-plugins/input-datasource", @@ -27,7 +29,7 @@ "packages:publishDev": "lerna publish from-package --contents dist --dist-tag dev --yes --registry http://grafana-npm.local:4873 --force-publish=*", "packages:typecheck": "lerna run typecheck", "packages:clean": "lerna run clean", - "precommit": "grafana-toolkit precommit", + "precommit": "yarn run lint-staged", "prettier:check": "prettier --list-different \"**/*.{ts,tsx,scss}\"", "prettier:write": "prettier --list-different \"**/*.{ts,tsx,scss}\" --write", "start": "grafana-toolkit core:start --watchTheme", @@ -38,10 +40,10 @@ "storybook": "yarn workspace @grafana/ui storybook --ci", "storybook:build": "yarn workspace @grafana/ui storybook:build", "themes:generate": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/generateSassVariableFiles.ts", - "typecheck": "tsc --noEmit", + "typecheck": "tsc --noEmit && yarn run packages:typecheck", "plugins:build-bundled": "grafana-toolkit plugin:bundle-managed", "watch": "yarn start -d watch,start core:start --watchTheme", - "ci:test-frontend": "yarn run prettier:check && yarn run packages:typecheck && yarn run typecheck && yarn grunt no-only-tests && yarn run test:ci" + "ci:test-frontend": "yarn run prettier:check && yarn run typecheck && yarn run lint && yarn run test:ci && yarn grafana-toolkit node-version-check" }, "grafana": { "whatsNewUrl": "https://grafana.com/docs/grafana/latest/guides/whats-new-in-v7-3/", @@ -49,7 +51,7 @@ }, "husky": { "hooks": { - "pre-commit": "lint-staged && npm run precommit" + "pre-commit": "yarn run precommit" } }, "lint-staged": { @@ -134,9 +136,10 @@ "es-abstract": "1.18.0-next.1", "es6-promise": "4.2.8", "es6-shim": "0.35.5", - "eslint": "7.4.0", + "eslint": "^7.14.0", "eslint-config-prettier": "6.11.0", "eslint-plugin-jsdoc": "28.6.1", + "eslint-plugin-no-only-tests": "^2.4.0", "eslint-plugin-prettier": "3.1.4", "eslint-plugin-react": "7.21.5", "eslint-plugin-react-hooks": "4.1.2", @@ -146,18 +149,6 @@ "fork-ts-checker-webpack-plugin": "5.0.0", "gaze": "1.1.3", "glob": "7.1.6", - "grunt": "1.0.4", - "grunt-angular-templates": "1.1.0", - "grunt-cli": "1.3.2", - "grunt-contrib-clean": "2.0.0", - "grunt-contrib-copy": "1.0.0", - "grunt-exec": "3.0.0", - "grunt-newer": "1.3.0", - "grunt-notify": "0.4.5", - "grunt-postcss": "0.9.0", - "grunt-sass-lint": "0.2.4", - "grunt-usemin": "3.1.1", - "grunt-webpack": "3.1.3", "html-loader": "0.5.5", "html-webpack-harddisk-plugin": "1.0.1", "html-webpack-plugin": "3.2.0", diff --git a/packages/grafana-toolkit/src/cli/index.ts b/packages/grafana-toolkit/src/cli/index.ts index d68e1d2da61..ecfc313b79c 100644 --- a/packages/grafana-toolkit/src/cli/index.ts +++ b/packages/grafana-toolkit/src/cli/index.ts @@ -5,7 +5,6 @@ import chalk from 'chalk'; import { startTask } from './tasks/core.start'; import { changelogTask } from './tasks/changelog'; import { cherryPickTask } from './tasks/cherrypick'; -import { precommitTask } from './tasks/precommit'; import { templateTask } from './tasks/template'; import { pluginBuildTask } from './tasks/plugin.build'; import { toolkitBuildTask } from './tasks/toolkit.build'; @@ -21,6 +20,7 @@ import { pluginCreateTask } from './tasks/plugin.create'; import { pluginSignTask } from './tasks/plugin.sign'; import { bundleManagedTask } from './tasks/plugin/bundle.managed'; import { componentCreateTask } from './tasks/component.create'; +import { nodeVersionCheckerTask } from './tasks/nodeVersionChecker'; export const run = (includeInternalScripts = false) => { if (includeInternalScripts) { @@ -74,10 +74,10 @@ export const run = (includeInternalScripts = false) => { }); program - .command('precommit') - .description('Executes checks') + .command('node-version-check') + .description('Verify node version') .action(async cmd => { - await execTask(precommitTask)({}); + await execTask(nodeVersionCheckerTask)({}); }); program diff --git a/packages/grafana-toolkit/src/cli/tasks/nodeVersionChecker.ts b/packages/grafana-toolkit/src/cli/tasks/nodeVersionChecker.ts index 0d33a487498..3e9942b08b5 100644 --- a/packages/grafana-toolkit/src/cli/tasks/nodeVersionChecker.ts +++ b/packages/grafana-toolkit/src/cli/tasks/nodeVersionChecker.ts @@ -15,7 +15,7 @@ const packageJsonFile = 'package.json'; const failures: FailedVersionCheck[] = []; -export const nodeVersionFiles = [packageJsonFile, 'Dockerfile', '.circleci/config.yml']; +export const nodeVersionFiles = [packageJsonFile, 'Dockerfile']; const nodeVersionCheckerRunner: TaskRunner = async () => { // Read version from package json and treat that as the expected version in all other locations diff --git a/packages/grafana-toolkit/src/cli/tasks/precommit.ts b/packages/grafana-toolkit/src/cli/tasks/precommit.ts deleted file mode 100644 index 2fea0df41d2..00000000000 --- a/packages/grafana-toolkit/src/cli/tasks/precommit.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { Task, TaskRunner } from './task'; -import chalk from 'chalk'; -// @ts-ignore -import get from 'lodash/get'; -// @ts-ignore -import flatten from 'lodash/flatten'; -import execa = require('execa'); -import { nodeVersionCheckerTask, nodeVersionFiles } from './nodeVersionChecker'; -import { execTask } from '../utils/execTask'; -const simpleGit = require('simple-git/promise')(process.cwd()); - -interface PrecommitOptions {} - -interface GitStatus { - files: GitFile[]; -} - -interface GitFile { - path: string; -} - -const precommitRunner: TaskRunner = async () => { - const status: GitStatus = await simpleGit.status(); - const sassFiles = status.files.filter( - file => file.path.match(/^[a-zA-Z0-9\_\-\/]+(\.scss)$/g) || file.path.indexOf('.sass-lint.yml') > -1 - ); - - const testFiles = status.files.filter(file => file.path.match(/^[a-zA-Z0-9\_\-\/]+(\.test.(ts|tsx))$/g)); - const goTestFiles = status.files.filter(file => file.path.match(/^[a-zA-Z0-9\_\-\/]+(\_test.go)$/g)); - const affectedNodeVersionFiles = status.files - .filter(file => nodeVersionFiles.indexOf(file.path) !== -1) - .map(f => f.path); - - const gruntTasks = []; - - if (affectedNodeVersionFiles.length > 0) { - await execTask(nodeVersionCheckerTask)({}); - } - - if (sassFiles.length > 0) { - gruntTasks.push('sasslint'); - } - - if (testFiles.length) { - gruntTasks.push('no-only-tests'); - } - - if (goTestFiles.length) { - gruntTasks.push('no-focus-convey-tests'); - } - - if (gruntTasks.length > 0) { - console.log(chalk.yellow(`Precommit checks: ${gruntTasks.join(', ')}`)); - const task = execa('grunt', gruntTasks); - // @ts-ignore - const stream = task.stdout; - if (stream) { - stream.pipe(process.stdout); - } - return task; - } - - console.log(chalk.yellow('Skipping precommit checks, no front-end changes detected')); - return undefined; -}; - -export const precommitTask = new Task('Precommit task', precommitRunner); diff --git a/scripts/grunt/build_task.js b/scripts/grunt/build_task.js deleted file mode 100644 index 0cdbbafd3c3..00000000000 --- a/scripts/grunt/build_task.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = function(grunt) { - 'use strict'; - - // Concat and Minify the src directory into dist - grunt.registerTask('build', ['clean:release', 'clean:build', 'exec:webpack']); -}; diff --git a/scripts/grunt/default_task.js b/scripts/grunt/default_task.js deleted file mode 100644 index 6f92d35221e..00000000000 --- a/scripts/grunt/default_task.js +++ /dev/null @@ -1,56 +0,0 @@ -// Lint and build CSS -module.exports = function(grunt) { - 'use strict'; - - // prettier-ignore - grunt.registerTask('default', [ - 'clean:build', - ]); - - // prettier-ignore - grunt.registerTask('test', [ - 'sasslint', - 'eslint', - 'typecheck', - 'exec:jest', - 'no-only-tests', - 'no-focus-convey-tests' - ]); - - // prettier-ignore - grunt.registerTask('eslint', [ - 'newer:exec:eslint' - ]); - - // prettier-ignore - grunt.registerTask('typecheck', [ - 'newer:exec:typecheckPackages', - 'newer:exec:typecheckRoot' - ]); - - grunt.registerTask('no-only-tests', function() { - var files = grunt.file.expand( - 'public/**/*@(_specs|.test).@(ts|js|tsx|jsx)', - 'packages/grafana-data/**/*@(_specs|.test).@(ts|js|tsx|jsx)', - 'packages/**/*@(_specs|.test).@(ts|js|tsx|jsx)' - ); - grepFiles(files, '.only(', 'found only statement in test: '); - }); - - grunt.registerTask('no-focus-convey-tests', function() { - var files = grunt.file.expand('pkg/**/*_test.go'); - grepFiles(files, 'FocusConvey(', 'found FocusConvey statement in test: '); - }); - - function grepFiles(files, pattern, errorMessage) { - files.forEach(function(spec) { - var rows = grunt.file.read(spec).split('\n'); - rows.forEach(function(row) { - if (row.indexOf(pattern) > 0) { - grunt.log.errorlns(row); - grunt.fail.warn(errorMessage + spec); - } - }); - }); - } -}; diff --git a/scripts/grunt/options/clean.js b/scripts/grunt/options/clean.js deleted file mode 100644 index 35cd9c2abd2..00000000000 --- a/scripts/grunt/options/clean.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = function(config) { - 'use strict'; - - return { - release: ['<%= destDir %>', '<%= tempDir %>', '<%= genDir %>'], - build: ['<%= srcDir %>/build'], - temp: ['<%= tempDir %>'], - packaging: [], - }; -}; diff --git a/scripts/grunt/options/compress.js b/scripts/grunt/options/compress.js deleted file mode 100644 index 461d997f238..00000000000 --- a/scripts/grunt/options/compress.js +++ /dev/null @@ -1,32 +0,0 @@ -module.exports = function(config) { - 'use strict'; - - var task = { - release: { - options: { - archive: - '<%= destDir %>/<%= pkg.name %><%= enterprise ? "-enterprise" : "" %>-<%= pkg.version %>.<%= platform %>-<%= arch %><%= libc ? "-" + libc : "" %>.tar.gz', - }, - files: [ - { - expand: true, - cwd: '<%= tempDir %>', - src: ['**/*'], - dest: '<%= pkg.name %>-<%= pkg.version %>/', - }, - { - expand: true, - src: ['LICENSE', 'README.md', 'NOTICE.md'], - dest: '<%= pkg.name %>-<%= pkg.version %>/', - }, - ], - }, - }; - - if (config.platform === 'windows') { - task.release.options.archive = - '<%= destDir %>/<%= pkg.name %><%= enterprise ? "-enterprise" : "" %>-<%= pkg.version %>.<%= platform %>-<%= arch %>.zip'; - } - - return task; -}; diff --git a/scripts/grunt/options/exec.js b/scripts/grunt/options/exec.js deleted file mode 100644 index 4a94b685b73..00000000000 --- a/scripts/grunt/options/exec.js +++ /dev/null @@ -1,16 +0,0 @@ -module.exports = function(config, grunt) { - 'use strict'; - - return { - eslint: { - command: 'yarn lint', - }, - typecheckPackages: { - command: 'yarn packages:typecheck', - }, - typecheckRoot: { - command: 'yarn typecheck', - }, - webpack: 'node ./node_modules/webpack/bin/webpack.js --config scripts/webpack/webpack.prod.js', - }; -}; diff --git a/scripts/grunt/options/sasslint.js b/scripts/grunt/options/sasslint.js deleted file mode 100644 index 7823429f1f6..00000000000 --- a/scripts/grunt/options/sasslint.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = function(config) { - 'use strict'; - return { - options: { - configFile: 'public/sass/.sass-lint.yml', - }, - src: ['public/sass/**/*.scss', 'packages/**/*.scss', '!**/node_modules/**/*.scss'], - }; -}; diff --git a/scripts/grunt/options/webpack.js b/scripts/grunt/options/webpack.js deleted file mode 100644 index 1d8a6d08147..00000000000 --- a/scripts/grunt/options/webpack.js +++ /dev/null @@ -1,13 +0,0 @@ -const dev = require('../../webpack/webpack.dev.js'); -const prod = require('../../webpack/webpack.prod.js'); - -module.exports = function() { - 'use strict'; - return { - options: { - stats: false, - }, - dev: dev, - prod: prod, - }; -}; diff --git a/scripts/grunt/release_task.js b/scripts/grunt/release_task.js deleted file mode 100644 index 3cc820700d5..00000000000 --- a/scripts/grunt/release_task.js +++ /dev/null @@ -1,40 +0,0 @@ -var path = require('path'); - -module.exports = function(grunt) { - 'use strict'; - - // build then zip - grunt.registerTask('release', ['build', 'build-post-process']); - - // package into archives - grunt.registerTask('package', ['clean:temp', 'build-post-process']); - - grunt.registerTask('build-post-process', function() { - grunt.config('copy.public_to_temp', { - expand: true, - cwd: '<%= srcDir %>', - src: '**/*', - dest: '<%= tempDir %>/public/', - }); - grunt.config('copy.backend_bin', { - cwd: 'bin/<%= platform %>-<%= arch %><%= libc ? "-" + libc : "" %>', - expand: true, - src: ['*'], - options: { mode: true }, - dest: '<%= tempDir %>/bin/', - }); - grunt.config('copy.backend_files', { - expand: true, - src: ['conf/**', 'tools/**', 'scripts/*'], - options: { mode: true }, - dest: '<%= tempDir %>', - }); - - grunt.task.run('copy:public_to_temp'); - grunt.task.run('copy:backend_bin'); - grunt.task.run('copy:backend_files'); - grunt.task.run('clean:packaging'); - - grunt.file.write(path.join(grunt.config('tempDir'), 'VERSION'), grunt.config('pkg.version')); - }); -}; diff --git a/scripts/grunt/style_guide_task.js b/scripts/grunt/style_guide_task.js deleted file mode 100644 index e27f8200b0c..00000000000 --- a/scripts/grunt/style_guide_task.js +++ /dev/null @@ -1,46 +0,0 @@ -module.exports = function(grunt) { - 'use strict'; - - function escapeRegExp(str) { - return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); - } - - function extractColour(line) { - var regex = /\s*:\s*(#[a-fA-F0-9]{3,6})\s*(!default|!default;)?/; - var matches = line.match(regex); - return matches ? matches[1] : matches; - } - - function extractVariable(line) { - var matches = line.match(/(\$[0-9a-zA-Z_-]+)\s*(!default|!default;)?/); - return matches ? matches[1] : matches; - } - - function readVars(file, obj) { - var content = grunt.file.read(file); - var lines = content.split('\n'); - - lines.forEach(function(line) { - var variable = extractVariable(line); - if (variable) { - var color = extractColour(line, variable); - if (color) { - obj[variable] = color; - } - } - }); - } - - grunt.registerTask('styleguide', function() { - var data = { - dark: {}, - light: {}, - }; - - readVars('public/sass/_variables.dark.scss', data.dark); - readVars('public/sass/_variables.light.scss', data.light); - - var styleGuideJson = grunt.config().srcDir + '/build/styleguide.json'; - grunt.file.write(styleGuideJson, JSON.stringify(data, null, 4)); - }); -}; diff --git a/yarn.lock b/yarn.lock index b85a645da7d..3c960c18d6d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3455,6 +3455,22 @@ resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46" integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== +"@eslint/eslintrc@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.2.1.tgz#f72069c330461a06684d119384435e12a5d76e3c" + integrity sha512-XRUeBZ5zBWLYgSANMpThFddrZZkEbGHgUdt5UJjZfnlN9BGCiUBrf+nvbRupSjMvqzwnQN0qwCmOxITt1cfywA== + dependencies: + ajv "^6.12.4" + debug "^4.1.1" + espree "^7.3.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.2.1" + js-yaml "^3.13.1" + lodash "^4.17.19" + minimatch "^3.0.4" + strip-json-comments "^3.1.1" + "@evocateur/libnpmaccess@^3.1.2": version "3.1.2" resolved "https://registry.yarnpkg.com/@evocateur/libnpmaccess/-/libnpmaccess-3.1.2.tgz#ecf7f6ce6b004e9f942b098d92200be4a4b1c845" @@ -7820,6 +7836,11 @@ acorn@^7.1.1, acorn@^7.2.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.3.1.tgz#85010754db53c3fbaf3b9ea3e083aa5c5d147ffd" integrity sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA== +acorn@^7.4.0: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + add-dom-event-listener@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/add-dom-event-listener/-/add-dom-event-listener-1.1.0.tgz#6a92db3a0dd0abc254e095c0f1dc14acbbaae310" @@ -8264,11 +8285,6 @@ array-differ@^3.0.0: resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-3.0.0.tgz#3cbb3d0f316810eafcc47624734237d6aee4ae6b" integrity sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg== -array-each@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f" - integrity sha1-p5SvDAWrF1KEbudTofIRoFugxE8= - array-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" @@ -8308,11 +8324,6 @@ array-includes@^3.0.3, array-includes@^3.1.1: es-abstract "^1.17.0" is-string "^1.0.5" -array-slice@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-1.1.0.tgz#e368ea15f89bc7069f7ffb89aec3a6c7d4ac22d4" - integrity sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w== - array-tree-filter@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/array-tree-filter/-/array-tree-filter-2.1.0.tgz#873ac00fec83749f255ac8dd083814b4f6329190" @@ -8478,12 +8489,7 @@ async@0.9.x: resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" integrity sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0= -async@^1.5.2, async@~1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" - integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= - -async@^2.6.1, async@^2.6.2: +async@^2.6.2: version "2.6.3" resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== @@ -8495,11 +8501,6 @@ async@^3.2.0: resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720" integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw== -async@~0.2.6: - version "0.2.10" - resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1" - integrity sha1-trvgsGdLnXGXCMo43owjfLUmw9E= - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -9573,7 +9574,7 @@ callsites@^3.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -camel-case@3.0.x, camel-case@^3.0.0: +camel-case@3.0.x: version "3.0.0" resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73" integrity sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M= @@ -9703,7 +9704,7 @@ centrifuge@^2.6.4: dependencies: protobufjs "^6.9.0" -chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2, chalk@~2.4.1: +chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -9744,30 +9745,6 @@ chance@^1.0.10: resolved "https://registry.yarnpkg.com/chance/-/chance-1.1.4.tgz#d8743bf8e40bb05e024c305ca1ff441195eb23db" integrity sha512-pXPDSu3knKlb6H7ahQfpq//J9mSOxYK8SMtp8MV/nRJh8aLRDIl0ipLH8At8+nVogVwtvPZzyIzY/EbcY/cLuQ== -change-case@3.0.x: - version "3.0.2" - resolved "https://registry.yarnpkg.com/change-case/-/change-case-3.0.2.tgz#fd48746cce02f03f0a672577d1d3a8dc2eceb037" - integrity sha512-Mww+SLF6MZ0U6kdg11algyKd5BARbyM4TbFBepwowYSR5ClfQGCGtxNXgykpN0uF/bstWeaGDT4JWaDh8zWAHA== - dependencies: - camel-case "^3.0.0" - constant-case "^2.0.0" - dot-case "^2.1.0" - header-case "^1.0.0" - is-lower-case "^1.1.0" - is-upper-case "^1.1.0" - lower-case "^1.1.1" - lower-case-first "^1.0.0" - no-case "^2.3.2" - param-case "^2.1.0" - pascal-case "^2.0.0" - path-case "^2.1.0" - sentence-case "^2.1.0" - snake-case "^2.1.0" - swap-case "^1.1.0" - title-case "^2.1.0" - upper-case "^1.1.1" - upper-case-first "^1.1.0" - change-emitter@^0.1.2: version "0.1.6" resolved "https://registry.yarnpkg.com/change-emitter/-/change-emitter-0.1.6.tgz#e8b2fe3d7f1ab7d69a32199aff91ea6931409515" @@ -9954,14 +9931,6 @@ classnames@2.2.6, classnames@2.x, classnames@^2.2.1, classnames@^2.2.5, classnam resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== -clean-css@3.4.x: - version "3.4.28" - resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-3.4.28.tgz#bf1945e82fc808f55695e6ddeaec01400efd03ff" - integrity sha1-vxlF6C/ICPVWlebd6uwBQA79A/8= - dependencies: - commander "2.8.x" - source-map "0.4.x" - clean-css@4.2.x: version "4.2.1" resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.1.tgz#2d411ef76b8569b6d0c84068dabe85b0aa5e5c17" @@ -10166,11 +10135,6 @@ code-point-at@^1.0.0: resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= -coffeescript@~1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/coffeescript/-/coffeescript-1.10.0.tgz#e7aa8301917ef621b35d8a39f348dcdd1db7e33e" - integrity sha1-56qDAZF+9iGzXYo580jc3R234z4= - collapse-white-space@^1.0.2: version "1.0.6" resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.6.tgz#e63629c0016665792060dbbeb79c42239d2c5287" @@ -10244,11 +10208,6 @@ colors@^1.1.2: resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== -colors@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" - integrity sha1-FopHAXVran9RoSzgyXv6KMCE7WM= - colors@~1.2.1: version "1.2.5" resolved "https://registry.yarnpkg.com/colors/-/colors-1.2.5.tgz#89c7ad9a374bc030df8013241f68136ed8835afc" @@ -10315,20 +10274,6 @@ commander@2.17.x: resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg== -commander@2.8.x: - version "2.8.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4" - integrity sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ= - dependencies: - graceful-readlink ">= 1.0.0" - -commander@2.9.x: - version "2.9.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" - integrity sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q= - dependencies: - graceful-readlink ">= 1.0.0" - commander@5.0.0, commander@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/commander/-/commander-5.0.0.tgz#dbf1909b49e5044f8fdaf0adc809f0c0722bdfd0" @@ -10496,14 +10441,6 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= -constant-case@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/constant-case/-/constant-case-2.0.0.tgz#4175764d389d3fa9c8ecd29186ed6005243b6a46" - integrity sha1-QXV2TTidP6nI7NKRhu1gBSQ7akY= - dependencies: - snake-case "^2.1.0" - upper-case "^1.1.1" - constants-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" @@ -11620,15 +11557,7 @@ dateformat@^3.0.0: resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== -dateformat@~1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-1.0.12.tgz#9f124b67594c937ff706932e4a642cca8dbbfee9" - integrity sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk= - dependencies: - get-stdin "^4.0.1" - meow "^3.3.0" - -debug@2.6.9, debug@^2.1.1, debug@^2.1.3, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: +debug@2.6.9, debug@^2.1.1, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -11721,13 +11650,6 @@ deep-extend@^0.6.0, deep-extend@~0.6.0: resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== -deep-for-each@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/deep-for-each/-/deep-for-each-2.0.3.tgz#640b17b88c69892e33caba853004aa89ce00f5c4" - integrity sha512-Y9mu+rplGcNZ7veer+5rqcdI9w3aPb7/WyE/nYnsuPevaE2z5YuC2u7/Gz/hIKsa0zo8sE8gKoBimSNsO/sr+A== - dependencies: - lodash.isplainobject "^4.0.6" - deep-freeze@^0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/deep-freeze/-/deep-freeze-0.0.1.tgz#3a0b0005de18672819dfd38cd31f91179c893e84" @@ -11919,7 +11841,7 @@ diff-sequences@^26.6.2: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q== -diff@3.5.0, diff@^3.0.0: +diff@3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== @@ -12124,13 +12046,6 @@ domutils@^1.5.1, domutils@^1.7.0: dom-serializer "0" domelementtype "1" -dot-case@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-2.1.1.tgz#34dcf37f50a8e93c2b3bca8bb7fb9155c7da3bee" - integrity sha1-NNzzf1Co6TwrO8qLt/uRVcfaO+4= - dependencies: - no-case "^2.2.0" - dot-case@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.3.tgz#21d3b52efaaba2ea5fda875bb1aa8124521cf4aa" @@ -12846,6 +12761,11 @@ eslint-plugin-jsdoc@28.6.1: semver "^7.3.2" spdx-expression-parse "^3.0.1" +eslint-plugin-no-only-tests@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-no-only-tests/-/eslint-plugin-no-only-tests-2.4.0.tgz#7d565434aa7d16ccc7eea957c391d98f827332ca" + integrity sha512-azP9PwQYfGtXJjW273nIxQH9Ygr+5/UyeW2wEjYoDtVYPI+WPKwbj0+qcAKYUXFZLRumq4HKkFaoDBAwBoXImQ== + eslint-plugin-prettier@3.1.4: version "3.1.4" resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.4.tgz#168ab43154e2ea57db992a2cd097c828171f75c2" @@ -12899,7 +12819,15 @@ eslint-scope@^5.1.0: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-utils@^2.0.0: +eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-utils@^2.0.0, eslint-utils@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== @@ -12911,7 +12839,7 @@ eslint-visitor-keys@^1.1.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2" integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A== -eslint-visitor-keys@^1.2.0: +eslint-visitor-keys@^1.2.0, eslint-visitor-keys@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== @@ -13002,6 +12930,49 @@ eslint@^2.7.0: text-table "~0.2.0" user-home "^2.0.0" +eslint@^7.14.0: + version "7.14.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.14.0.tgz#2d2cac1d28174c510a97b377f122a5507958e344" + integrity sha512-5YubdnPXrlrYAFCKybPuHIAH++PINe1pmKNc5wQRB9HSbqIK1ywAnntE3Wwua4giKu0bjligf1gLF6qxMGOYRA== + dependencies: + "@babel/code-frame" "^7.0.0" + "@eslint/eslintrc" "^0.2.1" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.0.1" + doctrine "^3.0.0" + enquirer "^2.3.5" + eslint-scope "^5.1.1" + eslint-utils "^2.1.0" + eslint-visitor-keys "^2.0.0" + espree "^7.3.0" + esquery "^1.2.0" + esutils "^2.0.2" + file-entry-cache "^5.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^5.0.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash "^4.17.19" + minimatch "^3.0.4" + natural-compare "^1.4.0" + optionator "^0.9.1" + progress "^2.0.0" + regexpp "^3.1.0" + semver "^7.2.1" + strip-ansi "^6.0.0" + strip-json-comments "^3.1.0" + table "^5.2.3" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + espree@^3.1.6: version "3.5.4" resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7" @@ -13019,6 +12990,15 @@ espree@^7.1.0: acorn-jsx "^5.2.0" eslint-visitor-keys "^1.2.0" +espree@^7.3.0: + version "7.3.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.0.tgz#dc30437cf67947cf576121ebd780f15eeac72348" + integrity sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw== + dependencies: + acorn "^7.4.0" + acorn-jsx "^5.2.0" + eslint-visitor-keys "^1.3.0" + esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" @@ -13050,6 +13030,13 @@ esrecurse@^4.1.0: dependencies: estraverse "^4.1.0" +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + esrever@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/esrever/-/esrever-0.2.0.tgz#96e9d28f4f1b1a76784cd5d490eaae010e7407b8" @@ -13065,6 +13052,11 @@ estraverse@^5.1.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.1.0.tgz#374309d39fd935ae500e7b92e8a6b4c720e59642" integrity sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw== +estraverse@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" + integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== + estree-walker@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362" @@ -13103,11 +13095,6 @@ eventemitter2@^6.4.2: resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.3.tgz#35c563619b13f3681e7eb05cbdaf50f56ba58820" integrity sha512-t0A2msp6BzOf+QAcI6z9XMktLj52OjGQg+8SJH6v5+3uxNpWYRR3wQmfA+6xtMU9kOC59qk9licus5dYcrYkMQ== -eventemitter2@~0.4.13: - version "0.4.14" - resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-0.4.14.tgz#8f61b75cde012b2e9eb284d4545583b5643b61ab" - integrity sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas= - eventemitter3@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.0.tgz#d65176163887ee59f386d64c82610b696a4a74eb" @@ -13243,7 +13230,7 @@ exit-hook@^1.0.0: resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" integrity sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g= -exit@^0.1.2, exit@~0.1.1: +exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= @@ -13624,11 +13611,6 @@ file-saver@2.0.2: resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-2.0.2.tgz#06d6e728a9ea2df2cce2f8d9e84dfcdc338ec17a" integrity sha512-Wz3c3XQ5xroCxd1G8b7yL0Ehkf0TC9oYC6buPFkNnU9EnaPlifeAFCyCh+iewXTyFRcg0a6j3J7FmJsIhlhBdw== -file-sync-cmp@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/file-sync-cmp/-/file-sync-cmp-0.1.1.tgz#a5e7a8ffbfa493b43b923bbd4ca89a53b63b612b" - integrity sha1-peeo/7+kk7Q7kju9TKiaU7Y7YSs= - file-system-cache@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/file-system-cache/-/file-system-cache-1.0.5.tgz#84259b36a2bbb8d3d6eb1021d3132ffe64cfff4f" @@ -13772,39 +13754,6 @@ findup-sync@3.0.0: micromatch "^3.0.4" resolve-dir "^1.0.1" -findup-sync@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-2.0.0.tgz#9326b1488c22d1a6088650a86901b2d9a90a2cbc" - integrity sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw= - dependencies: - detect-file "^1.0.0" - is-glob "^3.1.0" - micromatch "^3.0.4" - resolve-dir "^1.0.1" - -findup-sync@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.3.0.tgz#37930aa5d816b777c03445e1966cc6790a4c0b16" - integrity sha1-N5MKpdgWt3fANEXhlmzGeQpMCxY= - dependencies: - glob "~5.0.0" - -fined@^1.0.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/fined/-/fined-1.2.0.tgz#d00beccf1aa2b475d16d423b0238b713a2c4a37b" - integrity sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng== - dependencies: - expand-tilde "^2.0.2" - is-plain-object "^2.0.3" - object.defaults "^1.1.0" - object.pick "^1.2.0" - parse-filepath "^1.0.1" - -flagged-respawn@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-1.0.1.tgz#e7de6f1279ddd9ca9aac8a5971d618606b3aab41" - integrity sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q== - flat-cache@^1.2.1: version "1.3.4" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.4.tgz#2c2ef77525cc2929007dfffa1dd314aa9c9dee6f" @@ -13901,13 +13850,6 @@ for-own@^0.1.3: dependencies: for-in "^1.0.1" -for-own@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b" - integrity sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs= - dependencies: - for-in "^1.0.1" - forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -14316,11 +14258,6 @@ get-window@^1.1.1: dependencies: get-document "1" -getobject@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/getobject/-/getobject-0.1.0.tgz#047a449789fa160d018f5486ed91320b6ec7885c" - integrity sha1-BHpEl4n6Fg0Bj1SG7ZEyC27HiFw= - getos@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/getos/-/getos-3.2.1.tgz#0134d1f4e00eb46144c5a9c0ac4dc087cbb27dc5" @@ -14469,29 +14406,6 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, gl once "^1.3.0" path-is-absolute "^1.0.0" -glob@~5.0.0: - version "5.0.15" - resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" - integrity sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E= - dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "2 || 3" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@~7.0.0: - version "7.0.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.6.tgz#211bafaf49e525b8cd93260d14ab136152b3f57a" - integrity sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo= - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.2" - once "^1.3.0" - path-is-absolute "^1.0.0" - global-dirs@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" @@ -14714,11 +14628,6 @@ graceful-fs@^4.1.9, graceful-fs@^4.2.4: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== -"graceful-readlink@>= 1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" - integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU= - growl@1.10.5: version "1.10.5" resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" @@ -14729,165 +14638,6 @@ growly@^1.3.0: resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= -grunt-angular-templates@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/grunt-angular-templates/-/grunt-angular-templates-1.1.0.tgz#109603a2b95ff01019b718c70341268f09d8be19" - integrity sha1-EJYDorlf8BAZtxjHA0EmjwnYvhk= - dependencies: - html-minifier "~2.1.2" - -grunt-cli@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/grunt-cli/-/grunt-cli-1.3.2.tgz#60f12d12c1b5aae94ae3469c6b5fe24e960014e8" - integrity sha512-8OHDiZZkcptxVXtMfDxJvmN7MVJNE8L/yIcPb4HB7TlyFD1kDvjHrb62uhySsU14wJx9ORMnTuhRMQ40lH/orQ== - dependencies: - grunt-known-options "~1.1.0" - interpret "~1.1.0" - liftoff "~2.5.0" - nopt "~4.0.1" - v8flags "~3.1.1" - -grunt-cli@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/grunt-cli/-/grunt-cli-1.2.0.tgz#562b119ebb069ddb464ace2845501be97b35b6a8" - integrity sha1-VisRnrsGndtGSs4oRVAb6Xs1tqg= - dependencies: - findup-sync "~0.3.0" - grunt-known-options "~1.1.0" - nopt "~3.0.6" - resolve "~1.1.0" - -grunt-contrib-clean@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/grunt-contrib-clean/-/grunt-contrib-clean-2.0.0.tgz#3be7ca480da4b740aa5e9d863e2f7e8b24f8a68b" - integrity sha512-g5ZD3ORk6gMa5ugZosLDQl3dZO7cI3R14U75hTM+dVLVxdMNJCPVmwf9OUt4v4eWgpKKWWoVK9DZc1amJp4nQw== - dependencies: - async "^2.6.1" - rimraf "^2.6.2" - -grunt-contrib-copy@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/grunt-contrib-copy/-/grunt-contrib-copy-1.0.0.tgz#7060c6581e904b8ab0d00f076e0a8f6e3e7c3573" - integrity sha1-cGDGWB6QS4qw0A8HbgqPbj58NXM= - dependencies: - chalk "^1.1.1" - file-sync-cmp "^0.1.0" - -grunt-exec@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/grunt-exec/-/grunt-exec-3.0.0.tgz#881f868d64098788fddaf22fa25d8572a9d64dc7" - integrity sha512-cgAlreXf3muSYS5LzW0Cc4xHK03BjFOYk0MqCQ/MZ3k1Xz2GU7D+IAJg4UKicxpO+XdONJdx/NJ6kpy2wI+uHg== - -grunt-known-options@~1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/grunt-known-options/-/grunt-known-options-1.1.1.tgz#6cc088107bd0219dc5d3e57d91923f469059804d" - integrity sha512-cHwsLqoighpu7TuYj5RonnEuxGVFnztcUqTqp5rXFGYL4OuPFofwC4Ycg7n9fYwvK6F5WbYgeVOwph9Crs2fsQ== - -grunt-legacy-log-utils@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.0.1.tgz#d2f442c7c0150065d9004b08fd7410d37519194e" - integrity sha512-o7uHyO/J+i2tXG8r2bZNlVk20vlIFJ9IEYyHMCQGfWYru8Jv3wTqKZzvV30YW9rWEjq0eP3cflQ1qWojIe9VFA== - dependencies: - chalk "~2.4.1" - lodash "~4.17.10" - -grunt-legacy-log@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/grunt-legacy-log/-/grunt-legacy-log-2.0.0.tgz#c8cd2c6c81a4465b9bbf2d874d963fef7a59ffb9" - integrity sha512-1m3+5QvDYfR1ltr8hjiaiNjddxGdQWcH0rw1iKKiQnF0+xtgTazirSTGu68RchPyh1OBng1bBUjLmX8q9NpoCw== - dependencies: - colors "~1.1.2" - grunt-legacy-log-utils "~2.0.0" - hooker "~0.2.3" - lodash "~4.17.5" - -grunt-legacy-util@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/grunt-legacy-util/-/grunt-legacy-util-1.1.1.tgz#e10624e7c86034e5b870c8a8616743f0a0845e42" - integrity sha512-9zyA29w/fBe6BIfjGENndwoe1Uy31BIXxTH3s8mga0Z5Bz2Sp4UCjkeyv2tI449ymkx3x26B+46FV4fXEddl5A== - dependencies: - async "~1.5.2" - exit "~0.1.1" - getobject "~0.1.0" - hooker "~0.2.3" - lodash "~4.17.10" - underscore.string "~3.3.4" - which "~1.3.0" - -grunt-newer@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/grunt-newer/-/grunt-newer-1.3.0.tgz#83ccb7a1dda7cbd8ab23b059024ebe614ad2f342" - integrity sha1-g8y3od2ny9irI7BZAk6+YUrS80I= - dependencies: - async "^1.5.2" - rimraf "^2.5.2" - -grunt-notify@0.4.5: - version "0.4.5" - resolved "https://registry.yarnpkg.com/grunt-notify/-/grunt-notify-0.4.5.tgz#05293990616110db6bc0ad15e6c0592ffe18ac31" - integrity sha1-BSk5kGFhENtrwK0V5sBZL/4YrDE= - dependencies: - semver "^5.1.0" - stack-parser "^0.0.1" - which "^1.2.4" - -grunt-postcss@0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/grunt-postcss/-/grunt-postcss-0.9.0.tgz#fbe5934a6be9eac893af6d057e2318c97fae9da3" - integrity sha512-lglLcVaoOIqH0sFv7RqwUKkEFGQwnlqyAKbatxZderwZGV1nDyKHN7gZS9LUiTx1t5GOvRBx0BEalHMyVwFAIA== - dependencies: - chalk "^2.1.0" - diff "^3.0.0" - postcss "^6.0.11" - -grunt-sass-lint@0.2.4: - version "0.2.4" - resolved "https://registry.yarnpkg.com/grunt-sass-lint/-/grunt-sass-lint-0.2.4.tgz#06f77635ad8a5048968ea33c5584b40a18281e35" - integrity sha512-jV88yXoxFFvr4R3WVBl0uz4YBzNxXTrCJ7ZBKrYby/SjRCw2sieKPkt5tpWDcQZIj9XrKsOpKuHQn08MaECVwg== - dependencies: - sass-lint "^1.12.0" - -grunt-usemin@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/grunt-usemin/-/grunt-usemin-3.1.1.tgz#5ab679510d672cea566cc717abe8b8a009f641c2" - integrity sha1-WrZ5UQ1nLOpWbMcXq+i4oAn2QcI= - dependencies: - chalk "^1.1.1" - debug "^2.1.3" - lodash "^3.6.0" - path-exists "^1.0.0" - -grunt-webpack@3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/grunt-webpack/-/grunt-webpack-3.1.3.tgz#7e0a016773b105bb87718c19f308100b498ce39a" - integrity sha512-SaZ8K8lG4iTxs7ClZxOWCf3kxqS2y+Eel8SbaEGgBKwhAp6e45beIu+vhBZRLX3vonKML2kjemKsQ21REaqNFQ== - dependencies: - deep-for-each "^2.0.2" - lodash "^4.7.0" - -grunt@1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/grunt/-/grunt-1.0.4.tgz#c799883945a53a3d07622e0737c8f70bfe19eb38" - integrity sha512-PYsMOrOC+MsdGEkFVwMaMyc6Ob7pKmq+deg1Sjr+vvMWp35sztfwKE7qoN51V+UEtHsyNuMcGdgMLFkBHvMxHQ== - dependencies: - coffeescript "~1.10.0" - dateformat "~1.0.12" - eventemitter2 "~0.4.13" - exit "~0.1.1" - findup-sync "~0.3.0" - glob "~7.0.0" - grunt-cli "~1.2.0" - grunt-known-options "~1.1.0" - grunt-legacy-log "~2.0.0" - grunt-legacy-util "~1.1.1" - iconv-lite "~0.4.13" - js-yaml "~3.13.0" - minimatch "~3.0.2" - mkdirp "~0.5.1" - nopt "~3.0.6" - path-is-absolute "~1.0.0" - rimraf "~2.6.2" - gud@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0" @@ -15102,24 +14852,11 @@ hastscript@^6.0.0: property-information "^5.0.0" space-separated-tokens "^1.0.0" -he@1.1.x: - version "1.1.1" - resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" - integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0= - he@1.2.0, he@1.2.x, he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== -header-case@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/header-case/-/header-case-1.0.1.tgz#9535973197c144b09613cd65d317ef19963bd02d" - integrity sha1-lTWXMZfBRLCWE81l0xfvGZY70C0= - dependencies: - no-case "^2.2.0" - upper-case "^1.1.3" - hex-color-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" @@ -15170,11 +14907,6 @@ homedir-polyfill@^1.0.1: dependencies: parse-passwd "^1.0.0" -hooker@~0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/hooker/-/hooker-0.2.3.tgz#b834f723cc4a242aa65963459df6d984c5d3d959" - integrity sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk= - hoopy@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/hoopy/-/hoopy-0.1.4.tgz#609207d661100033a9a9402ad3dea677381c1b1d" @@ -15283,19 +15015,6 @@ html-minifier@^3.2.3, html-minifier@^3.5.8: relateurl "0.2.x" uglify-js "3.4.x" -html-minifier@~2.1.2: - version "2.1.7" - resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-2.1.7.tgz#9051d6fcbbcf214ed307e1ad74f432bb9ad655cc" - integrity sha1-kFHW/LvPIU7TB+GtdPQyu5rWVcw= - dependencies: - change-case "3.0.x" - clean-css "3.4.x" - commander "2.9.x" - he "1.1.x" - ncname "1.0.x" - relateurl "0.2.x" - uglify-js "2.6.x" - html-tags@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.1.0.tgz#7b5e6f7e665e9fb41f30007ed9e0d41e97fb2140" @@ -15581,6 +15300,14 @@ import-fresh@^3.0.0, import-fresh@^3.1.0: parent-module "^1.0.0" resolve-from "^4.0.0" +import-fresh@^3.2.1: + version "3.2.2" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.2.tgz#fc129c160c5d68235507f4331a6baad186bdbc3e" + integrity sha512-cTPNrlvJT6twpYy+YmKUKrTSjWFs3bjYjAhCwm+z4EOCubZxAuO+hHpRN64TqjEaYSHs7tJAE0w1CKMGmsG/lw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + import-from@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-from/-/import-from-2.1.0.tgz#335db7f2a7affd53aaa471d4b8021dee36b7f3b1" @@ -15814,11 +15541,6 @@ interpret@^2.0.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== -interpret@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" - integrity sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ= - invariant@^2.2.2, invariant@^2.2.3, invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" @@ -15861,14 +15583,6 @@ is-absolute-url@^3.0.0, is-absolute-url@^3.0.3: resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q== -is-absolute@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576" - integrity sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA== - dependencies: - is-relative "^1.0.0" - is-windows "^1.0.1" - is-accessor-descriptor@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" @@ -16171,13 +15885,6 @@ is-interactive@^1.0.0: resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== -is-lower-case@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/is-lower-case/-/is-lower-case-1.1.3.tgz#7e147be4768dc466db3bfb21cc60b31e6ad69393" - integrity sha1-fhR75HaNxGbbO/shzGCzHmrWk5M= - dependencies: - lower-case "^1.1.0" - is-map@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.1.tgz#520dafc4307bb8ebc33b813de5ce7c9400d644a1" @@ -16368,13 +16075,6 @@ is-regexp@^1.0.0: resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk= -is-relative@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d" - integrity sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA== - dependencies: - is-unc-path "^1.0.0" - is-resolvable@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" @@ -16455,20 +16155,6 @@ is-typedarray@^1.0.0, is-typedarray@~1.0.0: resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= -is-unc-path@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d" - integrity sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ== - dependencies: - unc-path-regex "^0.1.2" - -is-upper-case@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/is-upper-case/-/is-upper-case-1.1.2.tgz#8d0b1fa7e7933a1e58483600ec7d9661cbaf756f" - integrity sha1-jQsfp+eTOh5YSDYA7H2WYcuvdW8= - dependencies: - upper-case "^1.1.0" - is-utf8@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" @@ -17229,7 +16915,7 @@ js-tokens@^3.0.2: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= -js-yaml@3.13.1, js-yaml@^3.13.1, js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.4, js-yaml@~3.13.0, js-yaml@~3.13.1: +js-yaml@3.13.1, js-yaml@^3.13.1, js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.4, js-yaml@~3.13.1: version "3.13.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== @@ -17670,20 +17356,6 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" -liftoff@~2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/liftoff/-/liftoff-2.5.0.tgz#2009291bb31cea861bbf10a7c15a28caf75c31ec" - integrity sha1-IAkpG7Mc6oYbvxCnwVooyvdcMew= - dependencies: - extend "^3.0.0" - findup-sync "^2.0.0" - fined "^1.0.1" - flagged-respawn "^1.0.0" - is-plain-object "^2.0.4" - object.map "^1.0.0" - rechoir "^0.6.2" - resolve "^1.1.7" - lines-and-columns@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" @@ -18003,16 +17675,11 @@ lodash@4.17.15: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== -lodash@4.17.19, lodash@^4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.1.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.7.0, lodash@~4.17.10, lodash@~4.17.15, lodash@~4.17.5: +lodash@4.17.19, lodash@^4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.1.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.3.0, lodash@~4.17.10, lodash@~4.17.15: version "4.17.19" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== -lodash@^3.6.0: - version "3.10.1" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" - integrity sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y= - lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" @@ -18102,14 +17769,7 @@ loud-rejection@^1.0.0: currently-unhandled "^0.4.1" signal-exit "^3.0.0" -lower-case-first@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/lower-case-first/-/lower-case-first-1.0.2.tgz#e5da7c26f29a7073be02d52bac9980e5922adfa1" - integrity sha1-5dp8JvKacHO+AtUrrJmA5ZIq36E= - dependencies: - lower-case "^1.1.2" - -lower-case@^1.1.0, lower-case@^1.1.1, lower-case@^1.1.2: +lower-case@^1.1.1: version "1.1.4" resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw= @@ -18229,13 +17889,6 @@ make-fetch-happen@^5.0.0: socks-proxy-agent "^4.0.0" ssri "^6.0.0" -make-iterator@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/make-iterator/-/make-iterator-1.0.1.tgz#29b33f312aa8f547c4a5e490f56afcec99133ad6" - integrity sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw== - dependencies: - kind-of "^6.0.2" - makeerror@1.0.x: version "1.0.11" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" @@ -18255,7 +17908,7 @@ map-age-cleaner@^0.1.1: dependencies: p-defer "^1.0.0" -map-cache@^0.2.0, map-cache@^0.2.2: +map-cache@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= @@ -18648,13 +18301,6 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - minimatch@3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774" @@ -18662,6 +18308,13 @@ minimatch@3.0.3: dependencies: brace-expansion "^1.0.0" +minimatch@3.0.4, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + minimist-options@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-3.0.2.tgz#fba4c8191339e13ecf4d61beb03f070103f3d954" @@ -19063,13 +18716,6 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= -ncname@1.0.x: - version "1.0.0" - resolved "https://registry.yarnpkg.com/ncname/-/ncname-1.0.0.tgz#5b57ad18b1ca092864ef62b0b1ed8194f383b71c" - integrity sha1-W1etGLHKCShk72Kwse2BlPODtxw= - dependencies: - xml-char-classes "^1.0.0" - nearley@^2.7.10: version "2.19.0" resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.19.0.tgz#37717781d0fd0f2bfc95e233ebd75678ca4bda46" @@ -19135,7 +18781,7 @@ nise@^3.0.1: lolex "^5.0.1" path-to-regexp "^1.7.0" -no-case@^2.2.0, no-case@^2.3.2: +no-case@^2.2.0: version "2.3.2" resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" integrity sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ== @@ -19314,14 +18960,14 @@ nodemon@2.0.2: undefsafe "^2.0.2" update-notifier "^2.5.0" -"nopt@2 || 3", nopt@~3.0.6: +"nopt@2 || 3": version "3.0.6" resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" integrity sha1-xkZdvwirzU2zWTF/eaxopkayj/k= dependencies: abbrev "1" -nopt@^4.0.1, nopt@~4.0.1: +nopt@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00= @@ -19571,16 +19217,6 @@ object.assign@^4.1.1: has-symbols "^1.0.1" object-keys "^1.1.1" -object.defaults@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/object.defaults/-/object.defaults-1.1.0.tgz#3a7f868334b407dea06da16d88d5cd29e435fecf" - integrity sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8= - dependencies: - array-each "^1.0.1" - array-slice "^1.0.0" - for-own "^1.0.0" - isobject "^3.0.0" - object.entries@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.0.tgz#2024fc6d6ba246aee38bdb0ffd5cfbcf371b7519" @@ -19638,15 +19274,7 @@ object.getownpropertydescriptors@^2.0.3: define-properties "^1.1.2" es-abstract "^1.5.1" -object.map@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object.map/-/object.map-1.0.1.tgz#cf83e59dc8fcc0ad5f4250e1f78b3b81bd801d37" - integrity sha1-z4Plncj8wK1fQlDh94s7gb2AHTc= - dependencies: - for-own "^1.0.0" - make-iterator "^1.0.0" - -object.pick@^1.2.0, object.pick@^1.3.0: +object.pick@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= @@ -20084,7 +19712,7 @@ parallel-transform@^1.1.0: inherits "^2.0.3" readable-stream "^2.1.5" -param-case@2.1.x, param-case@^2.1.0: +param-case@2.1.x: version "2.1.1" resolved "https://registry.yarnpkg.com/param-case/-/param-case-2.1.1.tgz#df94fd8cf6531ecf75e6bef9a0858fbc72be2247" integrity sha1-35T9jPZTHs915r75oIWPvHK+Ikc= @@ -20130,15 +19758,6 @@ parse-entities@^2.0.0: is-decimal "^1.0.0" is-hexadecimal "^1.0.0" -parse-filepath@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" - integrity sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE= - dependencies: - is-absolute "^1.0.0" - map-cache "^0.2.0" - path-root "^0.1.1" - parse-github-repo-url@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/parse-github-repo-url/-/parse-github-repo-url-1.4.1.tgz#9e7d8bb252a6cb6ba42595060b7bf6df3dbc1f50" @@ -20219,14 +19838,6 @@ parseurl@~1.3.2, parseurl@~1.3.3: resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== -pascal-case@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-2.0.1.tgz#2d578d3455f660da65eca18ef95b4e0de912761e" - integrity sha1-LVeNNFX2YNpl7KGO+VtODekSdh4= - dependencies: - camel-case "^3.0.0" - upper-case-first "^1.1.0" - pascal-case@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.1.tgz#5ac1975133ed619281e88920973d2cd1f279de5f" @@ -20245,23 +19856,11 @@ path-browserify@0.0.1: resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== -path-case@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/path-case/-/path-case-2.1.1.tgz#94b8037c372d3fe2906e465bb45e25d226e8eea5" - integrity sha1-lLgDfDctP+KQbkZbtF4l0ibo7qU= - dependencies: - no-case "^2.2.0" - path-dirname@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= -path-exists@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-1.0.0.tgz#d5a8998eb71ef37a74c34eb0d9eba6e878eea081" - integrity sha1-1aiZjrce83p0w06w2eum6HjuoIE= - path-exists@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" @@ -20279,7 +19878,7 @@ path-exists@^4.0.0: resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== -path-is-absolute@^1.0.0, path-is-absolute@~1.0.0: +path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= @@ -20309,18 +19908,6 @@ path-parse@^1.0.6: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== -path-root-regex@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/path-root-regex/-/path-root-regex-0.1.2.tgz#bfccdc8df5b12dc52c8b43ec38d18d72c04ba96d" - integrity sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0= - -path-root@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/path-root/-/path-root-0.1.1.tgz#9a4a6814cac1c0cd73360a95f32083c8ea4745b7" - integrity sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc= - dependencies: - path-root-regex "^0.1.0" - path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" @@ -21180,15 +20767,6 @@ postcss-values-parser@^2.0.0, postcss-values-parser@^2.0.1: indexes-of "^1.0.1" uniq "^1.0.1" -postcss@^6.0.11: - version "6.0.23" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324" - integrity sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag== - dependencies: - chalk "^2.4.1" - source-map "^0.6.1" - supports-color "^5.4.0" - postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.5, postcss@^7.0.6, postcss@^7.0.7: version "7.0.18" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.18.tgz#4b9cda95ae6c069c67a4d933029eddd4838ac233" @@ -23441,7 +23019,7 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@1.15.1, resolve@^1.1.7: +resolve@1.15.1: version "1.15.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w== @@ -23477,11 +23055,6 @@ resolve@^1.14.2: dependencies: path-parse "^1.0.6" -resolve@~1.1.0: - version "1.1.7" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" - integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= - resolve@~1.11.1: version "1.11.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.11.1.tgz#ea10d8110376982fef578df8fc30b9ac30a07a3e" @@ -23567,7 +23140,7 @@ right-align@^0.1.1: dependencies: align-text "^0.1.1" -rimraf@2, rimraf@^2.2.8, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@^2.7.1: +rimraf@2, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@^2.7.1: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== @@ -23894,26 +23467,6 @@ sass-lint@1.12.1: path-is-absolute "^1.0.0" util "^0.10.3" -sass-lint@^1.12.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/sass-lint/-/sass-lint-1.13.1.tgz#5fd2b2792e9215272335eb0f0dc607f61e8acc8f" - integrity sha512-DSyah8/MyjzW2BWYmQWekYEKir44BpLqrCFsgs9iaWiVTcwZfwXHF586hh3D1n+/9ihUNMfd8iHAyb9KkGgs7Q== - dependencies: - commander "^2.8.1" - eslint "^2.7.0" - front-matter "2.1.2" - fs-extra "^3.0.1" - glob "^7.0.0" - globule "^1.0.0" - gonzales-pe-sl "^4.2.3" - js-yaml "^3.5.4" - known-css-properties "^0.3.0" - lodash.capitalize "^4.1.0" - lodash.kebabcase "^4.0.0" - merge "^1.2.0" - path-is-absolute "^1.0.0" - util "^0.10.3" - sass-loader@8.0.2: version "8.0.2" resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-8.0.2.tgz#debecd8c3ce243c76454f2e8290482150380090d" @@ -24132,14 +23685,6 @@ send@0.17.1: range-parser "~1.2.1" statuses "~1.5.0" -sentence-case@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/sentence-case/-/sentence-case-2.1.1.tgz#1f6e2dda39c168bf92d13f86d4a918933f667ed4" - integrity sha1-H24t2jnBaL+S0T+G1KkYkz9mftQ= - dependencies: - no-case "^2.2.0" - upper-case-first "^1.1.2" - serialize-javascript@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61" @@ -24446,13 +23991,6 @@ smart-buffer@4.0.2: resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.0.2.tgz#5207858c3815cc69110703c6b94e46c15634395d" integrity sha512-JDhEpTKzXusOqXZ0BUIdH+CjFdO/CR3tLlf5CN34IypI+xMmXW1uB16OOY8z3cICbJlDAVJzNbwBhNO0wt9OAw== -snake-case@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-2.1.0.tgz#41bdb1b73f30ec66a04d4e2cad1b76387d4d6d9f" - integrity sha1-Qb2xtz8w7GagTU4srRt2OH1NbZ8= - dependencies: - no-case "^2.2.0" - snapdragon-node@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" @@ -24603,13 +24141,6 @@ source-map@0.1.32: dependencies: amdefine ">=0.0.4" -source-map@0.4.x: - version "0.4.4" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" - integrity sha1-66T12pwNyZneaAMti092FzZSA2s= - dependencies: - amdefine ">=0.0.4" - source-map@0.5.6: version "0.5.6" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" @@ -24723,11 +24254,6 @@ split@^1.0.0: dependencies: through "2" -sprintf-js@^1.0.3: - version "1.1.2" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" - integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== - sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -24782,11 +24308,6 @@ stack-generator@^2.0.4: dependencies: stackframe "^1.1.0" -stack-parser@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/stack-parser/-/stack-parser-0.0.1.tgz#7d3b63a17887e9e2c2bf55dbd3318fe34a39d1e7" - integrity sha1-fTtjoXiH6eLCv1Xb0zGP40o50ec= - stack-utils@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8" @@ -25185,16 +24706,16 @@ strip-json-comments@^3.1.0: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.0.tgz#7638d31422129ecf4457440009fba03f9f9ac180" integrity sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w== +strip-json-comments@^3.1.1, strip-json-comments@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + strip-json-comments@~1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91" integrity sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E= -strip-json-comments@~3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - strong-log-transformer@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz#0f5ed78d325e0421ac6f90f7f10e691d6ae3ae10" @@ -25267,7 +24788,7 @@ supports-color@^4.5.0: dependencies: has-flag "^2.0.0" -supports-color@^5.3.0, supports-color@^5.4.0, supports-color@^5.5.0: +supports-color@^5.3.0, supports-color@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== @@ -25339,14 +24860,6 @@ svgo@^1.2.2: unquote "~1.1.1" util.promisify "~1.0.0" -swap-case@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/swap-case/-/swap-case-1.1.2.tgz#c39203a4587385fad3c850a0bd1bcafa081974e3" - integrity sha1-w5IDpFhzhfrTyFCgvRvK+ggZdOM= - dependencies: - lower-case "^1.1.1" - upper-case "^1.1.1" - symbol-observable@^1.0.3, symbol-observable@^1.0.4, symbol-observable@^1.1.0, symbol-observable@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" @@ -25747,14 +25260,6 @@ tinycolor2@1.4.1, tinycolor2@^1.4.1: resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.1.tgz#f4fad333447bc0b07d4dc8e9209d8f39a8ac77e8" integrity sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g= -title-case@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/title-case/-/title-case-2.1.1.tgz#3e127216da58d2bc5becf137ab91dae3a7cd8faa" - integrity sha1-PhJyFtpY0rxb7PE3q5Ha46fNj6o= - dependencies: - no-case "^2.2.0" - upper-case "^1.0.3" - tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -26174,16 +25679,6 @@ ua-parser-js@^0.7.18: resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.20.tgz#7527178b82f6a62a0f243d1f94fd30e3e3c21098" integrity sha512-8OaIKfzL5cpx8eCMAhhvTlft8GYF8b2eQr6JkCyVdrgjcytyOmPCXrqXFcUnhonRpLlh5yxEZVohm6mzaowUOw== -uglify-js@2.6.x: - version "2.6.4" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.6.4.tgz#65ea2fb3059c9394692f15fed87c2b36c16b9adf" - integrity sha1-ZeovswWck5RpLxX+2HwrNsFrmt8= - dependencies: - async "~0.2.6" - source-map "~0.5.1" - uglify-to-browserify "~1.0.0" - yargs "~3.10.0" - uglify-js@3.4.x: version "3.4.10" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.10.tgz#9ad9563d8eb3acdfb8d38597d2af1d815f6a755f" @@ -26222,11 +25717,6 @@ umask@^1.1.0: resolved "https://registry.yarnpkg.com/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d" integrity sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0= -unc-path-regex@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" - integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo= - undefsafe@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.2.tgz#225f6b9e0337663e0d8e7cfd686fc2836ccace76" @@ -26234,14 +25724,6 @@ undefsafe@^2.0.2: dependencies: debug "^2.2.0" -underscore.string@~3.3.4: - version "3.3.5" - resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-3.3.5.tgz#fc2ad255b8bd309e239cbc5816fd23a9b7ea4023" - integrity sha512-g+dpmgn+XBneLmXXo+sGlW5xQEt4ErkS3mgeN2GFbremYeMBSJKr9Wf2KJplQVaiPY/f7FN6atosWYNm9ovrYg== - dependencies: - sprintf-js "^1.0.3" - util-deprecate "^1.0.2" - underscore@1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.7.0.tgz#6bbaf0877500d36be34ecaa584e0db9fef035209" @@ -26486,14 +25968,7 @@ uplot@1.4.6: resolved "https://registry.yarnpkg.com/uplot/-/uplot-1.4.6.tgz#52b6812c06b8d3cb89d4a8d053f45415cb5d9a13" integrity sha512-nw3LdjLFhAqKQF/Rv7QjZICVnjJemOQVj2L3b7889gHBrnC1LwltkzTcawDcbMe6pxo++0KVev9xSC0YCw+m5w== -upper-case-first@^1.1.0, upper-case-first@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/upper-case-first/-/upper-case-first-1.1.2.tgz#5d79bedcff14419518fd2edb0a0507c9b6859115" - integrity sha1-XXm+3P8UQZUY/S7bCgUHybaFkRU= - dependencies: - upper-case "^1.1.1" - -upper-case@^1.0.3, upper-case@^1.1.0, upper-case@^1.1.1, upper-case@^1.1.3: +upper-case@^1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598" integrity sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg= @@ -26672,13 +26147,6 @@ v8-to-istanbul@^7.0.0: convert-source-map "^1.6.0" source-map "^0.7.3" -v8flags@~3.1.1: - version "3.1.3" - resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.1.3.tgz#fc9dc23521ca20c5433f81cc4eb9b3033bb105d8" - integrity sha512-amh9CCg3ZxkzQ48Mhcb8iX7xpAfYJgePHxWMQCBWECpOSqJUXgY26ncA61UTV0BkPqfhcy6mzwCIoP4ygxpW8w== - dependencies: - homedir-polyfill "^1.0.1" - validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.3: version "3.0.4" resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" @@ -27152,7 +26620,7 @@ which-pm-runs@^1.0.0: resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= -which@1, which@1.3.1, which@^1.2.14, which@^1.2.4, which@^1.2.8, which@^1.2.9, which@^1.3.1, which@~1.3.0: +which@1, which@1.3.1, which@^1.2.14, which@^1.2.8, which@^1.2.9, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== @@ -27403,11 +26871,6 @@ xdg-trashdir@^2.1.1: user-home "^2.0.0" xdg-basedir "^2.0.0" -xml-char-classes@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/xml-char-classes/-/xml-char-classes-1.0.0.tgz#64657848a20ffc5df583a42ad8a277b4512bbc4d" - integrity sha1-ZGV4SKIP/F31g6Qq2KJ3tFErvE0= - xml-name-validator@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" From 207831fa39a08ba2a8ec2f2a00caf528fb9e958c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Bedi?= Date: Tue, 1 Dec 2020 10:36:38 +0100 Subject: [PATCH 020/124] Prometheus: don't override displayName property (#29441) * Prometheus: don't override displayName property * Rename displayName to displayNameFromDS for consistency --- .../prometheus/result_transformer.test.ts | 58 ++++++++----------- .../prometheus/result_transformer.ts | 10 ++-- 2 files changed, 29 insertions(+), 39 deletions(-) diff --git a/public/app/plugins/datasource/prometheus/result_transformer.test.ts b/public/app/plugins/datasource/prometheus/result_transformer.test.ts index bb2c8e57af4..8fa8d3b1ef2 100644 --- a/public/app/plugins/datasource/prometheus/result_transformer.test.ts +++ b/public/app/plugins/datasource/prometheus/result_transformer.test.ts @@ -1,6 +1,22 @@ import { DataFrame } from '@grafana/data'; import { transform } from './result_transformer'; +const matrixResponse = { + status: 'success', + data: { + resultType: 'matrix', + result: [ + { + metric: { __name__: 'test', job: 'testjob' }, + values: [ + [1, '10'], + [2, '0'], + ], + }, + ], + }, +}; + describe('Prometheus Result Transformer', () => { const options: any = { target: {}, query: {} }; describe('When nothing is returned', () => { @@ -306,46 +322,14 @@ describe('Prometheus Result Transformer', () => { }); it('should fill null values', () => { - const response = { - status: 'success', - data: { - resultType: 'matrix', - result: [ - { - metric: { __name__: 'test', job: 'testjob' }, - values: [ - [1, '10'], - [2, '0'], - ], - }, - ], - }, - }; - - const result = transform({ data: response } as any, { ...options, query: { step: 1, start: 0, end: 2 } }); + const result = transform({ data: matrixResponse } as any, { ...options, query: { step: 1, start: 0, end: 2 } }); expect(result[0].fields[0].values.toArray()).toEqual([0, 1000, 2000]); expect(result[0].fields[1].values.toArray()).toEqual([null, 10, 0]); }); it('should use __name__ label as series name', () => { - const response = { - status: 'success', - data: { - resultType: 'matrix', - result: [ - { - metric: { __name__: 'test', job: 'testjob' }, - values: [ - [1, '10'], - [2, '0'], - ], - }, - ], - }, - }; - - const result = transform({ data: response } as any, { + const result = transform({ data: matrixResponse } as any, { ...options, query: { step: 1, @@ -384,6 +368,12 @@ describe('Prometheus Result Transformer', () => { expect(result[0].name).toBe('{job="testjob"}'); }); + it('should not set displayName for ValueFields', () => { + const result = transform({ data: matrixResponse } as any, options); + expect(result[0].fields[1].config.displayName).toBeUndefined(); + expect(result[0].fields[1].config.displayNameFromDS).toBe('test{job="testjob"}'); + }); + it('should align null values with step', () => { const response = { status: 'success', diff --git a/public/app/plugins/datasource/prometheus/result_transformer.ts b/public/app/plugins/datasource/prometheus/result_transformer.ts index 580b4fcb639..a9a65cd89a1 100644 --- a/public/app/plugins/datasource/prometheus/result_transformer.ts +++ b/public/app/plugins/datasource/prometheus/result_transformer.ts @@ -139,10 +139,10 @@ function transformToDataFrame(data: MatrixOrVectorResult, options: TransformOpti dps.push([t, null]); } fields.push(getTimeField(dps, true)); - fields.push(getValueField({ data: dps, parseValue: false, labels, displayName: name })); + fields.push(getValueField({ data: dps, parseValue: false, labels, displayNameFromDS: name })); } else { fields.push(getTimeField([data.value])); - fields.push(getValueField({ data: [data.value], labels, displayName: name })); + fields.push(getValueField({ data: [data.value], labels, displayNameFromDS: name })); } return { @@ -224,7 +224,7 @@ type ValueFieldOptions = { valueName?: string; parseValue?: boolean; labels?: Labels; - displayName?: string; + displayNameFromDS?: string; }; function getValueField({ @@ -232,13 +232,13 @@ function getValueField({ valueName = TIME_SERIES_VALUE_FIELD_NAME, parseValue = true, labels, - displayName, + displayNameFromDS, }: ValueFieldOptions): MutableField { return { name: valueName, type: FieldType.number, config: { - displayName, + displayNameFromDS, }, labels, values: new ArrayVector(data.map(val => (parseValue ? parseSampleValue(val[1]) : val[1]))), From c52fd933f626ea6be26778768b2f4007fc4dccac Mon Sep 17 00:00:00 2001 From: Marcus Olsson Date: Tue, 1 Dec 2020 13:52:32 +0100 Subject: [PATCH 021/124] Docs: Fix broken link for plugins (#29346) --- docs/sources/developers/plugins/_index.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/sources/developers/plugins/_index.md b/docs/sources/developers/plugins/_index.md index 252e3d10973..0531c67a4eb 100644 --- a/docs/sources/developers/plugins/_index.md +++ b/docs/sources/developers/plugins/_index.md @@ -1,5 +1,6 @@ +++ -title = "Build a plugin." +title = "Build a plugin" +aliases = ["/docs/grafana/latest/plugins/developing/"] +++ # Build a plugin From fb622650f3b20f88b67c30d6a9ac5c39be81934f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Tue, 1 Dec 2020 15:02:49 +0100 Subject: [PATCH 022/124] Variables: Fixes Constant variable persistence confusion (#29407) * Variables: Fixes savequery for Constant and TextBox variables * Refactor: reverts textbox changes * Refactor: Fixes dashboard export and tests * Refactor: hides or migrates Constant variables * Tests: fixes snapshots --- .../DashExportModal/DashboardExporter.test.ts | 1 + .../DashExportModal/DashboardExporter.ts | 5 +- .../__snapshots__/DashboardPage.test.tsx.snap | 14 +-- .../__snapshots__/DashboardGrid.test.tsx.snap | 8 +- .../dashboard/state/DashboardMigrator.test.ts | 104 +++++++++++++++++- .../dashboard/state/DashboardMigrator.ts | 23 +++- public/app/features/variables/adapters.ts | 1 + .../features/variables/constant/adapter.ts | 8 +- .../variables/editor/VariableEditorEditor.tsx | 6 +- .../variables/editor/VariableHideSelect.tsx | 9 +- .../features/variables/state/actions.test.ts | 22 +++- .../variables/state/sharedReducer.test.ts | 65 ++++++++--- .../features/variables/state/sharedReducer.ts | 7 +- 13 files changed, 233 insertions(+), 40 deletions(-) diff --git a/public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts b/public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts index 3aa4143d45e..8954c5c50e4 100644 --- a/public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts +++ b/public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts @@ -56,6 +56,7 @@ describe('given dashboard with repeated panels', () => { type: 'constant', current: { value: 'collectd', text: 'collectd' }, options: [], + query: 'collectd', }, { name: 'ds', diff --git a/public/app/features/dashboard/components/DashExportModal/DashboardExporter.ts b/public/app/features/dashboard/components/DashExportModal/DashboardExporter.ts index 83298d2ccce..7ea3d38d31c 100644 --- a/public/app/features/dashboard/components/DashExportModal/DashboardExporter.ts +++ b/public/app/features/dashboard/components/DashExportModal/DashboardExporter.ts @@ -182,16 +182,17 @@ export class DashboardExporter { name: refName, type: 'constant', label: variable.label || variable.name, - value: variable.current.value, + value: variable.query, description: '', }); // update current and option variable.query = '${' + refName + '}'; - variable.options[0] = variable.current = { + variable.current = { value: variable.query, text: variable.query, selected: false, }; + variable.options = [variable.current]; } } 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 8fb120764c0..fc6f9437980 100644 --- a/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap +++ b/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap @@ -80,7 +80,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1` ], "refresh": undefined, "revision": undefined, - "schemaVersion": 26, + "schemaVersion": 27, "snapshot": undefined, "style": "dark", "tags": Array [], @@ -207,7 +207,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1` ], "refresh": undefined, "revision": undefined, - "schemaVersion": 26, + "schemaVersion": 27, "snapshot": undefined, "style": "dark", "tags": Array [], @@ -302,7 +302,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1` ], "refresh": undefined, "revision": undefined, - "schemaVersion": 26, + "schemaVersion": 27, "snapshot": undefined, "style": "dark", "tags": Array [], @@ -452,7 +452,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti ], "refresh": undefined, "revision": undefined, - "schemaVersion": 26, + "schemaVersion": 27, "snapshot": undefined, "style": "dark", "tags": Array [], @@ -579,7 +579,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti ], "refresh": undefined, "revision": undefined, - "schemaVersion": 26, + "schemaVersion": 27, "snapshot": undefined, "style": "dark", "tags": Array [], @@ -674,7 +674,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti ], "refresh": undefined, "revision": undefined, - "schemaVersion": 26, + "schemaVersion": 27, "snapshot": undefined, "style": "dark", "tags": Array [], @@ -774,7 +774,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti ], "refresh": undefined, "revision": undefined, - "schemaVersion": 26, + "schemaVersion": 27, "snapshot": undefined, "style": "dark", "tags": Array [], 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 88313ad2513..c1150ebde3f 100644 --- a/public/app/features/dashboard/dashgrid/__snapshots__/DashboardGrid.test.tsx.snap +++ b/public/app/features/dashboard/dashgrid/__snapshots__/DashboardGrid.test.tsx.snap @@ -215,7 +215,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` ], "refresh": undefined, "revision": undefined, - "schemaVersion": 26, + "schemaVersion": 27, "snapshot": undefined, "style": "dark", "tags": Array [], @@ -437,7 +437,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` ], "refresh": undefined, "revision": undefined, - "schemaVersion": 26, + "schemaVersion": 27, "snapshot": undefined, "style": "dark", "tags": Array [], @@ -659,7 +659,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` ], "refresh": undefined, "revision": undefined, - "schemaVersion": 26, + "schemaVersion": 27, "snapshot": undefined, "style": "dark", "tags": Array [], @@ -881,7 +881,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` ], "refresh": undefined, "revision": undefined, - "schemaVersion": 26, + "schemaVersion": 27, "snapshot": undefined, "style": "dark", "tags": Array [], diff --git a/public/app/features/dashboard/state/DashboardMigrator.test.ts b/public/app/features/dashboard/state/DashboardMigrator.test.ts index 18307305f96..ee477e60c59 100644 --- a/public/app/features/dashboard/state/DashboardMigrator.test.ts +++ b/public/app/features/dashboard/state/DashboardMigrator.test.ts @@ -4,6 +4,7 @@ import { PanelModel } from '../state/PanelModel'; import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN } from 'app/core/constants'; import { expect } from 'test/lib/common'; import { DataLinkBuiltInVars } from '@grafana/data'; +import { VariableHide } from '../../variables/types'; jest.mock('app/core/services/context_srv', () => ({})); @@ -132,7 +133,7 @@ describe('DashboardModel', () => { }); it('dashboard schema version should be set to latest', () => { - expect(model.schemaVersion).toBe(26); + expect(model.schemaVersion).toBe(27); }); it('graph thresholds should be migrated', () => { @@ -798,6 +799,107 @@ describe('DashboardModel', () => { expect(reactPanel.options.angular).toBeUndefined(); }); }); + + describe('when migrating constant variables so they are always hidden', () => { + let model: DashboardModel; + + beforeEach(() => { + model = new DashboardModel({ + templating: { + list: [ + { + type: 'query', + hide: VariableHide.dontHide, + datasource: null, + allFormat: '', + }, + { + type: 'query', + hide: VariableHide.hideLabel, + datasource: null, + allFormat: '', + }, + { + type: 'query', + hide: VariableHide.hideVariable, + datasource: null, + allFormat: '', + }, + { + type: 'constant', + hide: VariableHide.dontHide, + query: 'default value', + current: { selected: true, text: 'A', value: 'B' }, + options: [{ selected: true, text: 'A', value: 'B' }], + datasource: null, + allFormat: '', + }, + { + type: 'constant', + hide: VariableHide.hideLabel, + query: 'default value', + current: { selected: true, text: 'A', value: 'B' }, + options: [{ selected: true, text: 'A', value: 'B' }], + datasource: null, + allFormat: '', + }, + { + type: 'constant', + hide: VariableHide.hideVariable, + query: 'default value', + current: { selected: true, text: 'A', value: 'B' }, + options: [{ selected: true, text: 'A', value: 'B' }], + datasource: null, + allFormat: '', + }, + ], + }, + }); + }); + + it('should have three variables after migration', () => { + expect(model.templating.list.length).toBe(6); + }); + + it('should not touch other variable types', () => { + expect(model.templating.list[0].hide).toEqual(VariableHide.dontHide); + expect(model.templating.list[1].hide).toEqual(VariableHide.hideLabel); + expect(model.templating.list[2].hide).toEqual(VariableHide.hideVariable); + }); + + it('should migrate visible constant variables to textbox variables', () => { + expect(model.templating.list[3]).toEqual({ + type: 'textbox', + hide: VariableHide.dontHide, + query: 'default value', + current: { selected: true, text: 'default value', value: 'default value' }, + options: [{ selected: true, text: 'default value', value: 'default value' }], + datasource: null, + allFormat: '', + }); + expect(model.templating.list[4]).toEqual({ + type: 'textbox', + hide: VariableHide.hideLabel, + query: 'default value', + current: { selected: true, text: 'default value', value: 'default value' }, + options: [{ selected: true, text: 'default value', value: 'default value' }], + datasource: null, + allFormat: '', + }); + }); + + it('should change current and options for hidden constant variables', () => { + expect(model.templating.list[5]).toEqual({ + type: 'constant', + hide: VariableHide.hideVariable, + query: 'default value', + current: { selected: true, text: 'default value', value: 'default value' }, + options: [{ selected: true, text: 'default value', value: 'default value' }], + datasource: null, + allFormat: '', + }); + }); + }); }); function createRow(options: any, panelDescriptions: any[]) { diff --git a/public/app/features/dashboard/state/DashboardMigrator.ts b/public/app/features/dashboard/state/DashboardMigrator.ts index 5517ae5bcc3..dee3bac16f3 100644 --- a/public/app/features/dashboard/state/DashboardMigrator.ts +++ b/public/app/features/dashboard/state/DashboardMigrator.ts @@ -6,7 +6,7 @@ import kbn from 'app/core/utils/kbn'; // Types import { PanelModel } from './PanelModel'; import { DashboardModel } from './DashboardModel'; -import { DataLinkBuiltInVars, DataLink, urlUtil } from '@grafana/data'; +import { DataLink, DataLinkBuiltInVars, urlUtil } from '@grafana/data'; // Constants import { DEFAULT_PANEL_SPAN, @@ -16,9 +16,9 @@ import { GRID_COLUMN_COUNT, MIN_PANEL_HEIGHT, } from 'app/core/constants'; -import { isMulti, isQuery } from 'app/features/variables/guard'; +import { isConstant, isMulti, isQuery } from 'app/features/variables/guard'; import { alignCurrentWithMulti } from 'app/features/variables/shared/multiOptions'; -import { VariableTag } from '../../variables/types'; +import { VariableHide, VariableTag } from '../../variables/types'; export class DashboardMigrator { dashboard: DashboardModel; @@ -31,7 +31,7 @@ export class DashboardMigrator { let i, j, k, n; const oldVersion = this.dashboard.schemaVersion; const panelUpgrades = []; - this.dashboard.schemaVersion = 26; + this.dashboard.schemaVersion = 27; if (oldVersion === this.dashboard.schemaVersion) { return; @@ -575,6 +575,21 @@ export class DashboardMigrator { }); } + if (oldVersion < 27) { + for (const variable of this.dashboard.templating.list) { + if (!isConstant(variable)) { + continue; + } + + if (variable.hide === VariableHide.dontHide || variable.hide === VariableHide.hideLabel) { + variable.type = 'textbox'; + } + + variable.current = { selected: true, text: variable.query ?? '', value: variable.query ?? '' }; + variable.options = [variable.current]; + } + } + if (panelUpgrades.length === 0) { return; } diff --git a/public/app/features/variables/adapters.ts b/public/app/features/variables/adapters.ts index 8bc05c9af01..f83fb0e50e8 100644 --- a/public/app/features/variables/adapters.ts +++ b/public/app/features/variables/adapters.ts @@ -39,6 +39,7 @@ export interface VariableAdapter { picker: ComponentType; editor: ComponentType; reducer: Reducer; + beforeAdding?: (model: any) => any; } export type VariableModels = diff --git a/public/app/features/variables/constant/adapter.ts b/public/app/features/variables/constant/adapter.ts index 57b89df4f25..78a6d629db5 100644 --- a/public/app/features/variables/constant/adapter.ts +++ b/public/app/features/variables/constant/adapter.ts @@ -31,11 +31,17 @@ export const createConstantVariableAdapter = (): VariableAdapter { - const { index, id, state, global, ...rest } = cloneDeep(variable); + const { index, id, state, global, current, options, ...rest } = cloneDeep(variable); return rest; }, getValueForUrl: variable => { return variable.current.value; }, + beforeAdding: model => { + const { current, options, query, ...rest } = cloneDeep(model); + const option = { selected: true, text: query, value: query }; + + return { ...rest, current: option, options: [option], query }; + }, }; }; diff --git a/public/app/features/variables/editor/VariableEditorEditor.tsx b/public/app/features/variables/editor/VariableEditorEditor.tsx index c96af34846f..2d281499dcf 100644 --- a/public/app/features/variables/editor/VariableEditorEditor.tsx +++ b/public/app/features/variables/editor/VariableEditorEditor.tsx @@ -152,7 +152,11 @@ export class VariableEditorEditorUnConnected extends PureComponent { placeholder="optional display name" ariaLabel={selectors.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInput} /> - + ) => void; hide: VariableHide; + type: VariableType; } const HIDE_OPTIONS = [ @@ -16,9 +17,13 @@ const HIDE_OPTIONS = [ { label: 'Variable', value: VariableHide.hideVariable }, ]; -export function VariableHideSelect({ onChange, hide }: PropsWithChildren) { +export function VariableHideSelect({ onChange, hide, type }: PropsWithChildren) { const value = useMemo(() => HIDE_OPTIONS.find(o => o.value === hide) ?? HIDE_OPTIONS[0], [hide]); + if (type === 'constant') { + return null; + } + return ( { data: { global: false, index: 1, - model: { ...constant, name: 'constant1', id: 'constant1', global: false, index: 1 }, + model: { + ...constant, + name: 'constant1', + id: 'constant1', + global: false, + index: 1, + current: { selected: true, text: '', value: '' }, + options: [{ selected: true, text: '', value: '' }], + } as ConstantVariableModel, }, }), changeVariableNameSucceeded({ type: 'constant', id: 'constant1', data: { newName: 'constant1' } }), @@ -434,7 +442,15 @@ describe('shared actions', () => { data: { global: false, index: 1, - model: { ...constant, name: 'constant1', id: 'constant1', global: false, index: 1 }, + model: { + ...constant, + name: 'constant1', + id: 'constant1', + global: false, + index: 1, + current: { selected: true, text: '', value: '' }, + options: [{ selected: true, text: '', value: '' }], + } as ConstantVariableModel, }, }), changeVariableNameSucceeded({ type: 'constant', id: 'constant1', data: { newName: 'constant1' } }), diff --git a/public/app/features/variables/state/sharedReducer.test.ts b/public/app/features/variables/state/sharedReducer.test.ts index 04ca95b1767..23f2b77cfec 100644 --- a/public/app/features/variables/state/sharedReducer.test.ts +++ b/public/app/features/variables/state/sharedReducer.test.ts @@ -1,6 +1,5 @@ import cloneDeep from 'lodash/cloneDeep'; -import { default as lodashDefaults } from 'lodash/defaults'; -import { LoadingState } from '@grafana/data'; +import { LoadingState, VariableType } from '@grafana/data'; import { reducerTester } from '../../../../test/core/redux/reducerTester'; import { @@ -16,7 +15,7 @@ import { variableStateFetching, variableStateNotStarted, } from './sharedReducer'; -import { QueryVariableModel, VariableHide } from '../types'; +import { ConstantVariableModel, QueryVariableModel, VariableHide, VariableOption } from '../types'; import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE, toVariablePayload } from './types'; import { variableAdapters } from '../adapters'; import { createQueryVariableAdapter } from '../query/adapter'; @@ -24,30 +23,70 @@ import { initialQueryVariableModelState } from '../query/reducer'; import { getVariableState, getVariableTestContext } from './helpers'; import { initialVariablesState, VariablesState } from './variablesReducer'; import { changeVariableNameSucceeded } from '../editor/reducer'; +import { createConstantVariableAdapter } from '../constant/adapter'; +import { initialConstantVariableModelState } from '../constant/reducer'; -variableAdapters.setInit(() => [createQueryVariableAdapter()]); +variableAdapters.setInit(() => [createQueryVariableAdapter(), createConstantVariableAdapter()]); describe('sharedReducer', () => { describe('when addVariable is dispatched', () => { it('then state should be correct', () => { - const model = ({ + const model: any = { name: 'name from model', type: 'type from model', current: undefined, - } as unknown) as QueryVariableModel; + }; - const payload = toVariablePayload({ id: '0', type: 'query' }, { global: true, index: 0, model }); + const expected: QueryVariableModel = { + ...initialQueryVariableModelState, + id: 'name from model', + global: true, + index: 0, + name: 'name from model', + type: ('type from model' as unknown) as VariableType, + current: ({} as unknown) as VariableOption, + }; + + const payload = toVariablePayload({ id: 'name from model', type: 'query' }, { global: true, index: 0, model }); reducerTester() .givenReducer(sharedReducer, { ...initialVariablesState }) .whenActionIsDispatched(addVariable(payload)) .thenStateShouldEqual({ - [0]: { - ...lodashDefaults({}, model, initialQueryVariableModelState), - id: '0', - global: true, - index: 0, - }, + ['name from model']: expected, + }); + }); + }); + + describe('when addVariable is dispatched for a constant model', () => { + it('then state should be correct', () => { + const model: any = { + name: 'constant', + type: 'constant', + query: 'a constant', + current: { selected: true, text: 'A', value: 'A' }, + options: [{ selected: true, text: 'A', value: 'A' }], + }; + + const expected: ConstantVariableModel = { + ...initialConstantVariableModelState, + id: 'constant', + global: true, + index: 0, + name: 'constant', + type: 'constant', + query: 'a constant', + current: { selected: true, text: 'a constant', value: 'a constant' }, + options: [{ selected: true, text: 'a constant', value: 'a constant' }], + }; + + const payload = toVariablePayload({ id: 'constant', type: 'constant' }, { global: true, index: 0, model }); + + reducerTester() + .givenReducer(sharedReducer, { ...initialVariablesState }) + .whenActionIsDispatched(addVariable(payload)) + .thenStateShouldEqual({ + ['constant']: expected, }); }); }); diff --git a/public/app/features/variables/state/sharedReducer.ts b/public/app/features/variables/state/sharedReducer.ts index d5ff994f5e9..cc57a443874 100644 --- a/public/app/features/variables/state/sharedReducer.ts +++ b/public/app/features/variables/state/sharedReducer.ts @@ -16,8 +16,11 @@ const sharedReducerSlice = createSlice({ reducers: { addVariable: (state: VariablesState, action: PayloadAction>) => { const id = action.payload.id ?? action.payload.data.model.name; // for testing purposes we can call this with an id - const initialState = cloneDeep(variableAdapters.get(action.payload.type).initialState); - const model = cloneDeep(action.payload.data.model); + const adapter = variableAdapters.get(action.payload.type); + const initialState = cloneDeep(adapter.initialState); + const model = adapter.beforeAdding + ? adapter.beforeAdding(action.payload.data.model) + : cloneDeep(action.payload.data.model); const variable = { ...lodashDefaults({}, model, initialState), From b7aa6fed1db90586d170d37fc8a793f0b9863c57 Mon Sep 17 00:00:00 2001 From: Carl Bergquist Date: Tue, 1 Dec 2020 15:04:59 +0100 Subject: [PATCH 023/124] Instrumentation: Add examplars for request histograms (#29357) Signed-off-by: bergquist --- go.mod | 5 +++-- go.sum | 22 ++++++++++++++++++++++ pkg/api/http_server.go | 2 +- pkg/middleware/logger.go | 19 ++----------------- pkg/middleware/request_metrics.go | 31 ++++++++++++++++++++++++------- pkg/server/server.go | 2 +- 6 files changed, 53 insertions(+), 28 deletions(-) diff --git a/go.mod b/go.mod index ae98cc7700b..72d78946c6c 100644 --- a/go.mod +++ b/go.mod @@ -62,9 +62,9 @@ require ( github.com/opentracing/opentracing-go v1.2.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.7.1 + github.com/prometheus/client_golang v1.8.0 github.com/prometheus/client_model v0.2.0 - github.com/prometheus/common v0.10.0 + github.com/prometheus/common v0.14.0 github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be // indirect github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967 github.com/robfig/cron/v3 v3.0.0 @@ -77,6 +77,7 @@ require ( github.com/uber/jaeger-client-go v2.25.0+incompatible github.com/unknwon/com v1.0.1 github.com/urfave/cli/v2 v2.1.1 + github.com/weaveworks/common v0.0.0-20201119133501-0619918236ec github.com/xorcare/pointer v1.1.0 github.com/yudai/gojsondiff v1.0.0 golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a diff --git a/go.sum b/go.sum index c15d001c14e..f144259db92 100644 --- a/go.sum +++ b/go.sum @@ -329,6 +329,8 @@ github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structtag v1.1.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= +github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA= github.com/fluent/fluent-bit-go v0.0.0-20190925192703-ea13c021720c/go.mod h1:WQX+afhrekY9rGK+WT4xvKSlzmia9gDoLYu4GGYGASQ= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= @@ -489,6 +491,7 @@ github.com/gocql/gocql v0.0.0-20200121121104-95d072f1b5bb/go.mod h1:DL0ekTmBSTdl github.com/gocql/gocql v0.0.0-20200526081602-cd04bd7f22a7/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY= github.com/godbus/dbus v0.0.0-20190402143921-271e53dc4968/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gogo/googleapis v1.1.0 h1:kFkMAZBNAn4j7K0GiZr8cRYzejq68VbheufiV3YuyFI= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -499,6 +502,7 @@ github.com/gogo/protobuf v1.2.2-0.20190730201129-28a6bbf47e48/go.mod h1:SlYgWuQ5 github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/status v1.0.3 h1:WkVBY59mw7qUNTr/bLwO7J2vesJ0rQ2C3tMXrTd3w5M= github.com/gogo/status v1.0.3/go.mod h1:SavQ51ycCLnc7dGyJxp8YAmudx8xqiVrRf+6IXRsugc= github.com/golang-migrate/migrate/v4 v4.7.0/go.mod h1:Qvut3N4xKWjoH3sokBccML6WyHSnggXm/DvMMnTsQIc= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= @@ -610,6 +614,7 @@ github.com/gopherjs/gopherjs v0.0.0-20191106031601-ce3c9ade29de/go.mod h1:wJfORR github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= @@ -803,6 +808,8 @@ github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH6 github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08/go.mod h1:dFWs1zEqDjFtnBXsd1vPOZaLsESovai349994nHx3e0= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -980,6 +987,7 @@ github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zM github.com/opentracing-contrib/go-grpc v0.0.0-20180928155321-4b5a12d3ff02/go.mod h1:JNdpVEzCpXBgIiv4ds+TzhN1hrtxq6ClLrTlT9OQRSc= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing-contrib/go-stdlib v0.0.0-20190519235532-cf7a6c988dc9/go.mod h1:PLldrQSroqzH70Xl+1DQcGnefIbqsKR7UDaiux3zV+w= +github.com/opentracing-contrib/go-stdlib v1.0.0 h1:TBS7YuVotp8myLon4Pv7BtCBzOTo1DeZCld0Z63mW2w= github.com/opentracing-contrib/go-stdlib v1.0.0/go.mod h1:qtI1ogk+2JhVPIXVc6q+NHziSmy2W5GbdQZFUHADCBU= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= @@ -1039,6 +1047,8 @@ github.com/prometheus/client_golang v1.4.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3O github.com/prometheus/client_golang v1.6.0/go.mod h1:ZLOG9ck3JLRdB5MgO8f+lLTe83AXG6ro35rLTxvnIl4= github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.8.0 h1:zvJNkoCFAnYFNC24FV8nW4JdRJ3GIFcLbg65lL/JDcw= +github.com/prometheus/client_golang v1.8.0/go.mod h1:O9VU6huf47PktckDQfMTX0Y8tY0/7TSWwj+ITvv0TnM= github.com/prometheus/client_model v0.0.0-20170216185247-6f3806018612/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= @@ -1057,6 +1067,8 @@ github.com/prometheus/common v0.8.0/go.mod h1:PC/OgXc+UN7B4ALwvn1yzVZmVwvhXp5Jsb github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.14.0 h1:RHRyE8UocrbjU+6UvRzwi6HjiDfxrrBU91TtbKzkGp4= +github.com/prometheus/common v0.14.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/node_exporter v1.0.0-rc.0.0.20200428091818-01054558c289/go.mod h1:FGbBv5OPKjch+jNUJmEQpMZytIdyW0NdBtWFcfSKusc= github.com/prometheus/procfs v0.0.0-20180612222113-7d6f385de8be/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -1071,6 +1083,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/prometheus v0.0.0-20180315085919-58e2a31db8de/go.mod h1:oAIUtOny2rjMX0OWN5vPR5/q/twIROJvdqnQKDdil/s= github.com/prometheus/prometheus v0.0.0-20190818123050-43acd0e2e93f/go.mod h1:rMTlmxGCvukf2KMu3fClMDKLLoJ5hl61MhcJ7xKakf0= github.com/prometheus/prometheus v1.8.2-0.20200107122003-4708915ac6ef/go.mod h1:7U90zPoLkWjEIQcy/rweQla82OCTUzxVHE51G3OhJbI= @@ -1135,7 +1149,10 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q= github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= +github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w= @@ -1194,6 +1211,7 @@ github.com/ua-parser/uap-go v0.0.0-20190826212731-daf92ba38329 h1:VBsKFh4W1JEMz3 github.com/ua-parser/uap-go v0.0.0-20190826212731-daf92ba38329/go.mod h1:OBcG9bn7sHtXgarhUEb3OfCnNsgtGnkVf41ilSZ3K3E= github.com/uber/jaeger-client-go v2.15.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-client-go v2.20.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-client-go v2.22.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-client-go v2.23.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-client-go v2.24.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-client-go v2.25.0+incompatible h1:IxcNZ7WRY1Y3G4poYlx24szfsn/3LvK9QHCq9oQw8+U= @@ -1222,6 +1240,9 @@ github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= github.com/weaveworks/common v0.0.0-20200206153930-760e36ae819a/go.mod h1:6enWAqfQBFrE8X/XdJwZr8IKgh1chStuFR0mjU/UOUw= github.com/weaveworks/common v0.0.0-20200625145055-4b1847531bc9/go.mod h1:c98fKi5B9u8OsKGiWHLRKus6ToQ1Tubeow44ECO1uxY= +github.com/weaveworks/common v0.0.0-20201119133501-0619918236ec h1:5JmevdpzK10Z2ua0VDToj7Kg2+/t0FzdYBjsurYRE8k= +github.com/weaveworks/common v0.0.0-20201119133501-0619918236ec/go.mod h1:ykzWac1LtVfOxdCK+jD754at1Ws9dKCwFeUzkFBffPs= +github.com/weaveworks/promrus v1.2.0 h1:jOLf6pe6/vss4qGHjXmGz4oDJQA+AOCqEL3FvvZGz7M= github.com/weaveworks/promrus v1.2.0/go.mod h1:SaE82+OJ91yqjrE1rsvBWVzNZKcHYFtMUyS1+Ogs/KA= github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs= @@ -1516,6 +1537,7 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd h1:WgqgiQvkiZWz7XLhphjt2GI2GcGCTIZs9jqXMWmH+oc= golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/pkg/api/http_server.go b/pkg/api/http_server.go index f99ce9701a5..625b142f39d 100644 --- a/pkg/api/http_server.go +++ b/pkg/api/http_server.go @@ -367,7 +367,7 @@ func (hs *HTTPServer) metricsEndpoint(ctx *macaron.Context) { } promhttp. - HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{}). + HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{EnableOpenMetrics: true}). ServeHTTP(ctx.Resp, ctx.Req.Request) } diff --git a/pkg/middleware/logger.go b/pkg/middleware/logger.go index c698d511802..94de372fee4 100644 --- a/pkg/middleware/logger.go +++ b/pkg/middleware/logger.go @@ -16,15 +16,13 @@ package middleware import ( - "context" "net/http" "time" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" - opentracing "github.com/opentracing/opentracing-go" "github.com/prometheus/client_golang/prometheus" - "github.com/uber/jaeger-client-go" + cw "github.com/weaveworks/common/middleware" "gopkg.in/macaron.v1" ) @@ -63,7 +61,7 @@ func Logger() macaron.Handler { "referer", req.Referer(), } - traceID, exist := extractTraceID(ctxTyped.Req.Request.Context()) + traceID, exist := cw.ExtractTraceID(ctxTyped.Req.Request.Context()) if exist { logParams = append(logParams, "traceID", traceID) } @@ -76,16 +74,3 @@ func Logger() macaron.Handler { } } } - -func extractTraceID(ctx context.Context) (string, bool) { - sp := opentracing.SpanFromContext(ctx) - if sp == nil { - return "", false - } - sctx, ok := sp.Context().(jaeger.SpanContext) - if !ok { - return "", false - } - - return sctx.TraceID().String(), true -} diff --git a/pkg/middleware/request_metrics.go b/pkg/middleware/request_metrics.go index 50c0f711da2..a55d707323d 100644 --- a/pkg/middleware/request_metrics.go +++ b/pkg/middleware/request_metrics.go @@ -9,12 +9,17 @@ import ( "github.com/grafana/grafana/pkg/infra/metrics" "github.com/grafana/grafana/pkg/setting" "github.com/prometheus/client_golang/prometheus" + cw "github.com/weaveworks/common/middleware" "gopkg.in/macaron.v1" ) var ( httpRequestsInFlight prometheus.Gauge httpRequestDurationHistogram *prometheus.HistogramVec + + // DefBuckets are histogram buckets for the response time (in seconds) + // of a network service, including one that is responding very slowly. + defBuckets = []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5} ) func init() { @@ -30,9 +35,9 @@ func init() { Namespace: "grafana", Name: "http_request_duration_seconds", Help: "Histogram of latencies for HTTP requests.", - Buckets: []float64{.1, .2, .4, 1, 3, 8, 20, 60, 120}, + Buckets: defBuckets, }, - []string{"handler"}, + []string{"handler", "code", "method"}, ) prometheus.MustRegister(httpRequestsInFlight, httpRequestDurationHistogram) @@ -52,14 +57,26 @@ func RequestMetrics(cfg *setting.Cfg) func(handler string) macaron.Handler { code := sanitizeCode(status) method := sanitizeMethod(req.Method) - metrics.MHttpRequestTotal.WithLabelValues(handler, code, method).Inc() - duration := time.Since(now).Nanoseconds() / int64(time.Millisecond) - - // enable histogram and disable summaries for http requests. + // enable histogram and disable summaries + counters for http requests. if cfg.IsHTTPRequestHistogramEnabled() { - httpRequestDurationHistogram.WithLabelValues(handler).Observe(float64(duration)) + // avoiding the sanitize functions for in the new instrumentation + // since they dont make much sense. We should remove them later. + histogram := httpRequestDurationHistogram. + WithLabelValues(handler, strconv.Itoa(rw.Status()), req.Method) + if traceID, ok := cw.ExtractSampledTraceID(c.Req.Context()); ok { + // Need to type-convert the Observer to an + // ExemplarObserver. This will always work for a + // HistogramVec. + histogram.(prometheus.ExemplarObserver).ObserveWithExemplar( + time.Since(now).Seconds(), prometheus.Labels{"traceID": traceID}, + ) + return + } + histogram.Observe(time.Since(now).Seconds()) } else { + duration := time.Since(now).Nanoseconds() / int64(time.Millisecond) + metrics.MHttpRequestTotal.WithLabelValues(handler, code, method).Inc() metrics.MHttpRequestSummary.WithLabelValues(handler, code, method).Observe(float64(duration)) } diff --git a/pkg/server/server.go b/pkg/server/server.go index 228482e88b6..7372a329a7b 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -273,7 +273,7 @@ func (s *Server) buildServiceGraph(services []*registry.Descriptor) error { objs := []interface{}{ bus.GetBus(), s.cfg, - routing.NewRouteRegister(middleware.RequestMetrics(s.cfg), middleware.RequestTracing), + routing.NewRouteRegister(middleware.RequestTracing, middleware.RequestMetrics(s.cfg)), localcache.New(5*time.Minute, 10*time.Minute), s, } From 38caa80acdbb3f18d2ade47f66ced9ab514b8fe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 1 Dec 2020 15:17:33 +0100 Subject: [PATCH 024/124] DataSourceSrv: Look up data source by uid and name transparently (#29449) * Datasources: Look up data source by uid or name transparently * comment tweak * Removed config * fixed type issues * Initialize datasource srv * Added deprecation notice * Renamed getSettingsFor to getInstanceSettings * fixed ts issue --- .../src/services/dataSourceSrv.ts | 6 + .../src/utils/DataSourceWithBackend.test.ts | 20 +-- .../src/utils/DataSourceWithBackend.ts | 21 +-- .../getAlertingValidationMessage.test.ts | 5 + .../app/features/datasources/state/actions.ts | 2 +- public/app/features/live/scopes.ts | 3 + public/app/features/plugins/datasource_srv.ts | 73 +++++--- .../plugins/specs/datasource_srv.test.ts | 157 +++++++++++------- public/app/routes/GrafanaCtrl.ts | 2 + 9 files changed, 177 insertions(+), 112 deletions(-) diff --git a/packages/grafana-runtime/src/services/dataSourceSrv.ts b/packages/grafana-runtime/src/services/dataSourceSrv.ts index 0abf84952cc..f06d8408a2c 100644 --- a/packages/grafana-runtime/src/services/dataSourceSrv.ts +++ b/packages/grafana-runtime/src/services/dataSourceSrv.ts @@ -17,6 +17,7 @@ export interface DataSourceSrv { /** * Returns metadata based on UID. + * @deprecated use getInstanceSettings */ getDataSourceSettingsByUid(uid: string): DataSourceInstanceSettings | undefined; @@ -29,6 +30,11 @@ export interface DataSourceSrv { * Get all data sources except for internal ones that usually should not be listed like mixed data source. */ getExternal(): DataSourceInstanceSettings[]; + + /** + * Get settings and plugin metadata by name or uid + */ + getInstanceSettings(nameOrUid: string | null | undefined): DataSourceInstanceSettings | undefined; } let singletonInstance: DataSourceSrv; diff --git a/packages/grafana-runtime/src/utils/DataSourceWithBackend.test.ts b/packages/grafana-runtime/src/utils/DataSourceWithBackend.test.ts index 622fcd4bd93..9cc83b45edb 100644 --- a/packages/grafana-runtime/src/utils/DataSourceWithBackend.test.ts +++ b/packages/grafana-runtime/src/utils/DataSourceWithBackend.test.ts @@ -19,19 +19,10 @@ const backendSrv = ({ jest.mock('../services', () => ({ getBackendSrv: () => backendSrv, -})); -jest.mock('..', () => ({ - config: { - bootData: { - user: { - orgId: 77, - }, - }, - datasources: { - sample: { - id: 8674, - }, - }, + getDataSourceSrv: () => { + return { + getInstanceSettings: () => ({ id: 8674 }), + }; }, })); @@ -46,6 +37,7 @@ describe('DataSourceWithBackend', () => { mockDatasourceRequest.mockReset(); mockDatasourceRequest.mockReturnValue(Promise.resolve({})); const ds = new MyDataSource(settings); + ds.query({ maxDataPoints: 10, intervalMs: 5000, @@ -64,7 +56,6 @@ describe('DataSourceWithBackend', () => { "datasourceId": 1234, "intervalMs": 5000, "maxDataPoints": 10, - "orgId": 77, "refId": "A", }, Object { @@ -72,7 +63,6 @@ describe('DataSourceWithBackend', () => { "datasourceId": 8674, "intervalMs": 5000, "maxDataPoints": 10, - "orgId": 77, "refId": "B", }, ], diff --git a/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts b/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts index 4c8e719a8f7..ebe0b12774d 100644 --- a/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts +++ b/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts @@ -9,8 +9,7 @@ import { } from '@grafana/data'; import { Observable, of } from 'rxjs'; import { map, catchError } from 'rxjs/operators'; -import { config } from '..'; -import { getBackendSrv } from '../services'; +import { getBackendSrv, getDataSourceSrv } from '../services'; import { toDataQueryResponse } from './queryResponse'; const ExpressionDatasourceID = '__expr__'; @@ -57,34 +56,37 @@ export class DataSourceWithBackend< */ query(request: DataQueryRequest): Observable { const { intervalMs, maxDataPoints, range, requestId } = request; - const orgId = config.bootData.user.orgId; let targets = request.targets; + if (this.filterQuery) { targets = targets.filter(q => this.filterQuery!(q)); } + const queries = targets.map(q => { let datasourceId = this.id; + if (q.datasource === ExpressionDatasourceID) { return { ...q, datasourceId, - orgId, }; } + if (q.datasource) { - const dsName = q.datasource === 'default' ? config.defaultDatasource : q.datasource; - const ds = config.datasources[dsName]; + const ds = getDataSourceSrv().getInstanceSettings(q.datasource); + if (!ds) { throw new Error('Unknown Datasource: ' + q.datasource); } + datasourceId = ds.id; } + return { ...this.applyTemplateVariables(q, request.scopedVars), datasourceId, intervalMs, maxDataPoints, - orgId, }; }); @@ -93,9 +95,8 @@ export class DataSourceWithBackend< return of({ data: [] }); } - const body: any = { - queries, - }; + const body: any = { queries }; + if (range) { body.range = range; body.from = range.from.valueOf().toString(); diff --git a/public/app/features/alerting/getAlertingValidationMessage.test.ts b/public/app/features/alerting/getAlertingValidationMessage.test.ts index f98a765975c..57c06cdf507 100644 --- a/public/app/features/alerting/getAlertingValidationMessage.test.ts +++ b/public/app/features/alerting/getAlertingValidationMessage.test.ts @@ -26,6 +26,7 @@ describe('getAlertingValidationMessage', () => { getExternal(): DataSourceInstanceSettings[] { return []; }, + getInstanceSettings: (() => {}) as any, getAll(): DataSourceInstanceSettings[] { return []; }, @@ -66,6 +67,7 @@ describe('getAlertingValidationMessage', () => { return Promise.resolve(alertingDatasource); }, getDataSourceSettingsByUid(): any {}, + getInstanceSettings: (() => {}) as any, getExternal(): DataSourceInstanceSettings[] { return []; }, @@ -96,6 +98,7 @@ describe('getAlertingValidationMessage', () => { const datasourceSrv: DataSourceSrv = { get: getMock, getDataSourceSettingsByUid(): any {}, + getInstanceSettings: (() => {}) as any, getExternal(): DataSourceInstanceSettings[] { return []; }, @@ -128,6 +131,7 @@ describe('getAlertingValidationMessage', () => { const datasourceSrv: DataSourceSrv = { get: getMock, getDataSourceSettingsByUid(): any {}, + getInstanceSettings: (() => {}) as any, getExternal(): DataSourceInstanceSettings[] { return []; }, @@ -160,6 +164,7 @@ describe('getAlertingValidationMessage', () => { const datasourceSrv: DataSourceSrv = { get: getMock, getDataSourceSettingsByUid(): any {}, + getInstanceSettings: (() => {}) as any, getExternal(): DataSourceInstanceSettings[] { return []; }, diff --git a/public/app/features/datasources/state/actions.ts b/public/app/features/datasources/state/actions.ts index 9d66b201791..e09696dd312 100644 --- a/public/app/features/datasources/state/actions.ts +++ b/public/app/features/datasources/state/actions.ts @@ -214,7 +214,7 @@ function updateFrontendSettings() { .then((settings: any) => { config.datasources = settings.datasources; config.defaultDatasource = settings.defaultDatasource; - getDatasourceSrv().init(); + getDatasourceSrv().init(config.datasources, settings.defaultDatasource); }); } diff --git a/public/app/features/live/scopes.ts b/public/app/features/live/scopes.ts index 2097db5c3bd..21a68ce76d5 100644 --- a/public/app/features/live/scopes.ts +++ b/public/app/features/live/scopes.ts @@ -82,7 +82,9 @@ export class GrafanaLiveDataSourceScope extends GrafanaLiveScope { if (this.names) { return Promise.resolve(this.names); } + const names: Array> = []; + for (const [key, ds] of Object.entries(config.datasources)) { if (ds.meta.live) { try { @@ -99,6 +101,7 @@ export class GrafanaLiveDataSourceScope extends GrafanaLiveScope { } } } + return (this.names = names); } } diff --git a/public/app/features/plugins/datasource_srv.ts b/public/app/features/plugins/datasource_srv.ts index d892d171cbd..76d86c6000d 100644 --- a/public/app/features/plugins/datasource_srv.ts +++ b/public/app/features/plugins/datasource_srv.ts @@ -2,7 +2,6 @@ import sortBy from 'lodash/sortBy'; import coreModule from 'app/core/core_module'; // Services & Utils -import config from 'app/core/config'; import { importDataSourcePlugin } from './plugin_loader'; import { DataSourceSrv as DataSourceService, @@ -18,47 +17,74 @@ import { expressionDatasource } from 'app/features/expressions/ExpressionDatasou import { DataSourceVariableModel } from '../variables/types'; export class DatasourceSrv implements DataSourceService { - datasources: Record = {}; + private datasources: Record = {}; + private settingsMapByName: Record = {}; + private settingsMapByUid: Record = {}; + private defaultName = ''; /** @ngInject */ constructor( private $injector: auto.IInjectorService, private $rootScope: GrafanaRootScope, private templateSrv: TemplateSrv - ) { - this.init(); - } + ) {} - init() { + init(settingsMapByName: Record, defaultName: string) { this.datasources = {}; + this.settingsMapByUid = {}; + this.settingsMapByName = settingsMapByName; + this.defaultName = defaultName; + + for (const dsSettings of Object.values(settingsMapByName)) { + this.settingsMapByUid[dsSettings.uid] = dsSettings; + } } getDataSourceSettingsByUid(uid: string): DataSourceInstanceSettings | undefined { - return Object.values(config.datasources).find(ds => ds.uid === uid); + return this.settingsMapByUid[uid]; } - get(name?: string | null, scopedVars?: ScopedVars): Promise { - if (!name) { - return this.get(config.defaultDatasource); + getInstanceSettings(nameOrUid: string | null | undefined): DataSourceInstanceSettings | undefined { + if (nameOrUid === 'default' || nameOrUid === null || nameOrUid === undefined) { + return this.settingsMapByName[this.defaultName]; + } + + return this.settingsMapByUid[nameOrUid] ?? this.settingsMapByName[nameOrUid]; + } + + get(nameOrUid?: string | null, scopedVars?: ScopedVars): Promise { + if (!nameOrUid) { + return this.get(this.defaultName); + } + + // Check if nameOrUid matches a uid and then get the name + const byUid = this.settingsMapByUid[nameOrUid]; + if (byUid) { + nameOrUid = byUid.name; + } + + // This check is duplicated below, this is here mainly as performance optimization to skip interpolation + if (this.datasources[nameOrUid]) { + return Promise.resolve(this.datasources[nameOrUid]); } // Interpolation here is to support template variable in data source selection - name = this.templateSrv.replace(name, scopedVars, (value: any[]) => { + nameOrUid = this.templateSrv.replace(nameOrUid, scopedVars, (value: any[]) => { if (Array.isArray(value)) { return value[0]; } return value; }); - if (name === 'default') { - return this.get(config.defaultDatasource); + if (nameOrUid === 'default') { + return this.get(this.defaultName); } - if (this.datasources[name]) { - return Promise.resolve(this.datasources[name]); + if (this.datasources[nameOrUid]) { + return Promise.resolve(this.datasources[nameOrUid]); } - return this.loadDatasource(name); + return this.loadDatasource(nameOrUid); } async loadDatasource(name: string): Promise> { @@ -68,7 +94,7 @@ export class DatasourceSrv implements DataSourceService { return Promise.resolve(expressionDatasource); } - const dsConfig = config.datasources[name]; + const dsConfig = this.settingsMapByName[name]; if (!dsConfig) { return Promise.reject({ message: `Datasource named ${name} was not found` }); } @@ -101,8 +127,7 @@ export class DatasourceSrv implements DataSourceService { } getAll(): DataSourceInstanceSettings[] { - const { datasources } = config; - return Object.keys(datasources).map(name => datasources[name]); + return Object.values(this.settingsMapByName); } getExternal(): DataSourceInstanceSettings[] { @@ -115,7 +140,7 @@ export class DatasourceSrv implements DataSourceService { this.addDataSourceVariables(sources); - Object.values(config.datasources).forEach(value => { + Object.values(this.settingsMapByName).forEach(value => { if (value.meta?.annotations) { sources.push(value); } @@ -127,7 +152,7 @@ export class DatasourceSrv implements DataSourceService { getMetricSources(options?: { skipVariables?: boolean }) { const metricSources: DataSourceSelectItem[] = []; - Object.entries(config.datasources).forEach(([key, value]) => { + Object.entries(this.settingsMapByName).forEach(([key, value]) => { if (value.meta?.metrics) { let metricSource: DataSourceSelectItem = { value: key, name: key, meta: value.meta, sort: key }; @@ -142,7 +167,7 @@ export class DatasourceSrv implements DataSourceService { metricSources.push(metricSource); - if (key === config.defaultDatasource) { + if (key === this.defaultName) { metricSource = { value: null, name: 'default', meta: value.meta, sort: key }; metricSources.push(metricSource); } @@ -172,9 +197,9 @@ export class DatasourceSrv implements DataSourceService { .getVariables() .filter(variable => variable.type === 'datasource') .forEach((variable: DataSourceVariableModel) => { - const first = variable.current.value === 'default' ? config.defaultDatasource : variable.current.value; + const first = variable.current.value === 'default' ? this.defaultName : variable.current.value; const index = (first as unknown) as string; - const ds = config.datasources[index]; + const ds = this.settingsMapByName[index]; if (ds) { const key = `$${variable.name}`; diff --git a/public/app/features/plugins/specs/datasource_srv.test.ts b/public/app/features/plugins/specs/datasource_srv.test.ts index 36b59e9f6e9..ccd3e631394 100644 --- a/public/app/features/plugins/specs/datasource_srv.test.ts +++ b/public/app/features/plugins/specs/datasource_srv.test.ts @@ -1,7 +1,6 @@ -import config from 'app/core/config'; import 'app/features/plugins/datasource_srv'; import { DatasourceSrv } from 'app/features/plugins/datasource_srv'; -import { DataSourcePluginMeta, PluginMeta } from '@grafana/data'; +import { DataSourceInstanceSettings, DataSourcePlugin, DataSourcePluginMeta, PluginMeta } from '@grafana/data'; // Datasource variable $datasource with current value 'BBB' const templateSrv: any = { @@ -14,41 +13,72 @@ const templateSrv: any = { }, }, ], + replace: (v: string) => v, }; +class TestDataSource { + constructor(public instanceSettings: DataSourceInstanceSettings) {} +} + +jest.mock('../plugin_loader', () => ({ + importDataSourcePlugin: () => { + return Promise.resolve(new DataSourcePlugin(TestDataSource as any)); + }, +})); + describe('datasource_srv', () => { const _datasourceSrv = new DatasourceSrv({} as any, {} as any, templateSrv); + const datasources = { + buildIn: { + id: 1, + uid: '1', + type: 'b', + name: 'buildIn', + meta: { builtIn: true } as DataSourcePluginMeta, + jsonData: {}, + }, + external1: { + id: 2, + uid: '2', + type: 'e', + name: 'external1', + meta: { builtIn: false } as DataSourcePluginMeta, + jsonData: {}, + }, + external2: { + id: 3, + uid: '3', + type: 'e2', + name: 'external2', + meta: {} as PluginMeta, + jsonData: {}, + }, + }; - describe('when loading external datasources', () => { - beforeEach(() => { - config.datasources = { - buildInDs: { - id: 1, - uid: '1', - type: 'b', - name: 'buildIn', - meta: { builtIn: true } as DataSourcePluginMeta, - jsonData: {}, - }, - nonBuildIn: { - id: 2, - uid: '2', - type: 'e', - name: 'external1', - meta: { builtIn: false } as DataSourcePluginMeta, - jsonData: {}, - }, - nonExplore: { - id: 3, - uid: '3', - type: 'e2', - name: 'external2', - meta: {} as PluginMeta, - jsonData: {}, - }, - }; + beforeEach(() => { + _datasourceSrv.init(datasources, 'external1'); + }); + + describe('when getting data source class instance', () => { + it('should load plugin and create instance and set meta', async () => { + const ds = (await _datasourceSrv.get('external1')) as any; + expect(ds.meta).toBe(datasources.external1.meta); + expect(ds.instanceSettings).toBe(datasources.external1); + + // validate that it caches instance + const ds2 = await _datasourceSrv.get('external1'); + expect(ds).toBe(ds2); }); + it('should be able to load data source using uid as well', async () => { + const dsByUid = await _datasourceSrv.get('2'); + const dsByName = await _datasourceSrv.get('external1'); + expect(dsByUid.meta).toBe(datasources.external1.meta); + expect(dsByUid).toBe(dsByName); + }); + }); + + describe('when getting external metric sources', () => { it('should return list of explore sources', () => { const externalSources = _datasourceSrv.getExternal(); expect(externalSources.length).toBe(2); @@ -59,45 +89,48 @@ describe('datasource_srv', () => { describe('when loading metric sources', () => { let metricSources: any; - const unsortedDatasources = { - mmm: { - type: 'test-db', - meta: { metrics: { m: 1 } }, - }, - '--Grafana--': { - type: 'grafana', - meta: { builtIn: true, metrics: { m: 1 }, id: 'grafana' }, - }, - '--Mixed--': { - type: 'test-db', - meta: { builtIn: true, metrics: { m: 1 }, id: 'mixed' }, - }, - ZZZ: { - type: 'test-db', - meta: { metrics: { m: 1 } }, - }, - aaa: { - type: 'test-db', - meta: { metrics: { m: 1 } }, - }, - BBB: { - type: 'test-db', - meta: { metrics: { m: 1 } }, - }, - }; + beforeEach(() => { - config.datasources = unsortedDatasources as any; + _datasourceSrv.init( + { + mmm: { + type: 'test-db', + meta: { metrics: true } as any, + }, + '--Grafana--': { + type: 'grafana', + meta: { builtIn: true, metrics: true, id: 'grafana' }, + }, + '--Mixed--': { + type: 'test-db', + meta: { builtIn: true, metrics: true, id: 'mixed' }, + }, + ZZZ: { + type: 'test-db', + meta: { metrics: true }, + }, + aaa: { + type: 'test-db', + meta: { metrics: true }, + }, + BBB: { + type: 'test-db', + meta: { metrics: true }, + }, + } as any, + 'BBB' + ); metricSources = _datasourceSrv.getMetricSources({}); - config.defaultDatasource = 'BBB'; }); it('should return a list of sources sorted case insensitively with builtin sources last', () => { expect(metricSources[1].name).toBe('aaa'); expect(metricSources[2].name).toBe('BBB'); - expect(metricSources[3].name).toBe('mmm'); - expect(metricSources[4].name).toBe('ZZZ'); - expect(metricSources[5].name).toBe('--Grafana--'); - expect(metricSources[6].name).toBe('--Mixed--'); + expect(metricSources[3].name).toBe('default'); + expect(metricSources[4].name).toBe('mmm'); + expect(metricSources[5].name).toBe('ZZZ'); + expect(metricSources[6].name).toBe('--Grafana--'); + expect(metricSources[7].name).toBe('--Mixed--'); }); it('should set default data source', () => { diff --git a/public/app/routes/GrafanaCtrl.ts b/public/app/routes/GrafanaCtrl.ts index d51c2b9d4df..a6c58fa9aec 100644 --- a/public/app/routes/GrafanaCtrl.ts +++ b/public/app/routes/GrafanaCtrl.ts @@ -62,6 +62,8 @@ export class GrafanaCtrl { setDashboardSrv(dashboardSrv); setLegacyAngularInjector($injector); + datasourceSrv.init(config.datasources, config.defaultDatasource); + locationUtil.initialize({ getConfig: () => config, getTimeRangeForUrl: getTimeSrv().timeRangeForUrl, From ccac8d2294e61455dd1e5573fcd4a97581cfb9de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20=C5=9Alusarczyk?= Date: Tue, 1 Dec 2020 15:34:15 +0100 Subject: [PATCH 025/124] Alerting: Update alertDef.ts with more time options (#29498) there was a troublesome jump between time ranges of 1h and 24h in previous version --- public/app/features/alerting/state/alertDef.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/alerting/state/alertDef.ts b/public/app/features/alerting/state/alertDef.ts index f41065365ee..489ad3bfae2 100644 --- a/public/app/features/alerting/state/alertDef.ts +++ b/public/app/features/alerting/state/alertDef.ts @@ -8,7 +8,7 @@ const alertQueryDef = new QueryPartDef({ { name: 'from', type: 'string', - options: ['10s', '1m', '5m', '10m', '15m', '1h', '24h', '48h'], + options: ['10s', '1m', '5m', '10m', '15m', '1h', '2h', '6h', '12h', '24h', '48h'], }, { name: 'to', type: 'string', options: ['now', 'now-1m', 'now-5m', 'now-10m', 'now-1h'] }, ], From 546f569e0c467d4ba748b4f390ad289b9c74bec6 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Tue, 1 Dec 2020 23:19:52 +0800 Subject: [PATCH 026/124] Chore: Enable eslint-plugin-react partial rules (#29428) * Chore: Enable eslint react/display-name Enable react/display-name and fixed the corresponding linting issue part of: #29201 * Chore: Enable eslint react/no-deprecated Enable react/no-deprecated and add the UNSAFE_ prefix for deprected methods part of: #29201 * Chore: Enable eslint react/no-find-dom-node Enable react/no-find-dom-node rule and use ref instead part of: #29201 * Test: Update TeamGroupSync test snapshot Since we added the displayName for ToolTip compontent and tag name is changed. * Fix: Fixed ClickOutsideWrapper render The props.children might contains numbers of nodes which make cloneElement failed. Change to simply use a div to wrapper the children and assign the ref to div for this feature * Style: Use shorthand method definition style for inline component * Fix: Rebase master and fix linting Rebase from master branch and fix new displayName linting warning --- .eslintrc | 3 - .../ClickOutsideWrapper.tsx | 8 +-- .../ColorPicker/SeriesColorPickerPopover.tsx | 30 +++++----- .../ColorPicker/SpectrumPalette.tsx | 1 + .../components/ContextMenu/ContextMenu.tsx | 1 + .../Forms/Legacy/Select/ButtonSelect.tsx | 1 + .../src/components/InfoBox/FeatureInfoBox.tsx | 1 + .../src/components/InfoBox/InfoBox.tsx | 1 + .../FieldNameByRegexMatcherEditor.tsx | 1 + .../MatchersUI/FieldNameMatcherEditor.tsx | 1 + .../MatchersUI/FieldTypeMatcherEditor.tsx | 1 + .../MatchersUI/FieldsByFrameRefIdMatcher.tsx | 2 + .../src/components/OptionsUI/fieldColor.tsx | 4 +- .../src/components/Select/ButtonSelect.tsx | 2 + .../src/components/Select/SelectBase.tsx | 58 ++++++++++--------- .../src/components/Select/SelectMenu.tsx | 2 + .../src/components/Tabs/TabsBar.tsx | 2 + .../grafana-ui/src/components/Tags/Tag.tsx | 2 + .../src/components/Tags/TagList.tsx | 2 + .../components/TimePicker/TimeRangePicker.tsx | 2 + .../TimeRangePicker/TimePickerCalendar.tsx | 8 +++ .../TimeRangePicker/TimePickerContent.tsx | 2 + .../TimeRangePicker/TimePickerTitle.tsx | 2 + .../TimeRangePicker/TimeRangeOption.tsx | 2 + .../src/components/Tooltip/Tooltip.tsx | 2 + .../src/slate-plugins/suggestions.tsx | 2 +- .../grafana-ui/src/utils/standardEditors.tsx | 12 +++- .../src/utils/storybook/withTheme.tsx | 1 + .../src/TracePageHeader/SpanGraph/index.tsx | 2 +- .../src/TracePageHeader/TracePageHeader.tsx | 2 +- .../ReferencesButton.test.js | 4 +- .../SpanDetail/KeyValuesTable.test.js | 8 ++- .../TimelineViewingLayer.tsx | 2 +- .../VirtualizedTraceView.tsx | 2 +- public/app/core/components/Footer/Footer.tsx | 2 + .../PermissionList/PermissionsInfo.tsx | 4 +- .../core/components/TagFilter/TagFilter.tsx | 2 +- .../ValueMatchers/BasicMatcherEditor.tsx | 2 +- public/app/core/utils/UserProvider.tsx | 2 +- .../app/core/utils/connectWithReduxStore.tsx | 3 + .../PanelEditor/DynamicConfigValueEditor.tsx | 1 + .../PanelEditor/VisualizationTab.tsx | 3 + public/app/features/explore/JSONViewer.tsx | 2 +- .../app/features/explore/ResponsiveButton.tsx | 2 + .../features/explore/TraceView/uiElements.tsx | 28 +++++---- public/app/features/explore/Wrapper.test.tsx | 16 +++-- .../search/components/DashboardListPage.tsx | 1 + .../search/components/DashboardSearch.tsx | 1 + .../search/components/SearchCheckbox.tsx | 2 + .../search/components/SearchWrapper.tsx | 2 + .../__snapshots__/TeamGroupSync.test.tsx.snap | 8 +-- .../components/Aggregations.test.tsx | 4 +- .../components/AnnotationQueryEditor.tsx | 4 +- .../components/QueryEditor.tsx | 4 +- .../datasource/graphite/FunctionEditor.tsx | 6 +- .../influxdb/components/InfluxCheatSheet.tsx | 4 +- .../prometheus/components/PromCheatSheet.tsx | 4 +- public/app/plugins/panel/annolist/module.tsx | 2 +- public/app/plugins/panel/dashlist/module.tsx | 4 +- public/app/routes/ReactContainer.tsx | 1 + 60 files changed, 193 insertions(+), 97 deletions(-) diff --git a/.eslintrc b/.eslintrc index 6688e1bc107..c88e985c2d3 100644 --- a/.eslintrc +++ b/.eslintrc @@ -16,11 +16,8 @@ "react-hooks/exhaustive-deps": "off", "react/prop-types": "off", "react/no-unescaped-entities": "off", - "react/display-name": "off", - "react/no-deprecated": "off", "react/no-unknown-property": "off", "react/no-children-prop": "off", - "react/no-find-dom-node": "off", "react/no-render-return-value": "off", "no-only-tests/no-only-tests": "error" } diff --git a/packages/grafana-ui/src/components/ClickOutsideWrapper/ClickOutsideWrapper.tsx b/packages/grafana-ui/src/components/ClickOutsideWrapper/ClickOutsideWrapper.tsx index 227058a4530..e976629d1c6 100644 --- a/packages/grafana-ui/src/components/ClickOutsideWrapper/ClickOutsideWrapper.tsx +++ b/packages/grafana-ui/src/components/ClickOutsideWrapper/ClickOutsideWrapper.tsx @@ -1,5 +1,4 @@ -import { PureComponent } from 'react'; -import ReactDOM from 'react-dom'; +import React, { PureComponent, createRef } from 'react'; export interface Props { /** @@ -28,6 +27,7 @@ export class ClickOutsideWrapper extends PureComponent { parent: window, useCapture: false, }; + myRef = createRef(); state = { hasEventListener: false, }; @@ -48,7 +48,7 @@ export class ClickOutsideWrapper extends PureComponent { } onOutsideClick = (event: any) => { - const domNode = ReactDOM.findDOMNode(this) as Element; + const domNode = this.myRef.current; if (!domNode || !domNode.contains(event.target)) { this.props.onClick(); @@ -56,6 +56,6 @@ export class ClickOutsideWrapper extends PureComponent { }; render() { - return this.props.children; + return
{this.props.children}
; } } diff --git a/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx b/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx index 91c59ffb417..26e3f64fdee 100644 --- a/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx @@ -19,20 +19,22 @@ export const SeriesColorPickerPopover: FunctionComponent ( - { - if (onToggleAxis) { - onToggleAxis(); - } - }} - /> - ), + tabComponent() { + return ( + { + if (onToggleAxis) { + onToggleAxis(); + } + }} + /> + ); + }, }, }} /> diff --git a/packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.tsx b/packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.tsx index bd22d22b6ad..cc76477cd66 100644 --- a/packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.tsx @@ -13,6 +13,7 @@ export interface SpectrumPaletteProps extends Themeable { onChange: (color: string) => void; } +// eslint-disable-next-line react/display-name const renderPointer = (theme: GrafanaTheme) => (props: SpectrumPalettePointerProps) => ( ); diff --git a/packages/grafana-ui/src/components/ContextMenu/ContextMenu.tsx b/packages/grafana-ui/src/components/ContextMenu/ContextMenu.tsx index 6079ac5d3a6..7a584f08f1e 100644 --- a/packages/grafana-ui/src/components/ContextMenu/ContextMenu.tsx +++ b/packages/grafana-ui/src/components/ContextMenu/ContextMenu.tsx @@ -191,6 +191,7 @@ const ContextMenuItemComponent: React.FC = React.memo( ); } ); +ContextMenuItemComponent.displayName = 'ContextMenuItemComponent'; interface ContextMenuGroupProps { group: ContextMenuGroup; diff --git a/packages/grafana-ui/src/components/Forms/Legacy/Select/ButtonSelect.tsx b/packages/grafana-ui/src/components/Forms/Legacy/Select/ButtonSelect.tsx index 21e0427b5b2..8cceb55a4c4 100644 --- a/packages/grafana-ui/src/components/Forms/Legacy/Select/ButtonSelect.tsx +++ b/packages/grafana-ui/src/components/Forms/Legacy/Select/ButtonSelect.tsx @@ -11,6 +11,7 @@ interface ButtonComponentProps { iconClass?: string; } +// eslint-disable-next-line react/display-name const ButtonComponent = (buttonProps: ButtonComponentProps) => (props: any) => { const { label, className, iconClass } = buttonProps; diff --git a/packages/grafana-ui/src/components/InfoBox/FeatureInfoBox.tsx b/packages/grafana-ui/src/components/InfoBox/FeatureInfoBox.tsx index da5a5baace5..6b94c5b1d39 100644 --- a/packages/grafana-ui/src/components/InfoBox/FeatureInfoBox.tsx +++ b/packages/grafana-ui/src/components/InfoBox/FeatureInfoBox.tsx @@ -28,6 +28,7 @@ export const FeatureInfoBox = React.memo( return ; }) ); +FeatureInfoBox.displayName = 'FeatureInfoBox'; const getFeatureInfoBoxStyles = stylesFactory((theme: GrafanaTheme) => { return { diff --git a/packages/grafana-ui/src/components/InfoBox/InfoBox.tsx b/packages/grafana-ui/src/components/InfoBox/InfoBox.tsx index e635a459127..1646f9fde51 100644 --- a/packages/grafana-ui/src/components/InfoBox/InfoBox.tsx +++ b/packages/grafana-ui/src/components/InfoBox/InfoBox.tsx @@ -55,6 +55,7 @@ export const InfoBox = React.memo( } ) ); +InfoBox.displayName = 'InfoBox'; const getInfoBoxStyles = stylesFactory((theme: GrafanaTheme, severity: AlertVariant) => ({ wrapper: css` diff --git a/packages/grafana-ui/src/components/MatchersUI/FieldNameByRegexMatcherEditor.tsx b/packages/grafana-ui/src/components/MatchersUI/FieldNameByRegexMatcherEditor.tsx index ecd9f111c28..5192c21ba71 100644 --- a/packages/grafana-ui/src/components/MatchersUI/FieldNameByRegexMatcherEditor.tsx +++ b/packages/grafana-ui/src/components/MatchersUI/FieldNameByRegexMatcherEditor.tsx @@ -15,6 +15,7 @@ export const FieldNameByRegexMatcherEditor = memo>(props return ; }); +FieldNameByRegexMatcherEditor.displayName = 'FieldNameByRegexMatcherEditor'; export const fieldNameByRegexMatcherItem: FieldMatcherUIRegistryItem = { id: FieldMatcherID.byRegexp, diff --git a/packages/grafana-ui/src/components/MatchersUI/FieldNameMatcherEditor.tsx b/packages/grafana-ui/src/components/MatchersUI/FieldNameMatcherEditor.tsx index 1131777d1ad..6fb2d33463f 100644 --- a/packages/grafana-ui/src/components/MatchersUI/FieldNameMatcherEditor.tsx +++ b/packages/grafana-ui/src/components/MatchersUI/FieldNameMatcherEditor.tsx @@ -21,6 +21,7 @@ export const FieldNameMatcherEditor = memo>(props => { const selectedOption = selectOptions.find(v => v.value === options); return ; }); +FieldTypeMatcherEditor.displayName = 'FieldTypeMatcherEditor'; const allTypes: Array> = [ { value: FieldType.number, label: 'Numeric' }, diff --git a/packages/grafana-ui/src/components/MatchersUI/FieldsByFrameRefIdMatcher.tsx b/packages/grafana-ui/src/components/MatchersUI/FieldsByFrameRefIdMatcher.tsx index c80c742411c..01d9e3658ea 100644 --- a/packages/grafana-ui/src/components/MatchersUI/FieldsByFrameRefIdMatcher.tsx +++ b/packages/grafana-ui/src/components/MatchersUI/FieldsByFrameRefIdMatcher.tsx @@ -26,6 +26,8 @@ export const FieldsByFrameRefIdMatcher = memo>(props => { return , - InputGroup: ({ children, className, style }) => ( - - {children} - - ), + Input(props) { + return ; + }, + InputGroup({ children, className, style }) { + return ( + + {children} + + ); + }, }; const getStyles = stylesFactory((theme: GrafanaTheme) => { diff --git a/public/app/features/explore/Wrapper.test.tsx b/public/app/features/explore/Wrapper.test.tsx index 34a26ec7dfd..104b5c0974d 100644 --- a/public/app/features/explore/Wrapper.test.tsx +++ b/public/app/features/explore/Wrapper.test.tsx @@ -28,7 +28,9 @@ type Mock = jest.Mock; jest.mock('react-virtualized-auto-sizer', () => { return { __esModule: true, - default: (props: any) =>
{props.children({ width: 1000 })}
, + default(props: any) { + return
{props.children({ width: 1000 })}
; + }, }; }); @@ -273,11 +275,13 @@ function makeDatasourceSetup({ name = 'loki', id = 1 }: { name?: string; id?: nu }, api: { components: { - QueryEditor: (props: QueryEditorProps) => ( -
- {name} Editor input: {props.query.expr} -
- ), + QueryEditor(props: QueryEditorProps) { + return ( +
+ {name} Editor input: {props.query.expr} +
+ ); + }, }, name: name, query: jest.fn(), diff --git a/public/app/features/search/components/DashboardListPage.tsx b/public/app/features/search/components/DashboardListPage.tsx index 78fff6e26d6..742859d3755 100644 --- a/public/app/features/search/components/DashboardListPage.tsx +++ b/public/app/features/search/components/DashboardListPage.tsx @@ -41,6 +41,7 @@ export const DashboardListPage: FC = memo(({ navModel, uid, url }) => { ); }); +DashboardListPage.displayName = 'DashboardListPage'; const mapStateToProps: MapStateToProps = state => { return { diff --git a/public/app/features/search/components/DashboardSearch.tsx b/public/app/features/search/components/DashboardSearch.tsx index ca7aeb50bcb..ed92d860ec0 100644 --- a/public/app/features/search/components/DashboardSearch.tsx +++ b/public/app/features/search/components/DashboardSearch.tsx @@ -57,6 +57,7 @@ export const DashboardSearch: FC = memo(({ onCloseSearch, params, updateL
); }); +DashboardSearch.displayName = 'DashboardSearch'; export default connectWithRouteParams(DashboardSearch); diff --git a/public/app/features/search/components/SearchCheckbox.tsx b/public/app/features/search/components/SearchCheckbox.tsx index 205fa66bcaf..1877ac9d2d5 100644 --- a/public/app/features/search/components/SearchCheckbox.tsx +++ b/public/app/features/search/components/SearchCheckbox.tsx @@ -28,3 +28,5 @@ const getStyles = stylesFactory(() => ({ } `, })); + +SearchCheckbox.displayName = 'SearchCheckbox'; diff --git a/public/app/features/search/components/SearchWrapper.tsx b/public/app/features/search/components/SearchWrapper.tsx index 1cab70f02a0..d69d43ee887 100644 --- a/public/app/features/search/components/SearchWrapper.tsx +++ b/public/app/features/search/components/SearchWrapper.tsx @@ -40,6 +40,8 @@ export const SearchWrapper: FC = memo(({ search, folder, updateLocation } return isOpen ? : null; }); +SearchWrapper.displayName = 'SearchWrapper'; + const mapStateToProps: MapStateToProps<{}, OwnProps, StoreState> = (state: StoreState) => { const { search, folder } = getLocationQuery(state.location); return { search, folder }; diff --git a/public/app/features/teams/__snapshots__/TeamGroupSync.test.tsx.snap b/public/app/features/teams/__snapshots__/TeamGroupSync.test.tsx.snap index 631f895ccb9..cccf6538e73 100644 --- a/public/app/features/teams/__snapshots__/TeamGroupSync.test.tsx.snap +++ b/public/app/features/teams/__snapshots__/TeamGroupSync.test.tsx.snap @@ -10,7 +10,7 @@ exports[`Render should render component 1`] = ` > External group sync - @@ -18,7 +18,7 @@ exports[`Render should render component 1`] = ` className="icon--has-hover page-sub-heading-icon" name="question-circle" /> - +
@@ -92,7 +92,7 @@ exports[`Render should render groups table 1`] = ` > External group sync - @@ -100,7 +100,7 @@ exports[`Render should render groups table 1`] = ` className="icon--has-hover page-sub-heading-icon" name="question-circle" /> - +
diff --git a/public/app/plugins/datasource/cloud-monitoring/components/Aggregations.test.tsx b/public/app/plugins/datasource/cloud-monitoring/components/Aggregations.test.tsx index d8e2172fa23..a6a9d1ad85d 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/Aggregations.test.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/Aggregations.test.tsx @@ -16,7 +16,9 @@ const props: Props = { } as any, crossSeriesReducer: '', groupBys: [], - children: renderProps =>
, + children(renderProps) { + return
; + }, templateVariableOptions: [], }; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/AnnotationQueryEditor.tsx b/public/app/plugins/datasource/cloud-monitoring/components/AnnotationQueryEditor.tsx index 278d7bb46f0..4cbc38753c1 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/AnnotationQueryEditor.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/AnnotationQueryEditor.tsx @@ -42,8 +42,8 @@ const DefaultTarget: State = { export class AnnotationQueryEditor extends React.Component { state: State = DefaultTarget; - async UNSAFE_componentWillMount() { - // Unfortunately, migrations like this need to go componentWillMount. As soon as there's + async UNSAFE_UNSAFE_componentWillMount() { + // Unfortunately, migrations like this need to go UNSAFE_componentWillMount. As soon as there's // migration hook for this module.ts, we can do the migrations there instead. const { target, datasource } = this.props; if (!target.projectName) { diff --git a/public/app/plugins/datasource/cloud-monitoring/components/QueryEditor.tsx b/public/app/plugins/datasource/cloud-monitoring/components/QueryEditor.tsx index c595749a435..4317778fa4e 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/QueryEditor.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/QueryEditor.tsx @@ -18,10 +18,10 @@ interface State { export class QueryEditor extends PureComponent { state: State = { lastQueryError: '' }; - async UNSAFE_componentWillMount() { + async UNSAFE_UNSAFE_componentWillMount() { const { datasource, query } = this.props; - // Unfortunately, migrations like this need to go componentWillMount. As soon as there's + // Unfortunately, migrations like this need to go UNSAFE_componentWillMount. As soon as there's // migration hook for this module.ts, we can do the migrations there instead. if (!this.props.query.hasOwnProperty('metricQuery')) { const { hide, refId, datasource, key, queryType, maxLines, metric, ...metricQuery } = this.props.query as any; diff --git a/public/app/plugins/datasource/graphite/FunctionEditor.tsx b/public/app/plugins/datasource/graphite/FunctionEditor.tsx index 1c07341f0d4..d178904640a 100644 --- a/public/app/plugins/datasource/graphite/FunctionEditor.tsx +++ b/public/app/plugins/datasource/graphite/FunctionEditor.tsx @@ -13,9 +13,9 @@ const FunctionDescription = React.lazy(async () => { // @ts-ignore const { default: rst2html } = await import(/* webpackChunkName: "rst2html" */ 'rst2html'); return { - default: (props: { description?: string }) => ( -
- ), + default(props: { description?: string }) { + return
; + }, }; }); diff --git a/public/app/plugins/datasource/influxdb/components/InfluxCheatSheet.tsx b/public/app/plugins/datasource/influxdb/components/InfluxCheatSheet.tsx index 8c1479f5b2c..8366fae3049 100644 --- a/public/app/plugins/datasource/influxdb/components/InfluxCheatSheet.tsx +++ b/public/app/plugins/datasource/influxdb/components/InfluxCheatSheet.tsx @@ -8,7 +8,7 @@ const CHEAT_SHEET_ITEMS = [ }, ]; -export default (props: any) => ( +const InfluxCheatSheet = (props: any) => (

InfluxDB Cheat Sheet

{CHEAT_SHEET_ITEMS.map(item => ( @@ -19,3 +19,5 @@ export default (props: any) => ( ))}
); + +export default InfluxCheatSheet; diff --git a/public/app/plugins/datasource/prometheus/components/PromCheatSheet.tsx b/public/app/plugins/datasource/prometheus/components/PromCheatSheet.tsx index a38f0540d30..c114405e8bf 100644 --- a/public/app/plugins/datasource/prometheus/components/PromCheatSheet.tsx +++ b/public/app/plugins/datasource/prometheus/components/PromCheatSheet.tsx @@ -25,7 +25,7 @@ const CHEAT_SHEET_ITEMS = [ }, ]; -export default (props: ExploreStartPageProps) => ( +const PromCheatSheet = (props: ExploreStartPageProps) => (

PromQL Cheat Sheet

{CHEAT_SHEET_ITEMS.map((item, index) => ( @@ -44,3 +44,5 @@ export default (props: ExploreStartPageProps) => ( ))}
); + +export default PromCheatSheet; diff --git a/public/app/plugins/panel/annolist/module.tsx b/public/app/plugins/panel/annolist/module.tsx index ed6fcbb5f73..451dab488d3 100644 --- a/public/app/plugins/panel/annolist/module.tsx +++ b/public/app/plugins/panel/annolist/module.tsx @@ -60,7 +60,7 @@ export const plugin = new PanelPlugin(AnnoListPanel) path: 'tags', name: 'Tags', description: '', - editor: props => { + editor(props) { return ; }, }) diff --git a/public/app/plugins/panel/dashlist/module.tsx b/public/app/plugins/panel/dashlist/module.tsx index c70e2f2ca51..98f5505450f 100644 --- a/public/app/plugins/panel/dashlist/module.tsx +++ b/public/app/plugins/panel/dashlist/module.tsx @@ -44,7 +44,7 @@ export const plugin = new PanelPlugin(DashList) name: 'Folder', id: 'folderId', defaultValue: null, - editor: props => { + editor: function RenderFolderPicker(props) { return props.onChange(id)} />; }, }) @@ -54,7 +54,7 @@ export const plugin = new PanelPlugin(DashList) name: 'Tags', description: '', defaultValue: [], - editor: props => { + editor(props) { return ; }, }); diff --git a/public/app/routes/ReactContainer.tsx b/public/app/routes/ReactContainer.tsx index 75dc116551f..bf215a1f707 100644 --- a/public/app/routes/ReactContainer.tsx +++ b/public/app/routes/ReactContainer.tsx @@ -22,6 +22,7 @@ export function WrapInProvider(store: any, Component: any, props: any) { } export const provideModalsContext = (component: any) => { + // eslint-disable-next-line react/display-name return (props: any) => ( <> From e0dee06252cbcbc0de7169ccb7bc2351e0675189 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Tue, 1 Dec 2020 07:56:56 -0800 Subject: [PATCH 027/124] Live: publish all dashboard changes to a single channel (#29474) --- pkg/services/live/features/dashboard.go | 6 +++++- public/app/features/live/dashboard/dashboardWatcher.ts | 4 +--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/services/live/features/dashboard.go b/pkg/services/live/features/dashboard.go index 4f8cba42921..45db283aa35 100644 --- a/pkg/services/live/features/dashboard.go +++ b/pkg/services/live/features/dashboard.go @@ -48,7 +48,11 @@ func (h *DashboardHandler) publish(event dashboardEvent) error { if err != nil { return err } - return h.Publisher("grafana/dashboard/"+event.UID, msg) + err = h.Publisher("grafana/dashboard/uid/"+event.UID, msg) + if err != nil { + return err + } + return h.Publisher("grafana/dashboard/changes", msg) } // DashboardSaved will broadcast to all connected dashboards diff --git a/public/app/features/live/dashboard/dashboardWatcher.ts b/public/app/features/live/dashboard/dashboardWatcher.ts index 0dcac078f1d..8e3f71a7528 100644 --- a/public/app/features/live/dashboard/dashboardWatcher.ts +++ b/public/app/features/live/dashboard/dashboardWatcher.ts @@ -61,13 +61,11 @@ class DashboardWatcher { this.channel = live.getChannel({ scope: LiveChannelScope.Grafana, namespace: 'dashboard', - path: uid, + path: `uid/${uid}`, }); this.channel.getStream().subscribe(this.observer); this.uid = uid; } - - console.log('Watch', uid); } leave() { From 9913ac73fb1c797170e2669fcd1c691042994a6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Bedi?= Date: Tue, 1 Dec 2020 17:43:56 +0100 Subject: [PATCH 028/124] Bug: trace viewer doesn't show more than 300 spans (#29377) * Fix: trace viewer scroll issue * Fix lazy loading * Listview test fixes * Move scrollElement to props --- .../ListView/__snapshots__/index.test.js.snap | 3 -- .../ListView/index.test.js | 8 ++--- .../TraceTimelineViewer/ListView/index.tsx | 30 ++++++++++++++----- .../VirtualizedTraceView.tsx | 9 ++++-- .../src/TraceTimelineViewer/index.tsx | 1 + .../features/explore/TraceView/TraceView.tsx | 2 ++ 6 files changed, 33 insertions(+), 20 deletions(-) diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/ListView/__snapshots__/index.test.js.snap b/packages/jaeger-ui-components/src/TraceTimelineViewer/ListView/__snapshots__/index.test.js.snap index baf54ebe67e..3c12d1d9a8f 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/ListView/__snapshots__/index.test.js.snap +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/ListView/__snapshots__/index.test.js.snap @@ -2,11 +2,8 @@ exports[` shallow tests matches a snapshot 1`] = `
', () => { itemsWrapperClassName: 'SomeClassName', viewBuffer: 10, viewBufferMin: 5, - windowScroller: false, + windowScroller: true, }; describe('shallow tests', () => { @@ -152,10 +152,6 @@ describe('', () => { instance = wrapper.instance(); }); - it('getViewHeight() returns the viewHeight', () => { - expect(instance.getViewHeight()).toBe(clientHeight); - }); - it('getBottomVisibleIndex() returns a number', () => { const n = instance.getBottomVisibleIndex(); expect(Number.isNaN(n)).toBe(false); @@ -233,9 +229,9 @@ describe('', () => { }, }); const hasChanged = instance._isViewChanged(); + expect(hasChanged).toBe(true); expect(spyFns.clientHeight).toHaveBeenCalled(); expect(spyFns.scrollTop).toHaveBeenCalled(); - expect(hasChanged).toBe(true); }); }); }); diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/ListView/index.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/ListView/index.tsx index 55bd4dbf86d..22bc0aeb421 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/ListView/index.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/ListView/index.tsx @@ -67,7 +67,7 @@ type TListViewProps = { itemsWrapperClassName?: string; /** * When adding new items to the DOM, this is the number of items to add above - * and below the current view. E.g. if list is 100 items and is srcolled + * and below the current view. E.g. if list is 100 items and is scrolled * halfway down (so items [46, 55] are in view), then when a new range of * items is rendered, it will render items `46 - viewBuffer` to * `55 + viewBuffer`. @@ -89,15 +89,20 @@ type TListViewProps = { * - Ref:https://github.com/bvaughn/react-virtualized/blob/497e2a1942529560681d65a9ef9f5e9c9c9a49ba/docs/WindowScroller.md */ windowScroller?: boolean; + /** + * You need to pass in scrollElement when windowScroller is set to false. + * This element is responsible for tracking scrolling for lazy loading. + */ + scrollElement?: Element; }; -const DEFAULT_INITIAL_DRAW = 300; +const DEFAULT_INITIAL_DRAW = 100; /** * Virtualized list view component, for the most part, only renders the window * of items that are in-view with some buffer before and after. Listens for * scroll events and updates which items are rendered. See react-virtualized - * for a suite of components with similar, but generalized, functinality. + * for a suite of components with similar, but generalized, functionality. * https://github.com/bvaughn/react-virtualized * * Note: Presently, ListView cannot be a PureComponent. This is because ListView @@ -157,9 +162,9 @@ export default class ListView extends React.Component { _windowScrollListenerAdded: boolean; _htmlElm: HTMLElement; /** - * HTMLElement holding the scroller. + * Element holding the scroller. */ - _wrapperElm: HTMLElement | TNil; + _wrapperElm: Element | TNil; /** * HTMLElement holding the rendered items. */ @@ -202,6 +207,10 @@ export default class ListView extends React.Component { } window.addEventListener('scroll', this._onScroll); this._windowScrollListenerAdded = true; + } else { + // The wrapper element should be the one that handles the scrolling. Once we are not using scroll-canvas we can remove this. + this._wrapperElm = this.props.scrollElement; + this._wrapperElm?.addEventListener('scroll', this._onScroll); } } @@ -214,6 +223,8 @@ export default class ListView extends React.Component { componentWillUnmount() { if (this._windowScrollListenerAdded) { window.removeEventListener('scroll', this._onScroll); + } else { + this._wrapperElm?.removeEventListener('scroll', this._onScroll); } } @@ -308,8 +319,11 @@ export default class ListView extends React.Component { }; _initWrapper = (elm: HTMLElement | TNil) => { + if (!this.props.windowScroller) { + return; + } this._wrapperElm = elm; - if (!this.props.windowScroller && elm) { + if (elm) { this._viewHeight = elm.clientHeight; } }; @@ -374,8 +388,8 @@ export default class ListView extends React.Component { }; /** - * Get the height of the element at index `i`; first check the known heigths, - * fallbck to `.props.itemHeightGetter(...)`. + * Get the height of the element at index `i`; first check the known heights, + * fallback to `.props.itemHeightGetter(...)`. */ _getHeight = (i: number) => { const key = this.props.getKeyFromIndex(i); diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/VirtualizedTraceView.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/VirtualizedTraceView.tsx index 8b5a549a3c0..6e2e0b94f47 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/VirtualizedTraceView.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/VirtualizedTraceView.tsx @@ -82,6 +82,7 @@ type TVirtualizedTraceViewOwnProps = { createSpanLink?: ( span: TraceSpan ) => { href: string; onClick?: (e: React.MouseEvent) => void; content: React.ReactNode }; + scrollElement?: Element; }; type VirtualizedTraceViewProps = TVirtualizedTraceViewOwnProps & TExtractUiFindFromStateReturn & TTraceTimeline; @@ -445,6 +446,7 @@ export class UnthemedVirtualizedTraceView extends React.Component
); diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/index.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/index.tsx index be668aaa066..c2d229004bc 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/index.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/index.tsx @@ -102,6 +102,7 @@ type TProps = TExtractUiFindFromStateReturn & { createSpanLink?: ( span: TraceSpan ) => { href: string; onClick?: (e: React.MouseEvent) => void; content: React.ReactNode }; + scrollElement?: Element; }; type State = { diff --git a/public/app/features/explore/TraceView/TraceView.tsx b/public/app/features/explore/TraceView/TraceView.tsx index 23e1db961ac..b7e7dcd8083 100644 --- a/public/app/features/explore/TraceView/TraceView.tsx +++ b/public/app/features/explore/TraceView/TraceView.tsx @@ -83,6 +83,7 @@ export function TraceView(props: Props) { ); const createSpanLink = useMemo(() => createSpanLinkFactory(props.splitOpenFn), [props.splitOpenFn]); + const scrollElement = document.getElementsByClassName('scroll-canvas')[0]; if (!traceProp) { return null; @@ -148,6 +149,7 @@ export function TraceView(props: Props) { )} uiFind={search} createSpanLink={createSpanLink} + scrollElement={scrollElement} /> From 34f2a72ff2b64ef44abc1ddf206cb232900451c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 1 Dec 2020 19:10:23 +0100 Subject: [PATCH 029/124] DataLinks: Removes getDataSourceSettingsByUid from applyFieldOverrides (#29447) * DataLinks: Removes getDataSourceSettingsByUid from applyFieldOverrides and data linking code * Fixed test * Fixed a few tests * Fixed unit tests * Removed old getDataSourceSettingsByUid from interface, still there for runtime backward compatability --- .../src/field/fieldOverrides.test.ts | 8 +-- .../grafana-data/src/field/fieldOverrides.ts | 26 ++++---- .../field/getFieldDisplayValuesProxy.test.tsx | 1 - packages/grafana-data/src/types/dataLink.ts | 12 ++-- .../grafana-data/src/types/fieldOverrides.ts | 11 +--- .../grafana-data/src/utils/dataLinks.test.ts | 37 ++++++------ packages/grafana-data/src/utils/dataLinks.ts | 40 +++++-------- .../src/services/dataSourceSrv.ts | 6 -- .../grafana-ui/src/utils/storybook/data.ts | 1 - .../getAlertingValidationMessage.test.ts | 5 -- .../components/Inspector/InspectDataTab.tsx | 2 - .../dashboard/state/PanelModel.test.ts | 7 --- .../features/dashboard/state/PanelModel.ts | 2 - .../dashboard/utils/loadSnapshotData.ts | 2 - .../explore/TraceView/createSpanLink.test.ts | 6 +- .../explore/TraceView/createSpanLink.tsx | 11 +++- public/app/features/explore/state/main.ts | 2 +- .../app/features/explore/utils/links.test.ts | 27 +-------- public/app/features/explore/utils/links.ts | 10 +++- .../panel/panellinks/linkSuppliers.test.ts | 1 - .../query/state/PanelQueryRunner.test.ts | 10 +--- .../elasticsearch/datasource.test.ts | 8 +++ .../datasource/elasticsearch/datasource.ts | 60 +++++++++++-------- .../loki/result_transformer.test.ts | 16 ++++- .../datasource/loki/result_transformer.ts | 7 ++- public/app/plugins/panel/live/LivePanel.tsx | 1 - 26 files changed, 143 insertions(+), 176 deletions(-) diff --git a/packages/grafana-data/src/field/fieldOverrides.test.ts b/packages/grafana-data/src/field/fieldOverrides.test.ts index d8ebd19e783..6dec551e88d 100644 --- a/packages/grafana-data/src/field/fieldOverrides.test.ts +++ b/packages/grafana-data/src/field/fieldOverrides.test.ts @@ -183,7 +183,6 @@ describe('applyFieldOverrides', () => { overrides: [], }, replaceVariables: (value: any) => value, - getDataSourceSettingsByUid: undefined as any, theme: getTestTheme(), fieldConfigRegistry: new FieldConfigOptionsRegistry(), }); @@ -245,7 +244,6 @@ describe('applyFieldOverrides', () => { overrides: [], }, fieldConfigRegistry: customFieldRegistry, - getDataSourceSettingsByUid: undefined as any, replaceVariables: v => v, theme: getTestTheme(), })[0]; @@ -263,7 +261,6 @@ describe('applyFieldOverrides', () => { data: [f0], // the frame fieldConfig: src as FieldConfigSource, // defaults + overrides replaceVariables: (undefined as any) as InterpolateFunction, - getDataSourceSettingsByUid: undefined as any, theme: getTestTheme(), fieldConfigRegistry: customFieldRegistry, })[0]; @@ -291,7 +288,6 @@ describe('applyFieldOverrides', () => { data: [f0], // the frame fieldConfig: src as FieldConfigSource, // defaults + overrides replaceVariables: (undefined as any) as InterpolateFunction, - getDataSourceSettingsByUid: undefined as any, theme: getTestTheme(), })[0]; const valueColumn = data.fields[1]; @@ -314,7 +310,6 @@ describe('applyFieldOverrides', () => { replaceVariablesCalls.push(variables); return value; }) as InterpolateFunction, - getDataSourceSettingsByUid: undefined as any, theme: getTestTheme(), fieldConfigRegistry: customFieldRegistry, })[0]; @@ -565,7 +560,6 @@ describe('getLinksSupplier', () => { {}, replaceSpy, // this is used only for internal links so isn't needed here - () => ({} as any), { theme: getTestTheme(), } @@ -598,6 +592,7 @@ describe('getLinksSupplier', () => { title: '', internal: { datasourceUid: '0', + datasourceName: 'testDS', query: '12345', }, }, @@ -613,7 +608,6 @@ describe('getLinksSupplier', () => { {}, // We do not need to interpolate anything for this test (value, vars, format) => value, - uid => ({ name: 'testDS' } as any), { theme: getTestTheme() } ); const links = supplier({ valueRowIndex: 0 }); diff --git a/packages/grafana-data/src/field/fieldOverrides.ts b/packages/grafana-data/src/field/fieldOverrides.ts index a1f141a80a1..40e56d6f882 100644 --- a/packages/grafana-data/src/field/fieldOverrides.ts +++ b/packages/grafana-data/src/field/fieldOverrides.ts @@ -2,7 +2,6 @@ import { ApplyFieldOverrideOptions, DataFrame, DataLink, - DataSourceInstanceSettings, DynamicConfigValue, Field, FieldColorModeId, @@ -130,7 +129,6 @@ export function applyFieldOverrides(options: ApplyFieldOverrideOptions): DataFra data: options.data!, dataFrameIndex: index, replaceVariables: options.replaceVariables, - getDataSourceSettingsByUid: options.getDataSourceSettingsByUid, fieldConfigRegistry: fieldConfigRegistry, }; @@ -211,17 +209,10 @@ export function applyFieldOverrides(options: ApplyFieldOverrideOptions): DataFra }); // Attach data links supplier - newField.getLinks = getLinksSupplier( - newFrame, - newField, - fieldScopedVars, - context.replaceVariables, - context.getDataSourceSettingsByUid, - { - theme: options.theme, - timeZone: options.timeZone, - } - ); + newField.getLinks = getLinksSupplier(newFrame, newField, fieldScopedVars, context.replaceVariables, { + theme: options.theme, + timeZone: options.timeZone, + }); return newField; }); @@ -336,7 +327,6 @@ export const getLinksSupplier = ( field: Field, fieldScopedVars: ScopedVars, replaceVariables: InterpolateFunction, - getDataSourceSettingsByUid: (uid: string) => DataSourceInstanceSettings | undefined, options: { theme: GrafanaTheme; timeZone?: TimeZone; @@ -401,9 +391,13 @@ export const getLinksSupplier = ( if (link.internal) { // For internal links at the moment only destination is Explore. - return mapInternalLinkToExplore(link, variables, {} as any, field, { + return mapInternalLinkToExplore({ + link, + internalLink: link.internal, + scopedVars: variables, + field, + range: {} as any, replaceVariables, - getDataSourceSettingsByUid, }); } else { let href = locationUtil.assureBaseUrl(link.url.replace(/\n/g, '')); diff --git a/packages/grafana-data/src/field/getFieldDisplayValuesProxy.test.tsx b/packages/grafana-data/src/field/getFieldDisplayValuesProxy.test.tsx index 26aa02f9261..334069ec2ea 100644 --- a/packages/grafana-data/src/field/getFieldDisplayValuesProxy.test.tsx +++ b/packages/grafana-data/src/field/getFieldDisplayValuesProxy.test.tsx @@ -29,7 +29,6 @@ describe('getFieldDisplayValuesProxy', () => { overrides: [], }, replaceVariables: (val: string) => val, - getDataSourceSettingsByUid: (val: string) => ({} as any), timeZone: 'utc', theme: getTestTheme(), })[0]; diff --git a/packages/grafana-data/src/types/dataLink.ts b/packages/grafana-data/src/types/dataLink.ts index e27a65d0644..22bec524aea 100644 --- a/packages/grafana-data/src/types/dataLink.ts +++ b/packages/grafana-data/src/types/dataLink.ts @@ -36,10 +36,14 @@ export interface DataLink { // datas ource that we want to show to the user. Usually this results in a link to explore but can also lead to // more custom onClick behaviour if needed. // @internal and subject to change in future releases - internal?: { - query: T; - datasourceUid: string; - }; + internal?: InternalDataLink; +} + +/** @internal */ +export interface InternalDataLink { + query: T; + datasourceUid: string; + datasourceName: string; } export type LinkTarget = '_blank' | '_self' | undefined; diff --git a/packages/grafana-data/src/types/fieldOverrides.ts b/packages/grafana-data/src/types/fieldOverrides.ts index cedce20cbd2..3c8038e81e2 100644 --- a/packages/grafana-data/src/types/fieldOverrides.ts +++ b/packages/grafana-data/src/types/fieldOverrides.ts @@ -1,13 +1,5 @@ import { ComponentType } from 'react'; -import { - MatcherConfig, - FieldConfig, - Field, - DataFrame, - GrafanaTheme, - TimeZone, - DataSourceInstanceSettings, -} from '../types'; +import { MatcherConfig, FieldConfig, Field, DataFrame, GrafanaTheme, TimeZone } from '../types'; import { InterpolateFunction } from './panel'; import { StandardEditorProps, FieldConfigOptionsRegistry, StandardEditorContext } from '../field'; import { OptionsEditorItem } from './OptionsUIRegistryBuilder'; @@ -114,7 +106,6 @@ export interface ApplyFieldOverrideOptions { data?: DataFrame[]; fieldConfig: FieldConfigSource; replaceVariables: InterpolateFunction; - getDataSourceSettingsByUid: (uid: string) => DataSourceInstanceSettings | undefined; theme: GrafanaTheme; timeZone?: TimeZone; fieldConfigRegistry?: FieldConfigOptionsRegistry; diff --git a/packages/grafana-data/src/utils/dataLinks.test.ts b/packages/grafana-data/src/utils/dataLinks.test.ts index 087c2be4bd6..eaa21dd684d 100644 --- a/packages/grafana-data/src/utils/dataLinks.test.ts +++ b/packages/grafana-data/src/utils/dataLinks.test.ts @@ -4,33 +4,34 @@ import { ArrayVector } from '../vector'; describe('mapInternalLinkToExplore', () => { it('creates internal link', () => { - const link = mapInternalLinkToExplore( - { - url: '', - title: '', - internal: { - datasourceUid: 'uid', - query: { query: '12344' }, - }, + const dataLink = { + url: '', + title: '', + internal: { + datasourceUid: 'uid', + datasourceName: 'dsName', + query: { query: '12344' }, }, - {}, - {} as any, - { + }; + + const link = mapInternalLinkToExplore({ + link: dataLink, + internalLink: dataLink.internal, + scopedVars: {}, + range: {} as any, + field: { name: 'test', type: FieldType.number, config: {}, values: new ArrayVector([2]), }, - { - replaceVariables: val => val, - getDataSourceSettingsByUid: uid => ({ name: 'testDS' } as any), - } - ); + replaceVariables: val => val, + }); expect(link).toEqual( expect.objectContaining({ - title: 'testDS', - href: '/explore?left={"datasource":"testDS","queries":[{"query":"12344"}]}', + title: 'dsName', + href: '/explore?left={"datasource":"dsName","queries":[{"query":"12344"}]}', onClick: undefined, }) ); diff --git a/packages/grafana-data/src/utils/dataLinks.ts b/packages/grafana-data/src/utils/dataLinks.ts index 0783f59966d..957907a092b 100644 --- a/packages/grafana-data/src/utils/dataLinks.ts +++ b/packages/grafana-data/src/utils/dataLinks.ts @@ -1,8 +1,8 @@ import { DataLink, DataQuery, - DataSourceInstanceSettings, Field, + InternalDataLink, InterpolateFunction, LinkModel, ScopedVars, @@ -27,41 +27,31 @@ export const DataLinkBuiltInVars = { }; // We inject these because we cannot import them directly as they reside inside grafana main package. -type Options = { +export type LinkToExploreOptions = { + link: DataLink; + scopedVars: ScopedVars; + range: TimeRange; + field: Field; + internalLink: InternalDataLink; onClickFn?: (options: { datasourceUid: string; query: any; range?: TimeRange }) => void; replaceVariables: InterpolateFunction; - getDataSourceSettingsByUid: (uid: string) => DataSourceInstanceSettings | undefined; }; -export function mapInternalLinkToExplore( - link: DataLink, - scopedVars: ScopedVars, - range: TimeRange, - field: Field, - options: Options -): LinkModel { - if (!link.internal) { - throw new Error('Trying to map external link as internal'); - } - const { onClickFn, replaceVariables, getDataSourceSettingsByUid } = options; +export function mapInternalLinkToExplore(options: LinkToExploreOptions): LinkModel { + const { onClickFn, replaceVariables, link, scopedVars, range, field, internalLink } = options; const interpolatedQuery = interpolateQuery(link, scopedVars, replaceVariables); - return { - title: link.title - ? replaceVariables(link.title || '', scopedVars) - : getDataSourceSettingsByUid(link.internal.datasourceUid)?.name || 'Unknown datasource', + const title = link.title ? link.title : internalLink.datasourceName; + return { + title: replaceVariables(title), // In this case this is meant to be internal link (opens split view by default) the href will also points // to explore but this way you can open it in new tab. - href: generateInternalHref( - getDataSourceSettingsByUid(link.internal.datasourceUid)?.name || 'unknown', - interpolatedQuery, - range - ), + href: generateInternalHref(internalLink.datasourceName, interpolatedQuery, range), onClick: onClickFn ? () => { - onClickFn?.({ - datasourceUid: link.internal!.datasourceUid, + onClickFn({ + datasourceUid: internalLink.datasourceUid, query: interpolatedQuery, range, }); diff --git a/packages/grafana-runtime/src/services/dataSourceSrv.ts b/packages/grafana-runtime/src/services/dataSourceSrv.ts index f06d8408a2c..96758a0a4b2 100644 --- a/packages/grafana-runtime/src/services/dataSourceSrv.ts +++ b/packages/grafana-runtime/src/services/dataSourceSrv.ts @@ -15,12 +15,6 @@ export interface DataSourceSrv { */ get(name?: string | null, scopedVars?: ScopedVars): Promise; - /** - * Returns metadata based on UID. - * @deprecated use getInstanceSettings - */ - getDataSourceSettingsByUid(uid: string): DataSourceInstanceSettings | undefined; - /** * Get all data sources */ diff --git a/packages/grafana-ui/src/utils/storybook/data.ts b/packages/grafana-ui/src/utils/storybook/data.ts index be008d93ed6..0063fc482b7 100644 --- a/packages/grafana-ui/src/utils/storybook/data.ts +++ b/packages/grafana-ui/src/utils/storybook/data.ts @@ -9,6 +9,5 @@ export function prepDataForStorybook(data: DataFrame[], theme: GrafanaTheme) { }, theme, replaceVariables: (value: string) => value, - getDataSourceSettingsByUid: (value: string) => ({} as any), }); } diff --git a/public/app/features/alerting/getAlertingValidationMessage.test.ts b/public/app/features/alerting/getAlertingValidationMessage.test.ts index 57c06cdf507..e98c5c071a3 100644 --- a/public/app/features/alerting/getAlertingValidationMessage.test.ts +++ b/public/app/features/alerting/getAlertingValidationMessage.test.ts @@ -22,7 +22,6 @@ describe('getAlertingValidationMessage', () => { const getMock = jest.fn().mockResolvedValue(datasource); const datasourceSrv: DataSourceSrv = { get: getMock, - getDataSourceSettingsByUid(): any {}, getExternal(): DataSourceInstanceSettings[] { return []; }, @@ -66,7 +65,6 @@ describe('getAlertingValidationMessage', () => { return Promise.resolve(alertingDatasource); }, - getDataSourceSettingsByUid(): any {}, getInstanceSettings: (() => {}) as any, getExternal(): DataSourceInstanceSettings[] { return []; @@ -97,7 +95,6 @@ describe('getAlertingValidationMessage', () => { const getMock = jest.fn().mockResolvedValue(datasource); const datasourceSrv: DataSourceSrv = { get: getMock, - getDataSourceSettingsByUid(): any {}, getInstanceSettings: (() => {}) as any, getExternal(): DataSourceInstanceSettings[] { return []; @@ -130,7 +127,6 @@ describe('getAlertingValidationMessage', () => { const getMock = jest.fn().mockResolvedValue(datasource); const datasourceSrv: DataSourceSrv = { get: getMock, - getDataSourceSettingsByUid(): any {}, getInstanceSettings: (() => {}) as any, getExternal(): DataSourceInstanceSettings[] { return []; @@ -163,7 +159,6 @@ describe('getAlertingValidationMessage', () => { const getMock = jest.fn().mockResolvedValue(datasource); const datasourceSrv: DataSourceSrv = { get: getMock, - getDataSourceSettingsByUid(): any {}, getInstanceSettings: (() => {}) as any, getExternal(): DataSourceInstanceSettings[] { return []; diff --git a/public/app/features/dashboard/components/Inspector/InspectDataTab.tsx b/public/app/features/dashboard/components/Inspector/InspectDataTab.tsx index 65d90e5f985..ec3979d0563 100644 --- a/public/app/features/dashboard/components/Inspector/InspectDataTab.tsx +++ b/public/app/features/dashboard/components/Inspector/InspectDataTab.tsx @@ -23,7 +23,6 @@ import { GetDataOptions } from '../../../query/state/PanelQueryRunner'; import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOperationRow'; import { PanelModel } from 'app/features/dashboard/state'; import { DetailText } from './DetailText'; -import { getDatasourceSrv } from '../../../plugins/datasource_srv'; interface Props { panel: PanelModel; @@ -125,7 +124,6 @@ export class InspectDataTab extends PureComponent { replaceVariables: (value: string) => { return value; }, - getDataSourceSettingsByUid: getDatasourceSrv().getDataSourceSettingsByUid, }); } diff --git a/public/app/features/dashboard/state/PanelModel.test.ts b/public/app/features/dashboard/state/PanelModel.test.ts index 3af984340c9..8087ac5bb2f 100644 --- a/public/app/features/dashboard/state/PanelModel.test.ts +++ b/public/app/features/dashboard/state/PanelModel.test.ts @@ -7,13 +7,11 @@ import { standardEditorsRegistry, standardFieldConfigEditorRegistry, PanelData, - DataSourceInstanceSettings, FieldColorModeId, FieldColorConfigSettings, } from '@grafana/data'; import { ComponentClass } from 'react'; import { PanelQueryRunner } from '../../query/state/PanelQueryRunner'; -import { setDataSourceSrv } from '@grafana/runtime'; class TablePanelCtrl {} @@ -192,11 +190,6 @@ describe('PanelModel', () => { }); it('should apply field config defaults', () => { - setDataSourceSrv({ - getDataSourceSettingsByUid(uid: string): DataSourceInstanceSettings | undefined { - return undefined; - }, - } as any); // default unit is overriden by model expect(model.getFieldOverrideOptions().fieldConfig.defaults.unit).toBe('mpg'); // default decimals are aplied diff --git a/public/app/features/dashboard/state/PanelModel.ts b/public/app/features/dashboard/state/PanelModel.ts index 4496c7a44fb..36e7525749b 100644 --- a/public/app/features/dashboard/state/PanelModel.ts +++ b/public/app/features/dashboard/state/PanelModel.ts @@ -26,7 +26,6 @@ import { import { EDIT_PANEL_ID } from 'app/core/constants'; import config from 'app/core/config'; import { PanelQueryRunner } from '../../query/state/PanelQueryRunner'; -import { getDatasourceSrv } from '../../plugins/datasource_srv'; import { PanelOptionsChangedEvent, PanelQueriesChangedEvent, PanelTransformationsChangedEvent } from 'app/types/events'; export interface GridPos { @@ -462,7 +461,6 @@ export class PanelModel implements DataConfigSource { return { fieldConfig: this.fieldConfig, replaceVariables: this.replaceVariables, - getDataSourceSettingsByUid: getDatasourceSrv().getDataSourceSettingsByUid.bind(getDatasourceSrv()), fieldConfigRegistry: this.plugin.fieldConfigRegistry, theme: config.theme, }; diff --git a/public/app/features/dashboard/utils/loadSnapshotData.ts b/public/app/features/dashboard/utils/loadSnapshotData.ts index 64b1e40c5cb..0168a64ef4e 100644 --- a/public/app/features/dashboard/utils/loadSnapshotData.ts +++ b/public/app/features/dashboard/utils/loadSnapshotData.ts @@ -1,6 +1,5 @@ import { applyFieldOverrides, DefaultTimeRange, LoadingState, PanelData } from '@grafana/data'; import { config } from 'app/core/config'; -import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { DashboardModel, PanelModel } from '../state'; import { getProcessedDataFrames } from '../../query/state/runRequest'; @@ -17,7 +16,6 @@ export function loadSnapshotData(panel: PanelModel, dashboard: DashboardModel): overrides: [], }, replaceVariables: panel.replaceVariables, - getDataSourceSettingsByUid: getDatasourceSrv().getDataSourceSettingsByUid.bind(getDatasourceSrv()), fieldConfigRegistry: panel.plugin!.fieldConfigRegistry, theme: config.theme, timeZone: dashboard.getTimezone(), diff --git a/public/app/features/explore/TraceView/createSpanLink.test.ts b/public/app/features/explore/TraceView/createSpanLink.test.ts index 40da9256870..46088b4e198 100644 --- a/public/app/features/explore/TraceView/createSpanLink.test.ts +++ b/public/app/features/explore/TraceView/createSpanLink.test.ts @@ -41,10 +41,10 @@ describe('createSpanLinkFactory', () => { } as DataSourceInstanceSettings, ]; }, - getDataSourceSettingsByUid(uid: string): DataSourceInstanceSettings | undefined { + getInstanceSettings(uid: string): DataSourceInstanceSettings | undefined { if (uid === 'lokiUid') { return { - name: 'Loki1', + name: 'loki1', } as any; } return undefined; @@ -82,7 +82,7 @@ describe('createSpanLinkFactory', () => { } as any); expect(linkDef.href).toBe( - `/explore?left={"range":{"from":"20201014T000000","to":"20201014T010006"},"datasource":"Loki1","queries":[{"expr":"{cluster=\\"cluster1\\", hostname=\\"hostname1\\"}","refId":""}]}` + `/explore?left={"range":{"from":"20201014T000000","to":"20201014T010006"},"datasource":"loki1","queries":[{"expr":"{cluster=\\"cluster1\\", hostname=\\"hostname1\\"}","refId":""}]}` ); }); }); diff --git a/public/app/features/explore/TraceView/createSpanLink.tsx b/public/app/features/explore/TraceView/createSpanLink.tsx index 2a85d45d0cf..b9ac8dc5f7c 100644 --- a/public/app/features/explore/TraceView/createSpanLink.tsx +++ b/public/app/features/explore/TraceView/createSpanLink.tsx @@ -34,17 +34,24 @@ export function createSpanLinkFactory(splitOpenFn: (options: { datasourceUid: st url: '', internal: { datasourceUid: lokiDs.uid, + datasourceName: lokiDs.name, query: { expr: getLokiQueryFromSpan(span), refId: '', }, }, }; - const link = mapInternalLinkToExplore(dataLink, {}, getTimeRangeFromSpan(span), {} as Field, { + + const link = mapInternalLinkToExplore({ + link: dataLink, + internalLink: dataLink.internal!, + scopedVars: {}, + range: getTimeRangeFromSpan(span), + field: {} as Field, onClickFn: splitOpenFn, replaceVariables: getTemplateSrv().replace.bind(getTemplateSrv()), - getDataSourceSettingsByUid: getDataSourceSrv().getDataSourceSettingsByUid.bind(getDataSourceSrv()), }); + return { href: link.href, onClick: link.onClick, diff --git a/public/app/features/explore/state/main.ts b/public/app/features/explore/state/main.ts index 4ee5c1281ac..5e2bf7a5351 100644 --- a/public/app/features/explore/state/main.ts +++ b/public/app/features/explore/state/main.ts @@ -109,7 +109,7 @@ export function splitOpen(options?: { } as DataQuery, ]; - const dataSourceSettings = getDatasourceSrv().getDataSourceSettingsByUid(options.datasourceUid); + const dataSourceSettings = getDatasourceSrv().getInstanceSettings(options.datasourceUid); await dispatch(changeDatasource(ExploreId.right, dataSourceSettings!.name)); await dispatch(setQueriesAction({ exploreId: ExploreId.right, queries })); diff --git a/public/app/features/explore/utils/links.test.ts b/public/app/features/explore/utils/links.test.ts index 8d1c66c96be..57b275dcf1a 100644 --- a/public/app/features/explore/utils/links.test.ts +++ b/public/app/features/explore/utils/links.test.ts @@ -1,17 +1,6 @@ import { getFieldLinksForExplore } from './links'; -import { - ArrayVector, - DataLink, - DataSourceInstanceSettings, - dateTime, - Field, - FieldType, - LinkModel, - ScopedVars, - TimeRange, -} from '@grafana/data'; +import { ArrayVector, DataLink, dateTime, Field, FieldType, LinkModel, ScopedVars, TimeRange } from '@grafana/data'; import { setLinkSrv } from '../../panel/panellinks/link_srv'; -import { setDataSourceSrv } from '@grafana/runtime'; describe('getFieldLinksForExplore', () => { it('returns correct link model for external link', () => { @@ -43,6 +32,7 @@ describe('getFieldLinksForExplore', () => { internal: { query: { query: 'query_1' }, datasourceUid: 'uid_1', + datasourceName: 'test_ds', }, }); const splitfn = jest.fn(); @@ -82,18 +72,7 @@ function setup(link: DataLink) { return link.url; }, }); - setDataSourceSrv({ - getDataSourceSettingsByUid(uid: string) { - return { - id: 1, - uid: 'uid_1', - type: 'metrics', - name: 'test_ds', - meta: {}, - jsonData: {}, - } as DataSourceInstanceSettings; - }, - } as any); + const field: Field = { name: 'flux-dimensions', type: FieldType.string, diff --git a/public/app/features/explore/utils/links.ts b/public/app/features/explore/utils/links.ts index eb601f4b550..fc1a7c54842 100644 --- a/public/app/features/explore/utils/links.ts +++ b/public/app/features/explore/utils/links.ts @@ -1,6 +1,6 @@ import { Field, LinkModel, TimeRange, mapInternalLinkToExplore } from '@grafana/data'; import { getLinkSrv } from '../../panel/panellinks/link_srv'; -import { getDataSourceSrv, getTemplateSrv } from '@grafana/runtime'; +import { getTemplateSrv } from '@grafana/runtime'; import { splitOpen } from '../state/main'; /** @@ -33,10 +33,14 @@ export const getFieldLinksForExplore = ( } return linkModel; } else { - return mapInternalLinkToExplore(link, scopedVars, range, field, { + return mapInternalLinkToExplore({ + link, + internalLink: link.internal, + scopedVars: scopedVars, + range, + field, onClickFn: splitOpenFn, replaceVariables: getTemplateSrv().replace.bind(getTemplateSrv()), - getDataSourceSettingsByUid: getDataSourceSrv().getDataSourceSettingsByUid.bind(getDataSourceSrv()), }); } }) diff --git a/public/app/features/panel/panellinks/linkSuppliers.test.ts b/public/app/features/panel/panellinks/linkSuppliers.test.ts index d75a097d137..4abf5a4a3aa 100644 --- a/public/app/features/panel/panellinks/linkSuppliers.test.ts +++ b/public/app/features/panel/panellinks/linkSuppliers.test.ts @@ -90,7 +90,6 @@ describe('getFieldLinksSupplier', () => { overrides: [], }, replaceVariables: (val: string) => val, - getDataSourceSettingsByUid: (val: string) => ({} as any), timeZone: 'utc', theme: getTheme(), })[0]; diff --git a/public/app/features/query/state/PanelQueryRunner.test.ts b/public/app/features/query/state/PanelQueryRunner.test.ts index 092b33e16a0..f0362a8eecf 100644 --- a/public/app/features/query/state/PanelQueryRunner.test.ts +++ b/public/app/features/query/state/PanelQueryRunner.test.ts @@ -88,11 +88,8 @@ function describeQueryRunnerScenario(description: string, scenarioFn: ScenarioFn }, ], }; - setDataSourceSrv({ - getDataSourceSettingsByUid() { - return {} as any; - }, - } as any); + + setDataSourceSrv({} as any); beforeEach(async () => { setEchoSrv(new Echo()); @@ -241,7 +238,6 @@ describe('PanelQueryRunner', () => { overrides: [], }, replaceVariables: v => v, - getDataSourceSettingsByUid: undefined as any, theme: {} as GrafanaTheme, }), getTransformations: () => undefined, @@ -306,7 +302,6 @@ describe('PanelQueryRunner', () => { overrides: [], }, replaceVariables: v => v, - getDataSourceSettingsByUid: undefined as any, theme: {} as GrafanaTheme, }), // @ts-ignore @@ -349,7 +344,6 @@ describe('PanelQueryRunner', () => { overrides: [], }, replaceVariables: v => v, - getDataSourceSettingsByUid: undefined as any, theme: {} as GrafanaTheme, }), // @ts-ignore diff --git a/public/app/plugins/datasource/elasticsearch/datasource.test.ts b/public/app/plugins/datasource/elasticsearch/datasource.test.ts index 84f58745445..15a570c0498 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.test.ts +++ b/public/app/plugins/datasource/elasticsearch/datasource.test.ts @@ -22,6 +22,13 @@ const ELASTICSEARCH_MOCK_URL = 'http://elasticsearch.local'; jest.mock('@grafana/runtime', () => ({ ...((jest.requireActual('@grafana/runtime') as unknown) as object), getBackendSrv: () => backendSrv, + getDataSourceSrv: () => { + return { + getInstanceSettings: () => { + return { name: 'elastic25' }; + }, + }; + }, })); describe('ElasticDatasource', function(this: any) { @@ -932,6 +939,7 @@ describe('enhanceDataFrame', () => { url: '', internal: { query: { query: 'query' }, + datasourceName: 'elastic25', datasourceUid: 'dsUid', }, }); diff --git a/public/app/plugins/datasource/elasticsearch/datasource.ts b/public/app/plugins/datasource/elasticsearch/datasource.ts index 520a06dafb2..05665a5a302 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.ts +++ b/public/app/plugins/datasource/elasticsearch/datasource.ts @@ -17,7 +17,7 @@ import { IndexPattern } from './index_pattern'; import { ElasticQueryBuilder } from './query_builder'; import { toUtc } from '@grafana/data'; import * as queryDef from './query_def'; -import { getBackendSrv } from '@grafana/runtime'; +import { getBackendSrv, getDataSourceSrv } from '@grafana/runtime'; import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv'; import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { DataLinkConfig, ElasticsearchOptions, ElasticsearchQuery } from './types'; @@ -661,29 +661,41 @@ export class ElasticDatasource extends DataSourceApi field.name && field.name.match(dataLink.field)); - if (dataLinkConfig) { - let link: DataLink; - if (dataLinkConfig.datasourceUid) { - link = { - title: '', - url: '', - internal: { - query: { query: dataLinkConfig.url }, - datasourceUid: dataLinkConfig.datasourceUid, - }, - }; - } else { - link = { - title: '', - url: dataLinkConfig.url, - }; - } - field.config = field.config || {}; - field.config.links = [...(field.config.links || []), link]; - } + const dataSourceSrv = getDataSourceSrv(); + + if (!dataLinks.length) { + return; + } + + for (const field of dataFrame.fields) { + const dataLinkConfig = dataLinks.find(dataLink => field.name && field.name.match(dataLink.field)); + + if (!dataLinkConfig) { + continue; } + + let link: DataLink; + + if (dataLinkConfig.datasourceUid) { + const dsSettings = dataSourceSrv.getInstanceSettings(dataLinkConfig.datasourceUid); + + link = { + title: '', + url: '', + internal: { + query: { query: dataLinkConfig.url }, + datasourceUid: dataLinkConfig.datasourceUid, + datasourceName: dsSettings?.name ?? 'Data source not found', + }, + }; + } else { + link = { + title: '', + url: dataLinkConfig.url, + }; + } + + field.config = field.config || {}; + field.config.links = [...(field.config.links || []), link]; } } diff --git a/public/app/plugins/datasource/loki/result_transformer.test.ts b/public/app/plugins/datasource/loki/result_transformer.test.ts index 73d20dbfd84..9933d26e48d 100644 --- a/public/app/plugins/datasource/loki/result_transformer.test.ts +++ b/public/app/plugins/datasource/loki/result_transformer.test.ts @@ -33,6 +33,18 @@ const lokiResponse: LokiStreamResponse = { }, }; +jest.mock('@grafana/runtime', () => ({ + // @ts-ignore + ...jest.requireActual('@grafana/runtime'), + getDataSourceSrv: () => { + return { + getInstanceSettings: () => { + return { name: 'Loki1' }; + }, + }; + }, +})); + describe('loki result transformer', () => { beforeAll(() => { setTemplateSrv(new TemplateSrv()); @@ -167,12 +179,12 @@ describe('enhanceDataFrame', () => { expect(fc.getFieldByName('trace2')!.config.links!.length).toBe(2); expect(fc.getFieldByName('trace2')!.config.links![0]).toEqual({ title: '', - internal: { datasourceUid: 'uid', query: { query: 'test' } }, + internal: { datasourceName: 'Loki1', datasourceUid: 'uid', query: { query: 'test' } }, url: '', }); expect(fc.getFieldByName('trace2')!.config.links![1]).toEqual({ title: '', - internal: { datasourceUid: 'uid2', query: { query: 'test' } }, + internal: { datasourceName: 'Loki1', datasourceUid: 'uid2', query: { query: 'test' } }, url: '', }); }); diff --git a/public/app/plugins/datasource/loki/result_transformer.ts b/public/app/plugins/datasource/loki/result_transformer.ts index 3788224d568..b79ae0178db 100644 --- a/public/app/plugins/datasource/loki/result_transformer.ts +++ b/public/app/plugins/datasource/loki/result_transformer.ts @@ -19,7 +19,7 @@ import { ScopedVars, } from '@grafana/data'; -import { getTemplateSrv } from '@grafana/runtime'; +import { getTemplateSrv, getDataSourceSrv } from '@grafana/runtime'; import TableModel from 'app/core/table_model'; import { formatQuery, getHighlighterExpressionsFromQuery } from './query_utils'; import { @@ -387,9 +387,13 @@ export const enhanceDataFrame = (dataFrame: DataFrame, config: LokiOptions | nul * Transform derivedField config into dataframe field with config that contains link. */ function fieldFromDerivedFieldConfig(derivedFieldConfigs: DerivedFieldConfig[]): Field { + const dataSourceSrv = getDataSourceSrv(); + const dataLinks = derivedFieldConfigs.reduce((acc, derivedFieldConfig) => { // Having field.datasourceUid means it is an internal link. if (derivedFieldConfig.datasourceUid) { + const dsSettings = dataSourceSrv.getInstanceSettings(derivedFieldConfig.datasourceUid); + acc.push({ // Will be filled out later title: '', @@ -398,6 +402,7 @@ function fieldFromDerivedFieldConfig(derivedFieldConfigs: DerivedFieldConfig[]): internal: { query: { query: derivedFieldConfig.url }, datasourceUid: derivedFieldConfig.datasourceUid, + datasourceName: dsSettings?.name ?? 'Data source not found', }, }); } else if (derivedFieldConfig.url) { diff --git a/public/app/plugins/panel/live/LivePanel.tsx b/public/app/plugins/panel/live/LivePanel.tsx index 2567140735a..40859581203 100755 --- a/public/app/plugins/panel/live/LivePanel.tsx +++ b/public/app/plugins/panel/live/LivePanel.tsx @@ -174,7 +174,6 @@ export class LivePanel extends PureComponent { series: applyFieldOverrides({ data: message.getData(), theme: config.theme, - getDataSourceSettingsByUid: () => undefined, replaceVariables: (v: string) => v, fieldConfig: { defaults: {}, From 460883d0b291a5e497a07e6efd7c8ef88a507fa0 Mon Sep 17 00:00:00 2001 From: mnkii Date: Tue, 1 Dec 2020 18:27:19 +0000 Subject: [PATCH 030/124] Docs: Added Security Group support to Azure Auth (#29418) Support of both Application and Security groups in groupMembershipClaims, as per @pihai suggestion: https://github.com/grafana/grafana/issues/23358#issuecomment-685377338 --- docs/sources/auth/azuread.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/auth/azuread.md b/docs/sources/auth/azuread.md index 7fd8317d77f..fd5d604fd40 100644 --- a/docs/sources/auth/azuread.md +++ b/docs/sources/auth/azuread.md @@ -118,7 +118,7 @@ allowed_groups = 8bab1c86-8fba-33e5-2089-1d1c80ec267d You'll need to ensure that you've [enabled group attributes](https://docs.microsoft.com/en-us/azure/active-directory/hybrid/how-to-connect-fed-group-claims#configure-the-azure-ad-application-registration-for-group-attributes) in your Azure AD Application Registration manifest file (Azure Portal -> Azure Active Directory -> Application Registrations -> Select Application -> Manifest) ```json -"groupMembershipClaims": "ApplicationGroup" +"groupMembershipClaims": "ApplicationGroup, SecurityGroup" ``` The `allowed_domains` option limits access to the users belonging to the specific domains. Domains should be separated by space or comma. From a54e0ff79df4c2be5bbb4e62a6967bbc2a2220ab Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Tue, 1 Dec 2020 20:43:35 +0100 Subject: [PATCH 031/124] Loki: Add query type and line limit to query editor in dashboard (#29356) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * WIP Add line limit and query type switch toLoki dashboard editor * Refactor, reuse code for both - Explore and Dashboard * Üpdate snapshot tests * Refactor and unify * Rename test file * Update test --- .../loki/components/LokiExploreExtraField.tsx | 72 -------- .../LokiExploreQueryEditor.test.tsx | 4 +- .../components/LokiExploreQueryEditor.tsx | 53 ------ ...eld.test.tsx => LokiOptionFields.test.tsx} | 8 +- .../loki/components/LokiOptionFields.tsx | 131 ++++++++++++++ .../loki/components/LokiQueryEditor.test.tsx | 2 +- .../loki/components/LokiQueryEditor.tsx | 117 +++++-------- .../loki/components/LokiQueryFieldForm.tsx | 11 ++ .../LokiExploreQueryEditor.test.tsx.snap | 9 - .../LokiQueryEditor.test.tsx.snap | 164 +++++++++--------- 10 files changed, 276 insertions(+), 295 deletions(-) delete mode 100644 public/app/plugins/datasource/loki/components/LokiExploreExtraField.tsx rename public/app/plugins/datasource/loki/components/{LokiExploreExtraField.test.tsx => LokiOptionFields.test.tsx} (73%) create mode 100644 public/app/plugins/datasource/loki/components/LokiOptionFields.tsx diff --git a/public/app/plugins/datasource/loki/components/LokiExploreExtraField.tsx b/public/app/plugins/datasource/loki/components/LokiExploreExtraField.tsx deleted file mode 100644 index a8e96ffd9b6..00000000000 --- a/public/app/plugins/datasource/loki/components/LokiExploreExtraField.tsx +++ /dev/null @@ -1,72 +0,0 @@ -// Libraries -import React, { memo } from 'react'; -import { css, cx } from 'emotion'; - -// Types -import { InlineFormLabel, RadioButtonGroup } from '@grafana/ui'; - -export interface LokiExploreExtraFieldProps { - lineLimitValue: string; - queryType: string; - onLineLimitChange: (e: React.SyntheticEvent) => void; - onKeyDownFunc: (e: React.KeyboardEvent) => void; - onQueryTypeChange: (value: string) => void; -} - -export function LokiExploreExtraField(props: LokiExploreExtraFieldProps) { - const { onLineLimitChange, onKeyDownFunc, lineLimitValue, queryType, onQueryTypeChange } = props; - - const rangeOptions = [ - { value: 'range', label: 'Range' }, - { value: 'instant', label: 'Instant' }, - ]; - - return ( -
- {/*Query type field*/} -
- - Query type - - - -
- {/*Line limit field*/} -
- Line limit - -
-
- ); -} - -export default memo(LokiExploreExtraField); diff --git a/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.test.tsx b/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.test.tsx index 5e3f00df210..f9d4e74b810 100644 --- a/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.test.tsx +++ b/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.test.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { mount, shallow } from 'enzyme'; import { act } from 'react-dom/test-utils'; import LokiExploreQueryEditor from './LokiExploreQueryEditor'; -import { LokiExploreExtraField } from './LokiExploreExtraField'; +import { LokiOptionFields } from './LokiOptionFields'; import { LokiDatasource } from '../datasource'; import { LokiQuery } from '../types'; import { ExploreMode, LoadingState, PanelData, toUtc, TimeRange } from '@grafana/data'; @@ -93,7 +93,7 @@ describe('LokiExploreQueryEditor', () => { // @ts-ignore strict null error TS2345: Argument of type '() => Promise' is not assignable to parameter of type '() => void | undefined'. await act(async () => { const wrapper = setup(mount); - expect(wrapper.find(LokiExploreExtraField).length).toBe(1); + expect(wrapper.find(LokiOptionFields).length).toBe(1); }); }); }); diff --git a/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.tsx b/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.tsx index 01abd8807ae..0949fa8cb49 100644 --- a/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.tsx +++ b/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.tsx @@ -7,56 +7,12 @@ import { ExploreQueryFieldProps } from '@grafana/data'; import { LokiDatasource } from '../datasource'; import { LokiQuery, LokiOptions } from '../types'; import { LokiQueryField } from './LokiQueryField'; -import LokiExploreExtraField from './LokiExploreExtraField'; type Props = ExploreQueryFieldProps; export function LokiExploreQueryEditor(props: Props) { const { range, query, data, datasource, history, onChange, onRunQuery } = props; - function onChangeQueryLimit(value: string) { - const { query, onChange } = props; - const nextQuery = { ...query, maxLines: preprocessMaxLines(value) }; - onChange(nextQuery); - } - - function onQueryTypeChange(value: string) { - const { query, onChange } = props; - let nextQuery; - if (value === 'instant') { - nextQuery = { ...query, instant: true, range: false }; - } else { - nextQuery = { ...query, instant: false, range: true }; - } - onChange(nextQuery); - } - - function preprocessMaxLines(value: string): number { - if (value.length === 0) { - // empty input - falls back to dataSource.maxLines limit - return NaN; - } else if (value.length > 0 && (isNaN(+value) || +value < 0)) { - // input with at least 1 character and that is either incorrect (value in the input field is not a number) or negative - // falls back to the limit of 0 lines - return 0; - } else { - // default case - correct input - return +value; - } - } - - function onMaxLinesChange(e: React.SyntheticEvent) { - if (query.maxLines !== preprocessMaxLines(e.currentTarget.value)) { - onChangeQueryLimit(e.currentTarget.value); - } - } - - function onReturnKeyDown(e: React.KeyboardEvent) { - if (e.key === 'Enter') { - onRunQuery(); - } - } - return ( - } /> ); } diff --git a/public/app/plugins/datasource/loki/components/LokiExploreExtraField.test.tsx b/public/app/plugins/datasource/loki/components/LokiOptionFields.test.tsx similarity index 73% rename from public/app/plugins/datasource/loki/components/LokiExploreExtraField.test.tsx rename to public/app/plugins/datasource/loki/components/LokiOptionFields.test.tsx index 486d4114758..c91d0546c89 100644 --- a/public/app/plugins/datasource/loki/components/LokiExploreExtraField.test.tsx +++ b/public/app/plugins/datasource/loki/components/LokiOptionFields.test.tsx @@ -1,8 +1,8 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; -import { LokiExploreExtraFieldProps, LokiExploreExtraField } from './LokiExploreExtraField'; +import { LokiOptionFieldsProps, LokiOptionFields } from './LokiOptionFields'; -const setup = (propOverrides?: LokiExploreExtraFieldProps) => { +const setup = (propOverrides?: LokiOptionFieldsProps) => { const queryType = 'range'; const lineLimitValue = '1'; const onLineLimitChange = jest.fn(); @@ -19,10 +19,10 @@ const setup = (propOverrides?: LokiExploreExtraFieldProps) => { Object.assign(props, propOverrides); - return render(); + return render(); }; -describe('LokiExploreExtraField', () => { +describe('LokiOptionFields', () => { it('should render step field', () => { setup(); expect(screen.getByTestId('lineLimitField')).toBeInTheDocument(); diff --git a/public/app/plugins/datasource/loki/components/LokiOptionFields.tsx b/public/app/plugins/datasource/loki/components/LokiOptionFields.tsx new file mode 100644 index 00000000000..d5fd8edeb24 --- /dev/null +++ b/public/app/plugins/datasource/loki/components/LokiOptionFields.tsx @@ -0,0 +1,131 @@ +// Libraries +import React, { memo } from 'react'; +import { css, cx } from 'emotion'; +import { LokiQuery } from '../types'; + +// Types +import { InlineFormLabel, RadioButtonGroup } from '@grafana/ui'; + +export interface LokiOptionFieldsProps { + lineLimitValue: string; + queryType: LokiQueryType; + query: LokiQuery; + onChange: (value: LokiQuery) => void; + onRunQuery: () => void; + runOnBlur?: boolean; +} + +type LokiQueryType = 'instant' | 'range'; + +const queryTypeOptions = [ + { value: 'range', label: 'Range' }, + { value: 'instant', label: 'Instant' }, +]; + +export function LokiOptionFields(props: LokiOptionFieldsProps) { + const { lineLimitValue, queryType, query, onRunQuery, runOnBlur, onChange } = props; + + function onChangeQueryLimit(value: string) { + const nextQuery = { ...query, maxLines: preprocessMaxLines(value) }; + onChange(nextQuery); + } + + function onQueryTypeChange(value: LokiQueryType) { + let nextQuery; + if (value === 'instant') { + nextQuery = { ...query, instant: true, range: false }; + } else { + nextQuery = { ...query, instant: false, range: true }; + } + onChange(nextQuery); + } + + function preprocessMaxLines(value: string): number { + if (value.length === 0) { + // empty input - falls back to dataSource.maxLines limit + return NaN; + } else if (value.length > 0 && (isNaN(+value) || +value < 0)) { + // input with at least 1 character and that is either incorrect (value in the input field is not a number) or negative + // falls back to the limit of 0 lines + return 0; + } else { + // default case - correct input + return +value; + } + } + + function onMaxLinesChange(e: React.SyntheticEvent) { + if (query.maxLines !== preprocessMaxLines(e.currentTarget.value)) { + onChangeQueryLimit(e.currentTarget.value); + } + } + + function onReturnKeyDown(e: React.KeyboardEvent) { + if (e.key === 'Enter') { + onRunQuery(); + } + } + + return ( +
+ {/*Query type field*/} +
+ + Query type + + + { + onQueryTypeChange(type); + if (runOnBlur) { + onRunQuery(); + } + }} + /> +
+ {/*Line limit field*/} +
+ Line limit + { + if (runOnBlur) { + onRunQuery(); + } + }} + /> +
+
+ ); +} + +export default memo(LokiOptionFields); diff --git a/public/app/plugins/datasource/loki/components/LokiQueryEditor.test.tsx b/public/app/plugins/datasource/loki/components/LokiQueryEditor.test.tsx index dbae1054843..53485f9db95 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryEditor.test.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryEditor.test.tsx @@ -38,7 +38,7 @@ const setup = (propOverrides?: object) => { Object.assign(props, propOverrides); const wrapper = shallow(); - const instance = wrapper.instance() as LokiQueryEditor; + const instance = wrapper.instance(); return { instance, diff --git a/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx b/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx index 2db38ea19e9..67ded1bd8ad 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx @@ -1,90 +1,59 @@ // Libraries -import React, { PureComponent } from 'react'; +import React, { memo } from 'react'; // Types import { QueryEditorProps } from '@grafana/data'; import { InlineFormLabel } from '@grafana/ui'; import { LokiDatasource } from '../datasource'; -import { LokiQuery } from '../types'; +import { LokiQuery, LokiOptions } from '../types'; import { LokiQueryField } from './LokiQueryField'; -type Props = QueryEditorProps; +type Props = QueryEditorProps; -interface State { - legendFormat: string; -} +export function LokiQueryEditor(props: Props) { + const { range, query, data, datasource, onChange, onRunQuery } = props; -export class LokiQueryEditor extends PureComponent { - // Query target to be modified and used for queries - query: LokiQuery; - - constructor(props: Props) { - super(props); - // Use default query to prevent undefined input values - const defaultQuery: Partial = { expr: '', legendFormat: '' }; - const query = Object.assign({}, defaultQuery, props.query); - this.query = query; - // Query target properties that are fully controlled inputs - this.state = { - // Fully controlled text inputs - legendFormat: query.legendFormat ?? '', - }; - } - - onFieldChange = (query: LokiQuery, override?: any) => { - this.query.expr = query.expr; + const onLegendChange = (e: React.SyntheticEvent) => { + const nextQuery = { ...query, legendFormat: e.currentTarget.value }; + onChange(nextQuery); }; - onLegendChange = (e: React.SyntheticEvent) => { - const legendFormat = e.currentTarget.value; - this.query.legendFormat = legendFormat; - this.setState({ legendFormat }); - }; - - onRunQuery = () => { - const { query } = this; - this.props.onChange(query); - this.props.onRunQuery(); - }; - - render() { - const { datasource, query, data, range } = this.props; - const { legendFormat } = this.state; - - return ( -
- - -
-
- +
+ - Legend - - -
-
+ > + Legend + +
- ); - } +
+ ); + + return ( + + ); } -export default LokiQueryEditor; +export default memo(LokiQueryEditor); diff --git a/public/app/plugins/datasource/loki/components/LokiQueryFieldForm.tsx b/public/app/plugins/datasource/loki/components/LokiQueryFieldForm.tsx index 931fb679067..763603e4659 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryFieldForm.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryFieldForm.tsx @@ -23,6 +23,7 @@ import { LokiQuery, LokiOptions } from '../types'; import { Grammar } from 'prismjs'; import LokiLanguageProvider, { LokiHistoryItem } from '../language_provider'; import LokiDatasource from '../datasource'; +import LokiOptionFields from './LokiOptionFields'; function getChooserText(hasSyntax: boolean, hasLogLabels: boolean) { if (!hasSyntax) { @@ -70,6 +71,7 @@ export interface LokiQueryFieldFormProps extends ExploreQueryFieldProps void; onLabelsRefresh?: () => void; ExtraFieldElement?: ReactNode; + runOnBlur?: boolean; } export class LokiQueryFieldForm extends React.PureComponent { @@ -140,6 +142,7 @@ export class LokiQueryFieldForm extends React.PureComponent
+ {ExtraFieldElement} ); diff --git a/public/app/plugins/datasource/loki/components/__snapshots__/LokiExploreQueryEditor.test.tsx.snap b/public/app/plugins/datasource/loki/components/__snapshots__/LokiExploreQueryEditor.test.tsx.snap index fc69ac05249..aa142f94ec3 100644 --- a/public/app/plugins/datasource/loki/components/__snapshots__/LokiExploreQueryEditor.test.tsx.snap +++ b/public/app/plugins/datasource/loki/components/__snapshots__/LokiExploreQueryEditor.test.tsx.snap @@ -2,15 +2,6 @@ exports[`LokiExploreQueryEditor should render component 1`] = ` - } data={ Object { "request": Object { diff --git a/public/app/plugins/datasource/loki/components/__snapshots__/LokiQueryEditor.test.tsx.snap b/public/app/plugins/datasource/loki/components/__snapshots__/LokiQueryEditor.test.tsx.snap index b08d1b16b88..cd8848192c7 100644 --- a/public/app/plugins/datasource/loki/components/__snapshots__/LokiQueryEditor.test.tsx.snap +++ b/public/app/plugins/datasource/loki/components/__snapshots__/LokiQueryEditor.test.tsx.snap @@ -1,95 +1,99 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Render LokiQueryEditor with legend should render 1`] = ` -
- -
+ - - Legend - - + + Legend + + +
-
-
+ } + datasource={Object {}} + history={Array []} + onBlur={[MockFunction]} + onChange={[MockFunction]} + onRunQuery={[MockFunction]} + query={ + Object { + "expr": "", + "legendFormat": "My Legend", + "refId": "A", + } + } + range={ + Object { + "from": "2020-01-01T00:00:00.000Z", + "to": "2020-01-02T00:00:00.000Z", + } + } + runOnBlur={true} +/> `; exports[`Render LokiQueryEditor with legend should update timerange 1`] = ` -
- -
+ - - Legend - - + + Legend + + +
-
-
+ } + datasource={Object {}} + history={Array []} + onBlur={[MockFunction]} + onChange={[MockFunction]} + onRunQuery={[MockFunction]} + query={ + Object { + "expr": "", + "legendFormat": "My Legend", + "refId": "A", + } + } + range={ + Object { + "from": "2019-01-01T00:00:00.000Z", + "to": "2020-01-02T00:00:00.000Z", + } + } + runOnBlur={true} +/> `; From 05396ef7bd594f13c29894b2ebf2e855fdec2d28 Mon Sep 17 00:00:00 2001 From: Rami <50161027+rumsrami@users.noreply.github.com> Date: Tue, 1 Dec 2020 17:01:18 -0500 Subject: [PATCH 032/124] Docs: InfluxDB_V2 datasource: adding an example on how to add InfluxQL as a datasource (#29490) --- docs/sources/datasources/influxdb.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/docs/sources/datasources/influxdb.md b/docs/sources/datasources/influxdb.md index 314ef69639f..de41fb1546c 100644 --- a/docs/sources/datasources/influxdb.md +++ b/docs/sources/datasources/influxdb.md @@ -286,12 +286,12 @@ datasources: httpMode: GET ``` -### InfluxDB 2.x example +### InfluxDB 2.x for Flux example ```yaml apiVersion: 1 datasources: - - name: InfluxDB_v2 + - name: InfluxDB_v2_Flux type: influxdb access: proxy url: http://localhost:8086 @@ -303,3 +303,22 @@ datasources: defaultBucket: bucket tlsSkipVerify: true ``` + +### InfluxDB 2.x for InfluxQl example +```yaml +apiVersion: 1 + +datasources: + - name: InfluxDB_v2_InfluxQL + type: influxdb + access: proxy + url: http://localhost:8086 + # This database should be mapped to a bucket + database: site + jsonData: + httpMode: GET + httpHeaderName1: 'Authorization' + secureJsonData: + httpHeaderValue1: 'Token ' + +``` From 9b2592dddf2f2fbe3225d6774744f93d9c41c8f3 Mon Sep 17 00:00:00 2001 From: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> Date: Tue, 1 Dec 2020 14:51:06 -0800 Subject: [PATCH 033/124] Docs: Update filter-variables-with-regex.md (#29508) --- docs/sources/variables/filter-variables-with-regex.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sources/variables/filter-variables-with-regex.md b/docs/sources/variables/filter-variables-with-regex.md index e4e2cd03c43..fb25635b6ee 100644 --- a/docs/sources/variables/filter-variables-with-regex.md +++ b/docs/sources/variables/filter-variables-with-regex.md @@ -78,6 +78,8 @@ demo.robustperception.io:9100 ## Filter and modify using named text and value capture groups +> **Note:** This feature is available in Grafana 7.4+. + Using named capture groups, you can capture separate 'text' and 'value' parts from the options returned by the variable query. This allows the variable drop-down list to contain a friendly name for each value that can be selected. For example, when querying the `node_hwmon_chip_names` Prometheus metric, the `chip_name` is a lot friendlier that the `chip` value. So the following variable query result: From 1581d04e65970fa7c7dca225a6287cd669b77eaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Wed, 2 Dec 2020 08:47:04 +0100 Subject: [PATCH 034/124] Chore: Adds Panel Library featuretoggle (#29521) --- packages/grafana-data/src/types/config.ts | 1 + packages/grafana-runtime/src/config.ts | 1 + pkg/setting/setting.go | 6 ++++++ 3 files changed, 8 insertions(+) diff --git a/packages/grafana-data/src/types/config.ts b/packages/grafana-data/src/types/config.ts index 9223477f83d..0f5e796eaf4 100644 --- a/packages/grafana-data/src/types/config.ts +++ b/packages/grafana-data/src/types/config.ts @@ -37,6 +37,7 @@ export interface FeatureToggles { ngalert: boolean; // Just for demo at the moment traceToLogs: boolean; + panelLibrary: boolean; /** * @remarks diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts index bcb53c23dc8..64295fd97e5 100644 --- a/packages/grafana-runtime/src/config.ts +++ b/packages/grafana-runtime/src/config.ts @@ -57,6 +57,7 @@ export class GrafanaBootConfig implements GrafanaConfig { meta: false, ngalert: false, traceToLogs: false, + panelLibrary: false, }; licenseInfo: LicenseInfo = {} as LicenseInfo; rendererAvailable = false; diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 75b88df4f35..20c5691ffed 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -334,10 +334,16 @@ func (cfg Cfg) IsNgAlertEnabled() bool { return cfg.FeatureToggles["ngalert"] } +// IsHTTPRequestHistogramEnabled returns whether the http_request_histogram feature is enabled. func (cfg Cfg) IsHTTPRequestHistogramEnabled() bool { return cfg.FeatureToggles["http_request_histogram"] } +// IsPanelLibraryEnabled returns whether the panel library feature is enabled. +func (cfg Cfg) IsPanelLibraryEnabled() bool { + return cfg.FeatureToggles["panelLibrary"] +} + type CommandLineArgs struct { Config string HomePath string From d953a56eb66cbd2e096caa4e6a7e8dafb66a84e3 Mon Sep 17 00:00:00 2001 From: Brett Snyder Date: Wed, 2 Dec 2020 03:15:53 -0500 Subject: [PATCH 035/124] docker: fix Dockerfile after Gruntfile.js removed (#29515) --- Dockerfile | 2 +- Dockerfile.ubuntu | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index d44436bdb06..a710cbaf4cb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ COPY packages packages RUN yarn install --pure-lockfile --no-progress -COPY Gruntfile.js tsconfig.json .eslintrc .editorconfig .browserslistrc .prettierrc.js ./ +COPY tsconfig.json .eslintrc .editorconfig .browserslistrc .prettierrc.js ./ COPY public public COPY tools tools COPY scripts scripts diff --git a/Dockerfile.ubuntu b/Dockerfile.ubuntu index 195d3b9289b..a4d24559685 100644 --- a/Dockerfile.ubuntu +++ b/Dockerfile.ubuntu @@ -7,7 +7,7 @@ COPY packages packages RUN yarn install --pure-lockfile -COPY Gruntfile.js tsconfig.json .eslintrc .editorconfig .browserslistrc .prettierrc.js ./ +COPY tsconfig.json .eslintrc .editorconfig .browserslistrc .prettierrc.js ./ COPY public public COPY tools tools COPY scripts scripts From 53cd59a5a5581d6774cb340adcb50d9dfd798136 Mon Sep 17 00:00:00 2001 From: Uchechukwu Obasi Date: Wed, 2 Dec 2020 09:30:28 +0100 Subject: [PATCH 036/124] Datasources: fixed long error message overflowing container (#29440) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fixed: error message overflow container Signed-off-by: Uchechukwu Obasi * used min-width property for better styling Signed-off-by: Uchechukwu Obasi * Switched to Alert component * Fixed passing aria-label to Alert component * Fixed e2e test (I hope) * another attempt to fix e2e * Fixed display name Co-authored-by: Uchechukwu Obasi Co-authored-by: Torkel Ödegaard --- .../src/selectors/pages.ts | 1 - .../grafana-e2e/src/flows/addDataSource.ts | 6 +- .../grafana-ui/src/components/Alert/Alert.tsx | 66 +++++++++---------- .../settings/DataSourceSettingsPage.tsx | 21 +++--- public/sass/components/_alerts.scss | 2 + 5 files changed, 46 insertions(+), 50 deletions(-) diff --git a/packages/grafana-e2e-selectors/src/selectors/pages.ts b/packages/grafana-e2e-selectors/src/selectors/pages.ts index 4f7b571f4fc..ff00865f431 100644 --- a/packages/grafana-e2e-selectors/src/selectors/pages.ts +++ b/packages/grafana-e2e-selectors/src/selectors/pages.ts @@ -14,7 +14,6 @@ export const Pages = { delete: 'Data source settings page Delete button', saveAndTest: 'Data source settings page Save and Test button', alert: 'Data source settings page Alert', - alertMessage: 'Data source settings page Alert message', }, DataSources: { url: '/datasources', diff --git a/packages/grafana-e2e/src/flows/addDataSource.ts b/packages/grafana-e2e/src/flows/addDataSource.ts index d6b2353e820..cd1189f84b3 100644 --- a/packages/grafana-e2e/src/flows/addDataSource.ts +++ b/packages/grafana-e2e/src/flows/addDataSource.ts @@ -85,8 +85,10 @@ export const addDataSource = (config?: Partial) => { form(); e2e.pages.DataSource.saveAndTest().click(); - e2e.pages.DataSource.alert().should('exist'); - e2e.pages.DataSource.alertMessage().contains(expectedAlertMessage); // assertion + e2e.pages.DataSource.alert() + .should('exist') + .contains(expectedAlertMessage); // assertion + e2e().logToConsole('Added data source with name:', name); return e2e() diff --git a/packages/grafana-ui/src/components/Alert/Alert.tsx b/packages/grafana-ui/src/components/Alert/Alert.tsx index c4543d43abb..b009b01d5fc 100644 --- a/packages/grafana-ui/src/components/Alert/Alert.tsx +++ b/packages/grafana-ui/src/components/Alert/Alert.tsx @@ -1,4 +1,4 @@ -import React, { FC, ReactNode } from 'react'; +import React, { FC, HTMLAttributes, ReactNode } from 'react'; import { css } from 'emotion'; import { GrafanaTheme } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; @@ -9,7 +9,7 @@ import { getColorsFromSeverity } from '../../utils/colors'; export type AlertVariant = 'success' | 'warning' | 'error' | 'info'; -export interface Props { +export interface Props extends HTMLAttributes { title: string; /** On click handler for alert button, mostly used for dismissing the alert */ onRemove?: (event: React.MouseEvent) => void; @@ -39,40 +39,36 @@ function getIconFromSeverity(severity: AlertVariant): string { } } -export const Alert: FC = ({ - title, - buttonText, - onButtonClick, - onRemove, - children, - buttonContent, - severity = 'error', -}) => { - const theme = useTheme(); - const styles = getStyles(theme, severity, !!buttonContent); +export const Alert: FC = React.forwardRef( + ({ title, buttonText, onButtonClick, onRemove, children, buttonContent, severity = 'error', ...restProps }) => { + const theme = useTheme(); + const styles = getStyles(theme, severity, !!buttonContent); - return ( -
-
- + return ( +
+
+ +
+
+
{title}
+ {children &&
{children}
} +
+ {/* If onRemove is specified, giving preference to onRemove */} + {onRemove ? ( + + ) : onButtonClick ? ( + + ) : null}
-
-
{title}
- {children &&
{children}
} -
- {/* If onRemove is specified, giving preference to onRemove */} - {onRemove ? ( - - ) : onButtonClick ? ( - - ) : null} -
- ); -}; + ); + } +); + +Alert.displayName = 'Alert'; const getStyles = (theme: GrafanaTheme, severity: AlertVariant, outline: boolean) => { const { white } = theme.palette; @@ -107,6 +103,8 @@ const getStyles = (theme: GrafanaTheme, severity: AlertVariant, outline: boolean body: css` flex-grow: 1; margin: 0 ${theme.spacing.md} 0 0; + overflow-wrap: break-word; + word-break: break-word; a { color: ${white}; diff --git a/public/app/features/datasources/settings/DataSourceSettingsPage.tsx b/public/app/features/datasources/settings/DataSourceSettingsPage.tsx index 04234e7ad04..a2bb87ee604 100644 --- a/public/app/features/datasources/settings/DataSourceSettingsPage.tsx +++ b/public/app/features/datasources/settings/DataSourceSettingsPage.tsx @@ -2,8 +2,6 @@ import React, { PureComponent } from 'react'; import { hot } from 'react-hot-loader'; import isString from 'lodash/isString'; -import { Icon } from '@grafana/ui'; -import { selectors } from '@grafana/e2e-selectors'; // Components import Page from 'app/core/components/Page/Page'; import { GenericDataSourcePlugin, PluginSettings } from './PluginSettings'; @@ -25,10 +23,12 @@ import { getRouteParamsId } from 'app/core/selectors/location'; // Types import { CoreEvents, StoreState } from 'app/types/'; import { DataSourcePluginMeta, DataSourceSettings, NavModel, UrlQueryMap } from '@grafana/data'; +import { Alert } from '@grafana/ui'; import { getDataSourceLoadingNav } from '../state/navModel'; import PluginStateinfo from 'app/features/plugins/PluginStateInfo'; import { dataSourceLoaded, setDataSourceName, setIsDefault } from '../state/reducers'; import { connectWithCleanUp } from 'app/core/components/connectWithCleanUp'; +import { selectors } from '@grafana/e2e-selectors'; export interface Props { navModel: NavModel; @@ -172,7 +172,7 @@ export class DataSourceSettingsPage extends PureComponent { } renderSettings() { - const { dataSourceMeta, setDataSourceName, setIsDefault, dataSource, testingStatus, plugin } = this.props; + const { dataSourceMeta, setDataSourceName, setIsDefault, dataSource, plugin, testingStatus } = this.props; return (
@@ -204,16 +204,11 @@ export class DataSourceSettingsPage extends PureComponent {
{testingStatus && testingStatus.message && ( -
-
- {testingStatus.status === 'error' ? : } -
-
-
- {testingStatus.message} -
-
-
+ )}
diff --git a/public/sass/components/_alerts.scss b/public/sass/components/_alerts.scss index 628d0718113..3ffbcf2d249 100644 --- a/public/sass/components/_alerts.scss +++ b/public/sass/components/_alerts.scss @@ -77,6 +77,8 @@ } .alert-body { + overflow-wrap: break-word; + word-break: break-word; flex-grow: 1; } From eb16eb5d72d89bf9ddb017cc2d5e3fcbfa7ba067 Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Wed, 2 Dec 2020 09:44:35 +0100 Subject: [PATCH 037/124] Docs/Transformations: Add documentation about Binary operations in Add field from calculation (#29511) * Add also infor about binary operations * Update docs/sources/panels/transformations/types-options.md Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> * Update docs/sources/panels/transformations/types-options.md Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> * Update docs/sources/panels/transformations/types-options.md Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> --- .../panels/transformations/types-options.md | 85 +++++++++---------- 1 file changed, 42 insertions(+), 43 deletions(-) diff --git a/docs/sources/panels/transformations/types-options.md b/docs/sources/panels/transformations/types-options.md index a58152a8930..dc3b1a18dfd 100644 --- a/docs/sources/panels/transformations/types-options.md +++ b/docs/sources/panels/transformations/types-options.md @@ -7,17 +7,17 @@ weight = 300 Grafana comes with the following transformations: - - [Reduce](#reduce) - - [Filter by name](#filter-by-name) - - [Filter data by query](#filter-data-by-query) - - [Filter data by value](#filter-data-by-value) - - [Organize fields](#organize-fields) - - [Outer join](#join-by-field-outer-join) - - [Series to rows](#series-to-rows) - - [Add field from calculation](#add-field-from-calculation) - - [Labels to fields](#labels-to-fields) - - [Group by](#group-by) - - [Merge](#merge) +- [Reduce](#reduce) +- [Filter by name](#filter-by-name) +- [Filter data by query](#filter-data-by-query) +- [Filter data by value](#filter-data-by-value) +- [Organize fields](#organize-fields) +- [Outer join](#join-by-field-outer-join) +- [Series to rows](#series-to-rows) +- [Add field from calculation](#add-field-from-calculation) +- [Labels to fields](#labels-to-fields) +- [Group by](#group-by) +- [Merge](#merge) Keep reading for detailed descriptions of each type of transformation and the options available for each, as well as suggestions on how to use them. @@ -139,7 +139,8 @@ Use this transformation to add a new field calculated from two other fields. Eac - **Reduce row -** Apply selected calculation on each row of selected fields independently. - **Binary option -** Apply basic math operation(sum, multiply, etc) on values in a single row from two selected fields. - **Field name -** Select the names of fields you want to use in the calculation for the new field. -- **Calculation -** Select a calculation to use when Grafana creates the new field. Click in the field to see a list of calculation choices. For information about available calculations, refer to the [Calculation list]({{< relref "../calculations-list.md" >}}). +- **Calculation -** If you select **Reduce row** mode, then the **Calculation** field appears. Click in the field to see a list of calculation choices you can use to create the new field. For information about available calculations, refer to the [Calculation list]({{< relref "../calculations-list.md" >}}). +- **Operation -** If you select **Binary option** mode, then the **Operation** fields appear. These fields allow you to do basic math operations on values in a single row from two selected fields. You can also use numerical values for binary operations. - **Alias -** (Optional) Enter the name of your new field. If you leave this blank, then the field will be named to match the calculation. - **Replace all fields -** (Optional) Select this option if you want to hide all other fields and display only your calculated field in the visualization. @@ -185,7 +186,7 @@ After I apply the transformation, my labels appear in the table as fields. > **Note:** This transformation is only available in Grafana 7.2+. -This transformation groups the data by a specified field (column) value and processes calculations on each group. Click to see a list of calculation choices. For information about available calculations, refer to the [List of calculations]({{< relref "../calculations-list.md" >}}). +This transformation groups the data by a specified field (column) value and processes calculations on each group. Click to see a list of calculation choices. For information about available calculations, refer to the [List of calculations]({{< relref "../calculations-list.md" >}}). Here's an example of original data. @@ -203,17 +204,17 @@ Here's an example of original data. This transformation goes in two steps. First you specify one or multiple fields to group the data by. This will group all the same values of those fields together, as if you sorted them. For instance if we group by the Server ID field, then it would group the data this way: -| Time | Server ID | CPU Temperature | Server Status | -| ------------------- | ------------ | --------------- | ------------- | -| 2020-07-07 11:34:20 | **server 1** | 80 | Shutdown | -| 2020-07-07 09:28:06 | **server 1** | 80 | OK | -| 2020-07-07 09:23:07 | **server 1** | 86 | OK | -| 2020-07-07 10:32:20 | server 2 | 90 | Overload | -| 2020-07-07 09:30:05 | server 2 | 88 | OK | -| 2020-07-07 09:25:05 | server 2 | 88 | OK | -| 2020-07-07 11:34:20 | **_server 3_** | 62 | OK | -| 2020-07-07 10:31:22 | **_server 3_** | 55 | OK | -| 2020-07-07 09:30:57 | **_server 3_** | 62 | Rebooting | +| Time | Server ID | CPU Temperature | Server Status | +| ------------------- | -------------- | --------------- | ------------- | +| 2020-07-07 11:34:20 | **server 1** | 80 | Shutdown | +| 2020-07-07 09:28:06 | **server 1** | 80 | OK | +| 2020-07-07 09:23:07 | **server 1** | 86 | OK | +| 2020-07-07 10:32:20 | server 2 | 90 | Overload | +| 2020-07-07 09:30:05 | server 2 | 88 | OK | +| 2020-07-07 09:25:05 | server 2 | 88 | OK | +| 2020-07-07 11:34:20 | **_server 3_** | 62 | OK | +| 2020-07-07 10:31:22 | **_server 3_** | 55 | OK | +| 2020-07-07 09:30:57 | **_server 3_** | 62 | Rebooting | All rows with the same value of Server ID are grouped together. @@ -245,7 +246,7 @@ This transformation allows you to extract some key information out of your time > **Note:** This transformation is only available in Grafana 7.1+. -Use this transformation to combine the result from multiple time series data queries into one single result. This is helpful when using the table panel visualization. +Use this transformation to combine the result from multiple time series data queries into one single result. This is helpful when using the table panel visualization. The result from this transformation will contain three columns: Time, Metric, and Value. The Metric column is added so you easily can see from which query the metric originates from. Customize this value by defining Label on the source query. @@ -280,7 +281,6 @@ Here is the result after applying the Series to rows transformation. ## Filter by value - This transformation allows you to filter your data directly in Grafana and remove some data points from your query result. You have the option to include or exclude data that match one or more conditions you define. The conditions are applied on a selected field. The available conditions are: @@ -300,15 +300,15 @@ The available conditions are: Consider the following data set: -| Time | Temperature | Altitude -|---------------------|-------------|---------- -| 2020-07-07 11:34:23 | 32 | 101 -| 2020-07-07 11:34:22 | 28 | 125 -| 2020-07-07 11:34:21 | 26 | 110 -| 2020-07-07 11:34:20 | 23 | 98 -| 2020-07-07 10:32:24 | 31 | 95 -| 2020-07-07 10:31:22 | 20 | 85 -| 2020-07-07 09:30:57 | 19 | 101 +| Time | Temperature | Altitude | +| ------------------- | ----------- | -------- | +| 2020-07-07 11:34:23 | 32 | 101 | +| 2020-07-07 11:34:22 | 28 | 125 | +| 2020-07-07 11:34:21 | 26 | 110 | +| 2020-07-07 11:34:20 | 23 | 98 | +| 2020-07-07 10:32:24 | 31 | 95 | +| 2020-07-07 10:31:22 | 20 | 85 | +| 2020-07-07 09:30:57 | 19 | 101 | If you **Include** the data points that have a temperature below 30°C, the configuration will look as follows: @@ -317,14 +317,13 @@ If you **Include** the data points that have a temperature below 30°C, the conf And you will get the following result, where only the temperatures below 30°C are included: - -| Time | Temperature | Altitude -|---------------------|-------------|---------- -| 2020-07-07 11:34:22 | 28 | 125 -| 2020-07-07 11:34:21 | 26 | 110 -| 2020-07-07 11:34:20 | 23 | 98 -| 2020-07-07 10:31:22 | 20 | 85 -| 2020-07-07 09:30:57 | 19 | 101 +| Time | Temperature | Altitude | +| ------------------- | ----------- | -------- | +| 2020-07-07 11:34:22 | 28 | 125 | +| 2020-07-07 11:34:21 | 26 | 110 | +| 2020-07-07 11:34:20 | 23 | 98 | +| 2020-07-07 10:31:22 | 20 | 85 | +| 2020-07-07 09:30:57 | 19 | 101 | You can add more than one condition to the filter. For example, you might want to include the data only if the altitude is greater than 100. To do so, add that condition to the following configuration: From 73518bf1e74d1017e2fd8bc8e478d1253ed751aa Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 2 Dec 2020 17:03:37 +0800 Subject: [PATCH 038/124] Chore: Enable remaining eslint-plugin-react rules (#29519) * Chore: Enable eslint react/no-render-return-value rule Eanble the rule and remove the unused render return part of: #29201 * Chore: Enable eslint react/no-children-prop rule Not linting issues after turning on this. No other file changes requried part of: #29201 * Chore: Enable eslint react/no-unknown-property rule Correct enable-background to enableBackground part of: #29201 * Chore: Enable eslint react/no-unescaped-entities rule Replaced " with " replaced ' with ' part of: #29201 --- .eslintrc | 4 ---- packages/grafana-ui/src/components/Icon/assets/Import.tsx | 2 +- .../TimePicker/TimeRangePicker/TimePickerContent.tsx | 4 ++-- public/app/core/components/PluginHelp/PluginHelp.tsx | 2 +- public/app/core/services/util_srv.ts | 2 +- public/app/features/admin/AdminOrgsTable.tsx | 2 +- public/app/features/api-keys/ApiKeysAddedModal.tsx | 2 +- .../SaveDashboard/forms/SaveProvisionedDashboardForm.tsx | 4 ++-- .../dashboard/components/ShareModal/ShareSnapshot.tsx | 2 +- .../TransformationsEditor/TransformationsEditor.tsx | 4 ++-- public/app/features/variables/pickers/PickerRenderer.tsx | 2 +- .../datasource/influxdb/components/ConfigEditor.tsx | 3 ++- .../components/__snapshots__/ConfigEditor.test.tsx.snap | 8 ++++---- .../datasource/prometheus/components/PromQueryEditor.tsx | 2 +- public/app/plugins/datasource/testdata/QueryEditor.tsx | 2 +- 15 files changed, 21 insertions(+), 24 deletions(-) diff --git a/.eslintrc b/.eslintrc index c88e985c2d3..5d7f9ea5c0d 100644 --- a/.eslintrc +++ b/.eslintrc @@ -15,10 +15,6 @@ "react-hooks/rules-of-hooks": "off", "react-hooks/exhaustive-deps": "off", "react/prop-types": "off", - "react/no-unescaped-entities": "off", - "react/no-unknown-property": "off", - "react/no-children-prop": "off", - "react/no-render-return-value": "off", "no-only-tests/no-only-tests": "error" } } diff --git a/packages/grafana-ui/src/components/Icon/assets/Import.tsx b/packages/grafana-ui/src/components/Icon/assets/Import.tsx index 4659a307c0a..2eb978da784 100644 --- a/packages/grafana-ui/src/components/Icon/assets/Import.tsx +++ b/packages/grafana-ui/src/components/Icon/assets/Import.tsx @@ -11,7 +11,7 @@ export const Import: FunctionComponent = ({ size, ...rest }) => { height={size} {...rest} > - + {
- It looks like you haven't used this timer picker before. As soon as you enter some time intervals, recently - used intervals will appear here. + It looks like you haven't used this timer picker before. As soon as you enter some time intervals, + recently used intervals will appear here.
diff --git a/public/app/core/components/PluginHelp/PluginHelp.tsx b/public/app/core/components/PluginHelp/PluginHelp.tsx index 67364ea9366..cf55ac09ee3 100644 --- a/public/app/core/components/PluginHelp/PluginHelp.tsx +++ b/public/app/core/components/PluginHelp/PluginHelp.tsx @@ -71,7 +71,7 @@ export class PluginHelp extends PureComponent { } if (isError) { - return

'Error occurred when loading help'

; + return

'Error occurred when loading help'

; } if (type === 'panel_help' && help === '') { diff --git a/public/app/core/services/util_srv.ts b/public/app/core/services/util_srv.ts index 345410bed97..71e8c46e224 100644 --- a/public/app/core/services/util_srv.ts +++ b/public/app/core/services/util_srv.ts @@ -39,7 +39,7 @@ export class UtilSrv { const elem = React.createElement(provideTheme(AngularModalProxy), modalProps); this.reactModalRoot.appendChild(this.reactModalNode); - return ReactDOM.render(elem, this.reactModalNode); + ReactDOM.render(elem, this.reactModalNode); } onReactModalDismiss = () => { diff --git a/public/app/features/admin/AdminOrgsTable.tsx b/public/app/features/admin/AdminOrgsTable.tsx index e581478a5db..77e8d3fe986 100644 --- a/public/app/features/admin/AdminOrgsTable.tsx +++ b/public/app/features/admin/AdminOrgsTable.tsx @@ -40,7 +40,7 @@ export const AdminOrgsTable: FC = ({ orgs, onDelete }) => { title="Delete" body={
- Are you sure you want to delete '{deleteOrg.name}'? + Are you sure you want to delete '{deleteOrg.name}'?
All dashboards for this organization will be removed!
} diff --git a/public/app/features/api-keys/ApiKeysAddedModal.tsx b/public/app/features/api-keys/ApiKeysAddedModal.tsx index 3503555dc6a..0cb207311cb 100644 --- a/public/app/features/api-keys/ApiKeysAddedModal.tsx +++ b/public/app/features/api-keys/ApiKeysAddedModal.tsx @@ -36,7 +36,7 @@ export const ApiKeysAddedModal = (props: Props) => {

-            curl -H "Authorization: Bearer {props.apiKey}" {props.rootPath}/api/dashboards/home
+            curl -H "Authorization: Bearer {props.apiKey}" {props.rootPath}/api/dashboards/home
           
diff --git a/public/app/features/dashboard/components/SaveDashboard/forms/SaveProvisionedDashboardForm.tsx b/public/app/features/dashboard/components/SaveDashboard/forms/SaveProvisionedDashboardForm.tsx index 24089f3fd9a..d9eee7a6280 100644 --- a/public/app/features/dashboard/components/SaveDashboard/forms/SaveProvisionedDashboardForm.tsx +++ b/public/app/features/dashboard/components/SaveDashboard/forms/SaveProvisionedDashboardForm.tsx @@ -31,8 +31,8 @@ export const SaveProvisionedDashboardForm: React.FC = ({ <> - This dashboard cannot be saved from Grafana's UI since it has been provisioned from another source. Copy the - JSON or save it to a file below. Then you can update your dashboard in corresponding provisioning source. + This dashboard cannot be saved from Grafana's UI since it has been provisioned from another source. Copy + the JSON or save it to a file below. Then you can update your dashboard in corresponding provisioning source.
See{' '} diff --git a/public/app/features/dashboard/components/ShareModal/ShareSnapshot.tsx b/public/app/features/dashboard/components/ShareModal/ShareSnapshot.tsx index b2203fa012e..344a7ac5de5 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareSnapshot.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareSnapshot.tsx @@ -239,7 +239,7 @@ export class ShareSnapshot extends PureComponent {

- You may need to configure the timeout value if it takes a long time to collect your dashboard's metrics. + You may need to configure the timeout value if it takes a long time to collect your dashboard's metrics.

diff --git a/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx b/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx index a50aa36e8c4..42fecaee995 100644 --- a/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx +++ b/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx @@ -215,8 +215,8 @@ export class TransformationsEditor extends React.PureComponent Transformations allow you to join, calculate, re-order, hide and rename your query results before being visualized.
- Many transforms are not suitable if you're using the Graph visualization as it currently only supports - time series.
+ Many transforms are not suitable if you're using the Graph visualization as it currently only + supports time series.
It can help to switch to Table visualization to understand what a transformation is doing.

diff --git a/public/app/features/variables/pickers/PickerRenderer.tsx b/public/app/features/variables/pickers/PickerRenderer.tsx index ed5f42ce946..75eca6396d9 100644 --- a/public/app/features/variables/pickers/PickerRenderer.tsx +++ b/public/app/features/variables/pickers/PickerRenderer.tsx @@ -12,7 +12,7 @@ export const PickerRenderer: FunctionComponent = props => { const PickerToRender = useMemo(() => variableAdapters.get(props.variable.type).picker, [props.variable]); if (!props.variable) { - return
Couldn't load variable
; + return
Couldn't load variable
; } return ( diff --git a/public/app/plugins/datasource/influxdb/components/ConfigEditor.tsx b/public/app/plugins/datasource/influxdb/components/ConfigEditor.tsx index 55dd957a153..c050b2ff8a5 100644 --- a/public/app/plugins/datasource/influxdb/components/ConfigEditor.tsx +++ b/public/app/plugins/datasource/influxdb/components/ConfigEditor.tsx @@ -256,7 +256,8 @@ export class ConfigEditor extends PureComponent {

Setting the database for this datasource does not deny access to other databases. The InfluxDB query syntax allows switching the database in the query. For example: - SHOW MEASUREMENTS ON _internal or SELECT * FROM "_internal".."database" LIMIT 10 + SHOW MEASUREMENTS ON _internal or + SELECT * FROM "_internal".."database" LIMIT 10

To support data isolation and security, make sure appropriate permissions are configured in InfluxDB. diff --git a/public/app/plugins/datasource/influxdb/components/__snapshots__/ConfigEditor.test.tsx.snap b/public/app/plugins/datasource/influxdb/components/__snapshots__/ConfigEditor.test.tsx.snap index 4d560e5aa12..fd4dce305c6 100644 --- a/public/app/plugins/datasource/influxdb/components/__snapshots__/ConfigEditor.test.tsx.snap +++ b/public/app/plugins/datasource/influxdb/components/__snapshots__/ConfigEditor.test.tsx.snap @@ -245,7 +245,7 @@ exports[`Render should disable basic auth password input 1`] = ` SHOW MEASUREMENTS ON _internal - or + or SELECT * FROM "_internal".."database" LIMIT 10 @@ -533,7 +533,7 @@ exports[`Render should hide basic auth fields when switch off 1`] = ` SHOW MEASUREMENTS ON _internal - or + or SELECT * FROM "_internal".."database" LIMIT 10 @@ -821,7 +821,7 @@ exports[`Render should hide white listed cookies input when browser access chose SHOW MEASUREMENTS ON _internal - or + or SELECT * FROM "_internal".."database" LIMIT 10 @@ -1109,7 +1109,7 @@ exports[`Render should render component 1`] = ` SHOW MEASUREMENTS ON _internal - or + or SELECT * FROM "_internal".."database" LIMIT 10 diff --git a/public/app/plugins/datasource/prometheus/components/PromQueryEditor.tsx b/public/app/plugins/datasource/prometheus/components/PromQueryEditor.tsx index fbbb59018fb..fb6ba792d82 100644 --- a/public/app/plugins/datasource/prometheus/components/PromQueryEditor.tsx +++ b/public/app/plugins/datasource/prometheus/components/PromQueryEditor.tsx @@ -137,7 +137,7 @@ export class PromQueryEditor extends PureComponent { <> An additional lower limit for the step parameter of the Prometheus query and for the{' '} $__interval and $__rate_interval variables. The limit is absolute and not - modified by the "Resolution" setting. + modified by the "Resolution" setting. } > diff --git a/public/app/plugins/datasource/testdata/QueryEditor.tsx b/public/app/plugins/datasource/testdata/QueryEditor.tsx index e589cdcc812..f3fd3f889ec 100644 --- a/public/app/plugins/datasource/testdata/QueryEditor.tsx +++ b/public/app/plugins/datasource/testdata/QueryEditor.tsx @@ -168,7 +168,7 @@ export const QueryEditor = ({ query, datasource, onChange, onRunQuery }: Props)
{`{ key = "value", key2 = "value" }`}
- key="value", key2="value" + key="value", key2="value"
key=value, key2=value
From 6c4f6211cbc2b03b28e4efb70acb8755384e1309 Mon Sep 17 00:00:00 2001 From: Sofia Papagiannaki Date: Wed, 2 Dec 2020 11:06:53 +0200 Subject: [PATCH 039/124] Devenv: update mysql_tests and postgres_tests blocks for allowing dynamically change of underlying docker image (#29525) * Devenv: allow dynamically change mysql_tests docker image * Devenv: allow dynamically change postgres_tests docker image --- devenv/docker/blocks/mysql_tests/.env | 1 + devenv/docker/blocks/mysql_tests/Dockerfile | 3 ++- devenv/docker/blocks/mysql_tests/docker-compose.yaml | 2 ++ devenv/docker/blocks/postgres_tests/.env | 1 + devenv/docker/blocks/postgres_tests/Dockerfile | 3 ++- devenv/docker/blocks/postgres_tests/docker-compose.yaml | 2 ++ 6 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 devenv/docker/blocks/mysql_tests/.env create mode 100644 devenv/docker/blocks/postgres_tests/.env diff --git a/devenv/docker/blocks/mysql_tests/.env b/devenv/docker/blocks/mysql_tests/.env new file mode 100644 index 00000000000..29440f18d52 --- /dev/null +++ b/devenv/docker/blocks/mysql_tests/.env @@ -0,0 +1 @@ +mysql_version=5.6 diff --git a/devenv/docker/blocks/mysql_tests/Dockerfile b/devenv/docker/blocks/mysql_tests/Dockerfile index 89e16bc2ed6..63bc031a682 100644 --- a/devenv/docker/blocks/mysql_tests/Dockerfile +++ b/devenv/docker/blocks/mysql_tests/Dockerfile @@ -1,3 +1,4 @@ -FROM mysql:5.6 +ARG mysql_version=5.6 +FROM mysql:${mysql_version} ADD setup.sql /docker-entrypoint-initdb.d CMD ["mysqld"] diff --git a/devenv/docker/blocks/mysql_tests/docker-compose.yaml b/devenv/docker/blocks/mysql_tests/docker-compose.yaml index a7509d47880..485657cf152 100644 --- a/devenv/docker/blocks/mysql_tests/docker-compose.yaml +++ b/devenv/docker/blocks/mysql_tests/docker-compose.yaml @@ -1,6 +1,8 @@ mysqltests: build: context: docker/blocks/mysql_tests + args: + - mysql_version=${mysql_version} environment: MYSQL_ROOT_PASSWORD: rootpass MYSQL_DATABASE: grafana_tests diff --git a/devenv/docker/blocks/postgres_tests/.env b/devenv/docker/blocks/postgres_tests/.env new file mode 100644 index 00000000000..2494e5f2ee9 --- /dev/null +++ b/devenv/docker/blocks/postgres_tests/.env @@ -0,0 +1 @@ +postgres_version=9.3 diff --git a/devenv/docker/blocks/postgres_tests/Dockerfile b/devenv/docker/blocks/postgres_tests/Dockerfile index df188e1094d..f8deb7d6815 100644 --- a/devenv/docker/blocks/postgres_tests/Dockerfile +++ b/devenv/docker/blocks/postgres_tests/Dockerfile @@ -1,3 +1,4 @@ -FROM postgres:9.3 +ARG postgres_version=9.3 +FROM postgres:${postgres_version} ADD setup.sql /docker-entrypoint-initdb.d CMD ["postgres"] diff --git a/devenv/docker/blocks/postgres_tests/docker-compose.yaml b/devenv/docker/blocks/postgres_tests/docker-compose.yaml index 7e6da7d8517..fbd68ec0749 100644 --- a/devenv/docker/blocks/postgres_tests/docker-compose.yaml +++ b/devenv/docker/blocks/postgres_tests/docker-compose.yaml @@ -1,6 +1,8 @@ postgrestest: build: context: docker/blocks/postgres_tests + args: + - postgres_version=${postgres_version} environment: POSTGRES_USER: grafanatest POSTGRES_PASSWORD: grafanatest From 63c8945db5b02f6fb497aaa2fbc158b44b5ece0b Mon Sep 17 00:00:00 2001 From: Sebastian Poxhofer Date: Wed, 2 Dec 2020 10:09:33 +0100 Subject: [PATCH 040/124] AzureMonitor: Unit MilliSeconds naming (#29399) --- pkg/tsdb/azuremonitor/azuremonitor-datasource.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/tsdb/azuremonitor/azuremonitor-datasource.go b/pkg/tsdb/azuremonitor/azuremonitor-datasource.go index 6980ed3690a..9b0ef6ba8bc 100644 --- a/pkg/tsdb/azuremonitor/azuremonitor-datasource.go +++ b/pkg/tsdb/azuremonitor/azuremonitor-datasource.go @@ -421,7 +421,7 @@ func toGrafanaUnit(unit string) string { return "cps" case "Percent": return "percent" - case "Milliseconds": + case "MilliSeconds": return "ms" case "Seconds": return "s" From 863dbc7ba87d112bd699e78a601cbae8e5d3d394 Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Wed, 2 Dec 2020 11:04:42 +0100 Subject: [PATCH 041/124] PanelEdit: making sure the correct datasource query editor is being rendered. (#29500) * making sure we try to use the selected db in the query row. * fixing so we use || instead of ??. --- public/app/features/query/components/QueryEditorRow.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/public/app/features/query/components/QueryEditorRow.tsx b/public/app/features/query/components/QueryEditorRow.tsx index 9465fd5b4e4..37ce76f2dd4 100644 --- a/public/app/features/query/components/QueryEditorRow.tsx +++ b/public/app/features/query/components/QueryEditorRow.tsx @@ -88,12 +88,13 @@ export class QueryEditorRow extends PureComponent { } async loadDatasource() { - const { query, panel } = this.props; + const { query, panel, dataSourceValue } = this.props; const dataSourceSrv = getDatasourceSrv(); let datasource; try { - datasource = await dataSourceSrv.get(query.datasource || panel.datasource); + const datasourceName = dataSourceValue || query.datasource || panel.datasource; + datasource = await dataSourceSrv.get(datasourceName); } catch (error) { datasource = await dataSourceSrv.get(); } From d33c4e5e7c24e63fce7fc6c284ea15e5fbfaff72 Mon Sep 17 00:00:00 2001 From: Tomo Wang Date: Wed, 2 Dec 2020 18:09:49 +0800 Subject: [PATCH 042/124] Alarting: fix alarm messages in dingding (Fixes #29470) (#29482) --- pkg/services/alerting/notifiers/dingding.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/services/alerting/notifiers/dingding.go b/pkg/services/alerting/notifiers/dingding.go index 0070cfdacd7..cad82229253 100644 --- a/pkg/services/alerting/notifiers/dingding.go +++ b/pkg/services/alerting/notifiers/dingding.go @@ -119,14 +119,14 @@ func (dd *DingDingNotifier) genBody(evalContext *alerting.EvalContext, messageUR } for i, match := range evalContext.EvalMatches { - message += fmt.Sprintf("\\n%2d. %s: %s", i+1, match.Metric, match.Value) + message += fmt.Sprintf("\n%2d. %s: %s", i+1, match.Metric, match.Value) } var bodyMsg map[string]interface{} if dd.MsgType == "actionCard" { // Embed the pic into the markdown directly because actionCard doesn't have a picUrl field if dd.NeedsImage() && picURL != "" { - message = "![](" + picURL + ")\\n\\n" + message + message = "![](" + picURL + ")\n\n" + message } bodyMsg = map[string]interface{}{ From 1ac02390e24e51b85eab485ec1db1224e2c004b0 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Wed, 2 Dec 2020 12:22:49 +0100 Subject: [PATCH 043/124] GraphNG: simplify effects responsible for plot updates/initialization (#29496) * Add test coverage for uPlot initialization and data updates * fix ts * WIP * I see some light in the tunel * Tests update * Fix rendering before hooks are evaluated --- packages/grafana-ui/package.json | 2 + .../src/components/GraphNG/GraphNG.test.tsx | 167 ------------- .../src/components/GraphNG/GraphNG.tsx | 35 +-- .../src/components/uPlot/Plot.test.tsx | 221 ++++++++++++++++++ .../grafana-ui/src/components/uPlot/Plot.tsx | 117 +++++----- .../grafana-ui/src/components/uPlot/hooks.ts | 23 +- yarn.lock | 17 ++ 7 files changed, 336 insertions(+), 246 deletions(-) delete mode 100644 packages/grafana-ui/src/components/GraphNG/GraphNG.test.tsx create mode 100644 packages/grafana-ui/src/components/uPlot/Plot.test.tsx diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 1653dd92b0f..5f9febd2444 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -89,6 +89,7 @@ "@types/jest": "26.0.15", "@types/jquery": "3.3.38", "@types/lodash": "4.14.123", + "@types/mock-raf": "1.0.2", "@types/node": "10.14.1", "@types/papaparse": "5.2.0", "@types/react": "16.9.9", @@ -98,6 +99,7 @@ "@types/rollup-plugin-visualizer": "2.6.0", "@types/tinycolor2": "1.4.1", "common-tags": "^1.8.0", + "mock-raf": "1.0.1", "pretty-format": "25.1.0", "react-docgen-typescript-loader": "3.7.2", "react-test-renderer": "16.13.1", diff --git a/packages/grafana-ui/src/components/GraphNG/GraphNG.test.tsx b/packages/grafana-ui/src/components/GraphNG/GraphNG.test.tsx deleted file mode 100644 index a53d5b02a16..00000000000 --- a/packages/grafana-ui/src/components/GraphNG/GraphNG.test.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import React from 'react'; -import { GraphNG } from './GraphNG'; -import { render } from '@testing-library/react'; -import { ArrayVector, dateTime, FieldConfig, FieldType, MutableDataFrame } from '@grafana/data'; -import { GraphFieldConfig, GraphMode } from '../uPlot/config'; - -const mockData = () => { - const data = new MutableDataFrame(); - - data.addField({ - type: FieldType.time, - name: 'Time', - values: new ArrayVector([1602630000000, 1602633600000, 1602637200000]), - config: {}, - }); - - data.addField({ - type: FieldType.number, - name: 'Value', - values: new ArrayVector([10, 20, 5]), - config: { - custom: { - mode: GraphMode.Line, - }, - } as FieldConfig, - }); - - const timeRange = { - from: dateTime(1602673200000), - to: dateTime(1602680400000), - raw: { from: '1602673200000', to: '1602680400000' }, - }; - return { data, timeRange }; -}; - -// const defaultLegendOptions: LegendOptions = { -// displayMode: LegendDisplayMode.List, -// placement: 'bottom', -// }; - -describe('GraphNG', () => { - // describe('data update', () => { - // it('does not re-initialise uPlot when there are no field config changes', () => { - // const { data, timeRange } = mockData(); - // const onDataUpdateSpy = jest.fn(); - // const onPlotInitSpy = jest.fn(); - // - // const { rerender } = render( - // - // ); - // - // data.fields[1].values.set(0, 1); - // - // rerender( - // - // ); - // - // expect(onPlotInitSpy).toBeCalledTimes(1); - // expect(onDataUpdateSpy).toHaveBeenLastCalledWith([ - // [1602630000, 1602633600, 1602637200], - // [1, 20, 5], - // ]); - // }); - // }); - - describe('config update', () => { - it('should skip plot intialization for width and height equal 0', () => { - const { data, timeRange } = mockData(); - - const { queryAllByTestId } = render( - - ); - - expect(queryAllByTestId('uplot-main-div')).toHaveLength(1); - }); - - // it('reinitializes plot when number of series change', () => { - // const { data, timeRange } = mockData(); - // const onPlotInitSpy = jest.fn(); - // - // const { rerender } = render( - // - // ); - // - // data.addField({ - // name: 'Value1', - // type: FieldType.number, - // values: new ArrayVector([1, 2, 3]), - // config: { - // custom: { - // line: { show: true }, - // }, - // } as FieldConfig, - // }); - // - // rerender( - // - // ); - // - // expect(onPlotInitSpy).toBeCalledTimes(2); - // }); - // - // it('reinitializes plot when series field config changes', () => { - // const { data, timeRange } = mockData(); - // const onPlotInitSpy = jest.fn(); - // - // const { rerender } = render( - // - // ); - // expect(onPlotInitSpy).toBeCalledTimes(1); - // - // data.fields[1].config.custom.line.width = 5; - // - // rerender( - // - // ); - // - // expect(onPlotInitSpy).toBeCalledTimes(2); - // }); - }); -}); diff --git a/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx b/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx index 4d4268d688f..8ae6f641c86 100755 --- a/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx +++ b/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx @@ -51,28 +51,27 @@ export const GraphNG: React.FC = ({ ...plotProps }) => { const alignedFrameWithGapTest = useMemo(() => alignDataFrames(data, fields), [data, fields]); - - if (alignedFrameWithGapTest == null) { - return ( -

-

No data found in response

-
- ); - } - const theme = useTheme(); const legendItemsRef = useRef([]); const hasLegend = useRef(legend && legend.displayMode !== LegendDisplayMode.Hidden); - const alignedFrame = alignedFrameWithGapTest.frame; - const compareFrames = useCallback( - (a: DataFrame, b: DataFrame) => compareDataFrameStructures(a, b, ['min', 'max']), - [] - ); + const alignedFrame = alignedFrameWithGapTest?.frame; + + const compareFrames = useCallback((a?: DataFrame | null, b?: DataFrame | null) => { + if (a && b) { + return compareDataFrameStructures(a, b, ['min', 'max']); + } + return false; + }, []); + const configRev = useRevision(alignedFrame, compareFrames); const configBuilder = useMemo(() => { const builder = new UPlotConfigBuilder(); + if (!alignedFrame) { + return builder; + } + // X is the first field in the alligned frame const xField = alignedFrame.fields[0]; if (xField.type === FieldType.time) { @@ -163,6 +162,14 @@ export const GraphNG: React.FC = ({ return builder; }, [configRev]); + if (alignedFrameWithGapTest == null) { + return ( +
+

No data found in response

+
+ ); + } + let legendElement: React.ReactElement | undefined; if (hasLegend && legendItemsRef.current.length > 0) { diff --git a/packages/grafana-ui/src/components/uPlot/Plot.test.tsx b/packages/grafana-ui/src/components/uPlot/Plot.test.tsx new file mode 100644 index 00000000000..a514f310b15 --- /dev/null +++ b/packages/grafana-ui/src/components/uPlot/Plot.test.tsx @@ -0,0 +1,221 @@ +import React from 'react'; +import { UPlotChart } from './Plot'; +import { act, render } from '@testing-library/react'; +import { ArrayVector, dateTime, FieldConfig, FieldType, MutableDataFrame } from '@grafana/data'; +import { GraphFieldConfig, GraphMode } from '../uPlot/config'; +import uPlot from 'uplot'; +import createMockRaf from 'mock-raf'; +import { UPlotConfigBuilder } from './config/UPlotConfigBuilder'; + +const mockRaf = createMockRaf(); +const setDataMock = jest.fn(); +const setSizeMock = jest.fn(); +const initializeMock = jest.fn(); +const destroyMock = jest.fn(); + +jest.mock('uplot', () => { + return jest.fn().mockImplementation(() => { + return { + setData: setDataMock, + setSize: setSizeMock, + initialize: initializeMock, + destroy: destroyMock, + }; + }); +}); + +const mockData = () => { + const data = new MutableDataFrame(); + + data.addField({ + type: FieldType.time, + name: 'Time', + values: new ArrayVector([1602630000000, 1602633600000, 1602637200000]), + config: {}, + }); + + data.addField({ + type: FieldType.number, + name: 'Value', + values: new ArrayVector([10, 20, 5]), + config: { + custom: { + mode: GraphMode.Line, + }, + } as FieldConfig, + }); + + const timeRange = { + from: dateTime(1602673200000), + to: dateTime(1602680400000), + raw: { from: '1602673200000', to: '1602680400000' }, + }; + + return { data, timeRange, config: new UPlotConfigBuilder() }; +}; + +describe('UPlotChart', () => { + beforeEach(() => { + // @ts-ignore + uPlot.mockClear(); + setDataMock.mockClear(); + setSizeMock.mockClear(); + initializeMock.mockClear(); + destroyMock.mockClear(); + + jest.spyOn(window, 'requestAnimationFrame').mockImplementation(mockRaf.raf); + }); + + it('destroys uPlot instance when component unmounts', () => { + const { data, timeRange, config } = mockData(); + const uPlotData = { frame: data, isGap: () => false }; + + const { unmount } = render( + + ); + + // we wait 1 frame for plugins initialisation logic to finish + act(() => { + mockRaf.step({ count: 1 }); + }); + + expect(uPlot).toBeCalledTimes(1); + unmount(); + expect(destroyMock).toBeCalledTimes(1); + }); + + describe('data update', () => { + it('skips uPlot reinitialization when there are no field config changes', () => { + const { data, timeRange, config } = mockData(); + const uPlotData = { frame: data, isGap: () => false }; + + const { rerender } = render( + + ); + + // we wait 1 frame for plugins initialisation logic to finish + act(() => { + mockRaf.step({ count: 1 }); + }); + + expect(uPlot).toBeCalledTimes(1); + + data.fields[1].values.set(0, 1); + uPlotData.frame = data; + + rerender( + + ); + + expect(setDataMock).toBeCalledTimes(1); + }); + }); + + describe('config update', () => { + it('skips uPlot intialization for width and height equal 0', async () => { + const { data, timeRange, config } = mockData(); + const uPlotData = { frame: data, isGap: () => false }; + + const { queryAllByTestId } = render( + + ); + + expect(queryAllByTestId('uplot-main-div')).toHaveLength(1); + expect(uPlot).not.toBeCalled(); + }); + + it('reinitializes uPlot when config changes', () => { + const { data, timeRange, config } = mockData(); + const uPlotData = { frame: data, isGap: () => false }; + + const { rerender } = render( + + ); + + // we wait 1 frame for plugins initialisation logic to finish + act(() => { + mockRaf.step({ count: 1 }); + }); + + expect(uPlot).toBeCalledTimes(1); + + rerender( + + ); + + expect(destroyMock).toBeCalledTimes(1); + expect(uPlot).toBeCalledTimes(2); + }); + + it('skips uPlot reinitialization when only dimensions change', () => { + const { data, timeRange, config } = mockData(); + const uPlotData = { frame: data, isGap: () => false }; + + const { rerender } = render( + + ); + + // we wait 1 frame for plugins initialisation logic to finish + act(() => { + mockRaf.step({ count: 1 }); + }); + + rerender( + + ); + + expect(destroyMock).toBeCalledTimes(0); + expect(uPlot).toBeCalledTimes(1); + expect(setSizeMock).toBeCalledTimes(1); + }); + }); +}); diff --git a/packages/grafana-ui/src/components/uPlot/Plot.tsx b/packages/grafana-ui/src/components/uPlot/Plot.tsx index 40749114d02..8b202e18bd6 100755 --- a/packages/grafana-ui/src/components/uPlot/Plot.tsx +++ b/packages/grafana-ui/src/components/uPlot/Plot.tsx @@ -1,83 +1,78 @@ -import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; -import uPlot, { AlignedData, AlignedDataWithGapTest } from 'uplot'; +import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef } from 'react'; +import uPlot, { AlignedData, AlignedDataWithGapTest, Options } from 'uplot'; import { buildPlotContext, PlotContext } from './context'; import { pluginLog } from './utils'; import { usePlotConfig } from './hooks'; -import { PlotProps } from './types'; -import { usePrevious } from 'react-use'; +import { AlignedFrameWithGapTest, PlotProps } from './types'; import { DataFrame, FieldType } from '@grafana/data'; import isNumber from 'lodash/isNumber'; import { UPlotConfigBuilder } from './config/UPlotConfigBuilder'; +import usePrevious from 'react-use/lib/usePrevious'; // uPlot abstraction responsible for plot initialisation, setup and refresh // Receives a data frame that is x-axis aligned, as of https://github.com/leeoniya/uPlot/tree/master/docs#data-format // Exposes contexts for plugins registration and uPlot instance access export const UPlotChart: React.FC = props => { const canvasRef = useRef(null); - const [plotInstance, setPlotInstance] = useState(); - const plotData = useRef(); - const previousConfig = usePrevious(props.config); - - // uPlot config API - const { currentConfig, registerPlugin } = usePlotConfig(props.width, props.height, props.timeZone, props.config); - - const initializePlot = useCallback(() => { - if (!currentConfig || !plotData) { - return; - } - if (!canvasRef.current) { - throw new Error('Missing Canvas component as a child of the plot.'); - } - - pluginLog('UPlotChart: init uPlot', false, 'initialized with', plotData.current, currentConfig); - const instance = new uPlot(currentConfig, plotData.current, canvasRef.current); - - setPlotInstance(instance); - }, [setPlotInstance, currentConfig]); - + const plotInstance = useRef(); + const prevProps = usePrevious(props); + const { isConfigReady, currentConfig, registerPlugin } = usePlotConfig( + props.width, + props.height, + props.timeZone, + props.config + ); const getPlotInstance = useCallback(() => { - if (!plotInstance) { + if (!plotInstance.current) { throw new Error("Plot hasn't initialised yet"); } - return plotInstance; - }, [plotInstance]); + return plotInstance.current; + }, []); + // Effect responsible for uPlot updates/initialization logic. It's performed whenever component's props have changed useLayoutEffect(() => { - plotData.current = { - data: props.data.frame.fields.map(f => f.values.toArray()) as AlignedData, - isGap: props.data.isGap, - }; - - if (plotInstance && previousConfig === props.config) { - updateData(props.data.frame, props.config, plotInstance, plotData.current.data); + // 0. Exit early if the component is not ready to initialize uPlot + if (!currentConfig.current || !canvasRef.current || props.width === 0 || props.height === 0) { + return; } - }, [props.data, props.config]); - useLayoutEffect(() => { - initializePlot(); - }, [currentConfig]); + // 1. When config is ready and there is no uPlot instance, create new uPlot and return + if (isConfigReady && !plotInstance.current) { + plotInstance.current = initializePlot(prepareData(props.data), currentConfig.current, canvasRef.current); + return; + } - useEffect(() => { - const currentInstance = plotInstance; - return () => { - currentInstance?.destroy(); - }; - }, [plotInstance]); - - // When size props changed update plot size synchronously - useLayoutEffect(() => { - if (plotInstance) { - plotInstance.setSize({ - width: props.width, - height: props.height, + // 2. When dimensions have changed, update uPlot size and return + if (currentConfig.current.width !== prevProps?.width || currentConfig.current.height !== prevProps?.height) { + pluginLog('uPlot core', false, 'updating size'); + plotInstance.current!.setSize({ + width: currentConfig.current.width, + height: currentConfig.current?.height, }); + return; } - }, [plotInstance, props.width, props.height]); + + // 3. When config or timezone has changed, re-initialize plot + if (isConfigReady && (props.config !== prevProps.config || props.timeZone !== prevProps.timeZone)) { + if (plotInstance.current) { + pluginLog('uPlot core', false, 'destroying instance'); + plotInstance.current.destroy(); + } + plotInstance.current = initializePlot(prepareData(props.data), currentConfig.current, canvasRef.current); + return; + } + + // 4. Otherwise, assume only data has changed and update uPlot data + updateData(props.data.frame, props.config, plotInstance.current, prepareData(props.data).data); + }, [props, isConfigReady]); + + // When component unmounts, clean the existing uPlot instance + useEffect(() => () => plotInstance.current?.destroy(), []); // Memoize plot context const plotCtx = useMemo(() => { - return buildPlotContext(Boolean(plotInstance), canvasRef, props.data, registerPlugin, getPlotInstance); + return buildPlotContext(Boolean(plotInstance.current), canvasRef, props.data, registerPlugin, getPlotInstance); }, [plotInstance, canvasRef, props.data, registerPlugin, getPlotInstance]); return ( @@ -88,14 +83,24 @@ export const UPlotChart: React.FC = props => { ); }; -// Callback executed when there was no change in plot config +function prepareData(data: AlignedFrameWithGapTest) { + return { + data: data.frame.fields.map(f => f.values.toArray()) as AlignedData, + isGap: data.isGap, + }; +} + +function initializePlot(data: AlignedDataWithGapTest, config: Options, el: HTMLDivElement) { + pluginLog('UPlotChart: init uPlot', false, 'initialized with', data, config); + return new uPlot(config, data, el); +} + function updateData(frame: DataFrame, config: UPlotConfigBuilder, plotInstance?: uPlot, data?: AlignedData | null) { if (!plotInstance || !data) { return; } pluginLog('uPlot core', false, 'updating plot data(throttled log!)', data); updateScales(frame, config, plotInstance); - // If config hasn't changed just update uPlot's data plotInstance.setData(data); } diff --git a/packages/grafana-ui/src/components/uPlot/hooks.ts b/packages/grafana-ui/src/components/uPlot/hooks.ts index 5a46c8f7e70..1cb0e07dd1d 100644 --- a/packages/grafana-ui/src/components/uPlot/hooks.ts +++ b/packages/grafana-ui/src/components/uPlot/hooks.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import { PlotPlugin } from './types'; import { pluginLog } from './utils'; import uPlot, { Options } from 'uplot'; @@ -108,8 +108,9 @@ export const DEFAULT_PLOT_CONFIG = { //pass plain confsig object,memoize! export const usePlotConfig = (width: number, height: number, timeZone: TimeZone, configBuilder: UPlotConfigBuilder) => { const { arePluginsReady, plugins, registerPlugin } = usePlotPlugins(); - const [currentConfig, setCurrentConfig] = useState(); + const [isConfigReady, setIsConfigReady] = useState(false); + const currentConfig = useRef(); const tzDate = useMemo(() => { let fmt = undefined; @@ -122,11 +123,11 @@ export const usePlotConfig = (width: number, height: number, timeZone: TimeZone, return fmt; }, [timeZone]); - useEffect(() => { + useLayoutEffect(() => { if (!arePluginsReady) { return; } - setCurrentConfig({ + currentConfig.current = { ...DEFAULT_PLOT_CONFIG, width, height, @@ -136,10 +137,13 @@ export const usePlotConfig = (width: number, height: number, timeZone: TimeZone, })), tzDate, ...configBuilder.getConfig(), - }); + }; + + setIsConfigReady(true); }, [arePluginsReady, plugins, width, height, tzDate, configBuilder]); return { + isConfigReady, registerPlugin, currentConfig, }; @@ -174,13 +178,14 @@ export const useRefreshAfterGraphRendered = (pluginId: string) => { return renderToken; }; -export function useRevision(dep: T, cmp: (prev: T, next: T) => boolean) { +export function useRevision(dep?: T | null, cmp?: (prev?: T | null, next?: T | null) => boolean) { const [rev, setRev] = useState(0); const prevDep = usePrevious(dep); + const comparator = cmp ? cmp : (a?: T | null, b?: T | null) => a === b; - useEffect(() => { - const hasConfigChanged = prevDep ? !cmp(prevDep, dep) : true; - if (hasConfigChanged) { + useLayoutEffect(() => { + const hasChange = !comparator(prevDep, dep); + if (hasChange) { setRev(r => r + 1); } }, [dep]); diff --git a/yarn.lock b/yarn.lock index 3c960c18d6d..451a4d583fd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6648,6 +6648,11 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== +"@types/mock-raf@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@types/mock-raf/-/mock-raf-1.0.2.tgz#de0df16b1cbe2475cb1a4680a19f344f386d8252" + integrity sha1-3g3xaxy+JHXLGkaAoZ80TzhtglI= + "@types/moment-timezone@0.5.13": version "0.5.13" resolved "https://registry.yarnpkg.com/@types/moment-timezone/-/moment-timezone-0.5.13.tgz#0317ccc91eb4c7f4901704166166395c39276528" @@ -18484,6 +18489,13 @@ mocha@7.0.1: yargs-parser "13.1.1" yargs-unparser "1.6.0" +mock-raf@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mock-raf/-/mock-raf-1.0.1.tgz#7567d23fa1220439853317a8ff0eaad1588e9535" + integrity sha512-+25y56bblLzEnv+G4ODsHNck07A5uP5HFfu/1VBKeFrUXoFT9oru+R+jLxLz6rwdM5drUHFdqX9LYBsMP4dz/w== + dependencies: + object-assign "^3.0.0" + modify-values@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" @@ -19138,6 +19150,11 @@ object-assign@4.x, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4. resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= +object-assign@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2" + integrity sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I= + object-copy@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" From 8d5b0084f1a0560d3476c264f75f99ddb24e5059 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Wed, 2 Dec 2020 12:44:51 +0100 Subject: [PATCH 044/124] Middleware: Simplifications (#29491) * Middleware: Simplify Signed-off-by: Arve Knudsen * middleware: Rename auth_proxy directory to authproxy Signed-off-by: Arve Knudsen --- pkg/infra/remotecache/testing.go | 5 ++- pkg/middleware/auth.go | 2 +- pkg/middleware/auth_proxy.go | 4 +-- pkg/middleware/auth_proxy_test.go | 6 ++-- .../{auth_proxy => authproxy}/auth_proxy.go | 16 +++++----- .../auth_proxy_test.go | 18 +++++------ pkg/middleware/cookie.go | 32 ++++++++++++++++--- pkg/middleware/middleware.go | 16 ---------- pkg/middleware/middleware_test.go | 2 +- pkg/registry/registry.go | 15 +++++++-- 10 files changed, 66 insertions(+), 50 deletions(-) rename pkg/middleware/{auth_proxy => authproxy}/auth_proxy.go (95%) rename pkg/middleware/{auth_proxy => authproxy}/auth_proxy_test.go (93%) diff --git a/pkg/infra/remotecache/testing.go b/pkg/infra/remotecache/testing.go index 43cd449cb68..1333ef1ea04 100644 --- a/pkg/infra/remotecache/testing.go +++ b/pkg/infra/remotecache/testing.go @@ -5,6 +5,7 @@ import ( "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" + "github.com/stretchr/testify/require" ) // NewFakeStore creates store for testing @@ -26,9 +27,7 @@ func NewFakeStore(t *testing.T) *RemoteCache { } err := dc.Init() - if err != nil { - t.Fatalf("failed to init remote cache for test. error: %v", err) - } + require.NoError(t, err, "Failed to init remote cache for test") return dc } diff --git a/pkg/middleware/auth.go b/pkg/middleware/auth.go index c44d7dd9a7c..d24871d31ca 100644 --- a/pkg/middleware/auth.go +++ b/pkg/middleware/auth.go @@ -57,7 +57,7 @@ func notAuthorized(c *models.ReqContext) { // remove any forceLogin=true params redirectTo = removeForceLoginParams(redirectTo) - WriteCookie(c.Resp, "redirect_to", url.QueryEscape(redirectTo), 0, newCookieOptions) + WriteCookie(c.Resp, "redirect_to", url.QueryEscape(redirectTo), 0, nil) c.Redirect(setting.AppSubUrl + "/login") } diff --git a/pkg/middleware/auth_proxy.go b/pkg/middleware/auth_proxy.go index d69fd6dd2de..2365df81852 100644 --- a/pkg/middleware/auth_proxy.go +++ b/pkg/middleware/auth_proxy.go @@ -5,7 +5,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/remotecache" - authproxy "github.com/grafana/grafana/pkg/middleware/auth_proxy" + "github.com/grafana/grafana/pkg/middleware/authproxy" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" ) @@ -45,7 +45,7 @@ func initContextWithAuthProxy(store *remotecache.RemoteCache, ctx *models.ReqCon } // Check if allowed to continue with this IP - if result, err := auth.IsAllowedIP(); !result { + if err := auth.IsAllowedIP(); err != nil { logger.Error( "Failed to check whitelisted IP addresses", "message", err.Error(), diff --git a/pkg/middleware/auth_proxy_test.go b/pkg/middleware/auth_proxy_test.go index 1c678efe535..ee022d7f74c 100644 --- a/pkg/middleware/auth_proxy_test.go +++ b/pkg/middleware/auth_proxy_test.go @@ -8,7 +8,7 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/remotecache" - authproxy "github.com/grafana/grafana/pkg/middleware/auth_proxy" + "github.com/grafana/grafana/pkg/middleware/authproxy" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" "github.com/stretchr/testify/require" @@ -29,7 +29,7 @@ func TestInitContextWithAuthProxy_CachedInvalidUserID(t *testing.T) { cmd.Result = &models.User{Id: userID} return nil } - getSignedUserHandler := func(cmd *models.GetSignedInUserQuery) error { + getUserHandler := func(cmd *models.GetSignedInUserQuery) error { // Simulate that the cached user ID is stale if cmd.UserId != userID { return models.ErrUserNotFound @@ -46,7 +46,7 @@ func TestInitContextWithAuthProxy_CachedInvalidUserID(t *testing.T) { origEnabled := setting.AuthProxyEnabled origHeaderProperty := setting.AuthProxyHeaderProperty bus.AddHandler("", upsertHandler) - bus.AddHandler("", getSignedUserHandler) + bus.AddHandler("", getUserHandler) t.Cleanup(func() { setting.AuthProxyHeaderName = origHeaderName setting.AuthProxyEnabled = origEnabled diff --git a/pkg/middleware/auth_proxy/auth_proxy.go b/pkg/middleware/authproxy/auth_proxy.go similarity index 95% rename from pkg/middleware/auth_proxy/auth_proxy.go rename to pkg/middleware/authproxy/auth_proxy.go index 6df587a0579..0e0267721a9 100644 --- a/pkg/middleware/auth_proxy/auth_proxy.go +++ b/pkg/middleware/authproxy/auth_proxy.go @@ -114,11 +114,11 @@ func (auth *AuthProxy) HasHeader() bool { } // IsAllowedIP compares presented IP with the whitelist one -func (auth *AuthProxy) IsAllowedIP() (bool, *Error) { +func (auth *AuthProxy) IsAllowedIP() *Error { ip := auth.ctx.Req.RemoteAddr if len(strings.TrimSpace(auth.whitelistIP)) == 0 { - return true, nil + return nil } proxies := strings.Split(auth.whitelistIP, ",") @@ -126,7 +126,7 @@ func (auth *AuthProxy) IsAllowedIP() (bool, *Error) { for _, proxy := range proxies { result, err := coerceProxyAddress(proxy) if err != nil { - return false, newError("Could not get the network", err) + return newError("could not get the network", err) } proxyObjs = append(proxyObjs, result) @@ -134,13 +134,13 @@ func (auth *AuthProxy) IsAllowedIP() (bool, *Error) { sourceIP, _, err := net.SplitHostPort(ip) if err != nil { - return false, newError("could not parse address", err) + return newError("could not parse address", err) } sourceObj := net.ParseIP(sourceIP) for _, proxyObj := range proxyObjs { if proxyObj.Contains(sourceObj) { - return true, nil + return nil } } @@ -148,7 +148,7 @@ func (auth *AuthProxy) IsAllowedIP() (bool, *Error) { "request for user (%s) from %s is not from the authentication proxy", auth.header, sourceIP, ) - return false, newError("Proxy authentication required", err) + return newError("proxy authentication required", err) } func HashCacheKey(key string) string { @@ -232,7 +232,7 @@ func (auth *AuthProxy) RemoveUserFromCache(logger log.Logger) error { func (auth *AuthProxy) LoginViaLDAP() (int64, *Error) { config, err := getLDAPConfig() if err != nil { - return 0, newError("Failed to get LDAP config", nil) + return 0, newError("failed to get LDAP config", nil) } extUser, _, err := newLDAP(config.Servers).User(auth.header) @@ -273,7 +273,7 @@ func (auth *AuthProxy) LoginViaHeader() (int64, error) { extUser.Email = auth.header extUser.Login = auth.header default: - return 0, newError("Auth proxy header property invalid", nil) + return 0, newError("auth proxy header property invalid", nil) } auth.headersIterator(func(field string, header string) { diff --git a/pkg/middleware/auth_proxy/auth_proxy_test.go b/pkg/middleware/authproxy/auth_proxy_test.go similarity index 93% rename from pkg/middleware/auth_proxy/auth_proxy_test.go rename to pkg/middleware/authproxy/auth_proxy_test.go index e4a66a0d830..a6625407af8 100644 --- a/pkg/middleware/auth_proxy/auth_proxy_test.go +++ b/pkg/middleware/authproxy/auth_proxy_test.go @@ -17,31 +17,31 @@ import ( "gopkg.in/macaron.v1" ) -type TestMultiLDAP struct { +type fakeMultiLDAP struct { multildap.MultiLDAP ID int64 userCalled bool loginCalled bool } -func (stub *TestMultiLDAP) Login(query *models.LoginUserQuery) ( +func (m *fakeMultiLDAP) Login(query *models.LoginUserQuery) ( *models.ExternalUserInfo, error, ) { - stub.loginCalled = true + m.loginCalled = true result := &models.ExternalUserInfo{ - UserId: stub.ID, + UserId: m.ID, } return result, nil } -func (stub *TestMultiLDAP) User(login string) ( +func (m *fakeMultiLDAP) User(login string) ( *models.ExternalUserInfo, ldap.ServerConfig, error, ) { - stub.userCalled = true + m.userCalled = true result := &models.ExternalUserInfo{ - UserId: stub.ID, + UserId: m.ID, } return result, ldap.ServerConfig{}, nil } @@ -126,7 +126,7 @@ func TestMiddlewareContext(t *testing.T) { return true } - stub := &TestMultiLDAP{ + stub := &fakeMultiLDAP{ ID: 42, } @@ -181,7 +181,7 @@ func TestMiddlewareContext(t *testing.T) { auth := prepareMiddleware(t, req, store) - stub := &TestMultiLDAP{ + stub := &fakeMultiLDAP{ ID: 42, } diff --git a/pkg/middleware/cookie.go b/pkg/middleware/cookie.go index c81cb0dfa6c..78939632dbc 100644 --- a/pkg/middleware/cookie.go +++ b/pkg/middleware/cookie.go @@ -2,7 +2,10 @@ package middleware import ( "net/http" + "net/url" + "time" + "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" ) @@ -26,14 +29,18 @@ func newCookieOptions() CookieOptions { } } -type GetCookieOptionsFunc func() CookieOptions +type getCookieOptionsFunc func() CookieOptions -func DeleteCookie(w http.ResponseWriter, name string, getCookieOptionsFunc GetCookieOptionsFunc) { - WriteCookie(w, name, "", -1, getCookieOptionsFunc) +func DeleteCookie(w http.ResponseWriter, name string, getCookieOptions getCookieOptionsFunc) { + WriteCookie(w, name, "", -1, getCookieOptions) } -func WriteCookie(w http.ResponseWriter, name string, value string, maxAge int, getCookieOptionsFunc GetCookieOptionsFunc) { - options := getCookieOptionsFunc() +func WriteCookie(w http.ResponseWriter, name string, value string, maxAge int, getCookieOptions getCookieOptionsFunc) { + if getCookieOptions == nil { + getCookieOptions = newCookieOptions + } + + options := getCookieOptions() cookie := http.Cookie{ Name: name, MaxAge: maxAge, @@ -47,3 +54,18 @@ func WriteCookie(w http.ResponseWriter, name string, value string, maxAge int, g } http.SetCookie(w, &cookie) } + +func WriteSessionCookie(ctx *models.ReqContext, value string, maxLifetime time.Duration) { + if setting.Env == setting.Dev { + ctx.Logger.Info("New token", "unhashed token", value) + } + + var maxAge int + if maxLifetime <= 0 { + maxAge = -1 + } else { + maxAge = int(maxLifetime.Seconds()) + } + + WriteCookie(ctx.Resp, setting.LoginCookieName, url.QueryEscape(value), maxAge, nil) +} diff --git a/pkg/middleware/middleware.go b/pkg/middleware/middleware.go index ac82532e52b..6d5dd32fc26 100644 --- a/pkg/middleware/middleware.go +++ b/pkg/middleware/middleware.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "net/url" "strconv" "strings" "time" @@ -274,21 +273,6 @@ func rotateEndOfRequestFunc(ctx *models.ReqContext, authTokenService models.User } } -func WriteSessionCookie(ctx *models.ReqContext, value string, maxLifetime time.Duration) { - if setting.Env == setting.Dev { - ctx.Logger.Info("New token", "unhashed token", value) - } - - var maxAge int - if maxLifetime <= 0 { - maxAge = -1 - } else { - maxAge = int(maxLifetime.Seconds()) - } - - WriteCookie(ctx.Resp, setting.LoginCookieName, url.QueryEscape(value), maxAge, newCookieOptions) -} - func AddDefaultResponseHeaders() macaron.Handler { return func(ctx *macaron.Context) { ctx.Resp.Before(func(w macaron.ResponseWriter) { diff --git a/pkg/middleware/middleware_test.go b/pkg/middleware/middleware_test.go index 7a996f82c3c..f78906485c8 100644 --- a/pkg/middleware/middleware_test.go +++ b/pkg/middleware/middleware_test.go @@ -21,7 +21,7 @@ import ( "github.com/grafana/grafana/pkg/components/gtime" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/remotecache" - authproxy "github.com/grafana/grafana/pkg/middleware/auth_proxy" + "github.com/grafana/grafana/pkg/middleware/authproxy" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/login" diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go index 477079d34e4..dd60fa56f6e 100644 --- a/pkg/registry/registry.go +++ b/pkg/registry/registry.go @@ -17,7 +17,7 @@ type Descriptor struct { var services []*Descriptor func RegisterServiceWithPriority(instance Service, priority Priority) { - services = append(services, &Descriptor{ + Register(&Descriptor{ Name: reflect.TypeOf(instance).Elem().Name(), Instance: instance, InitPriority: priority, @@ -25,7 +25,7 @@ func RegisterServiceWithPriority(instance Service, priority Priority) { } func RegisterService(instance Service) { - services = append(services, &Descriptor{ + Register(&Descriptor{ Name: reflect.TypeOf(instance).Elem().Name(), Instance: instance, InitPriority: Medium, @@ -33,6 +33,17 @@ func RegisterService(instance Service) { } func Register(descriptor *Descriptor) { + if descriptor == nil { + return + } + // Overwrite any existing equivalent service + for i, svc := range services { + if svc.Name == descriptor.Name { + services[i] = descriptor + return + } + } + services = append(services, descriptor) } From 4edb1364e94c8289104cee679786b5873e5aeb7c Mon Sep 17 00:00:00 2001 From: Carl Bergquist Date: Wed, 2 Dec 2020 13:42:17 +0100 Subject: [PATCH 045/124] fixes bug with invalid handler name for metrics (#29529) closes https://github.com/grafana/grafana/issues/29487 Signed-off-by: bergquist --- pkg/api/routing/route_register.go | 8 +++++--- pkg/api/routing/route_register_test.go | 11 ++++++++++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/pkg/api/routing/route_register.go b/pkg/api/routing/route_register.go index b0acb581681..0d9952c6db0 100644 --- a/pkg/api/routing/route_register.go +++ b/pkg/api/routing/route_register.go @@ -118,22 +118,24 @@ func (rr *routeRegister) Register(router Router) { func (rr *routeRegister) route(pattern, method string, handlers ...macaron.Handler) { h := make([]macaron.Handler, 0) + fullPattern := rr.prefix + pattern + for _, fn := range rr.namedMiddleware { - h = append(h, fn(pattern)) + h = append(h, fn(fullPattern)) } h = append(h, rr.subfixHandlers...) h = append(h, handlers...) for _, r := range rr.routes { - if r.pattern == rr.prefix+pattern && r.method == method { + if r.pattern == fullPattern && r.method == method { panic("cannot add duplicate route") } } rr.routes = append(rr.routes, route{ method: method, - pattern: rr.prefix + pattern, + pattern: fullPattern, handlers: h, }) } diff --git a/pkg/api/routing/route_register_test.go b/pkg/api/routing/route_register_test.go index e7030d57eab..cf67abf06dc 100644 --- a/pkg/api/routing/route_register_test.go +++ b/pkg/api/routing/route_register_test.go @@ -214,8 +214,13 @@ func TestNamedMiddlewareRouteRegister(t *testing.T) { {method: "GET", pattern: "/user/admin/all", handlers: emptyHandlers(5)}, } + namedMiddlewares := map[string]bool{} // Setup - rr := NewRouteRegister(emptyHandler) + rr := NewRouteRegister(func(name string) macaron.Handler { + namedMiddlewares[name] = true + + return struct{ name string }{name: name} + }) rr.Delete("/admin", emptyHandler("1")) rr.Get("/down", emptyHandler("1"), emptyHandler("2")) @@ -247,6 +252,10 @@ func TestNamedMiddlewareRouteRegister(t *testing.T) { t.Errorf("want %s got %v", testTable[i].pattern, fr.route[i].pattern) } + if _, exist := namedMiddlewares[testTable[i].pattern]; !exist { + t.Errorf("could not find named route named %s", testTable[i].pattern) + } + if len(testTable[i].handlers) != len(fr.route[i].handlers) { t.Errorf("want %d handlers got %d handlers \ntestcase: %v\nroute: %v\n", len(testTable[i].handlers), From 88b1517794ad7c7a6005a6f07633274f11543e92 Mon Sep 17 00:00:00 2001 From: Uchechukwu Obasi Date: Wed, 2 Dec 2020 13:50:53 +0100 Subject: [PATCH 046/124] Icon: Changed the icon for signing in (#29530) * Icon: changed the icon for signing in Signed-off-by: Uchechukwu Obasi * fixes test-frontend (I hope) --- packages/grafana-ui/src/types/icon.ts | 1 + public/app/core/components/sidemenu/SignIn.tsx | 2 +- .../core/components/sidemenu/__snapshots__/SignIn.test.tsx.snap | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/grafana-ui/src/types/icon.ts b/packages/grafana-ui/src/types/icon.ts index 34483ff3459..56e205f44a6 100644 --- a/packages/grafana-ui/src/types/icon.ts +++ b/packages/grafana-ui/src/types/icon.ts @@ -34,6 +34,7 @@ export type IconName = | 'exclamation-triangle' | 'times' | 'signin' + | 'signout' | 'cloud-upload' | 'step-backward' | 'square-shape' diff --git a/public/app/core/components/sidemenu/SignIn.tsx b/public/app/core/components/sidemenu/SignIn.tsx index 5400c653417..4de185439b7 100644 --- a/public/app/core/components/sidemenu/SignIn.tsx +++ b/public/app/core/components/sidemenu/SignIn.tsx @@ -12,7 +12,7 @@ export const SignIn: FC = ({ url }) => {
- + diff --git a/public/app/core/components/sidemenu/__snapshots__/SignIn.test.tsx.snap b/public/app/core/components/sidemenu/__snapshots__/SignIn.test.tsx.snap index 69fc93a90c7..7c3c353d89a 100644 --- a/public/app/core/components/sidemenu/__snapshots__/SignIn.test.tsx.snap +++ b/public/app/core/components/sidemenu/__snapshots__/SignIn.test.tsx.snap @@ -13,7 +13,7 @@ exports[`Render should render component 1`] = ` className="icon-circle sidemenu-icon" > From 15f8dd44e5a58fb8960c0264f01fac7c9e6cc366 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 2 Dec 2020 04:51:43 -0800 Subject: [PATCH 047/124] OptionsEditor: simplify the options editor interfaces (#29518) * simplify options UI inheritance * simplify options UI inheritance * OptionsEditor to OptionEditor --- .../src/types/OptionsUIRegistryBuilder.ts | 51 +++--------------- .../grafana-data/src/types/fieldOverrides.ts | 43 ++------------- packages/grafana-data/src/types/options.ts | 53 +++++++++++++++++++ packages/grafana-data/src/types/panel.ts | 44 ++------------- 4 files changed, 65 insertions(+), 126 deletions(-) create mode 100644 packages/grafana-data/src/types/options.ts diff --git a/packages/grafana-data/src/types/OptionsUIRegistryBuilder.ts b/packages/grafana-data/src/types/OptionsUIRegistryBuilder.ts index 291239469c3..6d2db5b1950 100644 --- a/packages/grafana-data/src/types/OptionsUIRegistryBuilder.ts +++ b/packages/grafana-data/src/types/OptionsUIRegistryBuilder.ts @@ -6,64 +6,25 @@ import { SelectFieldConfigSettings, StringFieldConfigSettings, } from '../field'; +import { OptionEditorConfig } from './options'; /** * Option editor registry item */ -export interface OptionsEditorItem extends RegistryItem { - /** - * Path of the options property to control. - * - * @example - * Given options object of a type: - * ```ts - * interface CustomOptions { - * a: { - * b: string; - * } - * } - * ``` - * - * path can be either 'a' or 'a.b'. - */ - path: (keyof TOptions & string) | string; +export interface OptionsEditorItem + extends RegistryItem, + OptionEditorConfig { /** * React component used to edit the options property */ editor: ComponentType; - /** - * Custom settings of the editor. - */ - settings?: TSettings; - /** - * Array of strings representing category of the options property. - */ - category?: string[]; - defaultValue?: TValue; - /** - * Function that enables configuration of when option editor should be shown based on current options properties. - * - * @param currentConfig Current options values - */ - showIf?: (currentConfig: TOptions) => boolean | undefined; - /** - * Function that returns number of items if given option represents a collection, i.e. array of items. + + /* * @param value */ getItemsCount?: (value?: TValue) => number; } -/** - * Configuration of option editor registry item - */ -interface OptionEditorConfig { - id: keyof TOptions & string; - name: string; - description?: string; - settings?: TSettings; - defaultValue?: TValue; -} - /** * Describes an API for option editors UI builder */ diff --git a/packages/grafana-data/src/types/fieldOverrides.ts b/packages/grafana-data/src/types/fieldOverrides.ts index 3c8038e81e2..deb0e6e673a 100644 --- a/packages/grafana-data/src/types/fieldOverrides.ts +++ b/packages/grafana-data/src/types/fieldOverrides.ts @@ -3,6 +3,7 @@ import { MatcherConfig, FieldConfig, Field, DataFrame, GrafanaTheme, TimeZone } import { InterpolateFunction } from './panel'; import { StandardEditorProps, FieldConfigOptionsRegistry, StandardEditorContext } from '../field'; import { OptionsEditorItem } from './OptionsUIRegistryBuilder'; +import { OptionEditorConfig } from './options'; export interface DynamicConfigValue { id: string; @@ -40,51 +41,13 @@ export interface FieldOverrideEditorProps extends Omit { - /** - * Path of the field config property to control. - * - * @example - * Given field config object of a type: - * ```ts - * interface CustomFieldConfig { - * a: { - * b: string; - * } - * } - * ``` - * - * path can be either 'a' or 'a.b'. - */ - path: (keyof TOptions & string) | string; - /** - * Name of the field config property. Will be displayed in the UI as form element label. - */ - name: string; - /** - * Description of the field config property. Will be displayed in the UI as form element description. - */ - description?: string; - /** - * Array of strings representing category of the field config property. First element in the array will make option render as collapsible section. - */ - category?: string[]; - /** - * Custom settings of the editor. - */ - settings?: TSettings; +export interface FieldConfigEditorConfig + extends OptionEditorConfig { /** * Function that allows specifying whether or not this field config should apply to a given field. * @param field */ shouldApply?: (field: Field) => boolean; - defaultValue?: TValue; - /** - * Function that enables configuration of when field config property editor should be shown based on current panel field config. - * - * @param currentConfig Current field config values - */ - showIf?: (currentConfig: TOptions) => boolean; } export interface FieldConfigPropertyItem diff --git a/packages/grafana-data/src/types/options.ts b/packages/grafana-data/src/types/options.ts new file mode 100644 index 00000000000..d44879afefa --- /dev/null +++ b/packages/grafana-data/src/types/options.ts @@ -0,0 +1,53 @@ +/** + * Base class for editor builders + * + * @beta + */ +export interface OptionEditorConfig { + /** + * Path of the option property to control. + * + * @example + * Given options object of a type: + * ```ts + * interface Options { + * a: { + * b: string; + * } + * } + * ``` + * + * path can be either 'a' or 'a.b'. + */ + path: (keyof TOptions & string) | string; + + /** + * Name of the option. Will be displayed in the UI as form element label. + */ + name: string; + + /** + * Description of the option. Will be displayed in the UI as form element description. + */ + description?: string; + + /** + * Custom settings of the editor. + */ + settings?: TSettings; + + /** + * Array of strings representing category of the option. First element in the array will make option render as collapsible section. + */ + category?: string[]; + + /** + * Set this value if undefined + */ + defaultValue?: TValue; + + /** + * Function that enables configuration of when option editor should be shown based on current panel option properties. + */ + showIf?: (currentOptions: TOptions) => boolean | undefined; +} diff --git a/packages/grafana-data/src/types/panel.ts b/packages/grafana-data/src/types/panel.ts index a666b841641..5a6d33364b6 100644 --- a/packages/grafana-data/src/types/panel.ts +++ b/packages/grafana-data/src/types/panel.ts @@ -9,6 +9,7 @@ import { FieldConfigSource } from './fieldOverrides'; import { Registry } from '../utils'; import { StandardEditorProps } from '../field'; import { OptionsEditorItem } from './OptionsUIRegistryBuilder'; +import { OptionEditorConfig } from './options'; export type InterpolateFunction = (value: string, scopedVars?: ScopedVars, format?: string | Function) => string; @@ -140,47 +141,8 @@ export interface PanelOptionsEditorProps extends StandardEditorProps extends OptionsEditorItem, TValue> {} -export interface PanelOptionsEditorConfig { - /** - * Path of the option property to control. - * - * @example - * Given options object of a type: - * ```ts - * interface Options { - * a: { - * b: string; - * } - * } - * ``` - * - * path can be either 'a' or 'a.b'. - */ - path: (keyof TOptions & string) | string; - /** - * Name of the option. Will be displayed in the UI as form element label. - */ - name: string; - /** - * Description of the option. Will be displayed in the UI as form element description. - */ - description?: string; - /**al - * Custom settings of the editor. - */ - settings?: TSettings; - /** - * Array of strings representing category of the option. First element in the array will make option render as collapsible section. - */ - category?: string[]; - defaultValue?: TValue; - /** - * Function that enables configuration of when option editor should be shown based on current panel option properties. - * - * @param currentConfig Current panel options - */ - showIf?: (currentConfig: TOptions) => boolean | undefined; -} +export interface PanelOptionsEditorConfig + extends OptionEditorConfig {} /** * @internal From 3dcfe54d8d921c79b0d319e42441070a24b60028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Wed, 2 Dec 2020 14:08:35 +0100 Subject: [PATCH 048/124] Variables: Fixes Textbox current value persistence (#29481) * Variables: Fixes savequery for Constant and TextBox variables * Refactor: reverts textbox changes * Refactor: Fixes dashboard export and tests * Refactor: hides or migrates Constant variables * Tests: fixes snapshots * Variables: Fixes Textbox current value persistance * Refactor: fixes PR comments and adds e2e tests --- .../templating-textbox-e2e-scenarios.json | 116 +++++++ .../specs/variables/textbox-variables.ts | 299 ++++++++++++++++++ .../src/selectors/components.ts | 3 + .../src/selectors/pages.ts | 3 + .../DashboardSettings/template.html | 4 +- .../SaveDashboard/SaveDashboardButton.tsx | 3 + .../dashboard/state/DashboardMigrator.test.ts | 2 +- .../dashboard/state/DashboardModel.ts | 8 +- public/app/features/variables/adapters.ts | 2 +- .../variables/shared/testing/builders.ts | 3 +- .../shared/testing/textboxVariableBuilder.ts | 9 + .../textbox/TextBoxVariableEditor.tsx | 58 ++-- .../textbox/TextBoxVariablePicker.tsx | 60 ++-- .../variables/textbox/adapter.test.ts | 84 +++++ .../app/features/variables/textbox/adapter.ts | 14 +- .../app/features/variables/textbox/reducer.ts | 1 + public/app/features/variables/types.ts | 4 +- 17 files changed, 606 insertions(+), 67 deletions(-) create mode 100644 devenv/dev-dashboards/feature-templating/templating-textbox-e2e-scenarios.json create mode 100644 e2e/suite1/specs/variables/textbox-variables.ts create mode 100644 public/app/features/variables/shared/testing/textboxVariableBuilder.ts create mode 100644 public/app/features/variables/textbox/adapter.test.ts diff --git a/devenv/dev-dashboards/feature-templating/templating-textbox-e2e-scenarios.json b/devenv/dev-dashboards/feature-templating/templating-textbox-e2e-scenarios.json new file mode 100644 index 00000000000..37c853cafcc --- /dev/null +++ b/devenv/dev-dashboards/feature-templating/templating-textbox-e2e-scenarios.json @@ -0,0 +1,116 @@ +{ + "__inputs": [ + { + "name": "DS_GDEV-TESTDATA", + "label": "gdev-testdata", + "description": "", + "type": "datasource", + "pluginId": "testdata", + "pluginName": "TestData DB" + } + ], + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "7.4.0-pre" + }, + { + "type": "datasource", + "id": "testdata", + "name": "TestData DB", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "text", + "name": "Text", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": null, + "iteration": 1606804991052, + "links": [], + "panels": [ + { + "datasource": "${DS_GDEV-TESTDATA}", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 2, + "options": { + "content": "# variable: ${text}\n ", + "mode": "markdown" + }, + "pluginVersion": "7.4.0-pre", + "timeFrom": null, + "timeShift": null, + "title": "Panel Title", + "type": "text" + } + ], + "schemaVersion": 27, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "default value", + "value": "default value" + }, + "description": null, + "error": null, + "hide": 0, + "label": null, + "name": "text", + "options": [ + { + "selected": true, + "text": "default value", + "value": "default value" + } + ], + "query": "default value", + "skipUrlSync": false, + "type": "textbox" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Templating - Textbox e2e scenarios", + "uid": "AejrN1AMz", + "version": 1 +} diff --git a/e2e/suite1/specs/variables/textbox-variables.ts b/e2e/suite1/specs/variables/textbox-variables.ts new file mode 100644 index 00000000000..f85c5809d88 --- /dev/null +++ b/e2e/suite1/specs/variables/textbox-variables.ts @@ -0,0 +1,299 @@ +import { e2e } from '@grafana/e2e'; + +const PAGE_UNDER_TEST = 'AejrN1AMz'; + +describe('TextBox - load options scenarios', function() { + it('default options should be correct', function() { + e2e.flows.login('admin', 'admin'); + e2e.flows.openDashboard({ uid: PAGE_UNDER_TEST }); + e2e().server(); + e2e() + .route({ + method: 'GET', + url: `/api/dashboards/uid/${PAGE_UNDER_TEST}`, + }) + .as('dash'); + + e2e().wait('@dash'); + + validateTextboxAndMarkup('default value'); + }); + + it('loading variable from url should be correct', function() { + e2e.flows.login('admin', 'admin'); + e2e.flows.openDashboard({ uid: `${PAGE_UNDER_TEST}?var-text=not default value` }); + e2e().server(); + e2e() + .route({ + method: 'GET', + url: `/api/dashboards/uid/${PAGE_UNDER_TEST}`, + }) + .as('dash'); + + e2e().wait('@dash'); + + validateTextboxAndMarkup('not default value'); + }); +}); + +describe('TextBox - change query scenarios', function() { + it('when changing the query value and not saving current as default should revert query value', function() { + copyExistingDashboard(); + + changeQueryInput(); + + e2e.components.BackButton.backArrow() + .should('be.visible') + .click({ force: true }); + + validateTextboxAndMarkup('changed value'); + + saveDashboard(false); + + e2e() + .get('@dashuid') + .then((dashuid: any) => { + expect(dashuid).not.to.eq(PAGE_UNDER_TEST); + + e2e.flows.openDashboard({ uid: dashuid }); + + e2e().wait('@load-dash'); + + validateTextboxAndMarkup('default value'); + + validateVariable('changed value'); + }); + }); + + it('when changing the query value and saving current as default should change query value', function() { + copyExistingDashboard(); + + changeQueryInput(); + + e2e.components.BackButton.backArrow() + .should('be.visible') + .click({ force: true }); + + validateTextboxAndMarkup('changed value'); + + saveDashboard(true); + + e2e() + .get('@dashuid') + .then((dashuid: any) => { + expect(dashuid).not.to.eq(PAGE_UNDER_TEST); + + e2e.flows.openDashboard({ uid: dashuid }); + + e2e().wait('@load-dash'); + + validateTextboxAndMarkup('changed value'); + + validateVariable('changed value'); + }); + }); +}); + +describe('TextBox - change picker value scenarios', function() { + it('when changing the input value and not saving current as default should revert query value', function() { + copyExistingDashboard(); + + changeTextBoxInput(); + + validateTextboxAndMarkup('changed value'); + + saveDashboard(false); + + e2e() + .get('@dashuid') + .then((dashuid: any) => { + expect(dashuid).not.to.eq(PAGE_UNDER_TEST); + + e2e.flows.openDashboard({ uid: dashuid }); + + e2e().wait('@load-dash'); + + validateTextboxAndMarkup('default value'); + validateVariable('default value'); + }); + }); + + it('when changing the input value and saving current as default should change query value', function() { + copyExistingDashboard(); + + changeTextBoxInput(); + + validateTextboxAndMarkup('changed value'); + + saveDashboard(true); + + e2e() + .get('@dashuid') + .then((dashuid: any) => { + expect(dashuid).not.to.eq(PAGE_UNDER_TEST); + + e2e.flows.openDashboard({ uid: dashuid }); + + e2e().wait('@load-dash'); + + validateTextboxAndMarkup('changed value'); + validateVariable('changed value'); + }); + }); +}); + +function copyExistingDashboard() { + e2e.flows.login('admin', 'admin'); + e2e().server(); + e2e() + .route({ + method: 'GET', + url: '/api/search?query=&type=dash-folder&permission=Edit', + }) + .as('dash-settings'); + e2e() + .route({ + method: 'POST', + url: '/api/dashboards/db/', + }) + .as('save-dash'); + e2e() + .route({ + method: 'GET', + url: /\/api\/dashboards\/uid\/(?!AejrN1AMz)\w+/, + }) + .as('load-dash'); + e2e.flows.openDashboard({ uid: `${PAGE_UNDER_TEST}?editview=settings&orgId=1` }); + + e2e().wait('@dash-settings'); + + e2e.pages.Dashboard.Settings.General.saveAsDashBoard() + .should('be.visible') + .click(); + + e2e.pages.SaveDashboardAsModal.newName() + .should('be.visible') + .type(`${Date.now()}`); + + e2e.pages.SaveDashboardAsModal.save() + .should('be.visible') + .click(); + + e2e().wait('@save-dash'); + e2e().wait('@load-dash'); + + e2e.pages.Dashboard.SubMenu.submenuItem().should('be.visible'); + + e2e() + .location() + .then(loc => { + const dashuid = /\/d\/(\w+)\//.exec(loc.href)![1]; + e2e() + .wrap(dashuid) + .as('dashuid'); + }); + + e2e().wait(500); +} + +function saveDashboard(saveVariables: boolean) { + e2e.pages.Dashboard.Toolbar.toolbarItems('Save dashboard') + .should('be.visible') + .click(); + + if (saveVariables) { + e2e.pages.SaveDashboardModal.saveVariables() + .should('exist') + .click({ force: true }); + } + + e2e.pages.SaveDashboardModal.save() + .should('be.visible') + .click(); + + e2e().wait('@save-dash'); +} + +function validateTextboxAndMarkup(value: string) { + e2e.pages.Dashboard.SubMenu.submenuItem() + .should('be.visible') + .within(() => { + e2e.pages.Dashboard.SubMenu.submenuItemLabels('text').should('be.visible'); + e2e() + .get('input') + .should('be.visible') + .should('have.value', value); + }); + + e2e.components.Panels.Visualization.Text.container() + .should('be.visible') + .within(() => { + e2e() + .get('h1') + .should('be.visible') + .should('have.text', `variable: ${value}`); + }); +} + +function validateVariable(value: string) { + e2e.pages.Dashboard.Toolbar.toolbarItems('Dashboard settings') + .should('be.visible') + .click(); + + e2e.pages.Dashboard.Settings.General.sectionItems('Variables') + .should('be.visible') + .click(); + + e2e.pages.Dashboard.Settings.Variables.List.tableRowNameFields('text') + .should('be.visible') + .click(); + + e2e.pages.Dashboard.Settings.Variables.Edit.TextBoxVariable.textBoxOptionsQueryInput() + .should('be.visible') + .should('have.value', value); +} + +function changeTextBoxInput() { + e2e.pages.Dashboard.SubMenu.submenuItemLabels('text').should('be.visible'); + e2e.pages.Dashboard.SubMenu.submenuItem() + .should('be.visible') + .within(() => { + e2e() + .get('input') + .should('be.visible') + .should('have.value', 'default value') + .clear() + .type('changed value') + .type('{enter}'); + }); + + e2e() + .location() + .should(loc => { + expect(loc.search).to.contain('var-text=changed%20value'); + }); +} + +function changeQueryInput() { + e2e.pages.Dashboard.Toolbar.toolbarItems('Dashboard settings') + .should('be.visible') + .click(); + + e2e.pages.Dashboard.Settings.General.sectionItems('Variables') + .should('be.visible') + .click(); + + e2e.pages.Dashboard.Settings.Variables.List.tableRowNameFields('text') + .should('be.visible') + .click(); + + e2e.pages.Dashboard.Settings.Variables.Edit.TextBoxVariable.textBoxOptionsQueryInput() + .should('be.visible') + .clear() + .type('changed value') + .blur(); + + e2e.pages.Dashboard.Settings.Variables.Edit.General.previewOfValuesOption() + .should('have.length', 1) + .should('have.text', 'changed value'); +} diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts index 73deb45ab09..ebd777e28d4 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -37,6 +37,9 @@ export const Components = { BarGauge: { value: 'Bar gauge value', }, + Text: { + container: () => '.markdown-html', + }, }, }, Drawer: { diff --git a/packages/grafana-e2e-selectors/src/selectors/pages.ts b/packages/grafana-e2e-selectors/src/selectors/pages.ts index ff00865f431..2c753972131 100644 --- a/packages/grafana-e2e-selectors/src/selectors/pages.ts +++ b/packages/grafana-e2e-selectors/src/selectors/pages.ts @@ -99,6 +99,9 @@ export const Pages = { ConstantVariable: { constantOptionsQueryInput: 'Variable editor Form Constant Query field', }, + TextBoxVariable: { + textBoxOptionsQueryInput: 'Variable editor Form TextBox Query field', + }, }, }, }, diff --git a/public/app/features/dashboard/components/DashboardSettings/template.html b/public/app/features/dashboard/components/DashboardSettings/template.html index 6fb87f257f5..b951945faba 100644 --- a/public/app/features/dashboard/components/DashboardSettings/template.html +++ b/public/app/features/dashboard/components/DashboardSettings/template.html @@ -10,10 +10,10 @@
- +
- +
diff --git a/public/app/features/dashboard/components/SaveDashboard/SaveDashboardButton.tsx b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardButton.tsx index da7bf2bae57..7a07d4132fd 100644 --- a/public/app/features/dashboard/components/SaveDashboard/SaveDashboardButton.tsx +++ b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardButton.tsx @@ -5,6 +5,7 @@ import { connectWithProvider } from 'app/core/utils/connectWithReduxStore'; import { provideModalsContext } from 'app/routes/ReactContainer'; import { SaveDashboardAsModal } from './SaveDashboardAsModal'; import { SaveDashboardModalProxy } from './SaveDashboardModalProxy'; +import { selectors } from '@grafana/e2e-selectors'; interface SaveDashboardButtonProps { dashboard: DashboardModel; @@ -30,6 +31,7 @@ export const SaveDashboardButton: React.FC = ({ dashbo onDismiss: hideModal, }); }} + aria-label={selectors.pages.Dashboard.Settings.General.saveDashBoard} > Save dashboard @@ -63,6 +65,7 @@ export const SaveDashboardAsButton: React.FC Save As... diff --git a/public/app/features/dashboard/state/DashboardMigrator.test.ts b/public/app/features/dashboard/state/DashboardMigrator.test.ts index ee477e60c59..bdea27b2765 100644 --- a/public/app/features/dashboard/state/DashboardMigrator.test.ts +++ b/public/app/features/dashboard/state/DashboardMigrator.test.ts @@ -857,7 +857,7 @@ describe('DashboardModel', () => { }); }); - it('should have three variables after migration', () => { + it('should have six variables after migration', () => { expect(model.templating.list.length).toBe(6); }); diff --git a/public/app/features/dashboard/state/DashboardModel.ts b/public/app/features/dashboard/state/DashboardModel.ts index 429c4a5ea8b..947933bb9bd 100644 --- a/public/app/features/dashboard/state/DashboardModel.ts +++ b/public/app/features/dashboard/state/DashboardModel.ts @@ -14,12 +14,12 @@ import { dateTimeFormat, dateTimeFormatTimeAgo, DateTimeInput, + EventBusExtended, + EventBusSrv, PanelEvents, TimeRange, TimeZone, UrlQueryValue, - EventBusSrv, - EventBusExtended, } from '@grafana/data'; import { CoreEvents, DashboardMeta, KIOSK_MODE_TV } from 'app/types'; import { GetVariables, getVariables } from 'app/features/variables/state/selectors'; @@ -236,7 +236,9 @@ export class DashboardModel { const currentVariables = this.getVariablesFromState(); copy.templating = { - list: currentVariables.map(variable => variableAdapters.get(variable.type).getSaveModel(variable)), + list: currentVariables.map(variable => + variableAdapters.get(variable.type).getSaveModel(variable, defaults.saveVariables) + ), }; if (!defaults.saveVariables) { diff --git a/public/app/features/variables/adapters.ts b/public/app/features/variables/adapters.ts index f83fb0e50e8..6bfba8bd37c 100644 --- a/public/app/features/variables/adapters.ts +++ b/public/app/features/variables/adapters.ts @@ -34,7 +34,7 @@ export interface VariableAdapter { setValue: (variable: Model, option: VariableOption, emitChanges?: boolean) => Promise; setValueFromUrl: (variable: Model, urlValue: UrlQueryValue) => Promise; updateOptions: (variable: Model, searchFilter?: string) => Promise; - getSaveModel: (variable: Model) => Partial; + getSaveModel: (variable: Model, saveCurrentAsDefault?: boolean) => Partial; getValueForUrl: (variable: Model) => string | string[]; picker: ComponentType; editor: ComponentType; diff --git a/public/app/features/variables/shared/testing/builders.ts b/public/app/features/variables/shared/testing/builders.ts index 3a5e813ef56..c2351663973 100644 --- a/public/app/features/variables/shared/testing/builders.ts +++ b/public/app/features/variables/shared/testing/builders.ts @@ -11,11 +11,12 @@ import { initialCustomVariableModelState } from '../../custom/reducer'; import { MultiVariableBuilder } from './multiVariableBuilder'; import { initialConstantVariableModelState } from '../../constant/reducer'; import { QueryVariableBuilder } from './queryVariableBuilder'; +import { TextBoxVariableBuilder } from './textboxVariableBuilder'; export const adHocBuilder = () => new AdHocVariableBuilder(initialAdHocVariableModelState); export const intervalBuilder = () => new IntervalVariableBuilder(initialIntervalVariableModelState); export const datasourceBuilder = () => new DatasourceVariableBuilder(initialDataSourceVariableModelState); export const queryBuilder = () => new QueryVariableBuilder(initialQueryVariableModelState); -export const textboxBuilder = () => new OptionsVariableBuilder(initialTextBoxVariableModelState); +export const textboxBuilder = () => new TextBoxVariableBuilder(initialTextBoxVariableModelState); export const customBuilder = () => new MultiVariableBuilder(initialCustomVariableModelState); export const constantBuilder = () => new OptionsVariableBuilder(initialConstantVariableModelState); diff --git a/public/app/features/variables/shared/testing/textboxVariableBuilder.ts b/public/app/features/variables/shared/testing/textboxVariableBuilder.ts new file mode 100644 index 00000000000..6170c7ee8f7 --- /dev/null +++ b/public/app/features/variables/shared/testing/textboxVariableBuilder.ts @@ -0,0 +1,9 @@ +import { TextBoxVariableModel } from 'app/features/variables/types'; +import { OptionsVariableBuilder } from './optionsVariableBuilder'; + +export class TextBoxVariableBuilder extends OptionsVariableBuilder { + withOriginalQuery(original: string) { + this.variable.originalQuery = original; + return this; + } +} diff --git a/public/app/features/variables/textbox/TextBoxVariableEditor.tsx b/public/app/features/variables/textbox/TextBoxVariableEditor.tsx index 7ca8e3a204d..8f078c2a335 100644 --- a/public/app/features/variables/textbox/TextBoxVariableEditor.tsx +++ b/public/app/features/variables/textbox/TextBoxVariableEditor.tsx @@ -1,36 +1,40 @@ -import React, { ChangeEvent, PureComponent } from 'react'; +import React, { ChangeEvent, ReactElement, useCallback } from 'react'; import { VerticalGroup } from '@grafana/ui'; import { TextBoxVariableModel } from '../types'; import { VariableEditorProps } from '../editor/types'; import { VariableSectionHeader } from '../editor/VariableSectionHeader'; import { VariableTextField } from '../editor/VariableTextField'; +import { selectors } from '@grafana/e2e-selectors'; export interface Props extends VariableEditorProps {} -export class TextBoxVariableEditor extends PureComponent { - onQueryChange = (event: ChangeEvent) => { - event.preventDefault(); - this.props.onPropChange({ propName: 'query', propValue: event.target.value, updateOptions: false }); - }; - onQueryBlur = (event: ChangeEvent) => { - event.preventDefault(); - this.props.onPropChange({ propName: 'query', propValue: event.target.value, updateOptions: true }); - }; - render() { - const { query } = this.props.variable; - return ( - - - - - ); - } + +export function TextBoxVariableEditor({ onPropChange, variable: { query } }: Props): ReactElement { + const updateVariable = useCallback( + (event: ChangeEvent, updateOptions: boolean) => { + event.preventDefault(); + onPropChange({ propName: 'originalQuery', propValue: event.target.value, updateOptions: false }); + onPropChange({ propName: 'query', propValue: event.target.value, updateOptions }); + }, + [onPropChange] + ); + + const onChange = useCallback((e: ChangeEvent) => updateVariable(e, false), [updateVariable]); + const onBlur = useCallback((e: ChangeEvent) => updateVariable(e, true), [updateVariable]); + + return ( + + + + + ); } diff --git a/public/app/features/variables/textbox/TextBoxVariablePicker.tsx b/public/app/features/variables/textbox/TextBoxVariablePicker.tsx index 10632773665..b5f3e4a1570 100644 --- a/public/app/features/variables/textbox/TextBoxVariablePicker.tsx +++ b/public/app/features/variables/textbox/TextBoxVariablePicker.tsx @@ -1,43 +1,45 @@ -import React, { ChangeEvent, FocusEvent, KeyboardEvent, PureComponent } from 'react'; +import React, { ChangeEvent, FocusEvent, KeyboardEvent, ReactElement, useCallback, useEffect, useState } from 'react'; import { TextBoxVariableModel } from '../types'; -import { toVariableIdentifier, toVariablePayload } from '../state/types'; -import { dispatch } from '../../../store/store'; +import { toVariablePayload } from '../state/types'; import { changeVariableProp } from '../state/sharedReducer'; import { VariablePickerProps } from '../pickers/types'; -import { updateOptions } from '../state/actions'; +import { Input } from '@grafana/ui'; +import { variableAdapters } from '../adapters'; +import { useDispatch } from 'react-redux'; export interface Props extends VariablePickerProps {} -export class TextBoxVariablePicker extends PureComponent { - onQueryChange = (event: ChangeEvent) => { +export function TextBoxVariablePicker({ variable }: Props): ReactElement { + const dispatch = useDispatch(); + const [updatedValue, setUpdatedValue] = useState(variable.current.value); + useEffect(() => { + setUpdatedValue(variable.current.value); + }, [variable]); + + const updateVariable = useCallback(() => { + if (variable.current.value === updatedValue) { + return; + } + dispatch( - changeVariableProp(toVariablePayload(this.props.variable, { propName: 'query', propValue: event.target.value })) + changeVariableProp( + toVariablePayload({ id: variable.id, type: variable.type }, { propName: 'query', propValue: updatedValue }) + ) ); - }; + variableAdapters.get(variable.type).updateOptions(variable); + }, [dispatch, variable, updatedValue]); - onQueryBlur = (event: FocusEvent) => { - if (this.props.variable.current.value !== this.props.variable.query) { - dispatch(updateOptions(toVariableIdentifier(this.props.variable))); + const onChange = useCallback((event: ChangeEvent) => setUpdatedValue(event.target.value), [ + setUpdatedValue, + ]); + + const onBlur = (e: FocusEvent) => updateVariable(); + const onKeyDown = (event: KeyboardEvent) => { + if (event.keyCode === 13) { + updateVariable(); } }; - onQueryKeyDown = (event: KeyboardEvent) => { - if (event.keyCode === 13 && this.props.variable.current.value !== this.props.variable.query) { - dispatch(updateOptions(toVariableIdentifier(this.props.variable))); - } - }; - - render() { - return ( - - ); - } + return ; } diff --git a/public/app/features/variables/textbox/adapter.test.ts b/public/app/features/variables/textbox/adapter.test.ts new file mode 100644 index 00000000000..e2e9d1db7ba --- /dev/null +++ b/public/app/features/variables/textbox/adapter.test.ts @@ -0,0 +1,84 @@ +import { variableAdapters } from '../adapters'; +import { createTextBoxVariableAdapter } from './adapter'; +import { textboxBuilder } from '../shared/testing/builders'; +import { VariableHide } from '../types'; + +variableAdapters.setInit(() => [createTextBoxVariableAdapter()]); + +describe('createTextBoxVariableAdapter', () => { + describe('getSaveModel', () => { + describe('when called and query differs from the original query and not saving current as default', () => { + it('then the model should be correct', () => { + const text = textboxBuilder() + .withId('text') + .withName('text') + .withQuery('query') + .withOriginalQuery('original') + .withCurrent('query') + .withOptions('query') + .build(); + + const adapter = variableAdapters.get('textbox'); + + const result = adapter.getSaveModel(text, false); + + expect(result).toEqual({ + name: 'text', + query: 'original', + current: { selected: false, text: 'original', value: 'original' }, + options: [{ selected: false, text: 'original', value: 'original' }], + type: 'textbox', + label: null, + hide: VariableHide.dontHide, + skipUrlSync: false, + error: null, + description: null, + }); + }); + }); + + describe('when called and query differs from the original query and saving current as default', () => { + it('then the model should be correct', () => { + const text = textboxBuilder() + .withId('text') + .withName('text') + .withQuery('query') + .withOriginalQuery('original') + .withCurrent('query') + .withOptions('query') + .build(); + + const adapter = variableAdapters.get('textbox'); + + const result = adapter.getSaveModel(text, true); + + expect(result).toEqual({ + name: 'text', + query: 'query', + current: { selected: true, text: 'query', value: 'query' }, + options: [{ selected: false, text: 'query', value: 'query' }], + type: 'textbox', + label: null, + hide: VariableHide.dontHide, + skipUrlSync: false, + error: null, + description: null, + }); + }); + }); + }); + + describe('beforeAdding', () => { + describe('when called', () => { + it('then originalQuery should be same added', () => { + const model = { name: 'text', query: 'a query' }; + + const adapter = variableAdapters.get('textbox'); + + const result = adapter.beforeAdding!(model); + + expect(result).toEqual({ name: 'text', query: 'a query', originalQuery: 'a query' }); + }); + }); + }); +}); diff --git a/public/app/features/variables/textbox/adapter.ts b/public/app/features/variables/textbox/adapter.ts index 801983d4c54..d57fe26b7c8 100644 --- a/public/app/features/variables/textbox/adapter.ts +++ b/public/app/features/variables/textbox/adapter.ts @@ -31,12 +31,22 @@ export const createTextBoxVariableAdapter = (): VariableAdapter { await dispatch(updateTextBoxVariableOptions(toVariableIdentifier(variable))); }, - getSaveModel: variable => { - const { index, id, state, global, ...rest } = cloneDeep(variable); + getSaveModel: (variable, saveCurrentAsDefault) => { + const { index, id, state, global, originalQuery, ...rest } = cloneDeep(variable); + + if (variable.query !== originalQuery && !saveCurrentAsDefault) { + const origQuery = originalQuery ?? ''; + const current = { selected: false, text: origQuery, value: origQuery }; + return { ...rest, query: origQuery, current, options: [current] }; + } + return rest; }, getValueForUrl: variable => { return variable.current.value; }, + beforeAdding: model => { + return { ...cloneDeep(model), originalQuery: model.query }; + }, }; }; diff --git a/public/app/features/variables/textbox/reducer.ts b/public/app/features/variables/textbox/reducer.ts index ca962e87b08..bb4714ff55e 100644 --- a/public/app/features/variables/textbox/reducer.ts +++ b/public/app/features/variables/textbox/reducer.ts @@ -10,6 +10,7 @@ export const initialTextBoxVariableModelState: TextBoxVariableModel = { query: '', current: {} as VariableOption, options: [], + originalQuery: null, }; export const textBoxVariableSlice = createSlice({ diff --git a/public/app/features/variables/types.ts b/public/app/features/variables/types.ts index a466aa8d10c..2e3c916f62a 100644 --- a/public/app/features/variables/types.ts +++ b/public/app/features/variables/types.ts @@ -86,7 +86,9 @@ export interface QueryVariableModel extends DataSourceVariableModel { query: any; } -export interface TextBoxVariableModel extends VariableWithOptions {} +export interface TextBoxVariableModel extends VariableWithOptions { + originalQuery: string | null; +} export interface ConstantVariableModel extends VariableWithOptions {} From 2a61d7ff9f5a18bbb1956687c9b00e139959834b Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Wed, 2 Dec 2020 14:09:55 +0100 Subject: [PATCH 049/124] IntelliSense: Fix autocomplete and highlighting for Loki, Prometheus, Cloudwatch (#29381) * Pass languages directly to SlatePrism plugin * Update * Remove unused variables * Update packages/grafana-ui/src/components/DataLinks/DataLinkInput.tsx Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com> * Update public/app/plugins/datasource/prometheus/components/PromQueryField.tsx Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com> * Update public/app/plugins/datasource/prometheus/components/PromQueryField.tsx Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com> * Update public/app/plugins/datasource/loki/components/LokiQueryFieldForm.tsx Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com> * Update public/app/plugins/datasource/cloudwatch/components/LogsQueryField.tsx Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com> * Try Prism import instead of languages * Update webpack Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com> --- .../components/DataLinks/DataLinkInput.tsx | 28 +++++++++---------- .../src/slate-plugins/slate-prism/index.ts | 6 ++-- .../cloudwatch/components/LogsCheatSheet.tsx | 2 +- .../cloudwatch/components/LogsQueryField.tsx | 14 ++++++---- .../cloudwatch/useCloudwatchSyntax.ts | 5 +--- .../loki/components/LokiQueryFieldForm.tsx | 13 +++++---- .../loki/components/useLokiSyntaxAndLabels.ts | 5 +--- .../PromExploreQueryEditor.test.tsx | 6 +++- .../components/PromQueryField.test.tsx | 9 +++++- .../prometheus/components/PromQueryField.tsx | 15 +++++----- .../PromExploreQueryEditor.test.tsx.snap | 8 +++++- .../plugins/datasource/prometheus/promql.ts | 3 +- scripts/webpack/webpack.common.js | 3 ++ 13 files changed, 69 insertions(+), 48 deletions(-) diff --git a/packages/grafana-ui/src/components/DataLinks/DataLinkInput.tsx b/packages/grafana-ui/src/components/DataLinks/DataLinkInput.tsx index 821cde4c7ce..fa696622a1e 100644 --- a/packages/grafana-ui/src/components/DataLinks/DataLinkInput.tsx +++ b/packages/grafana-ui/src/components/DataLinks/DataLinkInput.tsx @@ -6,7 +6,7 @@ import { SelectionReference } from './SelectionReference'; import { Portal, getFormStyles } from '../index'; // @ts-ignore -import Prism from 'prismjs'; +import Prism, { Grammar, LanguageMap } from 'prismjs'; import { Editor } from '@grafana/slate-react'; import { Value } from 'slate'; import Plain from 'slate-plain-serializer'; @@ -27,11 +27,20 @@ interface DataLinkInputProps { placeholder?: string; } +const datalinksSyntax: Grammar = { + builtInVariable: { + pattern: /(\${\S+?})/, + }, +}; + const plugins = [ - SlatePrism({ - onlyIn: (node: any) => node.type === 'code_block', - getSyntax: () => 'links', - }), + SlatePrism( + { + onlyIn: (node: any) => node.type === 'code_block', + getSyntax: () => 'links', + }, + { ...(Prism.languages as LanguageMap), links: datalinksSyntax } + ), ]; const getStyles = stylesFactory((theme: GrafanaTheme) => ({ @@ -56,19 +65,10 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => ({ `, })); -export const enableDatalinksPrismSyntax = () => { - Prism.languages['links'] = { - builtInVariable: { - pattern: /(\${\S+?})/, - }, - }; -}; - // This memoised also because rerendering the slate editor grabs focus which created problem in some cases this // was used and changes to different state were propagated here. export const DataLinkInput: React.FC = memo( ({ value, onChange, suggestions, placeholder = 'http://your-grafana.com/d/000000010/annotations' }) => { - enableDatalinksPrismSyntax(); const editorRef = useRef() as RefObject; const theme = useContext(ThemeContext); const styles = getStyles(theme); diff --git a/packages/grafana-ui/src/slate-plugins/slate-prism/index.ts b/packages/grafana-ui/src/slate-plugins/slate-prism/index.ts index c54b1cefd70..41cf75bdba9 100644 --- a/packages/grafana-ui/src/slate-plugins/slate-prism/index.ts +++ b/packages/grafana-ui/src/slate-plugins/slate-prism/index.ts @@ -1,4 +1,4 @@ -import Prism from 'prismjs'; +import Prism, { LanguageMap } from 'prismjs'; import { Block, Text, Decoration } from 'slate'; import { Plugin } from '@grafana/slate-react'; import Options, { OptionsFormat } from './options'; @@ -19,7 +19,7 @@ export interface Token { /** * A Slate plugin to highlight code syntax. */ -export function SlatePrism(optsParam: OptionsFormat = {}): Plugin { +export function SlatePrism(optsParam: OptionsFormat = {}, prismLanguages = Prism.languages as LanguageMap): Plugin { const opts: Options = new Options(optsParam); return { @@ -30,7 +30,7 @@ export function SlatePrism(optsParam: OptionsFormat = {}): Plugin { const block = Block.create(node as Block); const grammarName = opts.getSyntax(block); - const grammar = Prism.languages[grammarName]; + const grammar = prismLanguages[grammarName]; if (!grammar) { // Grammar not loaded diff --git a/public/app/plugins/datasource/cloudwatch/components/LogsCheatSheet.tsx b/public/app/plugins/datasource/cloudwatch/components/LogsCheatSheet.tsx index af083254e67..cd636cbdbf0 100644 --- a/public/app/plugins/datasource/cloudwatch/components/LogsCheatSheet.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/LogsCheatSheet.tsx @@ -192,7 +192,7 @@ const CLIQ_EXAMPLES: QueryExample[] = [ ]; function renderHighlightedMarkup(code: string, keyPrefix: string) { - const grammar = Prism.languages['cloudwatch'] ?? tokenizer; + const grammar = tokenizer; const tokens = flattenTokens(Prism.tokenize(code, grammar)); const spans = tokens .filter(token => typeof token !== 'string') diff --git a/public/app/plugins/datasource/cloudwatch/components/LogsQueryField.tsx b/public/app/plugins/datasource/cloudwatch/components/LogsQueryField.tsx index f152a93e723..b40fdb4c6fa 100644 --- a/public/app/plugins/datasource/cloudwatch/components/LogsQueryField.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/LogsQueryField.tsx @@ -24,7 +24,7 @@ import syntax from '../syntax'; import { ExploreQueryFieldProps, AbsoluteTimeRange, SelectableValue, AppEvents } from '@grafana/data'; import { CloudWatchQuery, CloudWatchLogsQuery } from '../types'; import { CloudWatchDatasource } from '../datasource'; -import Prism, { Grammar } from 'prismjs'; +import { Grammar, LanguageMap, languages as prismLanguages } from 'prismjs'; import { CloudWatchLanguageProvider } from '../language_provider'; import { css } from 'emotion'; import { ExploreId } from 'app/types'; @@ -95,13 +95,15 @@ export class CloudWatchLogsQueryField extends React.PureComponent) { super(props, context); - Prism.languages['cloudwatch'] = syntax; this.plugins = [ BracesPlugin(), - SlatePrism({ - onlyIn: (node: Node) => node.object === 'block' && node.type === 'code_block', - getSyntax: (node: Node) => 'cloudwatch', - }), + SlatePrism( + { + onlyIn: (node: Node) => node.object === 'block' && node.type === 'code_block', + getSyntax: (node: Node) => 'cloudwatch', + }, + { ...(prismLanguages as LanguageMap), cloudwatch: syntax } + ), ]; } diff --git a/public/app/plugins/datasource/cloudwatch/useCloudwatchSyntax.ts b/public/app/plugins/datasource/cloudwatch/useCloudwatchSyntax.ts index 6fdb3a73a2c..5a982350f64 100644 --- a/public/app/plugins/datasource/cloudwatch/useCloudwatchSyntax.ts +++ b/public/app/plugins/datasource/cloudwatch/useCloudwatchSyntax.ts @@ -1,11 +1,9 @@ import { useState, useEffect } from 'react'; -import Prism, { Grammar } from 'prismjs'; +import { Grammar } from 'prismjs'; import { AbsoluteTimeRange } from '@grafana/data'; import { useRefMounted } from 'app/core/hooks/useRefMounted'; import { CloudWatchLanguageProvider } from './language_provider'; -const PRISM_SYNTAX = 'cloudwatch'; - /** * Initialise the language provider. Returns a languageProviderInitialized boolean cause there does not seem other way * to know if the provider is already initialised or not. By the initialisation it modifies the provided @@ -44,7 +42,6 @@ const useCloudwatchSyntax = (languageProvider: CloudWatchLanguageProvider, langu useEffect(() => { if (languageProviderInitialized) { const syntax = languageProvider.getSyntax(); - Prism.languages[PRISM_SYNTAX] = syntax; setSyntax(syntax); } }, [languageProviderInitialized, languageProvider]); diff --git a/public/app/plugins/datasource/loki/components/LokiQueryFieldForm.tsx b/public/app/plugins/datasource/loki/components/LokiQueryFieldForm.tsx index 763603e4659..a9d1dd7b56a 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryFieldForm.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryFieldForm.tsx @@ -20,7 +20,7 @@ import { Plugin, Node } from 'slate'; import { DOMUtil } from '@grafana/ui'; import { ExploreQueryFieldProps, AbsoluteTimeRange } from '@grafana/data'; import { LokiQuery, LokiOptions } from '../types'; -import { Grammar } from 'prismjs'; +import { Grammar, LanguageMap, languages as prismLanguages } from 'prismjs'; import LokiLanguageProvider, { LokiHistoryItem } from '../language_provider'; import LokiDatasource from '../datasource'; import LokiOptionFields from './LokiOptionFields'; @@ -82,10 +82,13 @@ export class LokiQueryFieldForm extends React.PureComponent node.object === 'block' && node.type === 'code_block', - getSyntax: (node: Node) => 'promql', - }), + SlatePrism( + { + onlyIn: (node: Node) => node.object === 'block' && node.type === 'code_block', + getSyntax: (node: Node) => 'logql', + }, + { ...(prismLanguages as LanguageMap), logql: this.props.datasource.languageProvider.getSyntax() } + ), ]; } diff --git a/public/app/plugins/datasource/loki/components/useLokiSyntaxAndLabels.ts b/public/app/plugins/datasource/loki/components/useLokiSyntaxAndLabels.ts index 19b66e22705..ac4f8384c9f 100644 --- a/public/app/plugins/datasource/loki/components/useLokiSyntaxAndLabels.ts +++ b/public/app/plugins/datasource/loki/components/useLokiSyntaxAndLabels.ts @@ -1,12 +1,10 @@ import { useState, useEffect } from 'react'; -import Prism, { Grammar } from 'prismjs'; +import { Grammar } from 'prismjs'; import { AbsoluteTimeRange } from '@grafana/data'; import LokiLanguageProvider from 'app/plugins/datasource/loki/language_provider'; import { useLokiLabels } from 'app/plugins/datasource/loki/components/useLokiLabels'; import { useRefMounted } from 'app/core/hooks/useRefMounted'; -const PRISM_SYNTAX = 'promql'; - /** * Initialise the language provider. Returns a languageProviderInitialized boolean cause there does not seem other way * to know if the provider is already initialised or not. By the initialisation it modifies the provided @@ -45,7 +43,6 @@ const useLokiSyntax = (languageProvider: LokiLanguageProvider, languageProviderI useEffect(() => { if (languageProviderInitialized) { const syntax = languageProvider.getSyntax(); - Prism.languages[PRISM_SYNTAX] = syntax; setSyntax(syntax); } }, [languageProviderInitialized, languageProvider]); diff --git a/public/app/plugins/datasource/prometheus/components/PromExploreQueryEditor.test.tsx b/public/app/plugins/datasource/prometheus/components/PromExploreQueryEditor.test.tsx index 50777e801af..6501fe7f9a1 100644 --- a/public/app/plugins/datasource/prometheus/components/PromExploreQueryEditor.test.tsx +++ b/public/app/plugins/datasource/prometheus/components/PromExploreQueryEditor.test.tsx @@ -7,7 +7,11 @@ import { PromQuery } from '../types'; import { LoadingState, PanelData, toUtc, TimeRange } from '@grafana/data'; const setup = (renderMethod: any, propOverrides?: object) => { - const datasourceMock: unknown = {}; + const datasourceMock: unknown = { + languageProvider: { + syntax: () => {}, + }, + }; const datasource: PrometheusDatasource = datasourceMock as PrometheusDatasource; const onRunQuery = jest.fn(); const onChange = jest.fn(); diff --git a/public/app/plugins/datasource/prometheus/components/PromQueryField.test.tsx b/public/app/plugins/datasource/prometheus/components/PromQueryField.test.tsx index 3bc79ea44d0..07175ecd17b 100644 --- a/public/app/plugins/datasource/prometheus/components/PromQueryField.test.tsx +++ b/public/app/plugins/datasource/prometheus/components/PromQueryField.test.tsx @@ -17,6 +17,7 @@ describe('PromQueryField', () => { const datasource = ({ languageProvider: { start: () => Promise.resolve([]), + syntax: () => {}, }, } as unknown) as DataSourceInstanceSettings; @@ -35,10 +36,16 @@ describe('PromQueryField', () => { }); it('renders a disabled metrics chooser if lookups are disabled in datasource settings', () => { + const datasource = ({ + languageProvider: { + start: () => Promise.resolve([]), + syntax: () => {}, + }, + } as unknown) as DataSourceInstanceSettings; const queryField = render( {}} onChange={() => {}} diff --git a/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx b/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx index b94322277a6..16ca7b0f20c 100644 --- a/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx +++ b/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx @@ -12,7 +12,7 @@ import { BracesPlugin, } from '@grafana/ui'; -import Prism from 'prismjs'; +import { LanguageMap, languages as prismLanguages } from 'prismjs'; // dom also includes Element polyfills import { PromQuery, PromOptions, PromMetricsMetadata } from '../types'; @@ -22,7 +22,6 @@ import { DOMUtil, SuggestionsState } from '@grafana/ui'; import { PrometheusDatasource } from '../datasource'; const HISTOGRAM_GROUP = '__histograms__'; -const PRISM_SYNTAX = 'promql'; export const RECORDING_RULES_GROUP = '__recording_rules__'; function getChooserText(metricsLookupDisabled: boolean, hasSyntax: boolean, metrics: string[]) { @@ -133,10 +132,13 @@ class PromQueryField extends React.PureComponent node.type === 'code_block', - getSyntax: (node: any) => 'promql', - }), + SlatePrism( + { + onlyIn: (node: any) => node.type === 'code_block', + getSyntax: (node: any) => 'promql', + }, + { ...(prismLanguages as LanguageMap), promql: this.props.datasource.languageProvider.syntax } + ), ]; this.state = { @@ -222,7 +224,6 @@ class PromQueryField extends React.PureComponent Date: Wed, 2 Dec 2020 14:21:39 +0100 Subject: [PATCH 050/124] Graph NG: Invalidate uPlot config on timezone changes (#29531) --- packages/grafana-ui/src/components/GraphNG/GraphNG.tsx | 2 +- packages/grafana-ui/src/components/uPlot/hooks.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx b/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx index 8ae6f641c86..411d7073b92 100755 --- a/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx +++ b/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx @@ -160,7 +160,7 @@ export const GraphNG: React.FC = ({ legendItemsRef.current = legendItems; return builder; - }, [configRev]); + }, [configRev, timeZone]); if (alignedFrameWithGapTest == null) { return ( diff --git a/packages/grafana-ui/src/components/uPlot/hooks.ts b/packages/grafana-ui/src/components/uPlot/hooks.ts index 1cb0e07dd1d..afa9a363534 100644 --- a/packages/grafana-ui/src/components/uPlot/hooks.ts +++ b/packages/grafana-ui/src/components/uPlot/hooks.ts @@ -105,7 +105,6 @@ export const DEFAULT_PLOT_CONFIG = { hooks: {}, }; -//pass plain confsig object,memoize! export const usePlotConfig = (width: number, height: number, timeZone: TimeZone, configBuilder: UPlotConfigBuilder) => { const { arePluginsReady, plugins, registerPlugin } = usePlotPlugins(); const [isConfigReady, setIsConfigReady] = useState(false); From b8fec209a93ccf328227303fbcbe65d902a4041e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Wed, 2 Dec 2020 15:16:57 +0100 Subject: [PATCH 051/124] Chore: skip flaky tests (#29537) --- e2e/suite1/specs/variables/textbox-variables.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/e2e/suite1/specs/variables/textbox-variables.ts b/e2e/suite1/specs/variables/textbox-variables.ts index f85c5809d88..185aadcb275 100644 --- a/e2e/suite1/specs/variables/textbox-variables.ts +++ b/e2e/suite1/specs/variables/textbox-variables.ts @@ -36,7 +36,7 @@ describe('TextBox - load options scenarios', function() { }); }); -describe('TextBox - change query scenarios', function() { +describe.skip('TextBox - change query scenarios', function() { it('when changing the query value and not saving current as default should revert query value', function() { copyExistingDashboard(); @@ -94,7 +94,7 @@ describe('TextBox - change query scenarios', function() { }); }); -describe('TextBox - change picker value scenarios', function() { +describe.skip('TextBox - change picker value scenarios', function() { it('when changing the input value and not saving current as default should revert query value', function() { copyExistingDashboard(); From 068fef8c7cbc492be919d650710b9249a66708fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 2 Dec 2020 15:42:54 +0100 Subject: [PATCH 052/124] QueryEditors: Refactoring & rewriting out dependency on PanelModel (#29419) * Removing PanelModel usage from query rows * More work removing dependency on panel model * Before big change to query options * Query options now have no dependency on panel model * Test page is working * Shared query now works again * Rename component * fix after merge * Fixed issue with old angular editors --- .../PanelEditor/PanelEditorQueries.tsx | 94 ++++++++- .../PanelEditor/PanelEditorTabs.tsx | 2 +- .../features/dashboard/state/PanelModel.ts | 4 +- .../query/components/QueryEditorRow.tsx | 28 +-- .../query/components/QueryEditorRows.tsx | 40 +--- .../{QueriesTab.tsx => QueryGroup.tsx} | 141 ++++++------- ...QueryOptions.tsx => QueryGroupOptions.tsx} | 198 ++++++++++-------- .../features/query/state/PanelQueryRunner.ts | 2 +- public/app/features/query/state/types.ts | 5 + public/app/features/sandbox/TestStuffPage.tsx | 116 ++++++++-- .../dashboard/DashboardQueryEditor.tsx | 20 +- 11 files changed, 409 insertions(+), 241 deletions(-) rename public/app/features/query/components/{QueriesTab.tsx => QueryGroup.tsx} (72%) rename public/app/features/query/components/{QueryOptions.tsx => QueryGroupOptions.tsx} (68%) create mode 100644 public/app/features/query/state/types.ts diff --git a/public/app/features/dashboard/components/PanelEditor/PanelEditorQueries.tsx b/public/app/features/dashboard/components/PanelEditor/PanelEditorQueries.tsx index 2e974c3ede6..dfe8f45e0d1 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelEditorQueries.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelEditorQueries.tsx @@ -1,16 +1,96 @@ import React, { PureComponent } from 'react'; -import { QueriesTab } from 'app/features/query/components/QueriesTab'; -import { DashboardModel, PanelModel } from '../../state'; +import { QueryGroup } from 'app/features/query/components/QueryGroup'; +import { QueryGroupOptions } from 'app/features/query/components/QueryGroupOptions'; +import { PanelModel } from '../../state'; +import { DataQuery, DataSourceSelectItem } from '@grafana/data'; +import { getLocationSrv } from '@grafana/runtime'; interface Props { panel: PanelModel; - dashboard: DashboardModel; } -export class PanelEditorQueries extends PureComponent { - render() { - const { panel, dashboard } = this.props; +interface State { + options: QueryGroupOptions; +} - return ; +export class PanelEditorQueries extends PureComponent { + constructor(props: Props) { + super(props); + + this.state = { options: this.buildQueryOptions(props) }; + } + + buildQueryOptions({ panel }: Props): QueryGroupOptions { + return { + maxDataPoints: panel.maxDataPoints, + minInterval: panel.interval, + timeRange: { + from: panel.timeFrom, + shift: panel.timeShift, + hide: panel.hideTimeOverride, + }, + }; + } + + onDataSourceChange = (ds: DataSourceSelectItem, queries: DataQuery[]) => { + const { panel } = this.props; + + panel.datasource = ds.value; + panel.targets = queries; + panel.refresh(); + + this.forceUpdate(); + }; + + onRunQueries = () => { + this.props.panel.refresh(); + }; + + onQueriesChange = (queries: DataQuery[]) => { + const { panel } = this.props; + + panel.targets = queries; + panel.refresh(); + + this.forceUpdate(); + }; + + onOpenQueryInspector = () => { + getLocationSrv().update({ + query: { inspect: this.props.panel.id, inspectTab: 'query' }, + partial: true, + }); + }; + + onQueryOptionsChange = (options: QueryGroupOptions) => { + const { panel } = this.props; + + panel.timeFrom = options.timeRange?.from; + panel.timeShift = options.timeRange?.shift; + panel.hideTimeOverride = options.timeRange?.hide; + panel.interval = options.minInterval; + panel.maxDataPoints = options.maxDataPoints; + panel.refresh(); + + this.setState({ options: options }); + }; + + render() { + const { panel } = this.props; + const { options } = this.state; + + return ( + + ); } } diff --git a/public/app/features/dashboard/components/PanelEditor/PanelEditorTabs.tsx b/public/app/features/dashboard/components/PanelEditor/PanelEditorTabs.tsx index 944cffda809..996b6c11d98 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelEditorTabs.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelEditorTabs.tsx @@ -76,7 +76,7 @@ export class PanelEditorTabs extends PureComponent { })} - {activeTab.id === PanelEditorTabId.Query && } + {activeTab.id === PanelEditorTabId.Query && } {activeTab.id === PanelEditorTabId.Alert && } {activeTab.id === PanelEditorTabId.Transform && } diff --git a/public/app/features/dashboard/state/PanelModel.ts b/public/app/features/dashboard/state/PanelModel.ts index 36e7525749b..da160c4ac97 100644 --- a/public/app/features/dashboard/state/PanelModel.ts +++ b/public/app/features/dashboard/state/PanelModel.ts @@ -132,8 +132,8 @@ export class PanelModel implements DataConfigSource { }; fieldConfig: FieldConfigSource; - maxDataPoints?: number; - interval?: string; + maxDataPoints?: number | null; + interval?: string | null; description?: string; links?: DataLink[]; transparent: boolean; diff --git a/public/app/features/query/components/QueryEditorRow.tsx b/public/app/features/query/components/QueryEditorRow.tsx index 37ce76f2dd4..ae3423136c4 100644 --- a/public/app/features/query/components/QueryEditorRow.tsx +++ b/public/app/features/query/components/QueryEditorRow.tsx @@ -6,9 +6,6 @@ import _ from 'lodash'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { AngularComponent, getAngularLoader } from '@grafana/runtime'; import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; -// Types -import { PanelModel } from '../../dashboard/state/PanelModel'; - import { ErrorBoundaryAlert, HorizontalGroup } from '@grafana/ui'; import { DataQuery, @@ -25,12 +22,11 @@ import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOp import { QueryOperationAction } from 'app/core/components/QueryOperationRow/QueryOperationAction'; import { DashboardModel } from '../../dashboard/state/DashboardModel'; import { selectors } from '@grafana/e2e-selectors'; +import { PanelModel } from 'app/features/dashboard/state'; interface Props { - panel: PanelModel; data: PanelData; query: DataQuery; - dashboard?: DashboardModel; dataSourceValue: string | null; inMixedMode?: boolean; id: string; @@ -38,6 +34,7 @@ interface Props { onAddQuery: (query?: DataQuery) => void; onRemoveQuery: (query: DataQuery) => void; onChange: (query: DataQuery) => void; + onRunQuery: () => void; } interface State { @@ -72,15 +69,20 @@ export class QueryEditorRow extends PureComponent { } getAngularQueryComponentScope(): AngularQueryComponentScope { - const { panel, query, dashboard } = this.props; + const { query, onChange } = this.props; const { datasource } = this.state; + const panel = new PanelModel({}); + const dashboard = {} as DashboardModel; return { datasource: datasource, target: query, panel: panel, - dashboard: dashboard!, - refresh: () => panel.refresh(), + dashboard: dashboard, + refresh: () => { + // Old angular editors modify the query model and just call refresh + onChange(query); + }, render: () => () => console.log('legacy render function called, it does nothing'), events: panel.events, range: getTimeSrv().timeRange(), @@ -88,12 +90,12 @@ export class QueryEditorRow extends PureComponent { } async loadDatasource() { - const { query, panel, dataSourceValue } = this.props; + const { query, dataSourceValue } = this.props; const dataSourceSrv = getDatasourceSrv(); let datasource; try { - const datasourceName = dataSourceValue || query.datasource || panel.datasource; + const datasourceName = dataSourceValue || query.datasource; datasource = await dataSourceSrv.get(datasourceName); } catch (error) { datasource = await dataSourceSrv.get(); @@ -108,7 +110,7 @@ export class QueryEditorRow extends PureComponent { componentDidUpdate(prevProps: Props) { const { loadedDataSourceValue } = this.state; - const { data, query, panel } = this.props; + const { data, query } = this.props; if (data !== prevProps.data) { this.setState({ data: filterPanelDataToQuery(data, query.refId) }); @@ -118,7 +120,7 @@ export class QueryEditorRow extends PureComponent { } if (this.angularQueryEditor) { - notifyAngularQueryEditorsOfData(panel, data, this.angularQueryEditor); + notifyAngularQueryEditorsOfData(this.angularScope?.panel!, data, this.angularQueryEditor); } } @@ -161,7 +163,7 @@ export class QueryEditorRow extends PureComponent { }; onRunQuery = () => { - this.props.panel.refresh(); + this.props.onRunQuery(); }; renderPluginEditor = () => { diff --git a/public/app/features/query/components/QueryEditorRows.tsx b/public/app/features/query/components/QueryEditorRows.tsx index 008a4e92374..09d43dcacef 100644 --- a/public/app/features/query/components/QueryEditorRows.tsx +++ b/public/app/features/query/components/QueryEditorRows.tsx @@ -2,11 +2,8 @@ import React, { PureComponent } from 'react'; // Types -import { PanelModel } from '../../dashboard/state/PanelModel'; import { DataQuery, PanelData, DataSourceSelectItem } from '@grafana/data'; -import { DashboardModel } from '../../dashboard/state/DashboardModel'; import { QueryEditorRow } from './QueryEditorRow'; -import { addQuery } from 'app/core/utils/query'; import { DragDropContext, Droppable, DropResult } from 'react-beautiful-dnd'; interface Props { @@ -15,35 +12,21 @@ interface Props { datasource: DataSourceSelectItem; // Query editing - onChangeQueries: (queries: DataQuery[]) => void; - onScrollBottom: () => void; - - // Dashboard Configs - panel: PanelModel; - dashboard?: DashboardModel; + onQueriesChange: (queries: DataQuery[]) => void; + onAddQuery: (query: DataQuery) => void; + onRunQueries: () => void; // Query Response Data data: PanelData; } export class QueryEditorRows extends PureComponent { - onAddQuery = (query?: Partial) => { - const { queries, onChangeQueries } = this.props; - onChangeQueries(addQuery(queries, query)); - this.props.onScrollBottom(); - }; - onRemoveQuery = (query: DataQuery) => { - const { queries, onChangeQueries, panel } = this.props; - const removed = queries.filter(q => { - return q !== query; - }); - onChangeQueries(removed); - panel.refresh(); + this.props.onQueriesChange(this.props.queries.filter(item => item !== query)); }; onChangeQuery(query: DataQuery, index: number) { - const { queries, onChangeQueries } = this.props; + const { queries, onQueriesChange } = this.props; const old = queries[index]; @@ -54,7 +37,7 @@ export class QueryEditorRows extends PureComponent { } // update query in array - onChangeQueries( + onQueriesChange( queries.map((item, itemIndex) => { if (itemIndex === index) { return query; @@ -65,7 +48,7 @@ export class QueryEditorRows extends PureComponent { } onDragEnd = (result: DropResult) => { - const { queries, onChangeQueries, panel } = this.props; + const { queries, onQueriesChange } = this.props; if (!result || !result.destination) { return; @@ -80,12 +63,12 @@ export class QueryEditorRows extends PureComponent { const update = Array.from(queries); const [removed] = update.splice(startIndex, 1); update.splice(endIndex, 0, removed); - onChangeQueries(update); - panel.refresh(); + onQueriesChange(update); }; render() { const { props } = this; + return ( @@ -98,13 +81,12 @@ export class QueryEditorRows extends PureComponent { id={query.refId} index={index} key={query.refId} - panel={props.panel} - dashboard={props.dashboard} data={props.data} query={query} onChange={query => this.onChangeQuery(query, index)} onRemoveQuery={this.onRemoveQuery} - onAddQuery={this.onAddQuery} + onAddQuery={this.props.onAddQuery} + onRunQuery={this.props.onRunQueries} inMixedMode={props.datasource.meta.mixed} /> ))} diff --git a/public/app/features/query/components/QueriesTab.tsx b/public/app/features/query/components/QueryGroup.tsx similarity index 72% rename from public/app/features/query/components/QueriesTab.tsx rename to public/app/features/query/components/QueryGroup.tsx index a292d6419b0..14086ec55a9 100644 --- a/public/app/features/query/components/QueriesTab.tsx +++ b/public/app/features/query/components/QueryGroup.tsx @@ -2,17 +2,14 @@ import React, { PureComponent } from 'react'; // Components import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker'; -import { QueryOptions } from './QueryOptions'; import { Button, CustomScrollbar, HorizontalGroup, Modal, stylesFactory, Field } from '@grafana/ui'; -import { getLocationSrv, getDataSourceSrv } from '@grafana/runtime'; +import { getDataSourceSrv } from '@grafana/runtime'; import { QueryEditorRows } from './QueryEditorRows'; // Services import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { backendSrv } from 'app/core/services/backend_srv'; import config from 'app/core/config'; // Types -import { PanelModel } from '../../dashboard/state/PanelModel'; -import { DashboardModel } from '../../dashboard/state/DashboardModel'; import { DataQuery, DataSourceSelectItem, @@ -24,14 +21,23 @@ import { import { PluginHelp } from 'app/core/components/PluginHelp/PluginHelp'; import { addQuery } from 'app/core/utils/query'; import { Unsubscribable } from 'rxjs'; -import { DashboardQueryEditor, isSharedDashboardQuery } from 'app/plugins/datasource/dashboard'; import { expressionDatasource, ExpressionDatasourceID } from 'app/features/expressions/ExpressionDatasource'; -import { css } from 'emotion'; import { selectors } from '@grafana/e2e-selectors'; +import { PanelQueryRunner } from '../state/PanelQueryRunner'; +import { QueryGroupOptions, QueryGroupOptionsEditor } from './QueryGroupOptions'; +import { DashboardQueryEditor, isSharedDashboardQuery } from 'app/plugins/datasource/dashboard'; +import { css } from 'emotion'; interface Props { - panel: PanelModel; - dashboard?: DashboardModel; + queryRunner: PanelQueryRunner; + queries: DataQuery[]; + dataSourceName: string | null; + options: QueryGroupOptions; + onOpenQueryInspector?: () => void; + onRunQueries: () => void; + onQueriesChange: (queries: DataQuery[]) => void; + onDataSourceChange: (ds: DataSourceSelectItem, queries: DataQuery[]) => void; + onOptionsChange: (options: QueryGroupOptions) => void; } interface State { @@ -47,14 +53,14 @@ interface State { isHelpOpen: boolean; } -export class QueriesTab extends PureComponent { +export class QueryGroup extends PureComponent { datasources: DataSourceSelectItem[] = getDatasourceSrv().getMetricSources(); backendSrv = backendSrv; querySubscription: Unsubscribable | null; state: State = { isLoadingHelp: false, - dataSourceItem: this.findCurrentDataSource(), + dataSourceItem: this.findCurrentDataSource(this.props.dataSourceName), helpContent: null, isPickerOpen: false, isAddingMixed: false, @@ -68,15 +74,14 @@ export class QueriesTab extends PureComponent { }; async componentDidMount() { - const { panel } = this.props; - const queryRunner = panel.getQueryRunner(); + const { queryRunner, dataSourceName: datasourceName } = this.props; this.querySubscription = queryRunner.getData({ withTransforms: false, withFieldConfig: false }).subscribe({ next: (data: PanelData) => this.onPanelDataUpdate(data), }); try { - const ds = await getDataSourceSrv().get(panel.datasource); + const ds = await getDataSourceSrv().get(datasourceName); this.setState({ dataSource: ds }); } catch (error) { const ds = await getDataSourceSrv().get(); @@ -96,85 +101,62 @@ export class QueriesTab extends PureComponent { this.setState({ data }); } - findCurrentDataSource(dataSourceName: string | null = this.props.panel.datasource): DataSourceSelectItem { + findCurrentDataSource(dataSourceName: string | null): DataSourceSelectItem { return this.datasources.find(datasource => datasource.value === dataSourceName) || this.datasources[0]; } onChangeDataSource = async (newDsItem: DataSourceSelectItem) => { - const { panel } = this.props; + let { queries } = this.props; const { dataSourceItem } = this.state; // switching to mixed if (newDsItem.meta.mixed) { - // Set the datasource on all targets - panel.targets.forEach(target => { - if (target.datasource !== ExpressionDatasourceID) { - target.datasource = panel.datasource; - if (!target.datasource) { - target.datasource = config.defaultDatasource; + for (const query of queries) { + if (query.datasource !== ExpressionDatasourceID) { + query.datasource = query.datasource; + if (!query.datasource) { + query.datasource = config.defaultDatasource; } } - }); + } } else if (dataSourceItem) { // if switching from mixed if (dataSourceItem.meta.mixed) { // Remove the explicit datasource - for (const target of panel.targets) { - if (target.datasource !== ExpressionDatasourceID) { - delete target.datasource; + for (const query of queries) { + if (query.datasource !== ExpressionDatasourceID) { + delete query.datasource; } } } else if (dataSourceItem.meta.id !== newDsItem.meta.id) { // we are changing data source type, clear queries - panel.targets = [{ refId: 'A' }]; + queries = [{ refId: 'A' }]; } } const dataSource = await getDataSourceSrv().get(newDsItem.value); - panel.datasource = newDsItem.value; + this.props.onDataSourceChange(newDsItem, queries); - this.setState( - { - dataSourceItem: newDsItem, - dataSource: dataSource, - dataSourceError: undefined, - }, - () => panel.refresh() - ); - }; - - openQueryInspector = () => { - const { panel } = this.props; - - getLocationSrv().update({ - query: { inspect: panel.id, inspectTab: 'query' }, - partial: true, + this.setState({ + dataSourceItem: newDsItem, + dataSource: dataSource, + dataSourceError: undefined, }); }; - /** - * Sets the queries for the panel - */ - onUpdateQueries = (queries: DataQuery[]) => { - this.props.panel.updateQueries(queries); - - // Need to force update to rerender query rows. - this.forceUpdate(); - }; - onAddQueryClick = () => { if (this.state.dataSourceItem.meta.mixed) { this.setState({ isAddingMixed: true }); return; } - this.onUpdateQueries(addQuery(this.props.panel.targets)); + this.props.onQueriesChange(addQuery(this.props.queries)); this.onScrollBottom(); }; onAddExpressionClick = () => { - this.onUpdateQueries(addQuery(this.props.panel.targets, expressionDatasource.newQuery())); + this.props.onQueriesChange(addQuery(this.props.queries, expressionDatasource.newQuery())); this.onScrollBottom(); }; @@ -183,8 +165,8 @@ export class QueriesTab extends PureComponent { }; renderTopSection(styles: QueriesTabStyls) { - const { panel } = this.props; - const { dataSourceItem, data, dataSource, dataSourceError } = this.state; + const { onOpenQueryInspector, options, onOptionsChange } = this.props; + const { dataSourceItem, dataSource, dataSourceError, data } = this.state; if (!dataSource) { return null; @@ -211,17 +193,19 @@ export class QueriesTab extends PureComponent { />
- -
-
- +
+ {onOpenQueryInspector && ( +
+ +
+ )}
); @@ -253,18 +237,18 @@ export class QueriesTab extends PureComponent { }; onAddMixedQuery = (datasource: any) => { - this.props.panel.targets = addQuery(this.props.panel.targets, { datasource: datasource.name }); + this.onAddQuery({ datasource: datasource.name }); this.setState({ isAddingMixed: false, scrollTop: this.state.scrollTop + 10000 }); - this.forceUpdate(); }; onMixedPickerBlur = () => { this.setState({ isAddingMixed: false }); }; - onQueryChange = (query: DataQuery, index: number) => { - this.props.panel.changeQuery(query, index); - this.forceUpdate(); + onAddQuery = (query: Partial) => { + const { queries, onQueriesChange } = this.props; + onQueriesChange(addQuery(queries, query)); + this.onScrollBottom(); }; setScrollTop = (event: React.MouseEvent) => { @@ -273,22 +257,21 @@ export class QueriesTab extends PureComponent { }; renderQueries() { - const { panel, dashboard } = this.props; + const { onQueriesChange, queries, onRunQueries } = this.props; const { dataSourceItem, data } = this.state; if (isSharedDashboardQuery(dataSourceItem.name)) { - return this.onUpdateQueries([query])} />; + return ; } return (
diff --git a/public/app/features/query/components/QueryOptions.tsx b/public/app/features/query/components/QueryGroupOptions.tsx similarity index 68% rename from public/app/features/query/components/QueryOptions.tsx rename to public/app/features/query/components/QueryGroupOptions.tsx index 0d820a682c4..dc0e27bbda1 100644 --- a/public/app/features/query/components/QueryOptions.tsx +++ b/public/app/features/query/components/QueryGroupOptions.tsx @@ -8,51 +8,47 @@ import { rangeUtil, PanelData, DataSourceApi } from '@grafana/data'; import { Switch, Input, InlineField, InlineFormLabel, stylesFactory } from '@grafana/ui'; // Types -import { PanelModel } from '../../dashboard/state'; import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOperationRow'; import { config } from 'app/core/config'; import { css } from 'emotion'; -const timeRangeValidation = (value: string) => { - if (!value) { - return true; - } - return rangeUtil.isValidTimeSpan(value); -}; - -const emptyToNull = (value: string) => { - return value === '' ? null : value; -}; +export interface QueryGroupOptions { + maxDataPoints?: number | null; + minInterval?: string | null; + cacheTimeout?: string | null; + timeRange?: { + from?: string | null; + shift?: string | null; + hide?: boolean; + }; +} interface Props { - panel: PanelModel; + options: QueryGroupOptions; dataSource: DataSourceApi; data: PanelData; + onChange: (options: QueryGroupOptions) => void; } interface State { - relativeTime: string; - timeShift: string; - cacheTimeout: string; - maxDataPoints: number | string; - interval: string; - hideTimeOverride: boolean; + timeRangeFrom: string; + timeRangeShift: string; + timeRangeHide: boolean; isOpen: boolean; relativeTimeIsValid: boolean; timeShiftIsValid: boolean; } -export class QueryOptions extends PureComponent { +export class QueryGroupOptionsEditor extends PureComponent { constructor(props: Props) { super(props); + const { options } = props; + this.state = { - relativeTime: props.panel.timeFrom || '', - timeShift: props.panel.timeShift || '', - cacheTimeout: props.panel.cacheTimeout || '', - maxDataPoints: props.panel.maxDataPoints ?? '', - interval: props.panel.interval || '', - hideTimeOverride: props.panel.hideTimeOverride || false, + timeRangeFrom: options.timeRange?.from || '', + timeRangeShift: options.timeRange?.shift || '', + timeRangeHide: options.timeRange?.hide ?? false, isOpen: false, relativeTimeIsValid: true, timeShiftIsValid: true, @@ -61,79 +57,103 @@ export class QueryOptions extends PureComponent { onRelativeTimeChange = (event: ChangeEvent) => { this.setState({ - relativeTime: event.target.value, + timeRangeFrom: event.target.value, }); }; onTimeShiftChange = (event: ChangeEvent) => { this.setState({ - timeShift: event.target.value, + timeRangeShift: event.target.value, }); }; onOverrideTime = (event: FocusEvent) => { - const { value } = event.target; - const { panel } = this.props; - const emptyToNullValue = emptyToNull(value); - const isValid = timeRangeValidation(value); + const { options, onChange } = this.props; - if (isValid && panel.timeFrom !== emptyToNullValue) { - panel.timeFrom = emptyToNullValue; - panel.refresh(); + const newValue = emptyToNull(event.target.value); + const isValid = timeRangeValidation(newValue); + + if (isValid && options.timeRange?.from !== newValue) { + onChange({ + ...options, + timeRange: { + ...(options.timeRange ?? {}), + from: newValue, + }, + }); } + this.setState({ relativeTimeIsValid: isValid }); }; onTimeShift = (event: FocusEvent) => { - const { value } = event.target; - const { panel } = this.props; - const emptyToNullValue = emptyToNull(value); - const isValid = timeRangeValidation(value); + const { options, onChange } = this.props; - if (isValid && panel.timeShift !== emptyToNullValue) { - panel.timeShift = emptyToNullValue; - panel.refresh(); + const newValue = emptyToNull(event.target.value); + const isValid = timeRangeValidation(newValue); + + if (isValid && options.timeRange?.shift !== newValue) { + onChange({ + ...options, + timeRange: { + ...(options.timeRange ?? {}), + shift: newValue, + }, + }); } + this.setState({ timeShiftIsValid: isValid }); }; onToggleTimeOverride = () => { - const { panel } = this.props; - this.setState({ hideTimeOverride: !this.state.hideTimeOverride }, () => { - panel.hideTimeOverride = this.state.hideTimeOverride; - panel.refresh(); + const { onChange, options } = this.props; + + this.setState({ timeRangeHide: !this.state.timeRangeHide }, () => { + onChange({ + ...options, + timeRange: { + ...(options.timeRange ?? {}), + hide: this.state.timeRangeHide, + }, + }); }); }; - onDataSourceOptionBlur = (panelKey: string) => () => { - const { panel } = this.props; - - // @ts-ignore - panel[panelKey] = this.state[panelKey]; - panel.refresh(); + onCacheTimeoutBlur = (event: ChangeEvent) => { + const { options, onChange } = this.props; + onChange({ + ...options, + cacheTimeout: emptyToNull(event.target.value), + }); }; - onDataSourceOptionChange = (panelKey: string) => (event: ChangeEvent) => { - this.setState({ ...this.state, [panelKey]: event.target.value }); - }; + onMaxDataPointsBlur = (event: ChangeEvent) => { + const { options, onChange } = this.props; - onMaxDataPointsBlur = () => { - const { panel } = this.props; + let maxDataPoints: number | null = parseInt(event.target.value as string, 10); - const maxDataPoints = parseInt(this.state.maxDataPoints as string, 10); - - if (isNaN(maxDataPoints)) { - delete panel.maxDataPoints; - } else { - panel.maxDataPoints = maxDataPoints; + if (isNaN(maxDataPoints) || maxDataPoints === 0) { + maxDataPoints = null; } - panel.refresh(); + onChange({ + ...options, + maxDataPoints: maxDataPoints, + }); + }; + + onMinIntervalBlur = (event: ChangeEvent) => { + const { options, onChange } = this.props; + + onChange({ + ...options, + minInterval: emptyToNull(event.target.value), + }); }; renderCacheTimeoutOption() { - const { dataSource } = this.props; - const { cacheTimeout } = this.state; + const { dataSource, options } = this.props; + const tooltip = `If your time series store has a query cache this option can override the default cache timeout. Specify a numeric value in seconds.`; @@ -153,9 +173,8 @@ export class QueryOptions extends PureComponent { placeholder="60" name={name} spellCheck={false} - onBlur={this.onDataSourceOptionBlur('cacheTimeout')} - onChange={this.onDataSourceOptionChange('cacheTimeout')} - value={cacheTimeout} + onBlur={this.onCacheTimeoutBlur} + defaultValue={options.cacheTimeout ?? ''} /> @@ -163,10 +182,10 @@ export class QueryOptions extends PureComponent { } renderMaxDataPointsOption() { - const { data } = this.props; - const { maxDataPoints } = this.state; + const { data, options } = this.props; const realMd = data.request?.maxDataPoints; - const isAuto = maxDataPoints === ''; + const value = options.maxDataPoints ?? ''; + const isAuto = value === ''; return (
@@ -189,8 +208,7 @@ export class QueryOptions extends PureComponent { name={name} spellCheck={false} onBlur={this.onMaxDataPointsBlur} - onChange={this.onDataSourceOptionChange('maxDataPoints')} - value={maxDataPoints} + defaultValue={value} /> {isAuto && ( <> @@ -204,8 +222,7 @@ export class QueryOptions extends PureComponent { } renderIntervalOption() { - const { data, dataSource } = this.props; - const { interval } = this.state; + const { data, dataSource, options } = this.props; const realInterval = data.request?.interval; const minIntervalOnDs = dataSource.interval ?? 'No limit'; @@ -231,9 +248,8 @@ export class QueryOptions extends PureComponent { placeholder={`${minIntervalOnDs}`} name={name} spellCheck={false} - onBlur={this.onDataSourceOptionBlur('interval')} - onChange={this.onDataSourceOptionChange('interval')} - value={interval} + onBlur={this.onMinIntervalBlur} + defaultValue={options.minInterval ?? ''} />
@@ -268,19 +284,19 @@ export class QueryOptions extends PureComponent { }; renderCollapsedText(styles: StylesType): React.ReactNode | undefined { - const { data } = this.props; - const { isOpen, maxDataPoints, interval } = this.state; + const { data, options } = this.props; + const { isOpen } = this.state; if (isOpen) { return undefined; } - let mdDesc = maxDataPoints; - if (maxDataPoints === '' && data.request) { + let mdDesc = options.maxDataPoints ?? ''; + if (mdDesc === '' && data.request) { mdDesc = `auto = ${data.request.maxDataPoints}`; } - let intervalDesc = interval; + let intervalDesc = options.minInterval; if (data.request) { intervalDesc = `${data.request.interval}`; } @@ -294,8 +310,8 @@ export class QueryOptions extends PureComponent { } render() { - const { hideTimeOverride, relativeTimeIsValid, timeShiftIsValid } = this.state; - const { relativeTime, timeShift, isOpen } = this.state; + const { timeRangeHide: hideTimeOverride, relativeTimeIsValid, timeShiftIsValid } = this.state; + const { timeRangeFrom: relativeTime, timeRangeShift: timeShift, isOpen } = this.state; const styles = getStyles(); return ( @@ -349,6 +365,18 @@ export class QueryOptions extends PureComponent { } } +const timeRangeValidation = (value: string | null) => { + if (!value) { + return true; + } + + return rangeUtil.isValidTimeSpan(value); +}; + +const emptyToNull = (value: string) => { + return value === '' ? null : value; +}; + const getStyles = stylesFactory(() => { const { theme } = config; diff --git a/public/app/features/query/state/PanelQueryRunner.ts b/public/app/features/query/state/PanelQueryRunner.ts index 7aa8daac174..ddc050d7f00 100644 --- a/public/app/features/query/state/PanelQueryRunner.ts +++ b/public/app/features/query/state/PanelQueryRunner.ts @@ -34,7 +34,7 @@ export interface QueryRunnerOptions< > { datasource: string | DataSourceApi | null; queries: TQuery[]; - panelId: number; + panelId?: number; dashboardId?: number; timezone: TimeZone; timeRange: TimeRange; diff --git a/public/app/features/query/state/types.ts b/public/app/features/query/state/types.ts new file mode 100644 index 00000000000..009688dc2c8 --- /dev/null +++ b/public/app/features/query/state/types.ts @@ -0,0 +1,5 @@ +import { EventBusExtended } from '@grafana/data'; + +export interface PanelModelForLegacyQueryEditors { + events: EventBusExtended; +} diff --git a/public/app/features/sandbox/TestStuffPage.tsx b/public/app/features/sandbox/TestStuffPage.tsx index 11d6bc4bdbb..cc7cff6cb73 100644 --- a/public/app/features/sandbox/TestStuffPage.tsx +++ b/public/app/features/sandbox/TestStuffPage.tsx @@ -1,32 +1,120 @@ -import React, { FC, useState } from 'react'; -import { PanelModel } from '../dashboard/state'; -import { QueriesTab } from '../query/components/QueriesTab'; +import { + ApplyFieldOverrideOptions, + DataQuery, + DataSourceSelectItem, + DataTransformerConfig, + dateMath, + FieldColorModeId, + PanelData, +} from '@grafana/data'; +import { GraphNG, Table } from '@grafana/ui'; +import { config } from 'app/core/config'; +import React, { FC, useMemo, useState } from 'react'; +import { useObservable } from 'react-use'; +import { QueryGroup } from '../query/components/QueryGroup'; +import { QueryGroupOptions } from '../query/components/QueryGroupOptions'; +import { PanelQueryRunner } from '../query/state/PanelQueryRunner'; interface State { - panel: PanelModel; + queries: DataQuery[]; + queryRunner: PanelQueryRunner; + dataSourceName: string | null; + queryOptions: QueryGroupOptions; + data?: PanelData; } export const TestStuffPage: FC = () => { - const [state] = useState(getDefaultState()); + const [state, setState] = useState(getDefaultState()); + const { queryOptions, queryRunner, queries, dataSourceName } = state; + + const onDataSourceChange = (ds: DataSourceSelectItem, queries: DataQuery[]) => { + setState({ + ...state, + dataSourceName: ds.value, + queries: queries, + }); + }; + + const onRunQueries = () => { + const timeRange = { from: 'now-1h', to: 'now' }; + + queryRunner.run({ + queries, + timezone: 'browser', + datasource: dataSourceName, + timeRange: { from: dateMath.parse(timeRange.from)!, to: dateMath.parse(timeRange.to)!, raw: timeRange }, + maxDataPoints: queryOptions.maxDataPoints ?? 100, + minInterval: queryOptions.minInterval, + }); + }; + + const onQueriesChange = (queries: DataQuery[]) => { + setState({ ...state, queries: queries }); + }; + + const onQueryOptionsChange = (queryOptions: QueryGroupOptions) => { + setState({ ...state, queryOptions }); + }; + + /** + * Subscribe to data + */ + const observable = useMemo(() => queryRunner.getData({ withFieldConfig: true, withTransforms: true }), []); + const data = useObservable(observable); return ( -
-

Hello

+
+

New page

+
+ +
- + {data && ( +
+ +
+ + + )} ); }; export function getDefaultState(): State { - const panel = new PanelModel({ - datasource: 'gdev-testdata', - id: 10, - targets: [], - }); + const options: ApplyFieldOverrideOptions = { + fieldConfig: { + defaults: { + color: { + mode: FieldColorModeId.PaletteClassic, + }, + }, + overrides: [], + }, + replaceVariables: (v: string) => v, + theme: config.theme, + }; + + const dataConfig = { + getTransformations: () => [] as DataTransformerConfig[], + getFieldOverrideOptions: () => options, + }; return { - panel, + queries: [], + dataSourceName: 'gdev-testdata', + queryRunner: new PanelQueryRunner(dataConfig), + queryOptions: { + maxDataPoints: 100, + }, }; } diff --git a/public/app/plugins/datasource/dashboard/DashboardQueryEditor.tsx b/public/app/plugins/datasource/dashboard/DashboardQueryEditor.tsx index a49afcb6133..bd23b8d9016 100644 --- a/public/app/plugins/datasource/dashboard/DashboardQueryEditor.tsx +++ b/public/app/plugins/datasource/dashboard/DashboardQueryEditor.tsx @@ -28,9 +28,9 @@ function getQueryDisplayText(query: DataQuery): string { } interface Props { - panel: PanelModel; + queries: DataQuery[]; panelData: PanelData; - onChange: (query: DashboardQuery) => void; + onChange: (queries: DataQuery[]) => void; } type State = { @@ -48,8 +48,7 @@ export class DashboardQueryEditor extends PureComponent { } getQuery(): DashboardQuery { - const { panel } = this.props; - return panel.targets[0] as DashboardQuery; + return this.props.queries[0] as DashboardQuery; } async componentDidMount() { @@ -57,10 +56,14 @@ export class DashboardQueryEditor extends PureComponent { } async componentDidUpdate(prevProps: Props) { - const { panelData } = this.props; + const { panelData, queries } = this.props; + + if (queries.length < 0) { + return; + } if (!prevProps || prevProps.panelData !== panelData) { - const query = this.props.panel.targets[0] as DashboardQuery; + const query = queries[0] as DashboardQuery; const defaultDS = await getDatasourceSrv().get(); const dashboard = getDashboardSrv().getCurrent(); const panel = dashboard.getPanelById(query.panelId ?? -124134); @@ -97,10 +100,7 @@ export class DashboardQueryEditor extends PureComponent { const { onChange } = this.props; const query = this.getQuery(); query.panelId = id; - onChange(query); - - // Update the - this.props.panel.refresh(); + onChange([query]); }; renderQueryData(editURL: string) { From 2d49d3f5b23a3f4c35d7b96c2d1451dc5012ebb0 Mon Sep 17 00:00:00 2001 From: Brett Snyder Date: Wed, 2 Dec 2020 09:49:48 -0500 Subject: [PATCH 053/124] docker: use yarn to build (#29538) --- Dockerfile | 2 +- Dockerfile.ubuntu | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index a710cbaf4cb..fb4c243bf44 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ COPY scripts scripts COPY emails emails ENV NODE_ENV production -RUN ./node_modules/.bin/grunt build +RUN yarn build FROM golang:1.15.1-alpine3.12 as go-builder diff --git a/Dockerfile.ubuntu b/Dockerfile.ubuntu index a4d24559685..9818055bedd 100644 --- a/Dockerfile.ubuntu +++ b/Dockerfile.ubuntu @@ -14,7 +14,7 @@ COPY scripts scripts COPY emails emails ENV NODE_ENV production -RUN ./node_modules/.bin/grunt build +RUN yarn build FROM golang:1.15.1 AS go-builder From aa70a3839110fc81f76182adb1c38c20791e0c30 Mon Sep 17 00:00:00 2001 From: Jack Westbrook Date: Wed, 2 Dec 2020 15:51:22 +0100 Subject: [PATCH 054/124] Dashboards: hide playlist edit functionality from viewers and snapshots link from unauthenticated users (#28992) * feat: hide snapshots menu item from viewers * feat(playlists): prevent viewers from creating/editing playlists * feat: prevent viewers seeing playlist nav link if no playlists * refactor(playlist): rename isViewer property to canEditPlaylists * revert(playlists): put back note if viewer and no playlists * refactor(snapshots): consider admin/editor permission in folders/dashboards for displaying menu item * feat(snapshots): only show snapshot nav item if user is signed in * fix(snapshots): revert snapshots to previous state if delete snapshot api error --- pkg/api/index.go | 10 +++++- .../components/SnapshotListTable.tsx | 12 +++---- .../features/playlist/partials/playlists.html | 36 +++++++++++-------- .../app/features/playlist/playlists_ctrl.ts | 5 +++ 4 files changed, 42 insertions(+), 21 deletions(-) diff --git a/pkg/api/index.go b/pkg/api/index.go index 5e66015471b..79d35c4bf75 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -164,7 +164,15 @@ func (hs *HTTPServer) getNavTree(c *models.ReqContext, hasEditPerm bool) ([]*dto {Text: "Divider", Divider: true, Id: "divider", HideFromTabs: true}, {Text: "Manage", Id: "manage-dashboards", Url: setting.AppSubUrl + "/dashboards", Icon: "sitemap"}, {Text: "Playlists", Id: "playlists", Url: setting.AppSubUrl + "/playlists", Icon: "presentation-play"}, - {Text: "Snapshots", Id: "snapshots", Url: setting.AppSubUrl + "/dashboard/snapshots", Icon: "camera"}, + } + + if c.IsSignedIn { + dashboardChildNavs = append(dashboardChildNavs, &dtos.NavLink{ + Text: "Snapshots", + Id: "snapshots", + Url: setting.AppSubUrl + "/dashboard/snapshots", + Icon: "camera", + }) } navTree = append(navTree, &dtos.NavLink{ diff --git a/public/app/features/manage-dashboards/components/SnapshotListTable.tsx b/public/app/features/manage-dashboards/components/SnapshotListTable.tsx index 2aa5d13f622..744f4c890b5 100644 --- a/public/app/features/manage-dashboards/components/SnapshotListTable.tsx +++ b/public/app/features/manage-dashboards/components/SnapshotListTable.tsx @@ -1,7 +1,6 @@ import React, { FC, useState, useCallback, useEffect } from 'react'; import { ConfirmModal, Button, LinkButton } from '@grafana/ui'; import { getBackendSrv } from '@grafana/runtime'; -import { noop } from 'rxjs'; import { Snapshot } from '../types'; interface Props { @@ -28,11 +27,12 @@ export const SnapshotListTable: FC = ({ url }) => { const doRemoveSnapshot = useCallback( async (snapshot: Snapshot) => { - setSnapshots(snapshots.filter(ss => ss.key !== snapshot.key)); + const filteredSnapshots = snapshots.filter(ss => ss.key !== snapshot.key); + setSnapshots(filteredSnapshots); await getBackendSrv() .delete(`/api/snapshots/${snapshot.key}`) - .then(noop, () => { - setSnapshots(snapshots.concat(snapshot)); + .catch(() => { + setSnapshots(snapshots); }); }, [snapshots] @@ -59,9 +59,9 @@ export const SnapshotListTable: FC = ({ url }) => { - {snapshots.map((snapshot, key) => { + {snapshots.map(snapshot => { return ( - + diff --git a/public/app/features/playlist/partials/playlists.html b/public/app/features/playlist/partials/playlists.html index 291fb1822f2..fccba3e0f9c 100644 --- a/public/app/features/playlist/partials/playlists.html +++ b/public/app/features/playlist/partials/playlists.html @@ -2,7 +2,7 @@ - + - -
{snapshot.name}
Name
+ {{playlist.name}} + {{playlist.name}} + @@ -53,16 +54,23 @@
- +
+ +
+
+
There are no playlists created yet
+

Unfortunately you don't have permission to create playlists.

+
+
diff --git a/public/app/features/playlist/playlists_ctrl.ts b/public/app/features/playlist/playlists_ctrl.ts index cf83a85b8e2..e42e0e262ca 100644 --- a/public/app/features/playlist/playlists_ctrl.ts +++ b/public/app/features/playlist/playlists_ctrl.ts @@ -1,9 +1,11 @@ import { IScope } from 'angular'; import _ from 'lodash'; import { AppEvents } from '@grafana/data'; +import { OrgRole } from 'app/types'; import { getBackendSrv } from '@grafana/runtime'; import coreModule from '../../core/core_module'; +import config from '../../core/config'; import { NavModelSrv } from 'app/core/nav_model_srv'; import { AppEventEmitter, CoreEvents } from 'app/types'; import { promiseToDigest } from '../../core/utils/promiseToDigest'; @@ -11,10 +13,13 @@ import { promiseToDigest } from '../../core/utils/promiseToDigest'; export class PlaylistsCtrl { playlists: any; navModel: any; + canEditPlaylists: boolean; /** @ngInject */ constructor(private $scope: IScope & AppEventEmitter, navModelSrv: NavModelSrv) { this.navModel = navModelSrv.getNav('dashboards', 'playlists', 0); + this.canEditPlaylists = config.bootData.user.orgRole !== OrgRole.Viewer; + promiseToDigest($scope)( getBackendSrv() .get('/api/playlists') From 2c535a9583f0a2c06d1a8d260727d2ea63dbbf89 Mon Sep 17 00:00:00 2001 From: Leon Sorokin Date: Wed, 2 Dec 2020 09:34:44 -0600 Subject: [PATCH 055/124] Data: use pre-defined output array length in vectorToArray() (#29516) --- packages/grafana-data/src/vector/vectorToArray.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grafana-data/src/vector/vectorToArray.ts b/packages/grafana-data/src/vector/vectorToArray.ts index 34212651ab8..a0a17d71cb9 100644 --- a/packages/grafana-data/src/vector/vectorToArray.ts +++ b/packages/grafana-data/src/vector/vectorToArray.ts @@ -1,7 +1,7 @@ import { Vector } from '../types/vector'; export function vectorToArray(v: Vector): T[] { - const arr: T[] = []; + const arr: T[] = Array(v.length); for (let i = 0; i < v.length; i++) { arr[i] = v.get(i); } From 752a424e1fcd0cc3e332b88dd004f2e6cd959cc0 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Wed, 2 Dec 2020 16:57:16 +0100 Subject: [PATCH 056/124] Auth proxy: Return standard error type (#29502) * Rewrite auth proxy tests to use standard lib Signed-off-by: Arve Knudsen * Auth proxy: Use standard error type Signed-off-by: Arve Knudsen --- pkg/middleware/auth_proxy.go | 78 ++++--- pkg/middleware/authproxy/auth_proxy.go | 40 ++-- pkg/middleware/authproxy/auth_proxy_test.go | 247 ++++++++++---------- pkg/services/ldap/settings.go | 5 +- 4 files changed, 198 insertions(+), 172 deletions(-) diff --git a/pkg/middleware/auth_proxy.go b/pkg/middleware/auth_proxy.go index 2365df81852..4c5f9d22c48 100644 --- a/pkg/middleware/auth_proxy.go +++ b/pkg/middleware/auth_proxy.go @@ -12,18 +12,41 @@ import ( var header = setting.AuthProxyHeaderName -func logUserIn(auth *authproxy.AuthProxy, username string, logger log.Logger, ignoreCache bool) (int64, *authproxy.Error) { +func logUserIn(auth *authproxy.AuthProxy, username string, logger log.Logger, ignoreCache bool) (int64, error) { logger.Debug("Trying to log user in", "username", username, "ignoreCache", ignoreCache) // Try to log in user via various providers - id, e := auth.Login(logger, ignoreCache) - if e != nil { - logger.Error("Failed to login", "username", username, "message", e.Error(), "error", e.DetailsError, + id, err := auth.Login(logger, ignoreCache) + if err != nil { + details := err + var e authproxy.Error + if errors.As(err, &e) { + details = e.DetailsError + } + logger.Error("Failed to login", "username", username, "message", err.Error(), "error", details, "ignoreCache", ignoreCache) - return 0, e + return 0, err } return id, nil } +// handleError calls ctx.Handle with the error message and the underlying error. +// If the error is of type authproxy.Error, its DetailsError is unwrapped and passed to ctx.Handle. +// If a callback is provided, it's called with either err.DetailsError, if err is of type +// authproxy.Error, otherwise err itself. +func handleError(ctx *models.ReqContext, err error, statusCode int, cb func(err error)) { + details := err + var e authproxy.Error + if errors.As(err, &e) { + details = e.DetailsError + } + + ctx.Handle(statusCode, err.Error(), details) + + if cb != nil { + cb(details) + } +} + func initContextWithAuthProxy(store *remotecache.RemoteCache, ctx *models.ReqContext, orgID int64) bool { username := ctx.Req.Header.Get(header) auth := authproxy.New(&authproxy.Options{ @@ -46,18 +69,15 @@ func initContextWithAuthProxy(store *remotecache.RemoteCache, ctx *models.ReqCon // Check if allowed to continue with this IP if err := auth.IsAllowedIP(); err != nil { - logger.Error( - "Failed to check whitelisted IP addresses", - "message", err.Error(), - "error", err.DetailsError, - ) - ctx.Handle(407, err.Error(), err.DetailsError) + handleError(ctx, err, 407, func(details error) { + logger.Error("Failed to check whitelisted IP addresses", "message", err.Error(), "error", details) + }) return true } - id, e := logUserIn(auth, username, logger, false) - if e != nil { - ctx.Handle(407, e.Error(), e.DetailsError) + id, err := logUserIn(auth, username, logger, false) + if err != nil { + handleError(ctx, err, 407, nil) return true } @@ -76,15 +96,16 @@ func initContextWithAuthProxy(store *remotecache.RemoteCache, ctx *models.ReqCon logger.Error("Got unexpected error when removing user from auth cache", "error", err) } } - id, e = logUserIn(auth, username, logger, true) - if e != nil { - ctx.Handle(407, e.Error(), e.DetailsError) + id, err = logUserIn(auth, username, logger, true) + if err != nil { + handleError(ctx, err, 407, nil) return true } - user, e = auth.GetSignedUser(id) - if e != nil { - ctx.Handle(407, e.Error(), e.DetailsError) + user, err = auth.GetSignedUser(id) + if err != nil { + handleError(ctx, err, 407, nil) + return true } } @@ -96,14 +117,15 @@ func initContextWithAuthProxy(store *remotecache.RemoteCache, ctx *models.ReqCon ctx.IsSignedIn = true // Remember user data in cache - if e := auth.Remember(id); e != nil { - logger.Error( - "Failed to store user in cache", - "username", username, - "message", e.Error(), - "error", e.DetailsError, - ) - ctx.Handle(500, e.Error(), e.DetailsError) + if err := auth.Remember(id); err != nil { + handleError(ctx, err, 500, func(details error) { + logger.Error( + "Failed to store user in cache", + "username", username, + "message", e.Error(), + "error", details, + ) + }) return true } diff --git a/pkg/middleware/authproxy/auth_proxy.go b/pkg/middleware/authproxy/auth_proxy.go index 0e0267721a9..2b80e0f8fd4 100644 --- a/pkg/middleware/authproxy/auth_proxy.go +++ b/pkg/middleware/authproxy/auth_proxy.go @@ -62,16 +62,16 @@ type Error struct { DetailsError error } -// newError creates the Error -func newError(message string, err error) *Error { - return &Error{ +// newError returns an Error. +func newError(message string, err error) Error { + return Error{ Message: message, DetailsError: err, } } -// Error returns a Error error string -func (err *Error) Error() string { +// Error returns the error message. +func (err Error) Error() string { return err.Message } @@ -114,7 +114,7 @@ func (auth *AuthProxy) HasHeader() bool { } // IsAllowedIP compares presented IP with the whitelist one -func (auth *AuthProxy) IsAllowedIP() *Error { +func (auth *AuthProxy) IsAllowedIP() error { ip := auth.ctx.Req.RemoteAddr if len(strings.TrimSpace(auth.whitelistIP)) == 0 { @@ -144,11 +144,10 @@ func (auth *AuthProxy) IsAllowedIP() *Error { } } - err = fmt.Errorf( + return newError("proxy authentication required", fmt.Errorf( "request for user (%s) from %s is not from the authentication proxy", auth.header, sourceIP, - ) - return newError("proxy authentication required", err) + )) } func HashCacheKey(key string) string { @@ -173,7 +172,7 @@ func (auth *AuthProxy) getKey() string { } // Login logs in user ID by whatever means possible. -func (auth *AuthProxy) Login(logger log.Logger, ignoreCache bool) (int64, *Error) { +func (auth *AuthProxy) Login(logger log.Logger, ignoreCache bool) (int64, error) { if !ignoreCache { // Error here means absent cache - we don't need to handle that id, err := auth.GetUserViaCache(logger) @@ -229,15 +228,15 @@ func (auth *AuthProxy) RemoveUserFromCache(logger log.Logger) error { } // LoginViaLDAP logs in user via LDAP request -func (auth *AuthProxy) LoginViaLDAP() (int64, *Error) { +func (auth *AuthProxy) LoginViaLDAP() (int64, error) { config, err := getLDAPConfig() if err != nil { - return 0, newError("failed to get LDAP config", nil) + return 0, newError("failed to get LDAP config", err) } extUser, _, err := newLDAP(config.Servers).User(auth.header) if err != nil { - return 0, newError(err.Error(), nil) + return 0, err } // Have to sync grafana and LDAP user during log in @@ -246,9 +245,8 @@ func (auth *AuthProxy) LoginViaLDAP() (int64, *Error) { SignupAllowed: auth.LDAPAllowSignup, ExternalUser: extUser, } - err = bus.Dispatch(upsert) - if err != nil { - return 0, newError(err.Error(), nil) + if err := bus.Dispatch(upsert); err != nil { + return 0, err } return upsert.Result.Id, nil @@ -273,7 +271,7 @@ func (auth *AuthProxy) LoginViaHeader() (int64, error) { extUser.Email = auth.header extUser.Login = auth.header default: - return 0, newError("auth proxy header property invalid", nil) + return 0, fmt.Errorf("auth proxy header property invalid") } auth.headersIterator(func(field string, header string) { @@ -314,21 +312,21 @@ func (auth *AuthProxy) headersIterator(fn func(field string, header string)) { } // GetSignedUser gets full signed user info. -func (auth *AuthProxy) GetSignedUser(userID int64) (*models.SignedInUser, *Error) { +func (auth *AuthProxy) GetSignedUser(userID int64) (*models.SignedInUser, error) { query := &models.GetSignedInUserQuery{ OrgId: auth.orgID, UserId: userID, } if err := bus.Dispatch(query); err != nil { - return nil, newError(err.Error(), nil) + return nil, err } return query.Result, nil } // Remember user in cache -func (auth *AuthProxy) Remember(id int64) *Error { +func (auth *AuthProxy) Remember(id int64) error { key := auth.getKey() // Check if user already in cache @@ -341,7 +339,7 @@ func (auth *AuthProxy) Remember(id int64) *Error { err := auth.store.Set(key, id, expiration) if err != nil { - return newError(err.Error(), nil) + return err } return nil diff --git a/pkg/middleware/authproxy/auth_proxy_test.go b/pkg/middleware/authproxy/auth_proxy_test.go index a6625407af8..5a3604946e0 100644 --- a/pkg/middleware/authproxy/auth_proxy_test.go +++ b/pkg/middleware/authproxy/auth_proxy_test.go @@ -13,7 +13,8 @@ import ( "github.com/grafana/grafana/pkg/services/ldap" "github.com/grafana/grafana/pkg/services/multildap" "github.com/grafana/grafana/pkg/setting" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "gopkg.in/macaron.v1" ) @@ -68,134 +69,142 @@ func prepareMiddleware(t *testing.T, req *http.Request, store *remotecache.Remot func TestMiddlewareContext(t *testing.T) { logger := log.New("test") - Convey("auth_proxy helper", t, func() { - req, err := http.NewRequest("POST", "http://example.com", nil) - So(err, ShouldBeNil) - setting.AuthProxyHeaderName = "X-Killa" - store := remotecache.NewFakeStore(t) + req, err := http.NewRequest("POST", "http://example.com", nil) + require.NoError(t, err) + setting.AuthProxyHeaderName = "X-Killa" + store := remotecache.NewFakeStore(t) - name := "markelog" - req.Header.Add(setting.AuthProxyHeaderName, name) + name := "markelog" + req.Header.Add(setting.AuthProxyHeaderName, name) - Convey("when the cache only contains the main header", func() { - Convey("with a simple cache key", func() { - // Set cache key - key := fmt.Sprintf(CachePrefix, HashCacheKey(name)) - err := store.Set(key, int64(33), 0) - So(err, ShouldBeNil) + t.Run("When the cache only contains the main header with a simple cache key", func(t *testing.T) { + const id int64 = 33 + // Set cache key + key := fmt.Sprintf(CachePrefix, HashCacheKey(name)) + err := store.Set(key, id, 0) + require.NoError(t, err) - // Set up the middleware - auth := prepareMiddleware(t, req, store) - So(auth.getKey(), ShouldEqual, "auth-proxy-sync-ttl:0a7f3374e9659b10980fd66247b0cf2f") + // Set up the middleware + auth := prepareMiddleware(t, req, store) + assert.Equal(t, "auth-proxy-sync-ttl:0a7f3374e9659b10980fd66247b0cf2f", auth.getKey()) - id, err := auth.Login(logger, false) - So(err, ShouldBeNil) + gotID, err := auth.Login(logger, false) + require.NoError(t, err) - So(id, ShouldEqual, 33) - }) + assert.Equal(t, id, gotID) + }) - Convey("when the cache key contains additional headers", func() { - setting.AuthProxyHeaders = map[string]string{"Groups": "X-WEBAUTH-GROUPS"} - group := "grafana-core-team" - req.Header.Add("X-WEBAUTH-GROUPS", group) + t.Run("When the cache key contains additional headers", func(t *testing.T) { + const id int64 = 33 + setting.AuthProxyHeaders = map[string]string{"Groups": "X-WEBAUTH-GROUPS"} + group := "grafana-core-team" + req.Header.Add("X-WEBAUTH-GROUPS", group) - key := fmt.Sprintf(CachePrefix, HashCacheKey(name+"-"+group)) - err := store.Set(key, int64(33), 0) - So(err, ShouldBeNil) + key := fmt.Sprintf(CachePrefix, HashCacheKey(name+"-"+group)) + err := store.Set(key, id, 0) + require.NoError(t, err) - auth := prepareMiddleware(t, req, store) - So(auth.getKey(), ShouldEqual, "auth-proxy-sync-ttl:14f69b7023baa0ac98c96b31cec07bc0") + auth := prepareMiddleware(t, req, store) + assert.Equal(t, "auth-proxy-sync-ttl:14f69b7023baa0ac98c96b31cec07bc0", auth.getKey()) - id, err := auth.Login(logger, false) - So(err, ShouldBeNil) - So(id, ShouldEqual, 33) - }) + gotID, err := auth.Login(logger, false) + require.NoError(t, err) + assert.Equal(t, id, gotID) + }) +} + +func TestMiddlewareContext_ldap(t *testing.T) { + logger := log.New("test") + req, err := http.NewRequest("POST", "http://example.com", nil) + require.NoError(t, err) + setting.AuthProxyHeaderName = "X-Killa" + + const headerName = "markelog" + req.Header.Add(setting.AuthProxyHeaderName, headerName) + + t.Run("Logs in via LDAP", func(t *testing.T) { + const id int64 = 42 + + bus.AddHandler("test", func(cmd *models.UpsertUserCommand) error { + cmd.Result = &models.User{ + Id: id, + } + + return nil }) - Convey("LDAP", func() { - Convey("logs in via LDAP", func() { - bus.AddHandler("test", func(cmd *models.UpsertUserCommand) error { - cmd.Result = &models.User{ - Id: 42, - } - - return nil - }) - - isLDAPEnabled = func() bool { - return true - } - - stub := &fakeMultiLDAP{ - ID: 42, - } - - getLDAPConfig = func() (*ldap.Config, error) { - config := &ldap.Config{ - Servers: []*ldap.ServerConfig{ - { - SearchBaseDNs: []string{"BaseDNHere"}, - }, - }, - } - return config, nil - } - - newLDAP = func(servers []*ldap.ServerConfig) multildap.IMultiLDAP { - return stub - } - - defer func() { - newLDAP = multildap.New - isLDAPEnabled = ldap.IsEnabled - getLDAPConfig = ldap.GetConfig - }() - - store := remotecache.NewFakeStore(t) - - auth := prepareMiddleware(t, req, store) - - id, err := auth.Login(logger, false) - - So(err, ShouldBeNil) - So(id, ShouldEqual, 42) - So(stub.userCalled, ShouldEqual, true) - }) - - Convey("gets nice error if ldap is enabled but not configured", func() { - isLDAPEnabled = func() bool { - return true - } - - getLDAPConfig = func() (*ldap.Config, error) { - return nil, errors.New("Something went wrong") - } - - defer func() { - newLDAP = multildap.New - isLDAPEnabled = ldap.IsEnabled - getLDAPConfig = ldap.GetConfig - }() - - store := remotecache.NewFakeStore(t) - - auth := prepareMiddleware(t, req, store) - - stub := &fakeMultiLDAP{ - ID: 42, - } - - newLDAP = func(servers []*ldap.ServerConfig) multildap.IMultiLDAP { - return stub - } - - id, err := auth.Login(logger, false) - - So(err, ShouldNotBeNil) - So(err.Error(), ShouldContainSubstring, "failed to get the user") - So(id, ShouldNotEqual, 42) - So(stub.loginCalled, ShouldEqual, false) - }) - }) + isLDAPEnabled = func() bool { + return true + } + + stub := &fakeMultiLDAP{ + ID: id, + } + + getLDAPConfig = func() (*ldap.Config, error) { + config := &ldap.Config{ + Servers: []*ldap.ServerConfig{ + { + SearchBaseDNs: []string{"BaseDNHere"}, + }, + }, + } + return config, nil + } + + newLDAP = func(servers []*ldap.ServerConfig) multildap.IMultiLDAP { + return stub + } + + defer func() { + newLDAP = multildap.New + isLDAPEnabled = ldap.IsEnabled + getLDAPConfig = ldap.GetConfig + }() + + store := remotecache.NewFakeStore(t) + + auth := prepareMiddleware(t, req, store) + + gotID, err := auth.Login(logger, false) + require.NoError(t, err) + + assert.Equal(t, id, gotID) + assert.True(t, stub.userCalled) + }) + + t.Run("Gets nice error if ldap is enabled but not configured", func(t *testing.T) { + const id int64 = 42 + isLDAPEnabled = func() bool { + return true + } + + getLDAPConfig = func() (*ldap.Config, error) { + return nil, errors.New("something went wrong") + } + + defer func() { + newLDAP = multildap.New + isLDAPEnabled = ldap.IsEnabled + getLDAPConfig = ldap.GetConfig + }() + + store := remotecache.NewFakeStore(t) + + auth := prepareMiddleware(t, req, store) + + stub := &fakeMultiLDAP{ + ID: id, + } + + newLDAP = func(servers []*ldap.ServerConfig) multildap.IMultiLDAP { + return stub + } + + gotID, err := auth.Login(logger, false) + require.EqualError(t, err, "failed to get the user") + + assert.NotEqual(t, id, gotID) + assert.False(t, stub.loginCalled) }) } diff --git a/pkg/services/ldap/settings.go b/pkg/services/ldap/settings.go index 19d6dd2c872..fa4248108e3 100644 --- a/pkg/services/ldap/settings.go +++ b/pkg/services/ldap/settings.go @@ -107,10 +107,7 @@ func GetConfig() (*Config, error) { loadingMutex.Lock() defer loadingMutex.Unlock() - var err error - config, err = readConfig(setting.LDAPConfigFile) - - return config, err + return readConfig(setting.LDAPConfigFile) } func readConfig(configFile string) (*Config, error) { From 74a4ffd870c20a0b32ccf30a9221474becb63990 Mon Sep 17 00:00:00 2001 From: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> Date: Wed, 2 Dec 2020 11:19:36 -0500 Subject: [PATCH 057/124] 3 minor changes, including updating the title TOC (#29501) * 3 minor changes, including updating the title TOC * Fixing the sentence lacking verb. --- docs/sources/getting-started/getting-started-sql.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sources/getting-started/getting-started-sql.md b/docs/sources/getting-started/getting-started-sql.md index 6e2f81dba37..45d5b56a47a 100644 --- a/docs/sources/getting-started/getting-started-sql.md +++ b/docs/sources/getting-started/getting-started-sql.md @@ -1,5 +1,5 @@ +++ -title = "With Grafana and MS SQL" +title = "With Grafana and MS SQL Server" description = "Guide for getting started with Grafana" keywords = ["grafana", "intro", "guide", "started", "SQL", "MSSQL"] aliases = ["/docs/grafana/latest/guides/gettingstarted","/docs/grafana/latest/guides/getting_started"] @@ -15,7 +15,7 @@ Use the instructions in [Getting started with Grafana]({{< relref "getting-start - Install Grafana. - Log in to Grafana. - Create your first dashboard. -> **Note:** You must install Grafana 5.1+ in order to use the integrated MSSQL data source. +> **Note:** You must install Grafana 5.1+ in order to use the integrated MS SQL data source. ## Step 2. Download MS SQL Server @@ -63,7 +63,7 @@ To add MS SQL Server data source: `User` | Database user's login/username. `Password` | Database user's password. -For installations from the [grafana/grafana](https://github.com/grafana/grafana/tree/master) repository, `gdev-mssql` data source is available. Once you add this data source, the `Datasource tests - MSSQL` dashboard with three panels showing metrics generated from test database is available. +For installations from the [grafana/grafana](https://github.com/grafana/grafana/tree/master) repository, `gdev-mssql` data source is available. Once you add this data source, you can use the `Datasource tests - MSSQL` dashboard with three panels showing metrics generated from a test database. From 18c3f7bba04cdace2eff8f80974688b641dc84e9 Mon Sep 17 00:00:00 2001 From: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> Date: Wed, 2 Dec 2020 08:54:09 -0800 Subject: [PATCH 058/124] Docs: Update repeat-panels-or-rows.md (#29540) --- docs/sources/variables/repeat-panels-or-rows.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/variables/repeat-panels-or-rows.md b/docs/sources/variables/repeat-panels-or-rows.md index 114b0a179dc..9ec9af93552 100644 --- a/docs/sources/variables/repeat-panels-or-rows.md +++ b/docs/sources/variables/repeat-panels-or-rows.md @@ -1,5 +1,5 @@ +++ -title = "Variables" +title = "Repeat panels or rows" keywords = ["grafana", "templating", "documentation", "guide", "template", "variable", "repeat"] weight = 800 +++ From 00d59ec1fc50d8be386188bad6dbe50979e4823f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 2 Dec 2020 18:18:49 +0100 Subject: [PATCH 059/124] NodeJS: Update to LTS (14) (#29467) * NodeJS: Update to LTS (14) * Bump Volta's Node version Signed-off-by: Arve Knudsen * Bump version of build image Signed-off-by: Arve Knudsen * Drone: Upgrade build image Co-authored-by: Arve Knudsen --- .drone.yml | 276 ++++++++++++------------- Dockerfile | 2 +- Dockerfile.ubuntu | 2 +- package.json | 4 +- scripts/build/ci-build/Dockerfile | 4 +- scripts/build/ci-build/build-deploy.sh | 2 +- scripts/lib.star | 2 +- 7 files changed, 146 insertions(+), 146 deletions(-) diff --git a/.drone.yml b/.drone.yml index b2d33dbfe1f..e027b23aa5f 100644 --- a/.drone.yml +++ b/.drone.yml @@ -14,7 +14,7 @@ steps: - echo $DRONE_RUNNER_NAME - name: initialize - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - mkdir -p bin - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.29/grabpl @@ -28,7 +28,7 @@ steps: DOCKERIZE_VERSION: 0.6.1 - name: lint-backend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - golangci-lint run --config scripts/go/configs/.golangci.toml ./pkg/... - revive -formatter stylish -config scripts/go/configs/revive.toml ./pkg/... @@ -42,7 +42,7 @@ steps: - initialize - name: codespell - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - "echo -e \"unknwon\nreferer\nerrorstring\neror\niam\" > words_to_ignore.txt" - codespell -I words_to_ignore.txt docs/ @@ -50,21 +50,21 @@ steps: - initialize - name: shellcheck - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl shellcheck depends_on: - initialize - name: check-dashboard-schemas - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - cue export --out openapi -o - ./dashboard-schemas/... depends_on: - initialize - name: test-backend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - "[ $(grep FocusConvey -R pkg | wc -l) -eq \"0\" ] || exit 1" - ./bin/grabpl test-backend @@ -74,7 +74,7 @@ steps: - lint-backend - name: test-frontend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - yarn run ci:test-frontend environment: @@ -83,7 +83,7 @@ steps: - initialize - name: build-backend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl build-backend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} --variants linux-x64,linux-x64-musl,osx64,win64 --no-pull-enterprise depends_on: @@ -92,7 +92,7 @@ steps: - test-backend - name: build-frontend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl build-frontend --jobs 8 --no-install-deps --edition oss --build-id ${DRONE_BUILD_NUMBER} --no-pull-enterprise depends_on: @@ -100,7 +100,7 @@ steps: - test-frontend - name: build-plugins - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl build-plugins --jobs 8 --edition oss --no-install-deps depends_on: @@ -108,7 +108,7 @@ steps: - lint-backend - name: package - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - . scripts/build/gpg-test-vars.sh && ./bin/grabpl package --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} --no-pull-enterprise --variants linux-x64,linux-x64-musl,osx64,win64 depends_on: @@ -122,7 +122,7 @@ steps: - check-dashboard-schemas - name: end-to-end-tests-server - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 detach: true commands: - ./e2e/start-server @@ -140,7 +140,7 @@ steps: - end-to-end-tests-server - name: build-storybook - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - yarn storybook:build - ./bin/grabpl verify-storybook @@ -150,7 +150,7 @@ steps: - package - name: build-frontend-docs - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./scripts/ci-reference-docs-lint.sh ci depends_on: @@ -167,7 +167,7 @@ steps: - build-frontend-docs - name: copy-packages-for-docker - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - cp dist/*.tar.gz* packaging/docker/ depends_on: @@ -183,7 +183,7 @@ steps: - copy-packages-for-docker - name: postgres-integration-tests - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - apt-get update - apt-get install -yq postgresql-client @@ -200,7 +200,7 @@ steps: - test-frontend - name: mysql-integration-tests - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - apt-get update - apt-get install -yq default-mysql-client @@ -251,7 +251,7 @@ steps: - echo $DRONE_RUNNER_NAME - name: initialize - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - mkdir -p bin - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.29/grabpl @@ -277,7 +277,7 @@ steps: from_secret: drone_token - name: lint-backend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - golangci-lint run --config scripts/go/configs/.golangci.toml ./pkg/... - revive -formatter stylish -config scripts/go/configs/revive.toml ./pkg/... @@ -291,7 +291,7 @@ steps: - initialize - name: codespell - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - "echo -e \"unknwon\nreferer\nerrorstring\neror\niam\" > words_to_ignore.txt" - codespell -I words_to_ignore.txt docs/ @@ -299,21 +299,21 @@ steps: - initialize - name: shellcheck - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl shellcheck depends_on: - initialize - name: check-dashboard-schemas - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - cue export --out openapi -o - ./dashboard-schemas/... depends_on: - initialize - name: test-backend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - "[ $(grep FocusConvey -R pkg | wc -l) -eq \"0\" ] || exit 1" - ./bin/grabpl test-backend @@ -323,7 +323,7 @@ steps: - lint-backend - name: test-frontend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - yarn run ci:test-frontend environment: @@ -332,7 +332,7 @@ steps: - initialize - name: publish-frontend-metrics - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./scripts/ci-frontend-metrics.sh | ./bin/grabpl publish-metrics $${GRAFANA_MISC_STATS_API_KEY} environment: @@ -343,7 +343,7 @@ steps: - initialize - name: build-backend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl build-backend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} --no-pull-enterprise depends_on: @@ -352,7 +352,7 @@ steps: - test-backend - name: build-frontend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl build-frontend --jobs 8 --no-install-deps --edition oss --build-id ${DRONE_BUILD_NUMBER} --no-pull-enterprise depends_on: @@ -360,7 +360,7 @@ steps: - test-frontend - name: build-plugins - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl build-plugins --jobs 8 --edition oss --no-install-deps --sign --signing-admin environment: @@ -371,7 +371,7 @@ steps: - lint-backend - name: package - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl package --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} --no-pull-enterprise --sign environment: @@ -396,7 +396,7 @@ steps: - check-dashboard-schemas - name: end-to-end-tests-server - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 detach: true commands: - ./e2e/start-server @@ -414,7 +414,7 @@ steps: - end-to-end-tests-server - name: build-storybook - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - yarn storybook:build - ./bin/grabpl verify-storybook @@ -437,14 +437,14 @@ steps: - end-to-end-tests - name: build-frontend-docs - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./scripts/ci-reference-docs-lint.sh ci depends_on: - build-frontend - name: copy-packages-for-docker - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - cp dist/*.tar.gz* packaging/docker/ depends_on: @@ -474,7 +474,7 @@ steps: - copy-packages-for-docker - name: postgres-integration-tests - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - apt-get update - apt-get install -yq postgresql-client @@ -491,7 +491,7 @@ steps: - test-frontend - name: mysql-integration-tests - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - apt-get update - apt-get install -yq default-mysql-client @@ -507,7 +507,7 @@ steps: - test-frontend - name: release-next-npm-packages - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./node_modules/.bin/lerna bootstrap - echo "//registry.npmjs.org/:_authToken=$${NPM_TOKEN}" >> ~/.npmrc @@ -620,7 +620,7 @@ steps: - echo $DRONE_RUNNER_NAME - name: initialize - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - mkdir -p bin - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.29/grabpl @@ -705,7 +705,7 @@ steps: - echo $DRONE_RUNNER_NAME - name: initialize - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - mkdir -p bin - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.29/grabpl @@ -720,7 +720,7 @@ steps: DOCKERIZE_VERSION: 0.6.1 - name: lint-backend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - golangci-lint run --config scripts/go/configs/.golangci.toml ./pkg/... - revive -formatter stylish -config scripts/go/configs/revive.toml ./pkg/... @@ -734,7 +734,7 @@ steps: - initialize - name: codespell - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - "echo -e \"unknwon\nreferer\nerrorstring\neror\niam\" > words_to_ignore.txt" - codespell -I words_to_ignore.txt docs/ @@ -742,21 +742,21 @@ steps: - initialize - name: shellcheck - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl shellcheck depends_on: - initialize - name: check-dashboard-schemas - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - cue export --out openapi -o - ./dashboard-schemas/... depends_on: - initialize - name: test-backend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - "[ $(grep FocusConvey -R pkg | wc -l) -eq \"0\" ] || exit 1" - ./bin/grabpl test-backend @@ -766,7 +766,7 @@ steps: - lint-backend - name: test-frontend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - yarn run ci:test-frontend environment: @@ -775,7 +775,7 @@ steps: - initialize - name: build-backend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl build-backend --jobs 8 --edition oss --github-token $${GITHUB_TOKEN} --no-pull-enterprise ${DRONE_TAG} environment: @@ -787,7 +787,7 @@ steps: - test-backend - name: build-frontend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl build-frontend --jobs 8 --github-token $${GITHUB_TOKEN} --no-install-deps --edition oss --no-pull-enterprise ${DRONE_TAG} depends_on: @@ -795,7 +795,7 @@ steps: - test-frontend - name: build-plugins - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl build-plugins --jobs 8 --edition oss --no-install-deps --sign --signing-admin environment: @@ -806,7 +806,7 @@ steps: - lint-backend - name: package - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl package --jobs 8 --edition oss --github-token $${GITHUB_TOKEN} --no-pull-enterprise --sign ${DRONE_TAG} environment: @@ -831,7 +831,7 @@ steps: - check-dashboard-schemas - name: end-to-end-tests-server - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 detach: true commands: - ./e2e/start-server @@ -849,7 +849,7 @@ steps: - end-to-end-tests-server - name: build-storybook - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - yarn storybook:build - ./bin/grabpl verify-storybook @@ -859,7 +859,7 @@ steps: - package - name: copy-packages-for-docker - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - cp dist/*.tar.gz* packaging/docker/ depends_on: @@ -889,7 +889,7 @@ steps: - copy-packages-for-docker - name: postgres-integration-tests - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - apt-get update - apt-get install -yq postgresql-client @@ -906,7 +906,7 @@ steps: - test-frontend - name: mysql-integration-tests - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - apt-get update - apt-get install -yq default-mysql-client @@ -949,7 +949,7 @@ steps: - end-to-end-tests - name: release-npm-packages - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./node_modules/.bin/lerna bootstrap - echo "//registry.npmjs.org/:_authToken=$${NPM_TOKEN}" >> ~/.npmrc @@ -1048,7 +1048,7 @@ steps: - echo $DRONE_RUNNER_NAME - name: clone - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - mkdir -p bin - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.29/grabpl @@ -1061,7 +1061,7 @@ steps: from_secret: github_token - name: initialize - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - mv bin/grabpl /tmp/ - rmdir bin @@ -1081,7 +1081,7 @@ steps: - clone - name: lint-backend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - golangci-lint run --config scripts/go/configs/.golangci.toml ./pkg/... - revive -formatter stylish -config scripts/go/configs/revive.toml ./pkg/... @@ -1095,7 +1095,7 @@ steps: - initialize - name: codespell - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - "echo -e \"unknwon\nreferer\nerrorstring\neror\niam\" > words_to_ignore.txt" - codespell -I words_to_ignore.txt docs/ @@ -1103,21 +1103,21 @@ steps: - initialize - name: shellcheck - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl shellcheck depends_on: - initialize - name: check-dashboard-schemas - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - cue export --out openapi -o - ./dashboard-schemas/... depends_on: - initialize - name: test-backend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - "[ $(grep FocusConvey -R pkg | wc -l) -eq \"0\" ] || exit 1" - ./bin/grabpl test-backend @@ -1127,7 +1127,7 @@ steps: - lint-backend - name: test-frontend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - yarn run ci:test-frontend environment: @@ -1136,7 +1136,7 @@ steps: - initialize - name: build-backend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl build-backend --jobs 8 --edition enterprise --github-token $${GITHUB_TOKEN} --no-pull-enterprise ${DRONE_TAG} environment: @@ -1148,7 +1148,7 @@ steps: - test-backend - name: build-frontend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl build-frontend --jobs 8 --github-token $${GITHUB_TOKEN} --no-install-deps --edition enterprise --no-pull-enterprise ${DRONE_TAG} depends_on: @@ -1156,7 +1156,7 @@ steps: - test-frontend - name: build-plugins - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl build-plugins --jobs 8 --edition enterprise --no-install-deps --sign --signing-admin environment: @@ -1167,7 +1167,7 @@ steps: - lint-backend - name: package - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl package --jobs 8 --edition enterprise --github-token $${GITHUB_TOKEN} --no-pull-enterprise --sign ${DRONE_TAG} environment: @@ -1192,7 +1192,7 @@ steps: - check-dashboard-schemas - name: end-to-end-tests-server - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 detach: true commands: - ./e2e/start-server @@ -1210,7 +1210,7 @@ steps: - end-to-end-tests-server - name: copy-packages-for-docker - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - cp dist/*.tar.gz* packaging/docker/ depends_on: @@ -1240,7 +1240,7 @@ steps: - copy-packages-for-docker - name: postgres-integration-tests - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - apt-get update - apt-get install -yq postgresql-client @@ -1257,7 +1257,7 @@ steps: - test-frontend - name: mysql-integration-tests - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - apt-get update - apt-get install -yq default-mysql-client @@ -1391,7 +1391,7 @@ steps: - echo $DRONE_RUNNER_NAME - name: initialize - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - mkdir -p bin - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.29/grabpl @@ -1496,7 +1496,7 @@ steps: - echo $DRONE_RUNNER_NAME - name: initialize - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - mkdir -p bin - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.29/grabpl @@ -1511,7 +1511,7 @@ steps: DOCKERIZE_VERSION: 0.6.1 - name: lint-backend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - golangci-lint run --config scripts/go/configs/.golangci.toml ./pkg/... - revive -formatter stylish -config scripts/go/configs/revive.toml ./pkg/... @@ -1525,7 +1525,7 @@ steps: - initialize - name: codespell - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - "echo -e \"unknwon\nreferer\nerrorstring\neror\niam\" > words_to_ignore.txt" - codespell -I words_to_ignore.txt docs/ @@ -1533,21 +1533,21 @@ steps: - initialize - name: shellcheck - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl shellcheck depends_on: - initialize - name: check-dashboard-schemas - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - cue export --out openapi -o - ./dashboard-schemas/... depends_on: - initialize - name: test-backend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - "[ $(grep FocusConvey -R pkg | wc -l) -eq \"0\" ] || exit 1" - ./bin/grabpl test-backend @@ -1557,7 +1557,7 @@ steps: - lint-backend - name: test-frontend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - yarn run ci:test-frontend environment: @@ -1566,7 +1566,7 @@ steps: - initialize - name: build-backend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl build-backend --jobs 8 --edition oss --github-token $${GITHUB_TOKEN} --no-pull-enterprise v7.3.0-test environment: @@ -1578,7 +1578,7 @@ steps: - test-backend - name: build-frontend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl build-frontend --jobs 8 --github-token $${GITHUB_TOKEN} --no-install-deps --edition oss --no-pull-enterprise v7.3.0-test depends_on: @@ -1586,7 +1586,7 @@ steps: - test-frontend - name: build-plugins - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl build-plugins --jobs 8 --edition oss --no-install-deps --sign --signing-admin environment: @@ -1597,7 +1597,7 @@ steps: - lint-backend - name: package - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl package --jobs 8 --edition oss --github-token $${GITHUB_TOKEN} --no-pull-enterprise --sign v7.3.0-test environment: @@ -1622,7 +1622,7 @@ steps: - check-dashboard-schemas - name: end-to-end-tests-server - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 detach: true commands: - ./e2e/start-server @@ -1640,7 +1640,7 @@ steps: - end-to-end-tests-server - name: build-storybook - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - yarn storybook:build - ./bin/grabpl verify-storybook @@ -1650,7 +1650,7 @@ steps: - package - name: copy-packages-for-docker - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - cp dist/*.tar.gz* packaging/docker/ depends_on: @@ -1674,7 +1674,7 @@ steps: - copy-packages-for-docker - name: postgres-integration-tests - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - apt-get update - apt-get install -yq postgresql-client @@ -1691,7 +1691,7 @@ steps: - test-frontend - name: mysql-integration-tests - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - apt-get update - apt-get install -yq default-mysql-client @@ -1731,7 +1731,7 @@ steps: - end-to-end-tests - name: release-npm-packages - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./node_modules/.bin/lerna bootstrap - echo "//registry.npmjs.org/:_authToken=$${NPM_TOKEN}" >> ~/.npmrc @@ -1829,7 +1829,7 @@ steps: - echo $DRONE_RUNNER_NAME - name: clone - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - mkdir -p bin - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.29/grabpl @@ -1842,7 +1842,7 @@ steps: from_secret: github_token - name: initialize - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - mv bin/grabpl /tmp/ - rmdir bin @@ -1862,7 +1862,7 @@ steps: - clone - name: lint-backend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - golangci-lint run --config scripts/go/configs/.golangci.toml ./pkg/... - revive -formatter stylish -config scripts/go/configs/revive.toml ./pkg/... @@ -1876,7 +1876,7 @@ steps: - initialize - name: codespell - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - "echo -e \"unknwon\nreferer\nerrorstring\neror\niam\" > words_to_ignore.txt" - codespell -I words_to_ignore.txt docs/ @@ -1884,21 +1884,21 @@ steps: - initialize - name: shellcheck - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl shellcheck depends_on: - initialize - name: check-dashboard-schemas - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - cue export --out openapi -o - ./dashboard-schemas/... depends_on: - initialize - name: test-backend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - "[ $(grep FocusConvey -R pkg | wc -l) -eq \"0\" ] || exit 1" - ./bin/grabpl test-backend @@ -1908,7 +1908,7 @@ steps: - lint-backend - name: test-frontend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - yarn run ci:test-frontend environment: @@ -1917,7 +1917,7 @@ steps: - initialize - name: build-backend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl build-backend --jobs 8 --edition enterprise --github-token $${GITHUB_TOKEN} --no-pull-enterprise v7.3.0-test environment: @@ -1929,7 +1929,7 @@ steps: - test-backend - name: build-frontend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl build-frontend --jobs 8 --github-token $${GITHUB_TOKEN} --no-install-deps --edition enterprise --no-pull-enterprise v7.3.0-test depends_on: @@ -1937,7 +1937,7 @@ steps: - test-frontend - name: build-plugins - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl build-plugins --jobs 8 --edition enterprise --no-install-deps --sign --signing-admin environment: @@ -1948,7 +1948,7 @@ steps: - lint-backend - name: package - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl package --jobs 8 --edition enterprise --github-token $${GITHUB_TOKEN} --no-pull-enterprise --sign v7.3.0-test environment: @@ -1973,7 +1973,7 @@ steps: - check-dashboard-schemas - name: end-to-end-tests-server - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 detach: true commands: - ./e2e/start-server @@ -1991,7 +1991,7 @@ steps: - end-to-end-tests-server - name: copy-packages-for-docker - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - cp dist/*.tar.gz* packaging/docker/ depends_on: @@ -2015,7 +2015,7 @@ steps: - copy-packages-for-docker - name: postgres-integration-tests - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - apt-get update - apt-get install -yq postgresql-client @@ -2032,7 +2032,7 @@ steps: - test-frontend - name: mysql-integration-tests - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - apt-get update - apt-get install -yq default-mysql-client @@ -2166,7 +2166,7 @@ steps: - echo $DRONE_RUNNER_NAME - name: initialize - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - mkdir -p bin - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.29/grabpl @@ -2253,7 +2253,7 @@ steps: - echo $DRONE_RUNNER_NAME - name: initialize - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - mkdir -p bin - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.29/grabpl @@ -2267,7 +2267,7 @@ steps: DOCKERIZE_VERSION: 0.6.1 - name: lint-backend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - golangci-lint run --config scripts/go/configs/.golangci.toml ./pkg/... - revive -formatter stylish -config scripts/go/configs/revive.toml ./pkg/... @@ -2281,7 +2281,7 @@ steps: - initialize - name: codespell - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - "echo -e \"unknwon\nreferer\nerrorstring\neror\niam\" > words_to_ignore.txt" - codespell -I words_to_ignore.txt docs/ @@ -2289,21 +2289,21 @@ steps: - initialize - name: shellcheck - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl shellcheck depends_on: - initialize - name: check-dashboard-schemas - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - cue export --out openapi -o - ./dashboard-schemas/... depends_on: - initialize - name: test-backend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - "[ $(grep FocusConvey -R pkg | wc -l) -eq \"0\" ] || exit 1" - ./bin/grabpl test-backend @@ -2313,7 +2313,7 @@ steps: - lint-backend - name: test-frontend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - yarn run ci:test-frontend environment: @@ -2322,7 +2322,7 @@ steps: - initialize - name: build-backend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl build-backend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} --no-pull-enterprise depends_on: @@ -2331,7 +2331,7 @@ steps: - test-backend - name: build-frontend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl build-frontend --jobs 8 --no-install-deps --edition oss --build-id ${DRONE_BUILD_NUMBER} --no-pull-enterprise depends_on: @@ -2339,7 +2339,7 @@ steps: - test-frontend - name: build-plugins - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl build-plugins --jobs 8 --edition oss --no-install-deps --sign --signing-admin environment: @@ -2350,7 +2350,7 @@ steps: - lint-backend - name: package - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl package --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} --no-pull-enterprise --sign environment: @@ -2375,7 +2375,7 @@ steps: - check-dashboard-schemas - name: end-to-end-tests-server - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 detach: true commands: - ./e2e/start-server @@ -2393,7 +2393,7 @@ steps: - end-to-end-tests-server - name: build-storybook - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - yarn storybook:build - ./bin/grabpl verify-storybook @@ -2403,7 +2403,7 @@ steps: - package - name: copy-packages-for-docker - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - cp dist/*.tar.gz* packaging/docker/ depends_on: @@ -2427,7 +2427,7 @@ steps: - copy-packages-for-docker - name: postgres-integration-tests - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - apt-get update - apt-get install -yq postgresql-client @@ -2444,7 +2444,7 @@ steps: - test-frontend - name: mysql-integration-tests - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - apt-get update - apt-get install -yq default-mysql-client @@ -2556,7 +2556,7 @@ steps: - echo $DRONE_RUNNER_NAME - name: clone - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - mkdir -p bin - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.29/grabpl @@ -2569,7 +2569,7 @@ steps: from_secret: github_token - name: initialize - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - mv bin/grabpl /tmp/ - rmdir bin @@ -2588,7 +2588,7 @@ steps: - clone - name: lint-backend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - golangci-lint run --config scripts/go/configs/.golangci.toml ./pkg/... - revive -formatter stylish -config scripts/go/configs/revive.toml ./pkg/... @@ -2602,7 +2602,7 @@ steps: - initialize - name: codespell - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - "echo -e \"unknwon\nreferer\nerrorstring\neror\niam\" > words_to_ignore.txt" - codespell -I words_to_ignore.txt docs/ @@ -2610,21 +2610,21 @@ steps: - initialize - name: shellcheck - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl shellcheck depends_on: - initialize - name: check-dashboard-schemas - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - cue export --out openapi -o - ./dashboard-schemas/... depends_on: - initialize - name: test-backend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - "[ $(grep FocusConvey -R pkg | wc -l) -eq \"0\" ] || exit 1" - ./bin/grabpl test-backend @@ -2634,7 +2634,7 @@ steps: - lint-backend - name: test-frontend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - yarn run ci:test-frontend environment: @@ -2643,7 +2643,7 @@ steps: - initialize - name: build-backend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl build-backend --jobs 8 --edition enterprise --build-id ${DRONE_BUILD_NUMBER} --no-pull-enterprise depends_on: @@ -2652,7 +2652,7 @@ steps: - test-backend - name: build-frontend - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl build-frontend --jobs 8 --no-install-deps --edition enterprise --build-id ${DRONE_BUILD_NUMBER} --no-pull-enterprise depends_on: @@ -2660,7 +2660,7 @@ steps: - test-frontend - name: build-plugins - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl build-plugins --jobs 8 --edition enterprise --no-install-deps --sign --signing-admin environment: @@ -2671,7 +2671,7 @@ steps: - lint-backend - name: package - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - ./bin/grabpl package --jobs 8 --edition enterprise --build-id ${DRONE_BUILD_NUMBER} --no-pull-enterprise --sign environment: @@ -2696,7 +2696,7 @@ steps: - check-dashboard-schemas - name: end-to-end-tests-server - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 detach: true commands: - ./e2e/start-server @@ -2714,7 +2714,7 @@ steps: - end-to-end-tests-server - name: build-storybook - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - yarn storybook:build - ./bin/grabpl verify-storybook @@ -2724,7 +2724,7 @@ steps: - package - name: copy-packages-for-docker - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - cp dist/*.tar.gz* packaging/docker/ depends_on: @@ -2748,7 +2748,7 @@ steps: - copy-packages-for-docker - name: postgres-integration-tests - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - apt-get update - apt-get install -yq postgresql-client @@ -2765,7 +2765,7 @@ steps: - test-frontend - name: mysql-integration-tests - image: grafana/build-container:1.2.30 + image: grafana/build-container:1.3.0 commands: - apt-get update - apt-get install -yq default-mysql-client diff --git a/Dockerfile b/Dockerfile index fb4c243bf44..3e9a4e96a3b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:12.19.0-alpine3.12 as js-builder +FROM node:14.15.1-alpine3.12 as js-builder WORKDIR /usr/src/app/ diff --git a/Dockerfile.ubuntu b/Dockerfile.ubuntu index 9818055bedd..04c5132f94a 100644 --- a/Dockerfile.ubuntu +++ b/Dockerfile.ubuntu @@ -1,4 +1,4 @@ -FROM node:12.19.0-slim AS js-builder +FROM node:14.15.1-slim AS js-builder WORKDIR /usr/src/app/ diff --git a/package.json b/package.json index 9f7fa18d1da..0b486f88d25 100644 --- a/package.json +++ b/package.json @@ -309,9 +309,9 @@ "puppeteer": "node_modules/puppeteer-core" }, "engines": { - "node": ">= 12" + "node": ">= 14" }, "volta": { - "node": "12.19.1" + "node": "14.15.1" } } diff --git a/scripts/build/ci-build/Dockerfile b/scripts/build/ci-build/Dockerfile index 1417978436a..21deda1c13b 100644 --- a/scripts/build/ci-build/Dockerfile +++ b/scripts/build/ci-build/Dockerfile @@ -100,7 +100,7 @@ FROM debian:stretch-20201012 ENV GOVERSION=1.15.5 \ PATH=/usr/local/go/bin:$PATH \ GOPATH=/go \ - NODEVERSION=12.19.1-1nodesource1 \ + NODEVERSION=14.15.1-1nodesource1 \ YARNVERSION=1.22.5-1 \ GO111MODULE=on @@ -135,7 +135,7 @@ RUN apt-get update && \ unzip && \ gem install -N fpm && \ ln -s /usr/bin/llvm-dsymutil-6.0 /usr/bin/dsymutil && \ - curl -fsL https://deb.nodesource.com/setup_12.x | bash - && \ + curl -fsL https://deb.nodesource.com/setup_14.x | bash - && \ apt-get update && apt-get install -yq nodejs=${NODEVERSION} && \ curl -fsS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \ diff --git a/scripts/build/ci-build/build-deploy.sh b/scripts/build/ci-build/build-deploy.sh index 1064efb9115..02df794d4a5 100755 --- a/scripts/build/ci-build/build-deploy.sh +++ b/scripts/build/ci-build/build-deploy.sh @@ -1,7 +1,7 @@ #!/bin/bash set -eo pipefail -_version="1.2.30" +_version="1.3.0" _tag="grafana/build-container:${_version}" _dpath=$(dirname "${BASH_SOURCE[0]}") diff --git a/scripts/lib.star b/scripts/lib.star index 6403a0081f5..0c9e446dec0 100644 --- a/scripts/lib.star +++ b/scripts/lib.star @@ -1,5 +1,5 @@ grabpl_version = '0.5.29' -build_image = 'grafana/build-container:1.2.30' +build_image = 'grafana/build-container:1.3.0' publish_image = 'grafana/grafana-ci-deploy:1.2.7' grafana_docker_image = 'grafana/drone-grafana-docker:0.3.2' alpine_image = 'alpine:3.12' From df0ef187311be097405057e1de127469b0ed97e8 Mon Sep 17 00:00:00 2001 From: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> Date: Wed, 2 Dec 2020 11:00:13 -0800 Subject: [PATCH 060/124] Docs: Update syntax.md (#29545) * Update syntax.md * Update syntax.md --- docs/sources/variables/syntax.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/variables/syntax.md b/docs/sources/variables/syntax.md index 8bb408ca273..f1ffcf80d0f 100644 --- a/docs/sources/variables/syntax.md +++ b/docs/sources/variables/syntax.md @@ -1,5 +1,5 @@ +++ -title = "Variables syntax and types" +title = "Variable syntax" keywords = ["grafana", "templating", "documentation", "guide", "template", "variable"] aliases = ["/docs/grafana/latest/reference/templating"] weight = 100 From 24086c63a30681859dbb80b89d4cf44264e04b47 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 2 Dec 2020 11:02:01 -0800 Subject: [PATCH 061/124] Arrow: toArray() on nullable values should include null values (#29520) --- .../src/dataframe/ArrowDataFrame.ts | 23 +++++++++++++++++++ .../__snapshots__/ArrowDataFrame.test.ts.snap | 2 +- .../src/dataframe/processDataFrame.ts | 13 ++++------- .../src/utils/queryResponse.test.ts | 2 +- 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/packages/grafana-data/src/dataframe/ArrowDataFrame.ts b/packages/grafana-data/src/dataframe/ArrowDataFrame.ts index 03a48fa7471..c09b3f18b58 100644 --- a/packages/grafana-data/src/dataframe/ArrowDataFrame.ts +++ b/packages/grafana-data/src/dataframe/ArrowDataFrame.ts @@ -55,6 +55,9 @@ export function arrowTableToDataFrame(table: Table): ArrowDataFrame { case ArrowType.Decimal: case ArrowType.FloatingPoint: { type = FieldType.number; + if (col.nullCount) { + values = new WrappedColumn(col); + } break; } case ArrowType.Int: { @@ -64,6 +67,9 @@ export function arrowTableToDataFrame(table: Table): ArrowDataFrame { } case ArrowType.Bool: { type = FieldType.boolean; + if (col.nullCount) { + values = new WrappedColumn(col); + } break; } case ArrowType.Timestamp: { @@ -202,3 +208,20 @@ class NumberColumn extends FunctionalVector { return Number(v); } } + +// The `toArray()` arrow function will return a native ArrayBuffer -- this works fine +// if there are no null values, but the native arrays do not support nulls. This +// class simply wraps the vector so `toArray` creates a new array +class WrappedColumn extends FunctionalVector { + constructor(private col: Column) { + super(); + } + + get length() { + return this.col.length; + } + + get(index: number): number { + return this.col.get(index); + } +} diff --git a/packages/grafana-data/src/dataframe/__snapshots__/ArrowDataFrame.test.ts.snap b/packages/grafana-data/src/dataframe/__snapshots__/ArrowDataFrame.test.ts.snap index 89c69fe8459..4b7e781ba0d 100644 --- a/packages/grafana-data/src/dataframe/__snapshots__/ArrowDataFrame.test.ts.snap +++ b/packages/grafana-data/src/dataframe/__snapshots__/ArrowDataFrame.test.ts.snap @@ -265,7 +265,7 @@ Object { "labels": undefined, "name": "float32_values", "type": "number", - "values": Array [ + "values": Float32Array [ 1.401298464324817e-45, 1.401298464324817e-45, 1, diff --git a/packages/grafana-data/src/dataframe/processDataFrame.ts b/packages/grafana-data/src/dataframe/processDataFrame.ts index 69d57700769..30fcc725fc9 100644 --- a/packages/grafana-data/src/dataframe/processDataFrame.ts +++ b/packages/grafana-data/src/dataframe/processDataFrame.ts @@ -24,6 +24,7 @@ import { SortedVector } from '../vector/SortedVector'; import { ArrayDataFrame } from './ArrayDataFrame'; import { getFieldDisplayName } from '../field/fieldState'; import { fieldIndexComparer } from '../field/fieldComparers'; +import { vectorToArray } from '../vector/vectorToArray'; function convertTableToDataFrame(table: TableData): DataFrame { const fields = table.columns.map(c => { @@ -444,16 +445,10 @@ export function getDataFrameRow(data: DataFrame, row: number): any[] { export function toDataFrameDTO(data: DataFrame): DataFrameDTO { const fields: FieldDTO[] = data.fields.map(f => { let values = f.values.toArray(); - if (!Array.isArray(values)) { - // Apache arrow will pack objects into typed arrays - // Float64Array, etc - // TODO: Float64Array could be used directly - values = []; - for (let i = 0; i < f.values.length; i++) { - values.push(f.values.get(i)); - } + // The byte buffers serialize like objects + if (values instanceof Float64Array) { + values = vectorToArray(f.values); } - return { name: f.name, type: f.type, diff --git a/packages/grafana-runtime/src/utils/queryResponse.test.ts b/packages/grafana-runtime/src/utils/queryResponse.test.ts index 25169669a0a..96d970c57cf 100644 --- a/packages/grafana-runtime/src/utils/queryResponse.test.ts +++ b/packages/grafana-runtime/src/utils/queryResponse.test.ts @@ -37,7 +37,7 @@ const emptyResults = { /* eslint-enable */ -describe('GEL Utils', () => { +describe('Query Response parser', () => { test('should parse output with dataframe', () => { const res = toDataQueryResponse(resp); const frames = res.data; From df1b0a1dafaab651cf43e10fccb2e01a88d1bb04 Mon Sep 17 00:00:00 2001 From: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> Date: Wed, 2 Dec 2020 12:03:45 -0800 Subject: [PATCH 062/124] Docs: Update formatting-multi-value-variables.md (#29547) --- docs/sources/variables/formatting-multi-value-variables.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sources/variables/formatting-multi-value-variables.md b/docs/sources/variables/formatting-multi-value-variables.md index 434d2637edd..2ec4d9df884 100644 --- a/docs/sources/variables/formatting-multi-value-variables.md +++ b/docs/sources/variables/formatting-multi-value-variables.md @@ -1,9 +1,9 @@ +++ -title = "Formatting multi-value variables" +title = "Multi-value variables" weight = 600 +++ -# Formatting multi-value variables +# Multi-value variables Interpolating a variable with multiple values selected is tricky as it is not straight forward how to format the multiple values into a string that is valid in the given context where the variable is used. Grafana tries to solve this by allowing each data source plugin to inform the templating interpolation engine what format to use for multiple values. @@ -21,7 +21,7 @@ InfluxDB and Prometheus use regex expressions, so the same variable would be int Elasticsearch uses lucene query syntax, so the same variable would be formatted as `("host1" OR "host2" OR "host3")`. In this case, every value must be escaped so that the value only contains lucene control words and quotation marks. -## Formatting troubles +## Troubleshoot multi-value variables Automatic escaping and formatting can cause problems and it can be tricky to grasp the logic behind it. Especially for InfluxDB and Prometheus where the use of regex syntax requires that the variable is used in regex operator context. From 22ee0a912c974324054cc9b09e6c65bf3aebddd4 Mon Sep 17 00:00:00 2001 From: Kyle Brandt Date: Wed, 2 Dec 2020 15:08:45 -0500 Subject: [PATCH 063/124] Chore: Update dev guide node version for Mac (#29548) --- contribute/developer-guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contribute/developer-guide.md b/contribute/developer-guide.md index 26ed45cacdf..5f3d4f1853d 100644 --- a/contribute/developer-guide.md +++ b/contribute/developer-guide.md @@ -20,7 +20,7 @@ We recommend using [Homebrew](https://brew.sh/) for installing any missing depen ``` brew install git brew install go -brew install node@12 +brew install node@14 npm install -g yarn ``` From 18534a1c9cfdacafb091270f6246a9965155905e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 2 Dec 2020 21:38:57 +0100 Subject: [PATCH 064/124] Docs: Fix docs sync actions (#29551) --- .github/workflows/publish.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 350a6100ff7..5fd37546d61 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -24,7 +24,7 @@ jobs: uses: actions/setup-node@v1 id: generate-docs with: - node-version: '12' + node-version: '14' - run: yarn install --pure-lockfile --no-progress - run: ./scripts/ci-reference-docs-build.sh - name: publish-to-git @@ -41,4 +41,4 @@ jobs: - shell: bash run: | test -n "${{ steps.publish.outputs.commit_hash }}" - test -n "${{ steps.publish.outputs.working_directory }}" \ No newline at end of file + test -n "${{ steps.publish.outputs.working_directory }}" From 22ec38b043a323526402fd6e9d4e61a00df5f209 Mon Sep 17 00:00:00 2001 From: Leon Sorokin Date: Wed, 2 Dec 2020 16:41:24 -0600 Subject: [PATCH 065/124] GraphNG: Bars, Staircase, Smooth modes (#29359) * initial impl of Bars, Staircase, Smooth modes * fix fillTo for staircase and smooth paths * fix pointSize * [bars] adjust gap factor & reduce bar width to include stroke width. remove rounding to favor gap & width uniformity over edge crispness. * remove line path fallback for too-dense data * "Auto" points mode implies "Never" for Bars * slightly reduce x axis tick density * fix bars height calc, make bars crisp again. * [bars] don't use hard-coded 'x' scale key * refactor catmullRomFitting() to accept x & y coord arrays. restrict to processing only visible data range. * move path building functions to own file * export typed functions Co-authored-by: Ryan McKinley --- .../src/components/GraphNG/GraphNG.tsx | 13 +- .../grafana-ui/src/components/uPlot/config.ts | 36 ++- .../uPlot/config/UPlotAxisBuilder.ts | 2 +- .../uPlot/config/UPlotConfigBuilder.test.ts | 6 +- .../uPlot/config/UPlotSeriesBuilder.ts | 63 +++-- .../grafana-ui/src/components/uPlot/paths.ts | 257 ++++++++++++++++++ public/app/plugins/panel/graph3/module.tsx | 23 +- 7 files changed, 348 insertions(+), 52 deletions(-) create mode 100644 packages/grafana-ui/src/components/uPlot/paths.ts diff --git a/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx b/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx index 411d7073b92..25a9a65b752 100755 --- a/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx +++ b/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx @@ -104,7 +104,10 @@ export const GraphNG: React.FC = ({ for (let i = 0; i < alignedFrame.fields.length; i++) { const field = alignedFrame.fields[i]; const config = field.config as FieldConfig; - const customConfig = config.custom || defaultConfig; + const customConfig: GraphFieldConfig = { + ...defaultConfig, + ...config.custom, + }; if (field === xField || field.type !== FieldType.number) { continue; @@ -134,14 +137,14 @@ export const GraphNG: React.FC = ({ builder.addSeries({ scaleKey: scale, - line: (customConfig.mode ?? GraphMode.Line) === GraphMode.Line, + mode: customConfig.mode!, lineColor: seriesColor, lineWidth: customConfig.lineWidth, + lineInterpolation: customConfig.lineInterpolation, points: pointsMode, - pointSize: customConfig.pointRadius, + pointSize: customConfig.pointSize, pointColor: seriesColor, - fill: customConfig.fillAlpha !== undefined, - fillOpacity: customConfig.fillAlpha, + fillOpacity: customConfig.fillOpacity, fillColor: seriesColor, }); diff --git a/packages/grafana-ui/src/components/uPlot/config.ts b/packages/grafana-ui/src/components/uPlot/config.ts index 00ee5e2850c..9da26df3227 100644 --- a/packages/grafana-ui/src/components/uPlot/config.ts +++ b/packages/grafana-ui/src/components/uPlot/config.ts @@ -11,13 +11,13 @@ export enum AxisPlacement { export enum PointMode { Auto = 'auto', // will show points when the density is low or line is hidden - Always = 'always', Never = 'never', + Always = 'always', } export enum GraphMode { Line = 'line', // default - Bar = 'bar', // will also have a gap percent + Bars = 'bars', // will also have a gap percent Points = 'points', // Only show points } @@ -27,31 +27,43 @@ export enum LineInterpolation { Smooth = 'smooth', // https://leeoniya.github.io/uPlot/demos/line-smoothing.html } -export interface GraphFieldConfig { - mode: GraphMode; +export interface LineConfig { + lineColor?: string; + lineWidth?: number; + lineInterpolation?: LineInterpolation; +} - lineMode?: LineInterpolation; - lineWidth?: number; // pixels - fillAlpha?: number; // 0-1 +export interface AreaConfig { + fillColor?: string; + fillOpacity?: number; +} +export interface PointsConfig { points?: PointMode; - pointRadius?: number; // pixels - symbol?: string; // eventually dot,star, etc + pointSize?: number; + pointColor?: string; + pointSymbol?: string; // eventually dot,star, etc +} - // Axis is actually unique based on the unit... not each field! +// Axis is actually unique based on the unit... not each field! +export interface AxisConfig { axisPlacement?: AxisPlacement; axisLabel?: string; axisWidth?: number; // pixels ideally auto? } +export interface GraphFieldConfig extends LineConfig, AreaConfig, PointsConfig, AxisConfig { + mode?: GraphMode; +} + export const graphFieldOptions = { mode: [ { label: 'Lines', value: GraphMode.Line }, - { label: 'Bars', value: GraphMode.Bar }, + { label: 'Bars', value: GraphMode.Bars }, { label: 'Points', value: GraphMode.Points }, ] as Array>, - lineMode: [ + lineInterpolation: [ { label: 'Linear', value: LineInterpolation.Linear }, { label: 'Staircase', value: LineInterpolation.Staircase }, { label: 'Smooth', value: LineInterpolation.Smooth }, diff --git a/packages/grafana-ui/src/components/uPlot/config/UPlotAxisBuilder.ts b/packages/grafana-ui/src/components/uPlot/config/UPlotAxisBuilder.ts index a5cfdd52125..8792d474d0d 100644 --- a/packages/grafana-ui/src/components/uPlot/config/UPlotAxisBuilder.ts +++ b/packages/grafana-ui/src/components/uPlot/config/UPlotAxisBuilder.ts @@ -83,7 +83,7 @@ function calculateSpace(self: uPlot, axisIdx: number, scaleMin: number, scaleMax // For x-axis (bottom) we need bigger spacing between labels if (axis.side === 2) { - return 50; + return 55; } return 30; diff --git a/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.test.ts b/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.test.ts index 9d5e6b2c19c..18a3ae243e9 100644 --- a/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.test.ts +++ b/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.test.ts @@ -3,7 +3,7 @@ import { UPlotConfigBuilder } from './UPlotConfigBuilder'; import { GrafanaTheme } from '@grafana/data'; import { expect } from '../../../../../../public/test/lib/common'; -import { AxisPlacement, PointMode } from '../config'; +import { AxisPlacement, GraphMode, PointMode } from '../config'; describe('UPlotConfigBuilder', () => { describe('scales config', () => { @@ -122,14 +122,13 @@ describe('UPlotConfigBuilder', () => { it('allows series configuration', () => { const builder = new UPlotConfigBuilder(); builder.addSeries({ + mode: GraphMode.Line, scaleKey: 'scale-x', - fill: true, fillColor: '#ff0000', fillOpacity: 0.5, points: PointMode.Auto, pointSize: 5, pointColor: '#00ff00', - line: true, lineColor: '#0000ff', lineWidth: 1, }); @@ -142,6 +141,7 @@ describe('UPlotConfigBuilder', () => { Object {}, Object { "fill": "rgba(255, 0, 0, 0.5)", + "paths": [Function], "points": Object { "fill": "#00ff00", "size": 5, diff --git a/packages/grafana-ui/src/components/uPlot/config/UPlotSeriesBuilder.ts b/packages/grafana-ui/src/components/uPlot/config/UPlotSeriesBuilder.ts index 09efd4ac57a..24e48258454 100755 --- a/packages/grafana-ui/src/components/uPlot/config/UPlotSeriesBuilder.ts +++ b/packages/grafana-ui/src/components/uPlot/config/UPlotSeriesBuilder.ts @@ -1,31 +1,52 @@ import tinycolor from 'tinycolor2'; -import { Series } from 'uplot'; -import { PointMode } from '../config'; +import uPlot, { Series } from 'uplot'; +import { GraphMode, LineConfig, AreaConfig, PointsConfig, PointMode, LineInterpolation } from '../config'; +import { barsBuilder, smoothBuilder, staircaseBuilder } from '../paths'; import { PlotConfigBuilder } from '../types'; -export interface SeriesProps { +export interface SeriesProps extends LineConfig, AreaConfig, PointsConfig { + mode: GraphMode; scaleKey: string; - line?: boolean; - lineColor?: string; - lineWidth?: number; - points?: PointMode; - pointSize?: number; - pointColor?: string; - fill?: boolean; - fillOpacity?: number; - fillColor?: string; } export class UPlotSeriesBuilder extends PlotConfigBuilder { getConfig() { - const { line, lineColor, lineWidth, points, pointColor, pointSize, fillColor, fillOpacity, scaleKey } = this.props; + const { + mode, + lineInterpolation, + lineColor, + lineWidth, + points, + pointColor, + pointSize, + fillColor, + fillOpacity, + scaleKey, + } = this.props; - const lineConfig = line - ? { - stroke: lineColor, - width: lineWidth, + let lineConfig: Partial = {}; + + if (mode === GraphMode.Points) { + lineConfig.paths = () => null; + } else { + lineConfig.stroke = lineColor; + lineConfig.width = lineWidth; + lineConfig.paths = (self: uPlot, seriesIdx: number, idx0: number, idx1: number) => { + let pathsBuilder = self.paths; + + if (mode === GraphMode.Bars) { + pathsBuilder = barsBuilder; + } else if (mode === GraphMode.Line) { + if (lineInterpolation === LineInterpolation.Staircase) { + pathsBuilder = staircaseBuilder; + } else if (lineInterpolation === LineInterpolation.Smooth) { + pathsBuilder = smoothBuilder; + } } - : {}; + + return pathsBuilder(self, seriesIdx, idx0, idx1); + }; + } const pointsConfig: Partial = { points: { @@ -36,7 +57,11 @@ export class UPlotSeriesBuilder extends PlotConfigBuilder { }; // we cannot set points.show property above (even to undefined) as that will clear uPlot's default auto behavior - if (points === PointMode.Never) { + if (points === PointMode.Auto) { + if (mode === GraphMode.Bars) { + pointsConfig.points!.show = false; + } + } else if (points === PointMode.Never) { pointsConfig.points!.show = false; } else if (points === PointMode.Always) { pointsConfig.points!.show = true; diff --git a/packages/grafana-ui/src/components/uPlot/paths.ts b/packages/grafana-ui/src/components/uPlot/paths.ts new file mode 100644 index 00000000000..f0818b81306 --- /dev/null +++ b/packages/grafana-ui/src/components/uPlot/paths.ts @@ -0,0 +1,257 @@ +import uPlot, { Series } from 'uplot'; + +export const barsBuilder: Series.PathBuilder = (u: uPlot, seriesIdx: number, idx0: number, idx1: number) => { + const series = u.series[seriesIdx]; + const xdata = u.data[0]; + const ydata = u.data[seriesIdx]; + const scaleX = u.series[0].scale as string; + const scaleY = series.scale as string; + + const gapFactor = 0.25; + + let gap = (u.width * gapFactor) / (idx1 - idx0); + let maxWidth = Infinity; + + //@ts-ignore + let fillTo = series.fillTo(u, seriesIdx, series.min, series.max); + + let y0Pos = u.valToPos(fillTo, scaleY, true); + let colWid = u.bbox.width / (idx1 - idx0); + + let strokeWidth = Math.round(series.width! * devicePixelRatio); + + let barWid = Math.round(Math.min(maxWidth, colWid - gap) - strokeWidth); + + let stroke = new Path2D(); + + for (let i = idx0; i <= idx1; i++) { + let yVal = ydata[i]; + + if (yVal == null) { + continue; + } + + let xVal = u.scales.x.distr === 2 ? i : xdata[i]; + + // TODO: all xPos can be pre-computed once for all series in aligned set + let xPos = u.valToPos(xVal, scaleX, true); + let yPos = u.valToPos(yVal, scaleY, true); + + let lft = Math.round(xPos - barWid / 2); + let btm = Math.round(Math.max(yPos, y0Pos)); + let top = Math.round(Math.min(yPos, y0Pos)); + let barHgt = btm - top; + + stroke.rect(lft, top, barWid, barHgt); + } + + let fill = series.fill != null ? new Path2D(stroke) : undefined; + + return { + stroke, + fill, + }; +}; + +export const staircaseBuilder: Series.PathBuilder = (u: uPlot, seriesIdx: number, idx0: number, idx1: number) => { + const series = u.series[seriesIdx]; + const xdata = u.data[0]; + const ydata = u.data[seriesIdx]; + const scaleX = u.series[0].scale as string; + const scaleY = series.scale as string; + + const stroke = new Path2D(); + stroke.moveTo(Math.round(u.valToPos(xdata[0], scaleX, true)), Math.round(u.valToPos(ydata[0]!, scaleY, true))); + + for (let i = idx0; i <= idx1 - 1; i++) { + let x0 = Math.round(u.valToPos(xdata[i], scaleX, true)); + let y0 = Math.round(u.valToPos(ydata[i]!, scaleY, true)); + let x1 = Math.round(u.valToPos(xdata[i + 1], scaleX, true)); + let y1 = Math.round(u.valToPos(ydata[i + 1]!, scaleY, true)); + + stroke.lineTo(x0, y0); + stroke.lineTo(x1, y0); + + if (i === idx1 - 1) { + stroke.lineTo(x1, y1); + } + } + + const fill = new Path2D(stroke); + + //@ts-ignore + let fillTo = series.fillTo(u, seriesIdx, series.min, series.max); + + let minY = Math.round(u.valToPos(fillTo, scaleY, true)); + let minX = Math.round(u.valToPos(u.scales[scaleX].min!, scaleX, true)); + let maxX = Math.round(u.valToPos(u.scales[scaleX].max!, scaleX, true)); + + fill.lineTo(maxX, minY); + fill.lineTo(minX, minY); + + return { + stroke, + fill, + }; +}; + +// adapted from https://gist.github.com/nicholaswmin/c2661eb11cad5671d816 (MIT) +/** + * Interpolates a Catmull-Rom Spline through a series of x/y points + * Converts the CR Spline to Cubic Beziers for use with SVG items + * + * If 'alpha' is 0.5 then the 'Centripetal' variant is used + * If 'alpha' is 1 then the 'Chordal' variant is used + * + * + * @param {Array} data - Array of points, each point in object literal holding x/y values + * @return {String} d - SVG string with cubic bezier curves representing the Catmull-Rom Spline + */ +function catmullRomFitting(xCoords: number[], yCoords: number[], alpha: number) { + const path = new Path2D(); + + const dataLen = xCoords.length; + + let p0x, + p0y, + p1x, + p1y, + p2x, + p2y, + p3x, + p3y, + bp1x, + bp1y, + bp2x, + bp2y, + d1, + d2, + d3, + A, + B, + N, + M, + d3powA, + d2powA, + d3pow2A, + d2pow2A, + d1pow2A, + d1powA; + + path.moveTo(Math.round(xCoords[0]), Math.round(yCoords[0])); + + for (let i = 0; i < dataLen - 1; i++) { + let p0i = i === 0 ? 0 : i - 1; + + p0x = xCoords[p0i]; + p0y = yCoords[p0i]; + + p1x = xCoords[i]; + p1y = yCoords[i]; + + p2x = xCoords[i + 1]; + p2y = yCoords[i + 1]; + + if (i + 2 < dataLen) { + p3x = xCoords[i + 2]; + p3y = yCoords[i + 2]; + } else { + p3x = p2x; + p3y = p2y; + } + + d1 = Math.sqrt(Math.pow(p0x - p1x, 2) + Math.pow(p0y - p1y, 2)); + d2 = Math.sqrt(Math.pow(p1x - p2x, 2) + Math.pow(p1y - p2y, 2)); + d3 = Math.sqrt(Math.pow(p2x - p3x, 2) + Math.pow(p2y - p3y, 2)); + + // Catmull-Rom to Cubic Bezier conversion matrix + + // A = 2d1^2a + 3d1^a * d2^a + d3^2a + // B = 2d3^2a + 3d3^a * d2^a + d2^2a + + // [ 0 1 0 0 ] + // [ -d2^2a /N A/N d1^2a /N 0 ] + // [ 0 d3^2a /M B/M -d2^2a /M ] + // [ 0 0 1 0 ] + + d3powA = Math.pow(d3, alpha); + d3pow2A = Math.pow(d3, 2 * alpha); + d2powA = Math.pow(d2, alpha); + d2pow2A = Math.pow(d2, 2 * alpha); + d1powA = Math.pow(d1, alpha); + d1pow2A = Math.pow(d1, 2 * alpha); + + A = 2 * d1pow2A + 3 * d1powA * d2powA + d2pow2A; + B = 2 * d3pow2A + 3 * d3powA * d2powA + d2pow2A; + N = 3 * d1powA * (d1powA + d2powA); + + if (N > 0) { + N = 1 / N; + } + + M = 3 * d3powA * (d3powA + d2powA); + + if (M > 0) { + M = 1 / M; + } + + bp1x = (-d2pow2A * p0x + A * p1x + d1pow2A * p2x) * N; + bp1y = (-d2pow2A * p0y + A * p1y + d1pow2A * p2y) * N; + + bp2x = (d3pow2A * p1x + B * p2x - d2pow2A * p3x) * M; + bp2y = (d3pow2A * p1y + B * p2y - d2pow2A * p3y) * M; + + if (bp1x === 0 && bp1y === 0) { + bp1x = p1x; + bp1y = p1y; + } + + if (bp2x === 0 && bp2y === 0) { + bp2x = p2x; + bp2y = p2y; + } + + path.bezierCurveTo(bp1x, bp1y, bp2x, bp2y, p2x, p2y); + } + + return path; +} + +export const smoothBuilder: Series.PathBuilder = (u: uPlot, seriesIdx: number, idx0: number, idx1: number) => { + const series = u.series[seriesIdx]; + const xdata = u.data[0]; + const ydata = u.data[seriesIdx]; + const scaleX = u.series[0].scale as string; + const scaleY = series.scale as string; + + const alpha = 0.5; + + let xCoords = []; + let yCoords = []; + + for (let i = idx0; i <= idx1; i++) { + if (ydata[i] != null) { + xCoords.push(u.valToPos(xdata[i], scaleX, true)); + yCoords.push(u.valToPos(ydata[i]!, scaleY, true)); + } + } + + const stroke = catmullRomFitting(xCoords, yCoords, alpha); + + const fill = new Path2D(stroke); + + //@ts-ignore + let fillTo = series.fillTo(u, seriesIdx, series.min, series.max); + + let minY = Math.round(u.valToPos(fillTo, scaleY, true)); + let minX = Math.round(u.valToPos(u.scales[scaleX].min!, scaleX, true)); + let maxX = Math.round(u.valToPos(u.scales[scaleX].max!, scaleX, true)); + + fill.lineTo(maxX, minY); + fill.lineTo(minX, minY); + + return { + stroke, + fill, + }; +}; diff --git a/public/app/plugins/panel/graph3/module.tsx b/public/app/plugins/panel/graph3/module.tsx index 32a4dc8bc9b..c2d0128e916 100644 --- a/public/app/plugins/panel/graph3/module.tsx +++ b/public/app/plugins/panel/graph3/module.tsx @@ -33,28 +33,27 @@ export const plugin = new PanelPlugin(GraphPanel) }, }) .addRadio({ - path: 'lineMode', + path: 'lineInterpolation', name: 'Line interpolation', - description: 'NOTE: not implemented yet', - defaultValue: graphFieldOptions.lineMode[0].value, + defaultValue: graphFieldOptions.lineInterpolation[0].value, settings: { - options: graphFieldOptions.lineMode, + options: graphFieldOptions.lineInterpolation, }, - showIf: c => !(c.mode === GraphMode.Bar || c.mode === GraphMode.Points), + showIf: c => c.mode === GraphMode.Line, }) .addSliderInput({ path: 'lineWidth', name: 'Line width', defaultValue: 1, settings: { - min: 1, + min: 0, max: 10, step: 1, }, - showIf: c => !(c.mode === GraphMode.Bar || c.mode === GraphMode.Points), + showIf: c => c.mode !== GraphMode.Points, }) .addSliderInput({ - path: 'fillAlpha', + path: 'fillOpacity', name: 'Fill area opacity', defaultValue: 0.1, settings: { @@ -62,7 +61,7 @@ export const plugin = new PanelPlugin(GraphPanel) max: 1, step: 0.1, }, - showIf: c => !(c.mode === GraphMode.Bar || c.mode === GraphMode.Points), + showIf: c => c.mode !== GraphMode.Points, }) .addRadio({ path: 'points', @@ -73,9 +72,9 @@ export const plugin = new PanelPlugin(GraphPanel) }, }) .addSliderInput({ - path: 'pointRadius', - name: 'Point radius', - defaultValue: 4, + path: 'pointSize', + name: 'Point size', + defaultValue: 5, settings: { min: 1, max: 10, From 0b6434d0e8753755fc0bec67f06655d2594ee2ba Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 2 Dec 2020 22:17:24 -0800 Subject: [PATCH 066/124] Overrides: show category on the overrides (#29556) --- .../components/PanelEditor/DynamicConfigValueEditor.tsx | 2 +- .../dashboard/components/PanelEditor/OverrideEditor.tsx | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx b/public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx index 39f51130290..28b207d83b3 100644 --- a/public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx +++ b/public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx @@ -33,7 +33,7 @@ export const DynamicConfigValueEditor: React.FC = // eslint-disable-next-line react/display-name const renderLabel = (includeDescription = true, includeCounter = false) => (isExpanded = false) => ( -
`; diff --git a/public/app/features/datasources/settings/__snapshots__/BasicSettings.test.tsx.snap b/public/app/features/datasources/settings/__snapshots__/BasicSettings.test.tsx.snap index 7fd90bb03c5..e9ee2a6f705 100644 --- a/public/app/features/datasources/settings/__snapshots__/BasicSettings.test.tsx.snap +++ b/public/app/features/datasources/settings/__snapshots__/BasicSettings.test.tsx.snap @@ -16,11 +16,11 @@ exports[`Render should render component 1`] = ` } } > - Name - + @@ -29,5 +29,5 @@ exports[`ErrorContainer should render component 1`] = ` - + `; diff --git a/public/app/features/explore/__snapshots__/Explore.test.tsx.snap b/public/app/features/explore/__snapshots__/Explore.test.tsx.snap index 3b9641966c5..1b67a99f918 100644 --- a/public/app/features/explore/__snapshots__/Explore.test.tsx.snap +++ b/public/app/features/explore/__snapshots__/Explore.test.tsx.snap @@ -37,7 +37,7 @@ exports[`Explore should render component 1`] = ` richHistoryButtonActive={false} /> - ; + abstract getChannelSupport(namespace: string): Promise; /** * List the possible values within this scope */ - abstract async listNamespaces(): Promise>>; + abstract listNamespaces(): Promise>>; } export interface CoreGrafanaLiveFeature { diff --git a/public/app/features/manage-dashboards/state/selectors.ts b/public/app/features/manage-dashboards/state/selectors.ts index 7113dc88913..4f9223b289d 100644 --- a/public/app/features/manage-dashboards/state/selectors.ts +++ b/public/app/features/manage-dashboards/state/selectors.ts @@ -14,7 +14,7 @@ export const getDashboardNavModel = (state: StoreState): NavModel => { } // This needs to be copied to avoid mutating the store in a selector - nav.main.children = [...navModel.main.children]; + nav.main.children = [...(navModel.main.children ?? [])]; for (const item of nav.main.children) { item.active = false; diff --git a/public/app/features/org/__snapshots__/OrgDetailsPage.test.tsx.snap b/public/app/features/org/__snapshots__/OrgDetailsPage.test.tsx.snap index 95b79ec31ad..682c28d0482 100644 --- a/public/app/features/org/__snapshots__/OrgDetailsPage.test.tsx.snap +++ b/public/app/features/org/__snapshots__/OrgDetailsPage.test.tsx.snap @@ -35,7 +35,7 @@ exports[`Render should render organization and preferences 1`] = ` - + - + `; diff --git a/public/app/features/plugins/PluginDashboards.tsx b/public/app/features/plugins/PluginDashboards.tsx index 5dbebc5c7a6..ee5461f1866 100644 --- a/public/app/features/plugins/PluginDashboards.tsx +++ b/public/app/features/plugins/PluginDashboards.tsx @@ -44,7 +44,7 @@ export class PluginDashboards extends PureComponent { const { dashboards } = this.state; return this.import(dashboards[index], true).then(() => { if (index + 1 < dashboards.length) { - return new Promise(resolve => { + return new Promise(resolve => { setTimeout(() => { this.importNext(index + 1).then(() => { resolve(); diff --git a/public/app/features/query/components/QueryGroupOptions.tsx b/public/app/features/query/components/QueryGroupOptions.tsx index dc0e27bbda1..b4166436fa5 100644 --- a/public/app/features/query/components/QueryGroupOptions.tsx +++ b/public/app/features/query/components/QueryGroupOptions.tsx @@ -171,7 +171,6 @@ export class QueryGroupOptionsEditor extends PureComponent { type="text" className="width-6" placeholder="60" - name={name} spellCheck={false} onBlur={this.onCacheTimeoutBlur} defaultValue={options.cacheTimeout ?? ''} @@ -205,7 +204,6 @@ export class QueryGroupOptionsEditor extends PureComponent { type="number" className="width-6" placeholder={`${realMd}`} - name={name} spellCheck={false} onBlur={this.onMaxDataPointsBlur} defaultValue={value} @@ -246,7 +244,6 @@ export class QueryGroupOptionsEditor extends PureComponent { type="text" className="width-6" placeholder={`${minIntervalOnDs}`} - name={name} spellCheck={false} onBlur={this.onMinIntervalBlur} defaultValue={options.minInterval ?? ''} diff --git a/public/app/features/teams/__snapshots__/TeamGroupSync.test.tsx.snap b/public/app/features/teams/__snapshots__/TeamGroupSync.test.tsx.snap index cccf6538e73..666898ba725 100644 --- a/public/app/features/teams/__snapshots__/TeamGroupSync.test.tsx.snap +++ b/public/app/features/teams/__snapshots__/TeamGroupSync.test.tsx.snap @@ -23,7 +23,7 @@ exports[`Render should render component 1`] = ` className="page-action-bar__spacer" /> -
-
+ -
-
+
diff --git a/public/app/features/teams/__snapshots__/TeamList.test.tsx.snap b/public/app/features/teams/__snapshots__/TeamList.test.tsx.snap index 5f9fdbbbf11..8c12eb61fd2 100644 --- a/public/app/features/teams/__snapshots__/TeamList.test.tsx.snap +++ b/public/app/features/teams/__snapshots__/TeamList.test.tsx.snap @@ -41,7 +41,7 @@ exports[`Render should render teams table 1`] = `
- - - - - - - - - - testName -
- + - testName -
-
+ - testName - - + - testName - - + - - -
-
+
@@ -72,13 +72,13 @@ exports[`Render should render component 1`] = ` Name - Permission - + -
-
-
+
@@ -166,13 +166,13 @@ exports[`Render should render team members 1`] = ` Name - Permission - + - + `; @@ -66,7 +66,7 @@ exports[`Render when feature toggle editorsCanAdmin is turned on should render s - + `; diff --git a/public/app/features/teams/__snapshots__/TeamSettings.test.tsx.snap b/public/app/features/teams/__snapshots__/TeamSettings.test.tsx.snap index 1967d04e1ca..a8e44c8514d 100644 --- a/public/app/features/teams/__snapshots__/TeamSettings.test.tsx.snap +++ b/public/app/features/teams/__snapshots__/TeamSettings.test.tsx.snap @@ -1,8 +1,8 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Render should render component 1`] = ` - - +
-
+
-
+ `; diff --git a/public/app/features/users/__snapshots__/UsersActionBar.test.tsx.snap b/public/app/features/users/__snapshots__/UsersActionBar.test.tsx.snap index 484356e654d..81da5fe74d5 100644 --- a/public/app/features/users/__snapshots__/UsersActionBar.test.tsx.snap +++ b/public/app/features/users/__snapshots__/UsersActionBar.test.tsx.snap @@ -7,7 +7,7 @@ exports[`Render should render component 1`] = `
- - - - - @@ -126,7 +126,7 @@ exports[`Render should render users table 1`] = ` size="sm" variant="destructive" /> - - @@ -195,7 +195,7 @@ exports[`Render should render users table 1`] = ` size="sm" variant="destructive" /> - - @@ -264,7 +264,7 @@ exports[`Render should render users table 1`] = ` size="sm" variant="destructive" /> - - @@ -333,7 +333,7 @@ exports[`Render should render users table 1`] = ` size="sm" variant="destructive" /> - - @@ -402,7 +402,7 @@ exports[`Render should render users table 1`] = ` size="sm" variant="destructive" /> - - @@ -471,7 +471,7 @@ exports[`Render should render users table 1`] = ` size="sm" variant="destructive" /> - { + await new Promise(resolve => { const unsubscribe = store.subscribe(() => { if (!isWaitingForDependencies(dependencies, store.getState())) { unsubscribe(); diff --git a/public/app/plugins/datasource/cloudwatch/components/__snapshots__/ConfigEditor.test.tsx.snap b/public/app/plugins/datasource/cloudwatch/components/__snapshots__/ConfigEditor.test.tsx.snap index 8fe9f7103e6..53fd134e641 100644 --- a/public/app/plugins/datasource/cloudwatch/components/__snapshots__/ConfigEditor.test.tsx.snap +++ b/public/app/plugins/datasource/cloudwatch/components/__snapshots__/ConfigEditor.test.tsx.snap @@ -16,12 +16,12 @@ exports[`Render should disable access key id field 1`] = `
- Authentication Provider - + - Custom Metrics - + - Authentication Provider - + - Custom Metrics - + - Authentication Provider - + - Custom Metrics - + - Authentication Provider - + - Custom Metrics - + - Authentication Provider - + - Custom Metrics - + - Default Workspace - +
@@ -129,12 +129,12 @@ exports[`Render should enable azure log analytics load workspaces button 1`] = `
- Default Workspace - +
@@ -225,12 +225,12 @@ exports[`Render should render component 1`] = `
- Default Workspace - +
diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/__snapshots__/AzureCredentialsForm.test.tsx.snap b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/__snapshots__/AzureCredentialsForm.test.tsx.snap index caf4a184fea..b7b4795554e 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/__snapshots__/AzureCredentialsForm.test.tsx.snap +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/__snapshots__/AzureCredentialsForm.test.tsx.snap @@ -11,12 +11,12 @@ exports[`Render should disable azure monitor secret input 1`] = `
- Azure Cloud - + - Default Subscription - +
@@ -228,12 +228,12 @@ exports[`Render should enable azure monitor load subscriptions button 1`] = `
- Azure Cloud - + - Directory (tenant) ID - +
@@ -523,11 +523,11 @@ exports[`Render should render component 1`] = `
- Application (client) ID - +
@@ -546,11 +546,11 @@ exports[`Render should render component 1`] = `
- Client Secret - +
@@ -569,11 +569,11 @@ exports[`Render should render component 1`] = `
- Default Subscription - +
diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/__snapshots__/InsightsConfig.test.tsx.snap b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/__snapshots__/InsightsConfig.test.tsx.snap index 89b735c5299..b2e1b769d2a 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/__snapshots__/InsightsConfig.test.tsx.snap +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/__snapshots__/InsightsConfig.test.tsx.snap @@ -16,11 +16,11 @@ exports[`Render should disable insights api key input 1`] = `
- API Key - + - Application ID - +
@@ -84,11 +84,11 @@ exports[`Render should enable insights api key input 1`] = `
- API Key - +
@@ -106,11 +106,11 @@ exports[`Render should enable insights api key input 1`] = `
- Application ID - +
@@ -141,11 +141,11 @@ exports[`Render should render component 1`] = `
- API Key - +
@@ -163,11 +163,11 @@ exports[`Render should render component 1`] = `
- Application ID - +
diff --git a/public/app/plugins/datasource/influxdb/components/__snapshots__/ConfigEditor.test.tsx.snap b/public/app/plugins/datasource/influxdb/components/__snapshots__/ConfigEditor.test.tsx.snap index fd4dce305c6..b311ad46086 100644 --- a/public/app/plugins/datasource/influxdb/components/__snapshots__/ConfigEditor.test.tsx.snap +++ b/public/app/plugins/datasource/influxdb/components/__snapshots__/ConfigEditor.test.tsx.snap @@ -72,7 +72,7 @@ exports[`Render should disable basic auth password input 1`] = `
- - Database - +
@@ -139,11 +139,11 @@ exports[`Render should disable basic auth password input 1`] = `
- User - +
@@ -177,14 +177,14 @@ exports[`Render should disable basic auth password input 1`] = `
- HTTP Method - + - Min time interval - +
@@ -648,7 +648,7 @@ exports[`Render should hide white listed cookies input when browser access chose
- - Database - +
@@ -715,11 +715,11 @@ exports[`Render should hide white listed cookies input when browser access chose
- User - +
@@ -753,14 +753,14 @@ exports[`Render should hide white listed cookies input when browser access chose
- HTTP Method - + - Min time interval - +
diff --git a/public/app/plugins/datasource/loki/components/__snapshots__/LokiExploreQueryEditor.test.tsx.snap b/public/app/plugins/datasource/loki/components/__snapshots__/LokiExploreQueryEditor.test.tsx.snap index aa142f94ec3..644a78e2d2a 100644 --- a/public/app/plugins/datasource/loki/components/__snapshots__/LokiExploreQueryEditor.test.tsx.snap +++ b/public/app/plugins/datasource/loki/components/__snapshots__/LokiExploreQueryEditor.test.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`LokiExploreQueryEditor should render component 1`] = ` - - Legend - + - Legend - + - Legend - + - An additional lower limit for the step parameter of the Prometheus query and for the @@ -62,7 +62,7 @@ exports[`Render PromQueryEditor with basic options should render 1`] = ` width={7} > Min step - + - @@ -180,7 +180,7 @@ exports[`Render PromQueryEditor with basic options should render 1`] = ` } } /> - +
diff --git a/public/app/plugins/datasource/zipkin/utils/transforms.ts b/public/app/plugins/datasource/zipkin/utils/transforms.ts index 048d8106af0..2e4f29f7025 100644 --- a/public/app/plugins/datasource/zipkin/utils/transforms.ts +++ b/public/app/plugins/datasource/zipkin/utils/transforms.ts @@ -52,7 +52,7 @@ function transformSpan(span: ZipkinSpan): TraceSpanData { type: 'string', value: span.kind, }, - ...jaegerSpan.tags, + ...(jaegerSpan.tags ?? []), ]; } diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index 3de95db23d4..abd78aa5bca 100644 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -273,7 +273,7 @@ class GraphElement { const hasLinksValue = hasLinks(field); if (hasLinksValue) { // Append the configured links to the panel datalinks - links = [...links, ...field.config.links]; + links = [...links, ...field.config.links!]; } const fieldConfig = { decimals: yAxisConfig.decimals, diff --git a/yarn.lock b/yarn.lock index e7d88186539..5305760c52c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16797,6 +16797,17 @@ jest-util@^24.0.0, jest-util@^24.9.0: slash "^2.0.0" source-map "^0.6.0" +jest-util@^25.5.0: + version "25.5.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-25.5.0.tgz#31c63b5d6e901274d264a4fec849230aa3fa35b0" + integrity sha512-KVlX+WWg1zUTB9ktvhsg2PXZVdkI1NBevOJSkTKYAyXyH4QSvh+Lay/e/v+bmaFfrkfx43xD8QTfgobzlEXdIA== + dependencies: + "@jest/types" "^25.5.0" + chalk "^3.0.0" + graceful-fs "^4.2.4" + is-ci "^2.0.0" + make-dir "^3.0.0" + jest-util@^26.1.0, jest-util@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.6.2.tgz#907535dbe4d5a6cb4c47ac9b926f6af29576cbc1" @@ -25510,6 +25521,23 @@ ts-jest@26.4.4: semver "7.x" yargs-parser "20.x" +ts-jest@26.4.4: + version "26.4.4" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.4.4.tgz#61f13fb21ab400853c532270e52cc0ed7e502c49" + integrity sha512-3lFWKbLxJm34QxyVNNCgXX1u4o/RV0myvA2y2Bxm46iGIjKlaY0own9gIckbjZJPn+WaJEnfPPJ20HHGpoq4yg== + dependencies: + "@types/jest" "26.x" + bs-logger "0.x" + buffer-from "1.x" + fast-json-stable-stringify "2.x" + jest-util "^26.1.0" + json5 "2.x" + lodash.memoize "4.x" + make-error "1.x" + mkdirp "1.x" + semver "7.x" + yargs-parser "20.x" + ts-loader@6.2.1: version "6.2.1" resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-6.2.1.tgz#67939d5772e8a8c6bdaf6277ca023a4812da02ef" @@ -25532,15 +25560,15 @@ ts-loader@8.0.11: micromatch "^4.0.0" semver "^6.0.0" -ts-node@8.8.1: - version "8.8.1" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.8.1.tgz#7c4d3e9ed33aa703b64b28d7f9d194768be5064d" - integrity sha512-10DE9ONho06QORKAaCBpPiFCdW+tZJuY/84tyypGtl6r+/C7Asq0dhqbRZURuUlLQtZxxDvT8eoj8cGW0ha6Bg== +ts-node@9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-9.0.0.tgz#e7699d2a110cc8c0d3b831715e417688683460b3" + integrity sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg== dependencies: arg "^4.1.0" diff "^4.0.1" make-error "^1.1.1" - source-map-support "^0.5.6" + source-map-support "^0.5.17" yn "3.1.1" ts-pnp@^1.1.6: @@ -25553,12 +25581,7 @@ tslib@1.10.0, tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== -tslib@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.1.tgz#410eb0d113e5b6356490eec749603725b021b43e" - integrity sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ== - -tslib@^2.0.0, tslib@^2.0.1: +tslib@2.0.3, tslib@^2.0.0, tslib@^2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.3.tgz#8e0741ac45fc0c226e58a17bfc3e64b9bc6ca61c" integrity sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ== @@ -25681,6 +25704,11 @@ typescript@4.0.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.2.tgz#7ea7c88777c723c681e33bf7988be5d008d05ac2" integrity sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ== +typescript@4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.2.tgz#6369ef22516fe5e10304aae5a5c4862db55380e9" + integrity sha512-thGloWsGH3SOxv1SoY7QojKi0tc+8FnOmiarEGMbd/lar7QOEd3hvlx3Fp5y6FlDUGl9L+pd4n2e+oToGMmhRQ== + typescript@~3.9.7: version "3.9.7" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" @@ -26968,6 +26996,11 @@ yargs-parser@20.x, yargs-parser@^20.2.2: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== +yargs-parser@20.x: + version "20.2.4" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" + integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== + yargs-parser@^11.1.1: version "11.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4" From b63e6959df63c43f701fe5150b4df195194e1f4d Mon Sep 17 00:00:00 2001 From: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> Date: Thu, 3 Dec 2020 08:12:46 -0800 Subject: [PATCH 076/124] Update documentation-style-guide.md (#29580) --- .../style-guides/documentation-style-guide.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/contribute/style-guides/documentation-style-guide.md b/contribute/style-guides/documentation-style-guide.md index 3e224196276..7bd5b4d0af0 100644 --- a/contribute/style-guides/documentation-style-guide.md +++ b/contribute/style-guides/documentation-style-guide.md @@ -115,7 +115,6 @@ If that is the case, then use the following format for headings: - Grafana, Loki, and Prometheus are always capitalized unless part of a code block. - API names are always Title Case, followed by "API"—for example, "Dashboard Permissions API" -- Git is always capitalized, unless part of a code block. - Abbreviations are always capitalized (such as API, HTTP, ID, JSON, SQL, or URL) unless they are part of a code block. - Menu and submenu titles always use sentence case: capitalize the first word, and lowercase the rest. - "Dashboards" when referring to the submenu title. @@ -125,6 +124,19 @@ If that is the case, then use the following format for headings: - Lowercase "dashboards" when referring to multiple dashboards. - **Exceptions:** If a term is lowercased in the Grafana UI, then match the UI. +#### Git, GitHub + +Git is always capitalized, unless part of a code block. GitHub is the correct spelling and capitalization. + +#### Integrations + +In general, "integration" is not capitalized. Only capitalize it if it is capitalized in the UI or part of a proper noun, like the name of a specific integration. + +**Examples:** +- MySQL Integration +- CockroachDB Integration +- I installed an integration on my local Grafana. + ### Links and references When referencing another document, use "Refer to" rather than alternatives such as "See" or "Check out." From fa64d7ece40fea4c5279580730e427a6fe6f3799 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 3 Dec 2020 17:35:34 +0100 Subject: [PATCH 077/124] Alert: Fix forwardRef warning (#29577) --- packages/grafana-ui/src/components/Alert/Alert.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/grafana-ui/src/components/Alert/Alert.tsx b/packages/grafana-ui/src/components/Alert/Alert.tsx index b009b01d5fc..451197091d9 100644 --- a/packages/grafana-ui/src/components/Alert/Alert.tsx +++ b/packages/grafana-ui/src/components/Alert/Alert.tsx @@ -9,7 +9,7 @@ import { getColorsFromSeverity } from '../../utils/colors'; export type AlertVariant = 'success' | 'warning' | 'error' | 'info'; -export interface Props extends HTMLAttributes { +export interface Props extends HTMLAttributes { title: string; /** On click handler for alert button, mostly used for dismissing the alert */ onRemove?: (event: React.MouseEvent) => void; @@ -39,13 +39,13 @@ function getIconFromSeverity(severity: AlertVariant): string { } } -export const Alert: FC = React.forwardRef( - ({ title, buttonText, onButtonClick, onRemove, children, buttonContent, severity = 'error', ...restProps }) => { +export const Alert: FC = React.forwardRef( + ({ title, buttonText, onButtonClick, onRemove, children, buttonContent, severity = 'error', ...restProps }, ref) => { const theme = useTheme(); const styles = getStyles(theme, severity, !!buttonContent); return ( -
+
From a7bdc059c1fdd74aeabdcf851445197f152c0ec5 Mon Sep 17 00:00:00 2001 From: Andreas Opferkuch Date: Thu, 3 Dec 2020 18:14:04 +0100 Subject: [PATCH 078/124] @grafana/ui: Add bell-slash to available icons (#29579) Used in Alerting UI for alert silencing. --- packages/grafana-ui/src/types/icon.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/grafana-ui/src/types/icon.ts b/packages/grafana-ui/src/types/icon.ts index 56e205f44a6..801a8b13705 100644 --- a/packages/grafana-ui/src/types/icon.ts +++ b/packages/grafana-ui/src/types/icon.ts @@ -80,6 +80,7 @@ export type IconName = | 'compass' | 'sliders-v-alt' | 'bell' + | 'bell-slash' | 'database' | 'user' | 'camera' @@ -198,6 +199,7 @@ export const getAvailableIcons = (): IconName[] => [ 'compass', 'sliders-v-alt', 'bell', + 'bell-slash', 'database', 'user', 'camera', From 4bf8a828d1b4db4683ce39354c85fb2383c09ee1 Mon Sep 17 00:00:00 2001 From: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> Date: Thu, 3 Dec 2020 10:42:12 -0800 Subject: [PATCH 079/124] Update documentation-style-guide.md (#29586) --- contribute/style-guides/documentation-style-guide.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contribute/style-guides/documentation-style-guide.md b/contribute/style-guides/documentation-style-guide.md index 7bd5b4d0af0..bb21b47485a 100644 --- a/contribute/style-guides/documentation-style-guide.md +++ b/contribute/style-guides/documentation-style-guide.md @@ -132,9 +132,12 @@ Git is always capitalized, unless part of a code block. GitHub is the correct sp In general, "integration" is not capitalized. Only capitalize it if it is capitalized in the UI or part of a proper noun, like the name of a specific integration. +The first letter of the name of an integration is always capitalized, even if the original named source is lowercase. + **Examples:** - MySQL Integration - CockroachDB Integration +- Etcd Integration - I installed an integration on my local Grafana. ### Links and references From 1f466f3a64073679b497d5d8c26fdc72597ac4f7 Mon Sep 17 00:00:00 2001 From: JoeyLemur Date: Thu, 3 Dec 2020 12:59:46 -0600 Subject: [PATCH 080/124] Add Microsoft.Network/natGateways (#29479) Adds Microsoft.Network/natGateways to the supported namespaces in Azure Monitor Datasource (issue #29383) --- .../azure_monitor/supported_namespaces.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/supported_namespaces.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/supported_namespaces.ts index 46b03f52982..4e64306b3bb 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/supported_namespaces.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/supported_namespaces.ts @@ -54,6 +54,7 @@ export default class SupportedNamespaces { 'Microsoft.Network/trafficManagerProfiles', 'Microsoft.Network/networkWatchers/connectionMonitors', 'Microsoft.Network/frontdoors', + 'Microsoft.Network/natGateways', 'Microsoft.NotificationHubs/namespaces/notificationHubs', 'Microsoft.OperationalInsights/workspaces', 'Microsoft.PowerBIDedicated/capacities', From 1053989686857c857a2af86aaa95fdb19449a49c Mon Sep 17 00:00:00 2001 From: Will Browne Date: Thu, 3 Dec 2020 20:04:38 +0100 Subject: [PATCH 081/124] Docs: Add missing key to enable SigV4 for provisioning Elasticsearch data source (#29584) * add missing key to enable SigV4 * use right key :o) * Add . Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> --- docs/sources/administration/provisioning.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/sources/administration/provisioning.md b/docs/sources/administration/provisioning.md index 98913aa9f60..1ce057d93ca 100644 --- a/docs/sources/administration/provisioning.md +++ b/docs/sources/administration/provisioning.md @@ -147,19 +147,20 @@ Since not all datasources have the same configuration settings we only have the | tlsAuthWithCACert | boolean | _All_ | Enable TLS authentication using CA cert | | tlsSkipVerify | boolean | _All_ | Controls whether a client verifies the server's certificate chain and host name. | | graphiteVersion | string | Graphite | Graphite version | -| timeInterval | string | Prometheus, Elasticsearch, InfluxDB, MySQL, PostgreSQL and MSSQL | Lowest interval/step value that should be used for this data source | -| httpMode | string | Influxdb | HTTP Method. 'GET', 'POST', defaults to GET | -| httpMethod | string | Prometheus | HTTP Method. 'GET', 'POST', defaults to GET | +| timeInterval | string | Prometheus, Elasticsearch, InfluxDB, MySQL, PostgreSQL and MSSQL | Lowest interval/step value that should be used for this data source. | +| httpMode | string | Influxdb | HTTP Method. 'GET', 'POST', defaults to GET | +| httpMethod | string | Prometheus | HTTP Method. 'GET', 'POST', defaults to GET | | esVersion | number | Elasticsearch | Elasticsearch version as a number (2/5/56/60/70) | | timeField | string | Elasticsearch | Which field that should be used as timestamp | | interval | string | Elasticsearch | Index date time format. nil(No Pattern), 'Hourly', 'Daily', 'Weekly', 'Monthly' or 'Yearly' | | logMessageField | string | Elasticsearch | Which field should be used as the log message | | logLevelField | string | Elasticsearch | Which field should be used to indicate the priority of the log message | -| sigV4AuthType | string | Elasticsearch | SigV4 auth provider. default/credentials/keys | -| sigV4ExternalId | string | Elasticsearch | Optional SigV4 External ID | -| sigV4AssumeRoleArn | string | Elasticsearch | Optional SigV4 ARN role to assume | -| sigV4Region | string | Elasticsearch | SigV4 AWS region | -| sigV4Profile | string | Elasticsearch | Optional SigV4 credentials profile | +| sigV4Auth | boolean | Elasticsearch | Enable usage of SigV4 | +| sigV4AuthType | string | Elasticsearch | SigV4 auth provider. default/credentials/keys | +| sigV4ExternalId | string | Elasticsearch | Optional SigV4 External ID | +| sigV4AssumeRoleArn | string | Elasticsearch | Optional SigV4 ARN role to assume | +| sigV4Region | string | Elasticsearch | SigV4 AWS region | +| sigV4Profile | string | Elasticsearch | Optional SigV4 credentials profile | | authType | string | Cloudwatch | Auth provider. default/credentials/keys | | externalId | string | Cloudwatch | Optional External ID | | assumeRoleArn | string | Cloudwatch | Optional ARN role to assume | @@ -194,7 +195,7 @@ Secure json data is a map of settings that will be encrypted with [secret key]({ | basicAuthPassword | string | _All_ | password for basic authentication | | accessKey | string | Cloudwatch | Access key for connecting to Cloudwatch | | secretKey | string | Cloudwatch | Secret key for connecting to Cloudwatch | -| sigV4AccessKey | string | Elasticsearch | SigV4 access key. Required when using keys auth provider | +| sigV4AccessKey | string | Elasticsearch | SigV4 access key. Required when using keys auth provider | | sigV4SecretKey | string | Elasticsearch | SigV4 secret key. Required when using keys auth provider | #### Custom HTTP headers for datasources From 69ac69b7c595e68cd94ee7c5a9022fe4738bbd2b Mon Sep 17 00:00:00 2001 From: Emil Hessman Date: Thu, 3 Dec 2020 21:31:01 +0100 Subject: [PATCH 082/124] Chore: Rewrite tsdb testdatasource scenarios test to standard library (#29591) Signed-off-by: Emil Hessman --- pkg/tsdb/testdatasource/scenarios_test.go | 82 +++++++++++------------ 1 file changed, 39 insertions(+), 43 deletions(-) diff --git a/pkg/tsdb/testdatasource/scenarios_test.go b/pkg/tsdb/testdatasource/scenarios_test.go index 559fd058490..9c6cd35604d 100644 --- a/pkg/tsdb/testdatasource/scenarios_test.go +++ b/pkg/tsdb/testdatasource/scenarios_test.go @@ -6,14 +6,14 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/tsdb" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/require" ) func TestTestdataScenarios(t *testing.T) { - Convey("random walk ", t, func() { + t.Run("random walk ", func(t *testing.T) { scenario := ScenarioRegistry["random_walk"] - Convey("Should start at the requested value", func() { + t.Run("Should start at the requested value", func(t *testing.T) { req := &tsdb.TsdbQuery{ TimeRange: tsdb.NewFakeTimeRange("5m", "now", time.Now()), Queries: []*tsdb.Query{ @@ -24,17 +24,17 @@ func TestTestdataScenarios(t *testing.T) { query.Model.Set("startValue", 1.234) result := scenario.Handler(req.Queries[0], req) - points := result.Series[0].Points + require.NotNil(t, result.Series) - So(result.Series, ShouldNotBeNil) - So(points[0][0].Float64, ShouldEqual, 1.234) + points := result.Series[0].Points + require.Equal(t, 1.234, points[0][0].Float64) }) }) - Convey("random walk table", t, func() { + t.Run("random walk table", func(t *testing.T) { scenario := ScenarioRegistry["random_walk_table"] - Convey("Should return a table that looks like value/min/max", func() { + t.Run("Should return a table that looks like value/min/max", func(t *testing.T) { req := &tsdb.TsdbQuery{ TimeRange: tsdb.NewFakeTimeRange("5m", "now", time.Now()), Queries: []*tsdb.Query{ @@ -45,18 +45,18 @@ func TestTestdataScenarios(t *testing.T) { result := scenario.Handler(req.Queries[0], req) table := result.Tables[0] - So(len(table.Rows), ShouldBeGreaterThan, 50) + require.Greater(t, len(table.Rows), 50) for _, row := range table.Rows { value := row[1] min := row[2] max := row[3] - So(min, ShouldBeLessThan, value) - So(max, ShouldBeGreaterThan, value) + require.Less(t, min, value) + require.Greater(t, max, value) } }) - Convey("Should return a table with some nil values", func() { + t.Run("Should return a table with some nil values", func(t *testing.T) { req := &tsdb.TsdbQuery{ TimeRange: tsdb.NewFakeTimeRange("5m", "now", time.Now()), Queries: []*tsdb.Query{ @@ -73,7 +73,7 @@ func TestTestdataScenarios(t *testing.T) { nil2 := false nil3 := false - So(len(table.Rows), ShouldBeGreaterThan, 50) + require.Greater(t, len(table.Rows), 50) for _, row := range table.Rows { if row[1] == nil { nil1 = true @@ -86,41 +86,37 @@ func TestTestdataScenarios(t *testing.T) { } } - So(nil1, ShouldBeTrue) - So(nil2, ShouldBeTrue) - So(nil3, ShouldBeTrue) + require.True(t, nil1) + require.True(t, nil2) + require.True(t, nil3) }) }) } -func TestToLabels(t *testing.T) { - Convey("read labels", t, func() { - tags := make(map[string]string) - tags["job"] = "foo" - tags["instance"] = "bar" +func TestParseLabels(t *testing.T) { + expectedTags := map[string]string{ + "job": "foo", + "instance": "bar", + } - query1 := tsdb.Query{ - Model: simplejson.NewFromAny(map[string]interface{}{ - "labels": `{job="foo", instance="bar"}`, - }), - } + query1 := tsdb.Query{ + Model: simplejson.NewFromAny(map[string]interface{}{ + "labels": `{job="foo", instance="bar"}`, + }), + } + require.Equal(t, expectedTags, parseLabels(&query1)) - So(parseLabels(&query1), ShouldResemble, tags) + query2 := tsdb.Query{ + Model: simplejson.NewFromAny(map[string]interface{}{ + "labels": `job=foo, instance=bar`, + }), + } + require.Equal(t, expectedTags, parseLabels(&query2)) - query2 := tsdb.Query{ - Model: simplejson.NewFromAny(map[string]interface{}{ - "labels": `job=foo, instance=bar`, - }), - } - - So(parseLabels(&query2), ShouldResemble, tags) - - query3 := tsdb.Query{ - Model: simplejson.NewFromAny(map[string]interface{}{ - "labels": `job = foo,instance = bar`, - }), - } - - So(parseLabels(&query3), ShouldResemble, tags) - }) + query3 := tsdb.Query{ + Model: simplejson.NewFromAny(map[string]interface{}{ + "labels": `job = foo,instance = bar`, + }), + } + require.Equal(t, expectedTags, parseLabels(&query3)) } From f326b79cc1ebded6a33e54ed23d82e320473620e Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Thu, 3 Dec 2020 22:13:06 +0100 Subject: [PATCH 083/124] Security: Add gosec G304 auditing annotations (#29578) * Security: Add gosec G304 auditing annotations Signed-off-by: Arve Knudsen * add G304 auditing comment Signed-off-by: bergquist * add G304 auditing comment Signed-off-by: bergquist * add G304 auditing comment Signed-off-by: bergquist * add G304 auditing comment Signed-off-by: bergquist * add G304 auditing comment Signed-off-by: bergquist * add G304 auditing comment Signed-off-by: bergquist * add G304 auditing comment Signed-off-by: bergquist * add G304 auditing comment Signed-off-by: bergquist * Add gosec annotations Signed-off-by: Arve Knudsen * add G304 auditing comment Signed-off-by: bergquist * add G304 auditing comment Signed-off-by: bergquist * Add gosec annotations Signed-off-by: Arve Knudsen * add G304 auditing comment Signed-off-by: bergquist * add G304 auditing comment Signed-off-by: bergquist * add G304 auditing comment Signed-off-by: bergquist * Add gosec annotations Signed-off-by: Arve Knudsen * space Signed-off-by: bergquist * Add gosec annotations Signed-off-by: Arve Knudsen Co-authored-by: bergquist --- pkg/api/avatar/avatar.go | 3 +++ pkg/api/dashboard.go | 3 +++ pkg/cmd/grafana-cli/commands/install_command.go | 4 ++++ pkg/cmd/grafana-cli/services/api_client.go | 5 ++++- pkg/cmd/grafana-cli/services/io_util.go | 4 ++++ pkg/cmd/grafana-cli/utils/grafana_path.go | 4 ++-- pkg/components/imguploader/azureblobuploader.go | 3 +++ pkg/components/imguploader/gcs/gcsuploader.go | 4 ++++ pkg/components/imguploader/s3uploader.go | 3 +++ pkg/components/imguploader/webdavuploader.go | 7 +++++-- pkg/middleware/recovery.go | 3 +++ pkg/plugins/dashboards.go | 4 ++++ pkg/plugins/manifest.go | 7 +++++++ pkg/plugins/plugins.go | 12 ++++++++++++ pkg/services/alerting/notifiers/discord.go | 3 +++ pkg/services/alerting/notifiers/slack.go | 6 ++++++ pkg/services/ldap/ldap.go | 2 ++ pkg/services/ldap/settings.go | 2 ++ .../provisioning/dashboards/config_reader.go | 3 +++ pkg/services/provisioning/dashboards/file_reader.go | 2 ++ .../provisioning/datasources/config_reader.go | 3 +++ pkg/services/provisioning/notifiers/config_reader.go | 3 +++ pkg/services/provisioning/plugins/config_reader.go | 2 ++ pkg/setting/expanders.go | 2 ++ 24 files changed, 89 insertions(+), 5 deletions(-) diff --git a/pkg/api/avatar/avatar.go b/pkg/api/avatar/avatar.go index 9eb99db8b3b..95b6cea3efc 100644 --- a/pkg/api/avatar/avatar.go +++ b/pkg/api/avatar/avatar.go @@ -132,6 +132,9 @@ func newNotFound() *Avatar { avatar := &Avatar{notFound: true} // load user_profile png into buffer + // It's safe to ignore gosec warning G304 since the variable part of the file path comes from a configuration + // variable. + // nolint:gosec path := filepath.Join(setting.StaticRootPath, "img", "user_profile.png") if data, err := ioutil.ReadFile(path); err != nil { diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index d4643773409..51d38bb0a77 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -341,6 +341,9 @@ func (hs *HTTPServer) GetHomeDashboard(c *models.ReqContext) Response { filePath = filepath.Join(hs.Cfg.StaticRootPath, "dashboards/home.json") } + // It's safe to ignore gosec warning G304 since the variable part of the file path comes from a configuration + // variable + // nolint:gosec file, err := os.Open(filePath) if err != nil { return Error(500, "Failed to load home dashboard", err) diff --git a/pkg/cmd/grafana-cli/commands/install_command.go b/pkg/cmd/grafana-cli/commands/install_command.go index 558ca9b3da4..311b686165e 100644 --- a/pkg/cmd/grafana-cli/commands/install_command.go +++ b/pkg/cmd/grafana-cli/commands/install_command.go @@ -291,6 +291,10 @@ func extractFile(file *zip.File, filePath string) (err error) { fileMode = os.FileMode(0755) } + // We can ignore the gosec G304 warning on this one, since the variable part of the file path stems + // from command line flag "pluginsDir", and the only possible damage would be writing to the wrong directory. + // If the user shouldn't be writing to this directory, they shouldn't have the permission in the file system. + // nolint:gosec dst, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, fileMode) if err != nil { if os.IsPermission(err) { diff --git a/pkg/cmd/grafana-cli/services/api_client.go b/pkg/cmd/grafana-cli/services/api_client.go index 2aa40a2a2de..1ac8cbf6458 100644 --- a/pkg/cmd/grafana-cli/services/api_client.go +++ b/pkg/cmd/grafana-cli/services/api_client.go @@ -44,8 +44,11 @@ func (client *GrafanaComClient) GetPlugin(pluginId, repoUrl string) (models.Plug } func (client *GrafanaComClient) DownloadFile(pluginName string, tmpFile *os.File, url string, checksum string) (err error) { - // Try handling url like local file path first + // Try handling URL as a local file path first if _, err := os.Stat(url); err == nil { + // We can ignore this gosec G304 warning since `url` stems from command line flag "pluginUrl". If the + // user shouldn't be able to read the file, it should be handled through filesystem permissions. + // nolint:gosec f, err := os.Open(url) if err != nil { return errutil.Wrap("Failed to read plugin archive", err) diff --git a/pkg/cmd/grafana-cli/services/io_util.go b/pkg/cmd/grafana-cli/services/io_util.go index b1f1c140e2c..38aa81d94f7 100644 --- a/pkg/cmd/grafana-cli/services/io_util.go +++ b/pkg/cmd/grafana-cli/services/io_util.go @@ -21,5 +21,9 @@ func (i IoUtilImp) ReadDir(path string) ([]os.FileInfo, error) { } func (i IoUtilImp) ReadFile(filename string) ([]byte, error) { + // We can ignore the gosec G304 warning on this one, since the variable part of the file path stems + // from command line flag "pluginsDir". If the user shouldn't be reading from this directory, they shouldn't have + // the permission in the file system. + // nolint:gosec return ioutil.ReadFile(filename) } diff --git a/pkg/cmd/grafana-cli/utils/grafana_path.go b/pkg/cmd/grafana-cli/utils/grafana_path.go index aba5e1b8d48..51f953d26cd 100644 --- a/pkg/cmd/grafana-cli/utils/grafana_path.go +++ b/pkg/cmd/grafana-cli/utils/grafana_path.go @@ -16,8 +16,8 @@ func GetGrafanaPluginDir(currentOS string) string { return returnOsDefault(currentOS) } -// getGrafanaRoot tries to get root of directory when developing grafana ie repo root. It is not perfect it just -// checks what is the binary path and tries to guess based on that but if it is not running in dev env you get a bogus +// getGrafanaRoot tries to get root of directory when developing grafana, ie. repo root. It is not perfect, it just +// checks what is the binary path and tries to guess based on that, but if it is not running in dev env you get a bogus // path back. func getGrafanaRoot() (string, error) { ex, err := os.Executable() diff --git a/pkg/components/imguploader/azureblobuploader.go b/pkg/components/imguploader/azureblobuploader.go index 160e2b5119a..80023b8c30c 100644 --- a/pkg/components/imguploader/azureblobuploader.go +++ b/pkg/components/imguploader/azureblobuploader.go @@ -45,6 +45,9 @@ func (az *AzureBlobUploader) Upload(ctx context.Context, imageDiskPath string) ( // setup client blob := NewStorageClient(az.account_name, az.account_key) + // We can ignore the gosec G304 warning on this one because `imageDiskPath` comes + // from alert notifiers and is only used to upload images generated by alerting. + // nolint:gosec file, err := os.Open(imageDiskPath) if err != nil { return "", err diff --git a/pkg/components/imguploader/gcs/gcsuploader.go b/pkg/components/imguploader/gcs/gcsuploader.go index e869623cb45..391e5b78ece 100644 --- a/pkg/components/imguploader/gcs/gcsuploader.go +++ b/pkg/components/imguploader/gcs/gcsuploader.go @@ -149,6 +149,10 @@ func (u *Uploader) uploadFile( key string, ) error { u.log.Debug("Opening image file", "path", imageDiskPath) + + // We can ignore the gosec G304 warning on this one because `imageDiskPath` comes + // from alert notifiers and is only used to upload images generated by alerting. + // nolint:gosec fileReader, err := os.Open(imageDiskPath) if err != nil { return err diff --git a/pkg/components/imguploader/s3uploader.go b/pkg/components/imguploader/s3uploader.go index 59ee5a11098..d0b8b251740 100644 --- a/pkg/components/imguploader/s3uploader.go +++ b/pkg/components/imguploader/s3uploader.go @@ -76,6 +76,9 @@ func (u *S3Uploader) Upload(ctx context.Context, imageDiskPath string) (string, key := u.path + rand + pngExt log.Debugf("Uploading image to s3. bucket = %s, path = %s", u.bucket, key) + // We can ignore the gosec G304 warning on this one because `imageDiskPath` comes + // from alert notifiers and is only used to upload images generated by alerting. + // nolint:gosec file, err := os.Open(imageDiskPath) if err != nil { return "", err diff --git a/pkg/components/imguploader/webdavuploader.go b/pkg/components/imguploader/webdavuploader.go index f0a157a8291..3fd9894a8cf 100644 --- a/pkg/components/imguploader/webdavuploader.go +++ b/pkg/components/imguploader/webdavuploader.go @@ -45,7 +45,7 @@ func (u *WebdavUploader) PublicURL(filename string) string { return publicURL.String() } -func (u *WebdavUploader) Upload(ctx context.Context, pa string) (string, error) { +func (u *WebdavUploader) Upload(ctx context.Context, imgToUpload string) (string, error) { url, _ := url.Parse(u.url) filename, err := util.GetRandomString(20) if err != nil { @@ -55,7 +55,10 @@ func (u *WebdavUploader) Upload(ctx context.Context, pa string) (string, error) filename += pngExt url.Path = path.Join(url.Path, filename) - imgData, err := ioutil.ReadFile(pa) + // We can ignore the gosec G304 warning on this one because `imgToUpload` comes + // from alert notifiers and is only used to upload images generated by alerting. + // nolint:gosec + imgData, err := ioutil.ReadFile(imgToUpload) if err != nil { return "", err } diff --git a/pkg/middleware/recovery.go b/pkg/middleware/recovery.go index c238ecbfeee..8048cc6f53b 100644 --- a/pkg/middleware/recovery.go +++ b/pkg/middleware/recovery.go @@ -52,6 +52,9 @@ func stack(skip int) []byte { // Print this much at least. If we can't find the source, it won't show. fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc) if file != lastFile { + // We can ignore the gosec G304 warning on this one because `file` + // comes from the runtime.Caller() function. + // nolint:gosec data, err := ioutil.ReadFile(file) if err != nil { continue diff --git a/pkg/plugins/dashboards.go b/pkg/plugins/dashboards.go index f2d533b1de1..6cd9f827449 100644 --- a/pkg/plugins/dashboards.go +++ b/pkg/plugins/dashboards.go @@ -95,6 +95,10 @@ func loadPluginDashboard(pluginId, path string) (*models.Dashboard, error) { return nil, PluginNotFoundError{pluginId} } + // nolint:gosec + // We can ignore the gosec G304 warning on this one because `plugin.PluginDir` is based + // on plugin folder structure on disk and not user input. `path` comes from the + // `plugin.json` configuration file for the loaded plugin dashboardFilePath := filepath.Join(plugin.PluginDir, path) reader, err := os.Open(dashboardFilePath) if err != nil { diff --git a/pkg/plugins/manifest.go b/pkg/plugins/manifest.go index b082dedf2d2..4eaf721bad6 100644 --- a/pkg/plugins/manifest.go +++ b/pkg/plugins/manifest.go @@ -87,6 +87,9 @@ func getPluginSignatureState(log log.Logger, plugin *PluginBase) PluginSignature log.Debug("Getting signature state of plugin", "plugin", plugin.Id, "isBackend", plugin.Backend) manifestPath := filepath.Join(plugin.PluginDir, "MANIFEST.txt") + // nolint:gosec + // We can ignore the gosec G304 warning on this one because `manifestPath` is based + // on plugin the folder structure on disk and not user input. byteValue, err := ioutil.ReadFile(manifestPath) if err != nil || len(byteValue) < 10 { log.Debug("Plugin is unsigned", "id", plugin.Id) @@ -109,6 +112,10 @@ func getPluginSignatureState(log log.Logger, plugin *PluginBase) PluginSignature for p, hash := range manifest.Files { // Open the file fp := filepath.Join(plugin.PluginDir, p) + + // nolint:gosec + // We can ignore the gosec G304 warning on this one because `fp` is based + // on the manifest file for a plugin and not user input. f, err := os.Open(fp) if err != nil { return PluginSignatureModified diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index 77a73f71a3f..754a5eab16e 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -269,6 +269,9 @@ func (pm *PluginManager) scan(pluginDir string, requireSigned bool) error { } } + // nolint:gosec + // We can ignore the gosec G304 warning on this one because `jsonFPath` is based + // on plugin the folder structure on disk and not user input. reader, err := os.Open(jsonFPath) if err != nil { return err @@ -332,6 +335,9 @@ func (s *PluginScanner) walker(currentPath string, f os.FileInfo, err error) err return nil } + // nolint:gosec + // We can ignore the gosec G304 warning on this one because `currentPath` is based + // on plugin the folder structure on disk and not user input. if err := s.loadPlugin(currentPath); err != nil { s.log.Error("Failed to load plugin", "error", err, "pluginPath", filepath.Dir(currentPath)) s.errors = append(s.errors, err) @@ -471,6 +477,9 @@ func GetPluginMarkdown(pluginId string, name string) ([]byte, error) { return nil, PluginNotFoundError{pluginId} } + // nolint:gosec + // We can ignore the gosec G304 warning on this one because `plug.PluginDir` is based + // on plugin the folder structure on disk and not user input. path := filepath.Join(plug.PluginDir, fmt.Sprintf("%s.md", strings.ToUpper(name))) exists, err := fs.Exists(path) if err != nil { @@ -488,6 +497,9 @@ func GetPluginMarkdown(pluginId string, name string) ([]byte, error) { return make([]byte, 0), nil } + // nolint:gosec + // We can ignore the gosec G304 warning on this one because `plug.PluginDir` is based + // on plugin the folder structure on disk and not user input. data, err := ioutil.ReadFile(path) if err != nil { return nil, err diff --git a/pkg/services/alerting/notifiers/discord.go b/pkg/services/alerting/notifiers/discord.go index a80d6ad429e..5bbc5039b93 100644 --- a/pkg/services/alerting/notifiers/discord.go +++ b/pkg/services/alerting/notifiers/discord.go @@ -159,6 +159,9 @@ func (dn *DiscordNotifier) Notify(evalContext *alerting.EvalContext) error { } func (dn *DiscordNotifier) embedImage(cmd *models.SendWebhookSync, imagePath string, existingJSONBody []byte) error { + // nolint:gosec + // We can ignore the gosec G304 warning on this one because `imagePath` comes + // from the alert `evalContext` that generates the images. f, err := os.Open(imagePath) if err != nil { if os.IsNotExist(err) { diff --git a/pkg/services/alerting/notifiers/slack.go b/pkg/services/alerting/notifiers/slack.go index e7f7e4c804b..cfd4c053cc6 100644 --- a/pkg/services/alerting/notifiers/slack.go +++ b/pkg/services/alerting/notifiers/slack.go @@ -331,6 +331,8 @@ func (sn *SlackNotifier) Notify(evalContext *alerting.EvalContext) error { func (sn *SlackNotifier) slackFileUpload(evalContext *alerting.EvalContext, log log.Logger, url string, recipient string, token string) error { if evalContext.ImageOnDiskPath == "" { + // nolint:gosec + // We can ignore the gosec G304 warning on this one because `setting.HomePath` comes from Grafana's configuration file. evalContext.ImageOnDiskPath = filepath.Join(setting.HomePath, "public/img/mixed_styles.png") } log.Info("Uploading to slack via file.upload API") @@ -360,6 +362,10 @@ func (sn *SlackNotifier) generateSlackBody(path string, token string, recipient }() // Add the generated image file + // We can ignore the gosec G304 warning on this one because `imagePath` comes + // from the alert `evalContext` that generates the images. `evalContext` in turn derives the root of the file + // path from configuration variables. + // nolint:gosec f, err := os.Open(path) if err != nil { return nil, b, err diff --git a/pkg/services/ldap/ldap.go b/pkg/services/ldap/ldap.go index 1d9b02d786e..c7084839b04 100644 --- a/pkg/services/ldap/ldap.go +++ b/pkg/services/ldap/ldap.go @@ -95,6 +95,8 @@ func (server *Server) Dial() error { if server.Config.RootCACert != "" { certPool = x509.NewCertPool() for _, caCertFile := range strings.Split(server.Config.RootCACert, " ") { + // nolint:gosec + // We can ignore the gosec G304 warning on this one because `caCertFile` comes from ldap config. pem, err := ioutil.ReadFile(caCertFile) if err != nil { return err diff --git a/pkg/services/ldap/settings.go b/pkg/services/ldap/settings.go index fa4248108e3..93e94f3e016 100644 --- a/pkg/services/ldap/settings.go +++ b/pkg/services/ldap/settings.go @@ -115,6 +115,8 @@ func readConfig(configFile string) (*Config, error) { logger.Info("LDAP enabled, reading config file", "file", configFile) + // nolint:gosec + // We can ignore the gosec G304 warning on this one because `filename` comes from grafana configuration file fileBytes, err := ioutil.ReadFile(configFile) if err != nil { return nil, errutil.Wrap("Failed to load LDAP config file", err) diff --git a/pkg/services/provisioning/dashboards/config_reader.go b/pkg/services/provisioning/dashboards/config_reader.go index df2d6c7bb39..92eb9a10eae 100644 --- a/pkg/services/provisioning/dashboards/config_reader.go +++ b/pkg/services/provisioning/dashboards/config_reader.go @@ -19,6 +19,9 @@ type configReader struct { func (cr *configReader) parseConfigs(file os.FileInfo) ([]*config, error) { filename, _ := filepath.Abs(filepath.Join(cr.path, file.Name())) + + // nolint:gosec + // We can ignore the gosec G304 warning on this one because `filename` comes from ps.Cfg.ProvisioningPath yamlFile, err := ioutil.ReadFile(filename) if err != nil { return nil, err diff --git a/pkg/services/provisioning/dashboards/file_reader.go b/pkg/services/provisioning/dashboards/file_reader.go index bbf4c0d7739..576b62d4cf0 100644 --- a/pkg/services/provisioning/dashboards/file_reader.go +++ b/pkg/services/provisioning/dashboards/file_reader.go @@ -343,6 +343,8 @@ type dashboardJSONFile struct { } func (fr *FileReader) readDashboardFromFile(path string, lastModified time.Time, folderID int64) (*dashboardJSONFile, error) { + // nolint:gosec + // We can ignore the gosec G304 warning on this one because `path` comes from the provisioning configuration file. reader, err := os.Open(path) if err != nil { return nil, err diff --git a/pkg/services/provisioning/datasources/config_reader.go b/pkg/services/provisioning/datasources/config_reader.go index 52a5aa430b8..191ab962342 100644 --- a/pkg/services/provisioning/datasources/config_reader.go +++ b/pkg/services/provisioning/datasources/config_reader.go @@ -49,6 +49,9 @@ func (cr *configReader) readConfig(path string) ([]*configs, error) { func (cr *configReader) parseDatasourceConfig(path string, file os.FileInfo) (*configs, error) { filename, _ := filepath.Abs(filepath.Join(path, file.Name())) + + // nolint:gosec + // We can ignore the gosec G304 warning on this one because `filename` comes from ps.Cfg.ProvisioningPath yamlFile, err := ioutil.ReadFile(filename) if err != nil { return nil, err diff --git a/pkg/services/provisioning/notifiers/config_reader.go b/pkg/services/provisioning/notifiers/config_reader.go index c2d40270bb4..17886adba36 100644 --- a/pkg/services/provisioning/notifiers/config_reader.go +++ b/pkg/services/provisioning/notifiers/config_reader.go @@ -61,6 +61,9 @@ func (cr *configReader) readConfig(path string) ([]*notificationsAsConfig, error func (cr *configReader) parseNotificationConfig(path string, file os.FileInfo) (*notificationsAsConfig, error) { filename, _ := filepath.Abs(filepath.Join(path, file.Name())) + + // nolint:gosec + // We can ignore the gosec G304 warning on this one because `filename` comes from ps.Cfg.ProvisioningPath yamlFile, err := ioutil.ReadFile(filename) if err != nil { return nil, err diff --git a/pkg/services/provisioning/plugins/config_reader.go b/pkg/services/provisioning/plugins/config_reader.go index ec95c40cf78..fe5175cf066 100644 --- a/pkg/services/provisioning/plugins/config_reader.go +++ b/pkg/services/provisioning/plugins/config_reader.go @@ -69,6 +69,8 @@ func (cr *configReaderImpl) parsePluginConfig(path string, file os.FileInfo) (*p return nil, err } + // nolint:gosec + // We can ignore the gosec G304 warning on this one because `filename` comes from ps.Cfg.ProvisioningPath yamlFile, err := ioutil.ReadFile(filename) if err != nil { return nil, err diff --git a/pkg/setting/expanders.go b/pkg/setting/expanders.go index 021722ba998..c6f9a61f582 100644 --- a/pkg/setting/expanders.go +++ b/pkg/setting/expanders.go @@ -138,6 +138,8 @@ func (e fileExpander) Expand(s string) (string, error) { return "", err } + // nolint:gosec + // We can ignore the gosec G304 warning on this one because `s` comes from configuration section keys f, err := ioutil.ReadFile(s) if err != nil { return "", err From 3c1bcc72753e93d95b4effa2fe71cad410e2d708 Mon Sep 17 00:00:00 2001 From: Emil Hessman Date: Fri, 4 Dec 2020 08:49:39 +0100 Subject: [PATCH 084/124] Chore: Rewrite tsdb prometheus test to standard library (#29592) Signed-off-by: Emil Hessman --- pkg/tsdb/prometheus/prometheus_test.go | 185 ++++++++++--------------- 1 file changed, 77 insertions(+), 108 deletions(-) diff --git a/pkg/tsdb/prometheus/prometheus_test.go b/pkg/tsdb/prometheus/prometheus_test.go index 0eec856be0b..43338258781 100644 --- a/pkg/tsdb/prometheus/prometheus_test.go +++ b/pkg/tsdb/prometheus/prometheus_test.go @@ -4,156 +4,125 @@ import ( "testing" "time" + "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/tsdb" - - "github.com/grafana/grafana/pkg/components/simplejson" p "github.com/prometheus/common/model" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/require" ) func TestPrometheus(t *testing.T) { - Convey("Prometheus", t, func() { - dsInfo := &models.DataSource{ - JsonData: simplejson.New(), + dsInfo := &models.DataSource{ + JsonData: simplejson.New(), + } + + t.Run("converting metric name", func(t *testing.T) { + metric := map[p.LabelName]p.LabelValue{ + p.LabelName("app"): p.LabelValue("backend"), + p.LabelName("device"): p.LabelValue("mobile"), } - Convey("converting metric name", func() { - metric := map[p.LabelName]p.LabelValue{ - p.LabelName("app"): p.LabelValue("backend"), - p.LabelName("device"): p.LabelValue("mobile"), - } + query := &PrometheusQuery{ + LegendFormat: "legend {{app}} {{ device }} {{broken}}", + } - query := &PrometheusQuery{ - LegendFormat: "legend {{app}} {{ device }} {{broken}}", - } + require.Equal(t, "legend backend mobile ", formatLegend(metric, query)) + }) - So(formatLegend(metric, query), ShouldEqual, "legend backend mobile ") - }) + t.Run("build full series name", func(t *testing.T) { + metric := map[p.LabelName]p.LabelValue{ + p.LabelName(p.MetricNameLabel): p.LabelValue("http_request_total"), + p.LabelName("app"): p.LabelValue("backend"), + p.LabelName("device"): p.LabelValue("mobile"), + } - Convey("build full series name", func() { - metric := map[p.LabelName]p.LabelValue{ - p.LabelName(p.MetricNameLabel): p.LabelValue("http_request_total"), - p.LabelName("app"): p.LabelValue("backend"), - p.LabelName("device"): p.LabelValue("mobile"), - } + query := &PrometheusQuery{ + LegendFormat: "", + } - query := &PrometheusQuery{ - LegendFormat: "", - } + require.Equal(t, `http_request_total{app="backend", device="mobile"}`, formatLegend(metric, query)) + }) - So(formatLegend(metric, query), ShouldEqual, `http_request_total{app="backend", device="mobile"}`) - }) - - Convey("parsing query model with step", func() { - json := `{ + t.Run("parsing query model with step", func(t *testing.T) { + json := `{ "expr": "go_goroutines", "format": "time_series", "refId": "A" }` - jsonModel, _ := simplejson.NewJson([]byte(json)) - queryContext := &tsdb.TsdbQuery{} - queryModels := []*tsdb.Query{ - {Model: jsonModel}, - } + jsonModel, _ := simplejson.NewJson([]byte(json)) + queryContext := &tsdb.TsdbQuery{} + queryModels := []*tsdb.Query{ + {Model: jsonModel}, + } - Convey("with 48h time range", func() { - queryContext.TimeRange = tsdb.NewTimeRange("12h", "now") + queryContext.TimeRange = tsdb.NewTimeRange("12h", "now") - models, err := parseQuery(dsInfo, queryModels, queryContext) - So(err, ShouldBeNil) + models, err := parseQuery(dsInfo, queryModels, queryContext) + require.NoError(t, err) + require.Equal(t, time.Second*30, models[0].Step) + }) - model := models[0] - So(model.Step, ShouldEqual, time.Second*30) - }) - }) - - Convey("parsing query model without step parameter", func() { - json := `{ + t.Run("parsing query model without step parameter", func(t *testing.T) { + json := `{ "expr": "go_goroutines", "format": "time_series", "intervalFactor": 1, "refId": "A" }` - jsonModel, _ := simplejson.NewJson([]byte(json)) - queryContext := &tsdb.TsdbQuery{} - queryModels := []*tsdb.Query{ - {Model: jsonModel}, - } + jsonModel, _ := simplejson.NewJson([]byte(json)) + queryContext := &tsdb.TsdbQuery{} + queryModels := []*tsdb.Query{ + {Model: jsonModel}, + } - Convey("with 48h time range", func() { - queryContext.TimeRange = tsdb.NewTimeRange("48h", "now") + queryContext.TimeRange = tsdb.NewTimeRange("48h", "now") + models, err := parseQuery(dsInfo, queryModels, queryContext) + require.NoError(t, err) + require.Equal(t, time.Minute*2, models[0].Step) - models, err := parseQuery(dsInfo, queryModels, queryContext) + queryContext.TimeRange = tsdb.NewTimeRange("1h", "now") + models, err = parseQuery(dsInfo, queryModels, queryContext) + require.NoError(t, err) + require.Equal(t, time.Second*15, models[0].Step) + }) - So(err, ShouldBeNil) - - model := models[0] - So(model.Step, ShouldEqual, time.Minute*2) - }) - - Convey("with 1h time range", func() { - queryContext.TimeRange = tsdb.NewTimeRange("1h", "now") - - models, err := parseQuery(dsInfo, queryModels, queryContext) - - So(err, ShouldBeNil) - - model := models[0] - So(model.Step, ShouldEqual, time.Second*15) - }) - }) - - Convey("parsing query model with intervalFactor", func() { - Convey("high intervalFactor", func() { - json := `{ + t.Run("parsing query model with high intervalFactor", func(t *testing.T) { + json := `{ "expr": "go_goroutines", "format": "time_series", "intervalFactor": 10, "refId": "A" }` - jsonModel, _ := simplejson.NewJson([]byte(json)) - queryContext := &tsdb.TsdbQuery{} - queryModels := []*tsdb.Query{ - {Model: jsonModel}, - } + jsonModel, _ := simplejson.NewJson([]byte(json)) + queryContext := &tsdb.TsdbQuery{} + queryModels := []*tsdb.Query{ + {Model: jsonModel}, + } - Convey("with 48h time range", func() { - queryContext.TimeRange = tsdb.NewTimeRange("48h", "now") + queryContext.TimeRange = tsdb.NewTimeRange("48h", "now") - models, err := parseQuery(dsInfo, queryModels, queryContext) + models, err := parseQuery(dsInfo, queryModels, queryContext) + require.NoError(t, err) + require.Equal(t, time.Minute*20, models[0].Step) + }) - So(err, ShouldBeNil) - - model := models[0] - So(model.Step, ShouldEqual, time.Minute*20) - }) - }) - - Convey("low intervalFactor", func() { - json := `{ + t.Run("parsing query model with low intervalFactor", func(t *testing.T) { + json := `{ "expr": "go_goroutines", "format": "time_series", "intervalFactor": 1, "refId": "A" }` - jsonModel, _ := simplejson.NewJson([]byte(json)) - queryContext := &tsdb.TsdbQuery{} - queryModels := []*tsdb.Query{ - {Model: jsonModel}, - } + jsonModel, _ := simplejson.NewJson([]byte(json)) + queryContext := &tsdb.TsdbQuery{} + queryModels := []*tsdb.Query{ + {Model: jsonModel}, + } - Convey("with 48h time range", func() { - queryContext.TimeRange = tsdb.NewTimeRange("48h", "now") + queryContext.TimeRange = tsdb.NewTimeRange("48h", "now") - models, err := parseQuery(dsInfo, queryModels, queryContext) - - So(err, ShouldBeNil) - - model := models[0] - So(model.Step, ShouldEqual, time.Minute*2) - }) - }) - }) + models, err := parseQuery(dsInfo, queryModels, queryContext) + require.NoError(t, err) + require.Equal(t, time.Minute*2, models[0].Step) }) } From db0fb1e2c124b95ddd2ccd871d4109593bb75b2d Mon Sep 17 00:00:00 2001 From: Emil Hessman Date: Fri, 4 Dec 2020 09:05:00 +0100 Subject: [PATCH 085/124] Chore: Rewrite sqlstore migration test to use standard library (#29589) Signed-off-by: Emil Hessman --- .../sqlstore/migrations/migrations_test.go | 65 ++++++++----------- 1 file changed, 27 insertions(+), 38 deletions(-) diff --git a/pkg/services/sqlstore/migrations/migrations_test.go b/pkg/services/sqlstore/migrations/migrations_test.go index 19b57ff93f0..0bf54752876 100644 --- a/pkg/services/sqlstore/migrations/migrations_test.go +++ b/pkg/services/sqlstore/migrations/migrations_test.go @@ -5,56 +5,45 @@ import ( . "github.com/grafana/grafana/pkg/services/sqlstore/migrator" "github.com/grafana/grafana/pkg/services/sqlstore/sqlutil" + "github.com/stretchr/testify/require" "xorm.io/xorm" - - . "github.com/smartystreets/goconvey/convey" ) func TestMigrations(t *testing.T) { - testDBs := []sqlutil.TestDB{ - sqlutil.SQLite3TestDB(), - } + testDB := sqlutil.SQLite3TestDB() + const query = `select count(*) as count from migration_log` + result := struct{ Count int }{} - for _, testDB := range testDBs { - sql := `select count(*) as count from migration_log` - r := struct { - Count int64 - }{} + x, err := xorm.NewEngine(testDB.DriverName, testDB.ConnStr) + require.NoError(t, err) - Convey("Initial "+testDB.DriverName+" migration", t, func() { - x, err := xorm.NewEngine(testDB.DriverName, testDB.ConnStr) - So(err, ShouldBeNil) + err = NewDialect(x).CleanDB() + require.NoError(t, err) - err = NewDialect(x).CleanDB() - So(err, ShouldBeNil) + _, err = x.SQL(query).Get(&result) + require.Error(t, err) - _, err = x.SQL(sql).Get(&r) - So(err, ShouldNotBeNil) + mg := NewMigrator(x) + AddMigrations(mg) + expectedMigrations := mg.MigrationsCount() - mg := NewMigrator(x) - AddMigrations(mg) + err = mg.Start() + require.NoError(t, err) - err = mg.Start() - So(err, ShouldBeNil) + has, err := x.SQL(query).Get(&result) + require.NoError(t, err) + require.True(t, has) - has, err := x.SQL(sql).Get(&r) - So(err, ShouldBeNil) - So(has, ShouldBeTrue) - // we currently skip to migrations. We should rewrite skipped migrations to write in the log as well. - // until then we have to keep this - expectedMigrations := mg.MigrationsCount() - So(r.Count, ShouldEqual, expectedMigrations) + require.Equal(t, expectedMigrations, result.Count) - mg = NewMigrator(x) - AddMigrations(mg) + mg = NewMigrator(x) + AddMigrations(mg) - err = mg.Start() - So(err, ShouldBeNil) + err = mg.Start() + require.NoError(t, err) - has, err = x.SQL(sql).Get(&r) - So(err, ShouldBeNil) - So(has, ShouldBeTrue) - So(r.Count, ShouldEqual, expectedMigrations) - }) - } + has, err = x.SQL(query).Get(&result) + require.NoError(t, err) + require.True(t, has) + require.Equal(t, expectedMigrations, result.Count) } From de395f24e456a2209d739d2edb4c443edf66f771 Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Fri, 4 Dec 2020 09:42:45 +0100 Subject: [PATCH 086/124] Panel: making sure we support all versions of chrome when detecting position of click event. (#29544) * making sure we are always using int value when compairing coordinates. * removed unused import. * Using Math.floor instead. * removed unused dep. --- .../dashboard/dashgrid/PanelHeader/PanelHeader.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx index a3773b4ce03..973d44de5a0 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx @@ -1,6 +1,5 @@ import React, { PureComponent } from 'react'; import classNames from 'classnames'; -import { isEqual } from 'lodash'; import { DataLink, LoadingState, PanelData, PanelMenuItem, QueryResultMetaNotice, ScopedVars } from '@grafana/data'; import { AngularComponent, config, getTemplateSrv } from '@grafana/runtime'; import { ClickOutsideWrapper, Icon, IconName, Tooltip, stylesFactory } from '@grafana/ui'; @@ -52,8 +51,8 @@ export class PanelHeader extends PureComponent { eventToClickCoordinates = (event: React.MouseEvent) => { return { - x: event.clientX, - y: event.clientY, + x: Math.floor(event.clientX), + y: Math.floor(event.clientY), }; }; @@ -62,7 +61,7 @@ export class PanelHeader extends PureComponent { }; isClick = (clickCoordinates: ClickCoordinates) => { - return isEqual(clickCoordinates, this.clickCoordinates); + return clickCoordinates.x === this.clickCoordinates.x && clickCoordinates.y === this.clickCoordinates.y; }; onMenuToggle = (event: React.MouseEvent) => { From 985c7934d97ae07a5d4fd17c9a935c3923eefdf1 Mon Sep 17 00:00:00 2001 From: Mathieu Rollet Date: Fri, 4 Dec 2020 10:06:01 +0100 Subject: [PATCH 087/124] Login: Fixes typo in tooltip (#29604) See "log in" (verb) vs "login" (noun) : https://english.stackexchange.com/a/5304/354852 --- public/app/core/components/ForgottenPassword/ChangePassword.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/core/components/ForgottenPassword/ChangePassword.tsx b/public/app/core/components/ForgottenPassword/ChangePassword.tsx index dda6ced168f..1e6faab9447 100644 --- a/public/app/core/components/ForgottenPassword/ChangePassword.tsx +++ b/public/app/core/components/ForgottenPassword/ChangePassword.tsx @@ -47,7 +47,7 @@ export const ChangePassword: FC = ({ onSubmit, onSkip }) => { {onSkip && ( From 55d536c6bc59c9506ca4fa7395a6ed9b5ac3a48a Mon Sep 17 00:00:00 2001 From: Jack Westbrook Date: Fri, 4 Dec 2020 10:10:47 +0100 Subject: [PATCH 088/124] Grafana/ui: Storybook controls understand component types (#29574) * feat(grafana-ui): use storybook docgen over webpack config * chore(grafana-ui): bump storybook to latest (6.1.9) --- packages/grafana-ui/.storybook/main.ts | 17 +- packages/grafana-ui/package.json | 12 +- yarn.lock | 778 +++++++------------------ 3 files changed, 229 insertions(+), 578 deletions(-) diff --git a/packages/grafana-ui/.storybook/main.ts b/packages/grafana-ui/.storybook/main.ts index e32a86bfa26..6a46779233e 100644 --- a/packages/grafana-ui/.storybook/main.ts +++ b/packages/grafana-ui/.storybook/main.ts @@ -26,7 +26,9 @@ module.exports = { check: true, reactDocgen: 'react-docgen-typescript', reactDocgenTypescriptOptions: { + tsconfigPath: path.resolve(__dirname, 'tsconfig.json'), shouldExtractLiteralValuesFromEnum: true, + shouldRemoveUndefinedFromOptional: true, propFilter: (prop: any) => (prop.parent ? !/node_modules/.test(prop.parent.fileName) : true), }, }, @@ -44,21 +46,6 @@ module.exports = { configFile: path.resolve(__dirname, 'tsconfig.json'), }, }, - { - loader: require.resolve('react-docgen-typescript-loader'), - options: { - tsconfigPath: path.resolve(__dirname, 'tsconfig.json'), - // https://github.com/styleguidist/react-docgen-typescript#parseroptions - // @ts-ignore - propFilter: prop => { - if (prop.parent) { - return !prop.parent.fileName.includes('node_modules/@types/react/'); - } - - return true; - }, - }, - }, ], }, { diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index b7f41516666..7f42046639f 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -77,12 +77,12 @@ "@rollup/plugin-commonjs": "16.0.0", "@rollup/plugin-image": "2.0.5", "@rollup/plugin-node-resolve": "10.0.0", - "@storybook/addon-essentials": "6.1.2", - "@storybook/addon-controls": "6.1.2", - "@storybook/addon-knobs": "6.1.2", - "@storybook/addon-storysource": "6.1.2", - "@storybook/react": "6.1.2", - "@storybook/theming": "6.1.2", + "@storybook/addon-essentials": "6.1.9", + "@storybook/addon-controls": "6.1.9", + "@storybook/addon-knobs": "6.1.9", + "@storybook/addon-storysource": "6.1.9", + "@storybook/react": "6.1.9", + "@storybook/theming": "6.1.9", "@types/classnames": "2.2.7", "@types/common-tags": "^1.8.0", "@types/d3": "5.7.2", diff --git a/yarn.lock b/yarn.lock index 5305760c52c..0c847232aa1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -236,7 +236,7 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/core@^7.12.1", "@babel/core@^7.12.3", "@babel/core@^7.9.0": +"@babel/core@^7.12.1", "@babel/core@^7.12.3": version "7.12.3" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.12.3.tgz#1b436884e1e3bff6fb1328dc02b208759de92ad8" integrity sha512-0qXcZYKZp3/6N2jKYVxZv0aNCsxTSVCiK72DTiTYZAu7sjg73W0/aynWjMbiGd87EQL4WyA8reiJVh92AVla9g== @@ -2280,13 +2280,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-react-constant-elements@^7.9.0": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.12.1.tgz#4471f0851feec3231cc9aaa0dccde39947c1ac1e" - integrity sha512-KOHd0tIRLoER+J+8f9DblZDa1fLGPwaaN1DI1TVHuQFOpjHV22C3CUB3obeC4fexHY9nx+fH0hQNvLFFfA1mxA== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-transform-react-display-name@^7.12.1": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.12.1.tgz#1cbcd0c3b1d6648c55374a22fc9b6b7e5341c00d" @@ -2762,7 +2755,7 @@ js-levenshtein "^1.1.3" semver "^5.5.0" -"@babel/preset-env@^7.12.1", "@babel/preset-env@^7.9.5": +"@babel/preset-env@^7.12.1": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.12.1.tgz#9c7e5ca82a19efc865384bb4989148d2ee5d7ac2" integrity sha512-H8kxXmtPaAGT7TyBvSSkoSTUK6RHh61So05SyEbpmr0MCZrsNYn7mGMzzeYoOUCdHzww61k8XBft2TaES+xPLg== @@ -2864,7 +2857,7 @@ "@babel/plugin-transform-react-jsx-self" "^7.8.3" "@babel/plugin-transform-react-jsx-source" "^7.8.3" -"@babel/preset-react@^7.12.1", "@babel/preset-react@^7.9.4": +"@babel/preset-react@^7.12.1": version "7.12.5" resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.12.5.tgz#d45625f65d53612078a43867c5c6750e78772c56" integrity sha512-jcs++VPrgyFehkMezHtezS2BpnUlR7tQFAyesJn1vGTO9aTFZrgIQrA5YydlTwxbcjMwkFY6i04flCigRRr3GA== @@ -3116,7 +3109,7 @@ lodash "^4.17.13" to-fast-properties "^2.0.0" -"@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.11.5", "@babel/types@^7.12.1", "@babel/types@^7.12.5", "@babel/types@^7.9.5": +"@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.11.5", "@babel/types@^7.12.1", "@babel/types@^7.12.5": version "7.12.6" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.12.6.tgz#ae0e55ef1cce1fbc881cd26f8234eb3e657edc96" integrity sha512-hwyjw6GvjBLiyy3W0YQf0Z5Zf4NpYejUnKFcfcUhZCSffoBBp30w6wP2Wn6pk31jMYZvcOrB/1b7cGXvEoKogA== @@ -5134,18 +5127,6 @@ "@sentry/types" "5.25.0" tslib "^1.9.3" -"@sindresorhus/df@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@sindresorhus/df/-/df-1.0.1.tgz#c69b66f52f6fcdd287c807df210305dbaf78500d" - integrity sha1-xptm9S9vzdKHyAffIQMF2694UA0= - -"@sindresorhus/df@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/df/-/df-2.1.0.tgz#d208cf27e06f0bb476d14d7deccd7d726e9aa389" - integrity sha1-0gjPJ+BvC7R20U197M19cm6ao4k= - dependencies: - execa "^0.2.2" - "@sinonjs/commons@^1", "@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0": version "1.7.0" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.7.0.tgz#f90ffc52a2e519f018b13b6c4da03cbff36ebed6" @@ -5182,17 +5163,17 @@ resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ== -"@storybook/addon-actions@6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-6.1.2.tgz#6df92f5ac4ed5a5d56c5ff2f52de47cf43e3c42b" - integrity sha512-LBsHgN9BNekagdw5a4Yi6noHH64K+/6/phpz6GgD9HIhULOtHNrL2pG94Zj10rdCdu1y7nscSkfwA52niqyYYg== +"@storybook/addon-actions@6.1.9": + version "6.1.9" + resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-6.1.9.tgz#b6d185b8e4e73691acc428b59e4d331195d64a40" + integrity sha512-vYqwuaBHrjRbbuyf4WBc5uDhrSejVEEiCabuu4g00R3dN7P5Ne/tbSw9EkYbbrRKxlEhdkk83DU1/J/+mCY5jw== dependencies: - "@storybook/addons" "6.1.2" - "@storybook/api" "6.1.2" - "@storybook/client-api" "6.1.2" - "@storybook/components" "6.1.2" - "@storybook/core-events" "6.1.2" - "@storybook/theming" "6.1.2" + "@storybook/addons" "6.1.9" + "@storybook/api" "6.1.9" + "@storybook/client-api" "6.1.9" + "@storybook/components" "6.1.9" + "@storybook/core-events" "6.1.9" + "@storybook/theming" "6.1.9" core-js "^3.0.1" fast-deep-equal "^3.1.1" global "^4.3.2" @@ -5205,17 +5186,17 @@ util-deprecate "^1.0.2" uuid "^8.0.0" -"@storybook/addon-backgrounds@6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@storybook/addon-backgrounds/-/addon-backgrounds-6.1.2.tgz#f768e1180a0d2838bbd47eac7884896118963624" - integrity sha512-FUNqS94lpkCi1OqsNZQZFkk56nEcxCSrkCauwa6+CeusZoQ2X7I2sHLPnGYKcBzoPX6E2l2map/k0Bk0cDDyJg== +"@storybook/addon-backgrounds@6.1.9": + version "6.1.9" + resolved "https://registry.yarnpkg.com/@storybook/addon-backgrounds/-/addon-backgrounds-6.1.9.tgz#4e9647001b2f396f56428218480f541e3a8a0e60" + integrity sha512-0/nnbnWZqo4NjyHFxcCHkcU+t8uUdk///9jSXRs4swmOLIFRaftEc/ZJ2rScu1Sc7y0CQdbk1Jjyx5/2cHBQtw== dependencies: - "@storybook/addons" "6.1.2" - "@storybook/api" "6.1.2" - "@storybook/client-logger" "6.1.2" - "@storybook/components" "6.1.2" - "@storybook/core-events" "6.1.2" - "@storybook/theming" "6.1.2" + "@storybook/addons" "6.1.9" + "@storybook/api" "6.1.9" + "@storybook/client-logger" "6.1.9" + "@storybook/components" "6.1.9" + "@storybook/core-events" "6.1.9" + "@storybook/theming" "6.1.9" core-js "^3.0.1" global "^4.3.2" memoizerific "^1.11.3" @@ -5223,24 +5204,24 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/addon-controls@6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@storybook/addon-controls/-/addon-controls-6.1.2.tgz#86b7d26226ac437ebd8708098a7bed5a95e951d5" - integrity sha512-0knHFTxGmyReS0/LU81YrXzkCB7vUHgV8QOE3efNjEHj0zDr4t4GFNvl+FLt+wlkZAJ6sOyIXOks7ezdxFm4sw== +"@storybook/addon-controls@6.1.9": + version "6.1.9" + resolved "https://registry.yarnpkg.com/@storybook/addon-controls/-/addon-controls-6.1.9.tgz#d7663d4c95db49163c372989f04264b11a4fa5cf" + integrity sha512-ctMXTcouqrPlNpQ0w3q6wjg16s1DL9VJtw1WvkySwezOE0ytfeC8M4nsbsKJnuY+dCadHKGn392DC1qVidY7Hg== dependencies: - "@storybook/addons" "6.1.2" - "@storybook/api" "6.1.2" - "@storybook/client-api" "6.1.2" - "@storybook/components" "6.1.2" - "@storybook/node-logger" "6.1.2" - "@storybook/theming" "6.1.2" + "@storybook/addons" "6.1.9" + "@storybook/api" "6.1.9" + "@storybook/client-api" "6.1.9" + "@storybook/components" "6.1.9" + "@storybook/node-logger" "6.1.9" + "@storybook/theming" "6.1.9" core-js "^3.0.1" ts-dedent "^2.0.0" -"@storybook/addon-docs@6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-6.1.2.tgz#7bb79fd60de516e29cbcae88ed286c48831d848b" - integrity sha512-h9oyd7D0zgSc/LM73oTaVHF8H+et5tzXQ1CDtnh9Fpe9YgxItsJ7jke9z8dNapH6X+pmHE2rN1itqvasjpTsgA== +"@storybook/addon-docs@6.1.9": + version "6.1.9" + resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-6.1.9.tgz#41cf82cece4abcf7640a1b7c2be6a7d5908c4678" + integrity sha512-5pwt2WU/yD4cnHU2KOpzU4t7A9FoePhZ15WOajtuV+W/hekNQSOGgNASQZbVOOldl954ireNIjZHtvthpI0tvQ== dependencies: "@babel/core" "^7.12.1" "@babel/generator" "^7.12.1" @@ -5251,18 +5232,18 @@ "@mdx-js/loader" "^1.6.19" "@mdx-js/mdx" "^1.6.19" "@mdx-js/react" "^1.6.19" - "@storybook/addons" "6.1.2" - "@storybook/api" "6.1.2" - "@storybook/client-api" "6.1.2" - "@storybook/client-logger" "6.1.2" - "@storybook/components" "6.1.2" - "@storybook/core" "6.1.2" - "@storybook/core-events" "6.1.2" + "@storybook/addons" "6.1.9" + "@storybook/api" "6.1.9" + "@storybook/client-api" "6.1.9" + "@storybook/client-logger" "6.1.9" + "@storybook/components" "6.1.9" + "@storybook/core" "6.1.9" + "@storybook/core-events" "6.1.9" "@storybook/csf" "0.0.1" - "@storybook/node-logger" "6.1.2" - "@storybook/postinstall" "6.1.2" - "@storybook/source-loader" "6.1.2" - "@storybook/theming" "6.1.2" + "@storybook/node-logger" "6.1.9" + "@storybook/postinstall" "6.1.9" + "@storybook/source-loader" "6.1.9" + "@storybook/theming" "6.1.9" acorn "^7.1.0" acorn-jsx "^5.1.0" acorn-walk "^7.0.0" @@ -5283,36 +5264,36 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/addon-essentials@6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@storybook/addon-essentials/-/addon-essentials-6.1.2.tgz#5cc5146bb257937e0602d1a4b9f8d05dffa8531b" - integrity sha512-wQmDD9+p36K26PMxpMcuZblyXFAjJ0pG6JNxy3XULuPGZfeRPQGlmcU2JTDUVbryOJfxHu0nzDI7D+CJ8Q2wAg== +"@storybook/addon-essentials@6.1.9": + version "6.1.9" + resolved "https://registry.yarnpkg.com/@storybook/addon-essentials/-/addon-essentials-6.1.9.tgz#e689de480ea626a784bc3784b36ceb4c9b366561" + integrity sha512-mw05LsBjWLu79u0lFsd3/kSpU7Dctt0xzcx8hXcaUKsiq7QvqPoKVutFuC8tvgoAVaCmmokLygscbMBOD2hq0w== dependencies: - "@storybook/addon-actions" "6.1.2" - "@storybook/addon-backgrounds" "6.1.2" - "@storybook/addon-controls" "6.1.2" - "@storybook/addon-docs" "6.1.2" - "@storybook/addon-toolbars" "6.1.2" - "@storybook/addon-viewport" "6.1.2" - "@storybook/addons" "6.1.2" - "@storybook/api" "6.1.2" - "@storybook/node-logger" "6.1.2" + "@storybook/addon-actions" "6.1.9" + "@storybook/addon-backgrounds" "6.1.9" + "@storybook/addon-controls" "6.1.9" + "@storybook/addon-docs" "6.1.9" + "@storybook/addon-toolbars" "6.1.9" + "@storybook/addon-viewport" "6.1.9" + "@storybook/addons" "6.1.9" + "@storybook/api" "6.1.9" + "@storybook/node-logger" "6.1.9" core-js "^3.0.1" regenerator-runtime "^0.13.7" ts-dedent "^2.0.0" -"@storybook/addon-knobs@6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@storybook/addon-knobs/-/addon-knobs-6.1.2.tgz#6daf89fd4fdf6830ca0546b22f448ade3f49b10f" - integrity sha512-pNQIJJF68PBBEHNKzgXGUb1IyO3jtF+Gwkuxn5r1msrp4p7yTckl5alH4Abiy9hvcjThhyCTULLO2eee7eawLg== +"@storybook/addon-knobs@6.1.9": + version "6.1.9" + resolved "https://registry.yarnpkg.com/@storybook/addon-knobs/-/addon-knobs-6.1.9.tgz#49773931770effc208db094c61eeecbceaf5836b" + integrity sha512-aUKD9FaQGl/WOkGT6utElspYZJ7cBtUARe41JN59qOao7RNviabipQUASrjORcYUUU55sfKsLPEqQaEKB3x2bw== dependencies: - "@storybook/addons" "6.1.2" - "@storybook/api" "6.1.2" - "@storybook/channels" "6.1.2" - "@storybook/client-api" "6.1.2" - "@storybook/components" "6.1.2" - "@storybook/core-events" "6.1.2" - "@storybook/theming" "6.1.2" + "@storybook/addons" "6.1.9" + "@storybook/api" "6.1.9" + "@storybook/channels" "6.1.9" + "@storybook/client-api" "6.1.9" + "@storybook/components" "6.1.9" + "@storybook/core-events" "6.1.9" + "@storybook/theming" "6.1.9" copy-to-clipboard "^3.0.8" core-js "^3.0.1" escape-html "^1.0.3" @@ -5326,18 +5307,18 @@ react-select "^3.0.8" regenerator-runtime "^0.13.7" -"@storybook/addon-storysource@6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@storybook/addon-storysource/-/addon-storysource-6.1.2.tgz#a8b3eb94c05cca06d6671942ac2d550934e8a687" - integrity sha512-yAuGvlNFrsurXbyJBlTcgUgDVI00JaR7rDabw+lZKQq2LjkR19j7y8HoeuOafSBVhqon4PXTxP/LgQ1UwQfwzQ== +"@storybook/addon-storysource@6.1.9": + version "6.1.9" + resolved "https://registry.yarnpkg.com/@storybook/addon-storysource/-/addon-storysource-6.1.9.tgz#9f7c50522b4de4bc90508a3882ba473bec82f435" + integrity sha512-xG3UAHRFfdn7dvoOjlvIHUNWgMPRLy87qjjHgmdW0jWyvGEtT7k10IRGgnozXp64ETy8dAnIyt3kmRUwXj/pkg== dependencies: - "@storybook/addons" "6.1.2" - "@storybook/api" "6.1.2" - "@storybook/client-logger" "6.1.2" - "@storybook/components" "6.1.2" - "@storybook/router" "6.1.2" - "@storybook/source-loader" "6.1.2" - "@storybook/theming" "6.1.2" + "@storybook/addons" "6.1.9" + "@storybook/api" "6.1.9" + "@storybook/client-logger" "6.1.9" + "@storybook/components" "6.1.9" + "@storybook/router" "6.1.9" + "@storybook/source-loader" "6.1.9" + "@storybook/theming" "6.1.9" core-js "^3.0.1" estraverse "^4.2.0" loader-utils "^2.0.0" @@ -5346,62 +5327,62 @@ react-syntax-highlighter "^13.5.0" regenerator-runtime "^0.13.7" -"@storybook/addon-toolbars@6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@storybook/addon-toolbars/-/addon-toolbars-6.1.2.tgz#ed59dc005c2a47c35c18f00fdac98e182934b032" - integrity sha512-eudMdRrMjNDJ8TctROqiCyUxHJZJhhcU6GCvZxU3JT2uiWeARx6zmGPnZh7BlMG+YCNUboSAxy3nXbeEJAkQuw== +"@storybook/addon-toolbars@6.1.9": + version "6.1.9" + resolved "https://registry.yarnpkg.com/@storybook/addon-toolbars/-/addon-toolbars-6.1.9.tgz#99c8b48afc24773faf9490753788ee354e97f5d8" + integrity sha512-rXYkVqSahrb6eG6CwyuI2TNzazUBGTsSQK1ywOmLwFK2xs+ybn1jDkkh5UmzuPN7g6ouT3qJfz1H2PMqfGEY8Q== dependencies: - "@storybook/addons" "6.1.2" - "@storybook/api" "6.1.2" - "@storybook/client-api" "6.1.2" - "@storybook/components" "6.1.2" + "@storybook/addons" "6.1.9" + "@storybook/api" "6.1.9" + "@storybook/client-api" "6.1.9" + "@storybook/components" "6.1.9" core-js "^3.0.1" -"@storybook/addon-viewport@6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@storybook/addon-viewport/-/addon-viewport-6.1.2.tgz#61d7f1e8597f177c5d68a4a46bce94ed7d2df464" - integrity sha512-da2zrACzkrMtsUpn9hXKgH5FqJZZLCkxeZVGh9juBg3OOmOE6mPEF3WLtZiYVcYCPeoobJNScsnC1o+x97CGtw== +"@storybook/addon-viewport@6.1.9": + version "6.1.9" + resolved "https://registry.yarnpkg.com/@storybook/addon-viewport/-/addon-viewport-6.1.9.tgz#7160b7598a34b8c19ff7571b781329fe8ad5de8d" + integrity sha512-+raES4+fDc0eCnpF4JW5kYwOTFEGiVmjmhFSrsXXuRXNGrCx4s5t9Bpklwm4YswIgmubB0Fpd0kaQS2LjRz94Q== dependencies: - "@storybook/addons" "6.1.2" - "@storybook/api" "6.1.2" - "@storybook/client-logger" "6.1.2" - "@storybook/components" "6.1.2" - "@storybook/core-events" "6.1.2" - "@storybook/theming" "6.1.2" + "@storybook/addons" "6.1.9" + "@storybook/api" "6.1.9" + "@storybook/client-logger" "6.1.9" + "@storybook/components" "6.1.9" + "@storybook/core-events" "6.1.9" + "@storybook/theming" "6.1.9" core-js "^3.0.1" global "^4.3.2" memoizerific "^1.11.3" prop-types "^15.7.2" regenerator-runtime "^0.13.7" -"@storybook/addons@6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-6.1.2.tgz#e073d4d930d84cb5d54cef5bd4376724369b56de" - integrity sha512-Q+xTYikVz4cfS3IWw4Ns9YDY8jzC4E8C7meuoAMWZMaQn3Y8CdvTNrnoPZ1XnxaArVvkROCF45cdsDX7t0GNRQ== +"@storybook/addons@6.1.9": + version "6.1.9" + resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-6.1.9.tgz#78f3cb27b7d934f091f311f89b6ca312d34f12b8" + integrity sha512-NRxdlGLmmSoVwlirVRgKC8xmW9cFkG+Sp5GEd4XkJDaaIg2vKR3RuFU9GuvIOVMxOhhERqhQ07bnDaAMKbFzGw== dependencies: - "@storybook/api" "6.1.2" - "@storybook/channels" "6.1.2" - "@storybook/client-logger" "6.1.2" - "@storybook/core-events" "6.1.2" - "@storybook/router" "6.1.2" - "@storybook/theming" "6.1.2" + "@storybook/api" "6.1.9" + "@storybook/channels" "6.1.9" + "@storybook/client-logger" "6.1.9" + "@storybook/core-events" "6.1.9" + "@storybook/router" "6.1.9" + "@storybook/theming" "6.1.9" core-js "^3.0.1" global "^4.3.2" regenerator-runtime "^0.13.7" -"@storybook/api@6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@storybook/api/-/api-6.1.2.tgz#d941f4b6d449e204d8bb42e251a92b62274451f3" - integrity sha512-tuYpQiBPjHse6OwodWXPdUiwwYuFNIlgQmGS2gKRv5Aw3dZEIvB7BLymi+cgz8KqMf5T1NaZhsQO/+3FPiQ+Kw== +"@storybook/api@6.1.9": + version "6.1.9" + resolved "https://registry.yarnpkg.com/@storybook/api/-/api-6.1.9.tgz#3f9bf00b2b18fa02965079fe775bd713677b30a3" + integrity sha512-S9SXlSiMeI450NIbOnx3UU9TZNyVD7jcBCjfNzhj0PqzRX/IG5Usj+R88Jm6MSIDjtsVjrWRCou+PrCh2xMnlQ== dependencies: "@reach/router" "^1.3.3" - "@storybook/channels" "6.1.2" - "@storybook/client-logger" "6.1.2" - "@storybook/core-events" "6.1.2" + "@storybook/channels" "6.1.9" + "@storybook/client-logger" "6.1.9" + "@storybook/core-events" "6.1.9" "@storybook/csf" "0.0.1" - "@storybook/router" "6.1.2" + "@storybook/router" "6.1.9" "@storybook/semver" "^7.3.2" - "@storybook/theming" "6.1.2" + "@storybook/theming" "6.1.9" "@types/reach__router" "^1.3.5" core-js "^3.0.1" fast-deep-equal "^3.1.1" @@ -5414,38 +5395,38 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/channel-postmessage@6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-6.1.2.tgz#92205f381059d4d155679aba4a1d89d2f438f9b5" - integrity sha512-zKEPK4MAgBy3RanCpl4afJpcYJMlb56gpIFALm2WJZ8zsHUUFb45NgruqNPiBsF0IthD6bvemcCJ5mmyn5D4xg== +"@storybook/channel-postmessage@6.1.9": + version "6.1.9" + resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-6.1.9.tgz#5d73c67ba94bcf68b14138bba6c5bb0850c72c5e" + integrity sha512-tX7pD9Xrf1WsatpJqtJ6o8MlgxG7jH+oFhNPkGvUbWiolVDQmuDndwM8Hh1kUnOWlyE1AN5hlM7av8MY+9D3NA== dependencies: - "@storybook/channels" "6.1.2" - "@storybook/client-logger" "6.1.2" - "@storybook/core-events" "6.1.2" + "@storybook/channels" "6.1.9" + "@storybook/client-logger" "6.1.9" + "@storybook/core-events" "6.1.9" core-js "^3.0.1" global "^4.3.2" qs "^6.6.0" telejson "^5.0.2" -"@storybook/channels@6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-6.1.2.tgz#e42b2cb3502291cfde36082b4844bd6de839ce86" - integrity sha512-jek1xyLXgMnk3XbNjjVxx4xirOE7QwbwCbiESS7g2ELhRum4C636iVzLYUpt8rujXFmGibqwnoeOH4rPNIFuvA== +"@storybook/channels@6.1.9": + version "6.1.9" + resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-6.1.9.tgz#94f07ff3615b11c07d1902be6b6cd298c0eea55c" + integrity sha512-aV+KsZPuoTtFKSMUkSCyVlVmtVHkSH35dSbyMazjlUD9cOLwkXB1s+LZL/GxxSR6a6uR75V0QWxItfNxaJETMQ== dependencies: core-js "^3.0.1" ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/client-api@6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-6.1.2.tgz#52fc44883ad6004ace16638ec9837e841ae38c47" - integrity sha512-E5iS9vcElKpL8ve9Ifr2gLLWl+Ig7SXty0DjX7B5rtBFZmq7JENtdWXCOKbHvYiP/wQfxdoQLMDFW1dsg3FnAA== +"@storybook/client-api@6.1.9": + version "6.1.9" + resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-6.1.9.tgz#d4a8d38bc657f26e4837831b961e085da1954d51" + integrity sha512-b2DFaGAS5G2ly3UJY5NJNXh/LxgLgSJLbqPL4t48MFW5XjH+rmEWXE9P+ujCaPclH1/y7mZRMprDj3ycDbRo3Q== dependencies: - "@storybook/addons" "6.1.2" - "@storybook/channel-postmessage" "6.1.2" - "@storybook/channels" "6.1.2" - "@storybook/client-logger" "6.1.2" - "@storybook/core-events" "6.1.2" + "@storybook/addons" "6.1.9" + "@storybook/channel-postmessage" "6.1.9" + "@storybook/channels" "6.1.9" + "@storybook/client-logger" "6.1.9" + "@storybook/core-events" "6.1.9" "@storybook/csf" "0.0.1" "@types/qs" "^6.9.0" "@types/webpack-env" "^1.15.3" @@ -5460,23 +5441,23 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/client-logger@6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-6.1.2.tgz#4036d6fdd8d8865da17c4da3dd8e23322c89140b" - integrity sha512-s/OGSBxVQ5PAWeoXtVoPJXOaJ8eL4V8a7+szd/3Gvl81WLDDqJnH9x/mTM4qzRpfEL8LXfCRBOilt7R3x6EG/A== +"@storybook/client-logger@6.1.9": + version "6.1.9" + resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-6.1.9.tgz#1d61a64000d4691780d75e19b78fd44adfdb5d9c" + integrity sha512-i7Q2ky9+Jwv+wmnlOGxmDOEdmaTIB69OQnnZNWGKufOwoIMjn6QO0VifARyA9W++nNSijjJ5th84tLJALaoCTA== dependencies: core-js "^3.0.1" global "^4.3.2" -"@storybook/components@6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@storybook/components/-/components-6.1.2.tgz#8d543206a662d0ca95ce47c1ab912523a7b487d4" - integrity sha512-JUW3KxRuX7Fzp7rEtQ7MhpozXcKlQJJbIctgsUdie+1CkWQHXma7gPUqDcBJjrU4nPV1EoTC2xs1c7TduavlpQ== +"@storybook/components@6.1.9": + version "6.1.9" + resolved "https://registry.yarnpkg.com/@storybook/components/-/components-6.1.9.tgz#f25d18f3a410cc7e9549ddb3c971c40d9108d4d8" + integrity sha512-cYYm3fHo9MW0bbl47lu1ncwulV7V9VEF8FC96uvys07oaCTFWKzQ0z/FD0nCqeK6eEz1+SEqnGwLFmOtqlRXDQ== dependencies: "@popperjs/core" "^2.4.4" - "@storybook/client-logger" "6.1.2" + "@storybook/client-logger" "6.1.9" "@storybook/csf" "0.0.1" - "@storybook/theming" "6.1.2" + "@storybook/theming" "6.1.9" "@types/overlayscrollbars" "^1.9.0" "@types/react-color" "^3.0.1" "@types/react-syntax-highlighter" "11.0.4" @@ -5494,17 +5475,17 @@ react-textarea-autosize "^8.1.1" ts-dedent "^2.0.0" -"@storybook/core-events@6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-6.1.2.tgz#d29e9a8c7ef6bb41135282f4da8027bb6f399545" - integrity sha512-Vhv22JyDLLJ9kqPCiXyMmePFYVbvYHREzXOtA3vgcf/leKxp6aGHE+OId1e4fycQN4ZU6kPbVnRrI9WFJwV+pA== +"@storybook/core-events@6.1.9": + version "6.1.9" + resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-6.1.9.tgz#0a88281837d1aa657a93a9abf7f5aad65b8d68e7" + integrity sha512-oOpqpjCTJCt0U5lnQ16OZU0iKIDh2/MIg4yrnDw+Pt6zGyX3zSvtB+9W8LQFnMwm+cXaNmiizGwt/W+4OiORjQ== dependencies: core-js "^3.0.1" -"@storybook/core@6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@storybook/core/-/core-6.1.2.tgz#999f80da29f7e476a51a34e953c837278917aeb0" - integrity sha512-LR5YnqrEciIOYc5MkAAOKW4/ijibC7x+eM9sB/pjwdq/+Rspj4XKUzVl1J5oAUHKl8EAdtUGkdWB8fpU6l4fdg== +"@storybook/core@6.1.9": + version "6.1.9" + resolved "https://registry.yarnpkg.com/@storybook/core/-/core-6.1.9.tgz#e6575e294cb4d2d9b57d5976a145cae8f4a88594" + integrity sha512-guz+R6eDX923Cw7NqgS5PrpTmmjDB+m5X1iF9pwKlpPTfzIiT/wTzJm4PwhFoGONNoXrItObX/6hW6OQbX4aOA== dependencies: "@babel/core" "^7.12.3" "@babel/plugin-proposal-class-properties" "^7.12.1" @@ -5528,20 +5509,20 @@ "@babel/preset-react" "^7.12.1" "@babel/preset-typescript" "^7.12.1" "@babel/register" "^7.12.1" - "@storybook/addons" "6.1.2" - "@storybook/api" "6.1.2" - "@storybook/channel-postmessage" "6.1.2" - "@storybook/channels" "6.1.2" - "@storybook/client-api" "6.1.2" - "@storybook/client-logger" "6.1.2" - "@storybook/components" "6.1.2" - "@storybook/core-events" "6.1.2" + "@storybook/addons" "6.1.9" + "@storybook/api" "6.1.9" + "@storybook/channel-postmessage" "6.1.9" + "@storybook/channels" "6.1.9" + "@storybook/client-api" "6.1.9" + "@storybook/client-logger" "6.1.9" + "@storybook/components" "6.1.9" + "@storybook/core-events" "6.1.9" "@storybook/csf" "0.0.1" - "@storybook/node-logger" "6.1.2" - "@storybook/router" "6.1.2" + "@storybook/node-logger" "6.1.9" + "@storybook/router" "6.1.9" "@storybook/semver" "^7.3.2" - "@storybook/theming" "6.1.2" - "@storybook/ui" "6.1.2" + "@storybook/theming" "6.1.9" + "@storybook/ui" "6.1.9" "@types/glob-base" "^0.3.0" "@types/micromatch" "^4.0.1" "@types/node-fetch" "^2.5.4" @@ -5598,7 +5579,6 @@ style-loader "^1.2.1" telejson "^5.0.2" terser-webpack-plugin "^3.0.0" - trash "^6.1.1" ts-dedent "^2.0.0" unfetch "^4.1.0" url-loader "^4.0.0" @@ -5616,10 +5596,10 @@ dependencies: lodash "^4.17.15" -"@storybook/node-logger@6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-6.1.2.tgz#fed92ebd537f9e5888e42cc46f393936fcd382ae" - integrity sha512-T/lIUVActNhvNhmuF8HxBT87e3N012HqnYsCo+blTpOqsntmP2m2D7Ipu46EwShMYiBUAr4JaGSt/yjhITcvag== +"@storybook/node-logger@6.1.9": + version "6.1.9" + resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-6.1.9.tgz#c63a61f72209d76eeeffe9d151fec043864b9438" + integrity sha512-2gP9BSBXEOGIcUyzRdIkIJi1UEINUAIyuv9bfKODo4GfujRg7DLz/mpi/FdwmulGg/viXWSXa6ccb6ziIgY9RA== dependencies: "@types/npmlog" "^4.1.2" chalk "^4.0.0" @@ -5627,26 +5607,25 @@ npmlog "^4.1.2" pretty-hrtime "^1.0.3" -"@storybook/postinstall@6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@storybook/postinstall/-/postinstall-6.1.2.tgz#2d07588b1a3946be9eea70a6d069a0db1be4fba3" - integrity sha512-/+1AM/5OaGK2Hg/JygxLgYnUVkiKcMQ3/5JCGbDd/px9SE8kL570tS3LvMS+t3jnMCGKXQW0B5iIVTndDG0AYQ== +"@storybook/postinstall@6.1.9": + version "6.1.9" + resolved "https://registry.yarnpkg.com/@storybook/postinstall/-/postinstall-6.1.9.tgz#1c62cf46b7b8c50e939c8b14de9e7133368aab45" + integrity sha512-9aL0+ALWHKGJAnC8CdRBE7PlX87sKMEWZkDQJL5uT1eikfgfAmS1AgkpIbFD8mcxstcZmFKRwc1BD4mvwzqtQQ== dependencies: core-js "^3.0.1" -"@storybook/react@6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@storybook/react/-/react-6.1.2.tgz#863662543a18c3a2ecea7a5646ec711c29163b05" - integrity sha512-q7Nj++ucjk9pVt/0jUlhoqzeypbzKdmRyN80hPAsSmvpzONlJtK82VA0iujBh9G6T+cT289nq2pf555tQfiAdQ== +"@storybook/react@6.1.9": + version "6.1.9" + resolved "https://registry.yarnpkg.com/@storybook/react/-/react-6.1.9.tgz#063427015b3d0ce582b1b6b7826d40d963d265ce" + integrity sha512-HJWHQE+eCC7sz1vqvgmBMn2sA1uc0ByEj+NeSgyi45jBFI+Ke4a8hxx6k5XA7k9gLznqG8TPGg0z6EdQTJTLkQ== dependencies: "@babel/preset-flow" "^7.12.1" "@babel/preset-react" "^7.12.1" "@pmmmwh/react-refresh-webpack-plugin" "^0.4.2" - "@storybook/addons" "6.1.2" - "@storybook/core" "6.1.2" - "@storybook/node-logger" "6.1.2" + "@storybook/addons" "6.1.9" + "@storybook/core" "6.1.9" + "@storybook/node-logger" "6.1.9" "@storybook/semver" "^7.3.2" - "@svgr/webpack" "^5.4.0" "@types/webpack-env" "^1.15.3" babel-plugin-add-react-displayname "^0.0.5" babel-plugin-named-asset-import "^0.3.1" @@ -5662,10 +5641,10 @@ ts-dedent "^2.0.0" webpack "^4.44.2" -"@storybook/router@6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@storybook/router/-/router-6.1.2.tgz#606790a89e5f25d9bd808de765e9292b609ad191" - integrity sha512-fNKWCEJlA0uCFBNbRgI9VD3a6hyelr528MEfdWcNvjffYXsF4DnMwewWYEPx01JUrWiAeZ3Qs5FYphuHn8BKEw== +"@storybook/router@6.1.9": + version "6.1.9" + resolved "https://registry.yarnpkg.com/@storybook/router/-/router-6.1.9.tgz#c0b24dc3ab53d58541b81c7abea2f11d7fbbebf6" + integrity sha512-kIlmSFBnqI198oMCncFZR7MxoV5/kP6KS0paFcyu1XE1zO2ovV6eQZ8pPpOjSsD/ISu4Y44uE+ZDNsEehjj6GQ== dependencies: "@reach/router" "^1.3.3" "@types/reach__router" "^1.3.5" @@ -5682,13 +5661,13 @@ core-js "^3.6.5" find-up "^4.1.0" -"@storybook/source-loader@6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@storybook/source-loader/-/source-loader-6.1.2.tgz#0982fcfa252c72f139da8bac7f478edbb3a7db13" - integrity sha512-rxfqecE4AaG+hyGUJRh4yXz/bqVmboo0X39AEObq7zwf3vz+6iw3j4MmepGHnyBotCVLw2AmKZ1405b4wO4fBg== +"@storybook/source-loader@6.1.9": + version "6.1.9" + resolved "https://registry.yarnpkg.com/@storybook/source-loader/-/source-loader-6.1.9.tgz#10eebf12ecd7baa70e51f368c41b89b4b5084eb1" + integrity sha512-2h3TS1O9qJ0xmOOJrWxY9ODEFK0/RLxEmWcXngWAcH1ZrHAuz6UcEatO2bIgykrorf9xKqqk7/AYyE9KxW668A== dependencies: - "@storybook/addons" "6.1.2" - "@storybook/client-logger" "6.1.2" + "@storybook/addons" "6.1.9" + "@storybook/client-logger" "6.1.9" "@storybook/csf" "0.0.1" core-js "^3.0.1" estraverse "^4.2.0" @@ -5699,15 +5678,15 @@ regenerator-runtime "^0.13.7" source-map "^0.7.3" -"@storybook/theming@6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-6.1.2.tgz#55fb60017acdf952450cc0cb9b530d36aa039178" - integrity sha512-MC89aDLIrng3zqpoTys0gylP76rlBYyEKUWtcZqrnFm6b+IqhmeP8e6KV/e5wmBfjuFqmDsKLLVMN6BwwZJlgg== +"@storybook/theming@6.1.9": + version "6.1.9" + resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-6.1.9.tgz#8c584aa623f3d6e33b1e3b3de2ec1f41bdc5d9ab" + integrity sha512-orzMQkyEhAQEi0E9iwmUkzh5yPHoYGBz17t2aydDeT6oGKii6if8Mq2oPVycfVKZ84QO7GFAS9q1nVCRcuD8oA== dependencies: "@emotion/core" "^10.1.1" "@emotion/is-prop-valid" "^0.8.6" "@emotion/styled" "^10.0.23" - "@storybook/client-logger" "6.1.2" + "@storybook/client-logger" "6.1.9" core-js "^3.0.1" deep-object-diff "^1.1.0" emotion-theming "^10.0.19" @@ -5717,21 +5696,21 @@ resolve-from "^5.0.0" ts-dedent "^2.0.0" -"@storybook/ui@6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-6.1.2.tgz#ee288141b70f908a8dcea646cb2ec52cf69a2483" - integrity sha512-ZBfL4x+E43DR+GnC/wjJ9QYP0sdXyZxxKWLQoDkNPIdDnNtFGyKFQyElo9GgtyGxIukssO0sW6fwWWpnErKTyw== +"@storybook/ui@6.1.9": + version "6.1.9" + resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-6.1.9.tgz#1ed3168d9fe5827285c13d8507dd1fd872830542" + integrity sha512-4MK5iTf7kI5DYVeWRiD6lkXdd0S6eiQJu9lvWqMOQJLOH5Bq77g0Ejo+38RTEQpV6we7hCPWWnRXQBjmJ2+19w== dependencies: "@emotion/core" "^10.1.1" - "@storybook/addons" "6.1.2" - "@storybook/api" "6.1.2" - "@storybook/channels" "6.1.2" - "@storybook/client-logger" "6.1.2" - "@storybook/components" "6.1.2" - "@storybook/core-events" "6.1.2" - "@storybook/router" "6.1.2" + "@storybook/addons" "6.1.9" + "@storybook/api" "6.1.9" + "@storybook/channels" "6.1.9" + "@storybook/client-logger" "6.1.9" + "@storybook/components" "6.1.9" + "@storybook/core-events" "6.1.9" + "@storybook/router" "6.1.9" "@storybook/semver" "^7.3.2" - "@storybook/theming" "6.1.2" + "@storybook/theming" "6.1.9" "@types/markdown-to-jsx" "^6.11.0" copy-to-clipboard "^3.0.8" core-js "^3.0.1" @@ -5753,114 +5732,6 @@ resolve-from "^5.0.0" store2 "^2.7.1" -"@stroncium/procfs@^1.0.0": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@stroncium/procfs/-/procfs-1.2.1.tgz#6b9be6fd20fb0a4c20e99a8695e083c699bb2b45" - integrity sha512-X1Iui3FUNZP18EUvysTHxt+Avu2nlVzyf90YM8OYgP6SGzTzzX/0JgObfO1AQQDzuZtNNz29bVh8h5R97JrjxA== - -"@svgr/babel-plugin-add-jsx-attribute@^5.4.0": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz#81ef61947bb268eb9d50523446f9c638fb355906" - integrity sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg== - -"@svgr/babel-plugin-remove-jsx-attribute@^5.4.0": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz#6b2c770c95c874654fd5e1d5ef475b78a0a962ef" - integrity sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg== - -"@svgr/babel-plugin-remove-jsx-empty-expression@^5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz#25621a8915ed7ad70da6cea3d0a6dbc2ea933efd" - integrity sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA== - -"@svgr/babel-plugin-replace-jsx-attribute-value@^5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz#0b221fc57f9fcd10e91fe219e2cd0dd03145a897" - integrity sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ== - -"@svgr/babel-plugin-svg-dynamic-title@^5.4.0": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz#139b546dd0c3186b6e5db4fefc26cb0baea729d7" - integrity sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg== - -"@svgr/babel-plugin-svg-em-dimensions@^5.4.0": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz#6543f69526632a133ce5cabab965deeaea2234a0" - integrity sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw== - -"@svgr/babel-plugin-transform-react-native-svg@^5.4.0": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz#00bf9a7a73f1cad3948cdab1f8dfb774750f8c80" - integrity sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q== - -"@svgr/babel-plugin-transform-svg-component@^5.4.0": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.4.0.tgz#a2212b4d018e6075a058bb7e220a66959ef7a03c" - integrity sha512-zLl4Fl3NvKxxjWNkqEcpdSOpQ3LGVH2BNFQ6vjaK6sFo2IrSznrhURIPI0HAphKiiIwNYjAfE0TNoQDSZv0U9A== - -"@svgr/babel-preset@^5.4.0": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-5.4.0.tgz#da21854643e1c4ad2279239baa7d5a8b128c1f15" - integrity sha512-Gyx7cCxua04DBtyILTYdQxeO/pwfTBev6+eXTbVbxe4HTGhOUW6yo7PSbG2p6eJMl44j6XSequ0ZDP7bl0nu9A== - dependencies: - "@svgr/babel-plugin-add-jsx-attribute" "^5.4.0" - "@svgr/babel-plugin-remove-jsx-attribute" "^5.4.0" - "@svgr/babel-plugin-remove-jsx-empty-expression" "^5.0.1" - "@svgr/babel-plugin-replace-jsx-attribute-value" "^5.0.1" - "@svgr/babel-plugin-svg-dynamic-title" "^5.4.0" - "@svgr/babel-plugin-svg-em-dimensions" "^5.4.0" - "@svgr/babel-plugin-transform-react-native-svg" "^5.4.0" - "@svgr/babel-plugin-transform-svg-component" "^5.4.0" - -"@svgr/core@^5.4.0": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@svgr/core/-/core-5.4.0.tgz#655378ee43679eb94fee3d4e1976e38252dff8e7" - integrity sha512-hWGm1DCCvd4IEn7VgDUHYiC597lUYhFau2lwJBYpQWDirYLkX4OsXu9IslPgJ9UpP7wsw3n2Ffv9sW7SXJVfqQ== - dependencies: - "@svgr/plugin-jsx" "^5.4.0" - camelcase "^6.0.0" - cosmiconfig "^6.0.0" - -"@svgr/hast-util-to-babel-ast@^5.4.0": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.4.0.tgz#bb5d002e428f510aa5b53ec0a02377a95b367715" - integrity sha512-+U0TZZpPsP2V1WvVhqAOSTk+N+CjYHdZx+x9UBa1eeeZDXwH8pt0CrQf2+SvRl/h2CAPRFkm+Ey96+jKP8Bsgg== - dependencies: - "@babel/types" "^7.9.5" - -"@svgr/plugin-jsx@^5.4.0": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-5.4.0.tgz#ab47504c55615833c6db70fca2d7e489f509787c" - integrity sha512-SGzO4JZQ2HvGRKDzRga9YFSqOqaNrgLlQVaGvpZ2Iht2gwRp/tq+18Pvv9kS9ZqOMYgyix2LLxZMY1LOe9NPqw== - dependencies: - "@babel/core" "^7.7.5" - "@svgr/babel-preset" "^5.4.0" - "@svgr/hast-util-to-babel-ast" "^5.4.0" - svg-parser "^2.0.2" - -"@svgr/plugin-svgo@^5.4.0": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-5.4.0.tgz#45d9800b7099a6f7b4d85ebac89ab9abe8592f64" - integrity sha512-3Cgv3aYi1l6SHyzArV9C36yo4kgwVdF3zPQUC6/aCDUeXAofDYwE5kk3e3oT5ZO2a0N3lB+lLGvipBG6lnG8EA== - dependencies: - cosmiconfig "^6.0.0" - merge-deep "^3.0.2" - svgo "^1.2.2" - -"@svgr/webpack@^5.4.0": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@svgr/webpack/-/webpack-5.4.0.tgz#b68bc86e29cf007292b96ced65f80971175632e0" - integrity sha512-LjepnS/BSAvelnOnnzr6Gg0GcpLmnZ9ThGFK5WJtm1xOqdBE/1IACZU7MMdVzjyUkfFqGz87eRE4hFaSLiUwYg== - dependencies: - "@babel/core" "^7.9.0" - "@babel/plugin-transform-react-constant-elements" "^7.9.0" - "@babel/preset-env" "^7.9.5" - "@babel/preset-react" "^7.9.4" - "@svgr/core" "^5.4.0" - "@svgr/plugin-jsx" "^5.4.0" - "@svgr/plugin-svgo" "^5.4.0" - loader-utils "^2.0.0" - "@testing-library/dom@^7.26.6": version "7.26.6" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.26.6.tgz#d558db63070a3acea5bea7e2497e631cd12541cc" @@ -10091,17 +9962,6 @@ cliui@^7.0.2: strip-ansi "^6.0.0" wrap-ansi "^7.0.0" -clone-deep@^0.2.4: - version "0.2.4" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-0.2.4.tgz#4e73dd09e9fb971cc38670c5dced9c1896481cc6" - integrity sha1-TnPdCen7lxzDhnDF3O2cGJZIHMY= - dependencies: - for-own "^0.1.3" - is-plain-object "^2.0.1" - kind-of "^3.0.2" - lazy-cache "^1.0.3" - shallow-clone "^0.1.2" - clone-deep@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" @@ -10689,17 +10549,6 @@ cosmiconfig@^6.0.0: path-type "^4.0.0" yaml "^1.7.2" -cp-file@^6.1.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/cp-file/-/cp-file-6.2.0.tgz#40d5ea4a1def2a9acdd07ba5c0b0246ef73dc10d" - integrity sha512-fmvV4caBnofhPe8kOcitBwSn2f39QLjnAnGq3gO9dfd75mUytzKNZB1hde6QHunW2Rt+OwuBOMc3i1tNElbszA== - dependencies: - graceful-fs "^4.1.2" - make-dir "^2.0.0" - nested-error-stacks "^2.0.0" - pify "^4.0.1" - safe-buffer "^5.0.1" - cp-file@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/cp-file/-/cp-file-7.0.0.tgz#b9454cfd07fe3b974ab9ea0e5f29655791a9b8cd" @@ -10789,14 +10638,6 @@ create-react-context@<=0.2.2: fbjs "^0.8.0" gud "^1.0.0" -cross-spawn-async@^2.1.1: - version "2.2.5" - resolved "https://registry.yarnpkg.com/cross-spawn-async/-/cross-spawn-async-2.2.5.tgz#845ff0c0834a3ded9d160daca6d390906bb288cc" - integrity sha1-hF/wwINKPe2dFg2sptOQkGuyiMw= - dependencies: - lru-cache "^4.0.0" - which "^1.2.8" - cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -10998,22 +10839,6 @@ css-tree@1.0.0-alpha.33: mdn-data "2.0.4" source-map "^0.5.3" -css-tree@1.0.0-alpha.37: - version "1.0.0-alpha.37" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" - integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg== - dependencies: - mdn-data "2.0.4" - source-map "^0.6.1" - -css-tree@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0.tgz#21993fa270d742642a90409a2c0cb3ac0298adf6" - integrity sha512-CdVYz/Yuqw0VdKhXPBIgi8DO3NicJVYZNWeX9XcIuSp9ZoFT5IcleVRW07O5rMjdcx1mb+MEJPknTTEW7DdsYw== - dependencies: - mdn-data "2.0.12" - source-map "^0.6.1" - css-tree@^1.0.0-alpha.28: version "1.0.0-alpha.36" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.36.tgz#48a848ecad1859c9d005bf900ce58a3465987954" @@ -11146,13 +10971,6 @@ csso@^3.5.1: dependencies: css-tree "1.0.0-alpha.29" -csso@^4.0.2: - version "4.1.0" - resolved "https://registry.yarnpkg.com/csso/-/csso-4.1.0.tgz#1d31193efa99b87aa6bad6c0cef155e543d09e8b" - integrity sha512-h+6w/W1WqXaJA4tb1dk7r5tVbOm97MsKxzwnvOR04UQ6GILroryjMWu3pmCCtL2mLaEStQ0fZgeGiy99mo7iyg== - dependencies: - css-tree "^1.0.0" - cssom@^0.4.1, cssom@^0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" @@ -13155,17 +12973,6 @@ execa@4.0.0: signal-exit "^3.0.2" strip-final-newline "^2.0.0" -execa@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.2.2.tgz#e2ead472c2c31aad6f73f1ac956eef45e12320cb" - integrity sha1-4urUcsLDGq1vc/GslW7vReEjIMs= - dependencies: - cross-spawn-async "^2.1.1" - npm-run-path "^1.0.0" - object-assign "^4.0.1" - path-key "^1.0.0" - strip-eof "^1.0.0" - execa@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" @@ -13838,23 +13645,11 @@ for-each@~0.3.3: dependencies: is-callable "^1.1.3" -for-in@^0.1.3: - version "0.1.8" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1" - integrity sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE= - -for-in@^1.0.1, for-in@^1.0.2: +for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= -for-own@^0.1.3: - version "0.1.5" - resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" - integrity sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4= - dependencies: - for-in "^1.0.1" - forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -15649,7 +15444,7 @@ is-boolean-object@^1.0.1: resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.1.tgz#10edc0900dd127697a92f6f9807c7617d68ac48e" integrity sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ== -is-buffer@^1.0.2, is-buffer@^1.1.5, is-buffer@~1.1.1: +is-buffer@^1.1.5, is-buffer@~1.1.1: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== @@ -15986,7 +15781,7 @@ is-path-inside@^2.1.0: dependencies: path-is-inside "^1.0.2" -is-path-inside@^3.0.1, is-path-inside@^3.0.2: +is-path-inside@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017" integrity sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg== @@ -16006,7 +15801,7 @@ is-plain-object@3.0.1: resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-3.0.1.tgz#662d92d24c0aa4302407b0d45d21f2251c85f85b" integrity sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g== -is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: +is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== @@ -17198,13 +16993,6 @@ killable@^1.0.1: resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" integrity sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg== -kind-of@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-2.0.1.tgz#018ec7a4ce7e3a86cb9141be519d24c8faa981b5" - integrity sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU= - dependencies: - is-buffer "^1.0.2" - kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" @@ -17266,11 +17054,6 @@ lazy-ass@^1.6.0: resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513" integrity sha1-eZllXoZGwX8In90YfRUNMyTVRRM= -lazy-cache@^0.2.3: - version "0.2.7" - resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-0.2.7.tgz#7feddf2dcb6edb77d11ef1d117ab5ffdf0ab1b65" - integrity sha1-f+3fLctu23fRHvHRF6tf/fCrG2U= - lazy-cache@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" @@ -17810,7 +17593,7 @@ lowlight@^1.14.0: fault "^1.0.0" highlight.js "~10.4.0" -lru-cache@^4.0.0, lru-cache@^4.0.1: +lru-cache@^4.0.1: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== @@ -18037,11 +17820,6 @@ mdast-util-to-string@^1.0.0: resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz#27055500103f51637bd07d01da01eb1967a43527" integrity sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A== -mdn-data@2.0.12: - version "2.0.12" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.12.tgz#bbb658d08b38f574bbb88f7b83703defdcc46844" - integrity sha512-ULbAlgzVb8IqZ0Hsxm6hHSlQl3Jckst2YEQS7fODu9ilNWy2LvcoSY7TRFIktABP2mdppBioc66va90T+NUs8Q== - mdn-data@2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" @@ -18147,15 +17925,6 @@ merge-class-names@^1.1.1: resolved "https://registry.yarnpkg.com/merge-class-names/-/merge-class-names-1.2.0.tgz#cb30ecfc3bdbd96b6f76d0a98777907e5fbb3462" integrity sha512-ifHxhC8DojHT1rG3PHCaJYInUqPd0WO+PxsaYDMkgy7RzfyOFtnlpr/hbhki+m/3R/ujIRVnZkD/AHjgjb5uhg== -merge-deep@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/merge-deep/-/merge-deep-3.0.2.tgz#f39fa100a4f1bd34ff29f7d2bf4508fbb8d83ad2" - integrity sha512-T7qC8kg4Zoti1cFd8Cr0M+qaZfOwjlPDEdZIIPPB2JZctjaPM4fX+i7HOId69tAti2fvO6X5ldfYUONDODsrkA== - dependencies: - arr-union "^3.1.0" - clone-deep "^0.2.4" - kind-of "^3.0.2" - merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" @@ -18429,14 +18198,6 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mixin-object@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/mixin-object/-/mixin-object-2.0.1.tgz#4fb949441dab182540f1fe035ba60e1947a5e57e" - integrity sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4= - dependencies: - for-in "^0.1.3" - is-extendable "^0.1.1" - mkdirp-promise@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/mkdirp-promise/-/mkdirp-promise-5.0.1.tgz#e9b8f68e552c68a9c1713b84883f7a1dd039b8a1" @@ -18563,15 +18324,6 @@ moo@^0.4.3: resolved "https://registry.yarnpkg.com/moo/-/moo-0.4.3.tgz#3f847a26f31cf625a956a87f2b10fbc013bfd10e" integrity sha512-gFD2xGCl8YFgGHsqJ9NKRVdwlioeW3mI1iqfLNYQOv0+6JRwG58Zk9DIGQgyIaffSYaO1xsKnMaYzzNr1KyIAw== -mount-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/mount-point/-/mount-point-3.0.0.tgz#665cb9edebe80d110e658db56c31d0aef51a8f97" - integrity sha1-Zly57evoDREOZY21bDHQrvUaj5c= - dependencies: - "@sindresorhus/df" "^1.0.1" - pify "^2.3.0" - pinkie-promise "^2.0.1" - mousetrap-global-bind@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/mousetrap-global-bind/-/mousetrap-global-bind-1.1.0.tgz#cd7de9222bd0646fa2e010d54c84a74c26a88edd" @@ -18594,15 +18346,6 @@ move-concurrently@^1.0.1: rimraf "^2.5.4" run-queue "^1.0.3" -move-file@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/move-file/-/move-file-1.2.0.tgz#789f92d276c62511d214b1b285aa16e015c2f2fc" - integrity sha512-USHrRmxzGowUWAGBbJPdFjHzEqtxDU03pLHY0Rfqgtnq+q8FOIs8wvkkf+Udmg77SJKs47y9sI0jJvQeYsmiCA== - dependencies: - cp-file "^6.1.0" - make-dir "^3.0.0" - path-exists "^3.0.0" - ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -19098,13 +18841,6 @@ npm-pick-manifest@^3.0.0: npm-package-arg "^6.0.0" semver "^5.4.1" -npm-run-path@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-1.0.0.tgz#f5c32bf595fe81ae927daec52e82f8b000ac3c8f" - integrity sha1-9cMr9ZX+ga6Sfa7FLoL4sACsPI8= - dependencies: - path-key "^1.0.0" - npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -19682,7 +19418,7 @@ p-try@^1.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= -p-try@^2.0.0, p-try@^2.2.0: +p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== @@ -19916,11 +19652,6 @@ path-is-inside@^1.0.1, path-is-inside@^1.0.2: resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= -path-key@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-1.0.0.tgz#5d53d578019646c0d68800db4e146e6bdc2ac7af" - integrity sha1-XVPVeAGWRsDWiADbThRua9wqx68= - path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" @@ -20020,7 +19751,7 @@ pify@^4.0.1: resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== -pinkie-promise@^2.0.0, pinkie-promise@^2.0.1: +pinkie-promise@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o= @@ -23802,16 +23533,6 @@ sha.js@^2.4.0, sha.js@^2.4.8: inherits "^2.0.1" safe-buffer "^5.0.1" -shallow-clone@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-0.1.2.tgz#5909e874ba77106d73ac414cfec1ffca87d97060" - integrity sha1-WQnodLp3EG1zrEFM/sH/yofZcGA= - dependencies: - is-extendable "^0.1.1" - kind-of "^2.0.1" - lazy-cache "^0.2.3" - mixin-object "^2.0.1" - shallow-clone@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" @@ -24845,11 +24566,6 @@ supports-hyperlinks@^2.0.0: has-flag "^4.0.0" supports-color "^7.0.0" -svg-parser@^2.0.2: - version "2.0.4" - resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" - integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== - svgo@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.0.tgz#bae51ba95ded9a33a36b7c46ce9c359ae9154313" @@ -24869,25 +24585,6 @@ svgo@^1.0.0: unquote "~1.1.1" util.promisify "~1.0.0" -svgo@^1.2.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167" - integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw== - dependencies: - chalk "^2.4.1" - coa "^2.0.2" - css-select "^2.0.0" - css-select-base-adapter "^0.1.1" - css-tree "1.0.0-alpha.37" - csso "^4.0.2" - js-yaml "^3.13.1" - mkdirp "~0.5.1" - object.values "^1.1.0" - sax "~1.2.4" - stable "^0.1.8" - unquote "~1.1.1" - util.promisify "~1.0.0" - symbol-observable@^1.0.3, symbol-observable@^1.0.4, symbol-observable@^1.1.0, symbol-observable@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" @@ -25429,21 +25126,6 @@ tr46@^2.0.2: dependencies: punycode "^2.1.1" -trash@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/trash/-/trash-6.1.1.tgz#8fb863421b31f32571f2650b53534934d5e63025" - integrity sha512-4i56lCmz2RG6WZN018hf4L75L5HboaFuKkHx3wDG/ihevI99e0OgFyl8w6G4ioqBm62V4EJqCy5xw3vQSNXU8A== - dependencies: - "@stroncium/procfs" "^1.0.0" - globby "^7.1.1" - is-path-inside "^3.0.2" - make-dir "^3.0.0" - move-file "^1.1.0" - p-map "^3.0.0" - p-try "^2.2.0" - uuid "^3.3.2" - xdg-trashdir "^2.1.1" - traverse@^0.6.6: version "0.6.6" resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137" @@ -26665,7 +26347,7 @@ which-pm-runs@^1.0.0: resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= -which@1, which@1.3.1, which@^1.2.14, which@^1.2.8, which@^1.2.9, which@^1.3.1: +which@1, which@1.3.1, which@^1.2.14, which@^1.2.9, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== @@ -26893,29 +26575,11 @@ ws@^7.2.3: resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.0.tgz#4b2f7f219b3d3737bc1a2fbf145d825b94d38ffd" integrity sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w== -xdg-basedir@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-2.0.0.tgz#edbc903cc385fc04523d966a335504b5504d1bd2" - integrity sha1-7byQPMOF/ARSPZZqM1UEtVBNG9I= - dependencies: - os-homedir "^1.0.0" - xdg-basedir@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ= -xdg-trashdir@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/xdg-trashdir/-/xdg-trashdir-2.1.1.tgz#59a60aaf8e6f9240c1daed9a0944b2f514c27d8e" - integrity sha512-KcVhPaOu2ZurYNHSRTf1+ZHORkTZGCQ+u0JHN17QixRISJq4pXOnjt/lQcehvtHL5QAKhSzKgyjrcNnPdkPBHA== - dependencies: - "@sindresorhus/df" "^2.1.0" - mount-point "^3.0.0" - pify "^2.2.0" - user-home "^2.0.0" - xdg-basedir "^2.0.0" - xml-name-validator@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" From 4e0ad5010218c85769e707a959889d57049a0bcf Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Fri, 4 Dec 2020 11:09:32 +0100 Subject: [PATCH 089/124] middleware: Make scenario test functions take a testing.T argument (#29564) Signed-off-by: Arve Knudsen --- pkg/middleware/auth_test.go | 33 ++++++----- pkg/middleware/dashboard_redirect_test.go | 6 +- pkg/middleware/middleware_basic_auth_test.go | 14 ++--- pkg/middleware/middleware_test.go | 59 +++++++++++--------- pkg/middleware/org_redirect_test.go | 4 +- pkg/middleware/quota_test.go | 14 ++--- pkg/middleware/recovery_test.go | 6 +- pkg/middleware/testing.go | 2 +- 8 files changed, 74 insertions(+), 64 deletions(-) diff --git a/pkg/middleware/auth_test.go b/pkg/middleware/auth_test.go index ea96dcb9d1a..1cf03aa4df2 100644 --- a/pkg/middleware/auth_test.go +++ b/pkg/middleware/auth_test.go @@ -14,7 +14,7 @@ import ( func TestMiddlewareAuth(t *testing.T) { reqSignIn := Auth(&AuthOptions{ReqSignedIn: true}) - middlewareScenario(t, "ReqSignIn true and unauthenticated request", func(sc *scenarioContext) { + middlewareScenario(t, "ReqSignIn true and unauthenticated request", func(t *testing.T, sc *scenarioContext) { sc.m.Get("/secure", reqSignIn, sc.defaultHandler) sc.fakeReq("GET", "/secure").exec() @@ -22,7 +22,7 @@ func TestMiddlewareAuth(t *testing.T) { assert.Equal(t, 302, sc.resp.Code) }) - middlewareScenario(t, "ReqSignIn true and unauthenticated API request", func(sc *scenarioContext) { + middlewareScenario(t, "ReqSignIn true and unauthenticated API request", func(t *testing.T, sc *scenarioContext) { sc.m.Get("/api/secure", reqSignIn, sc.defaultHandler) sc.fakeReq("GET", "/api/secure").exec() @@ -44,7 +44,8 @@ func TestMiddlewareAuth(t *testing.T) { setting.AnonymousEnabled = true setting.AnonymousOrgName = "test" - middlewareScenario(t, "ReqSignIn true and request with forceLogin in query string", func(sc *scenarioContext) { + middlewareScenario(t, "ReqSignIn true and request with forceLogin in query string", func( + t *testing.T, sc *scenarioContext) { bus.AddHandler("test", func(query *models.GetOrgByNameQuery) error { query.Result = &models.Org{Id: orgID, Name: "test"} return nil @@ -54,13 +55,14 @@ func TestMiddlewareAuth(t *testing.T) { sc.fakeReq("GET", "/secure?forceLogin=true").exec() - assert.Equal(sc.t, 302, sc.resp.Code) + assert.Equal(t, 302, sc.resp.Code) location, ok := sc.resp.Header()["Location"] assert.True(t, ok) assert.Equal(t, "/login", location[0]) }) - middlewareScenario(t, "ReqSignIn true and request with same org provided in query string", func(sc *scenarioContext) { + middlewareScenario(t, "ReqSignIn true and request with same org provided in query string", func( + t *testing.T, sc *scenarioContext) { bus.AddHandler("test", func(query *models.GetOrgByNameQuery) error { query.Result = &models.Org{Id: orgID, Name: "test"} return nil @@ -70,10 +72,11 @@ func TestMiddlewareAuth(t *testing.T) { sc.fakeReq("GET", fmt.Sprintf("/secure?orgId=%d", orgID)).exec() - assert.Equal(sc.t, 200, sc.resp.Code) + assert.Equal(t, 200, sc.resp.Code) }) - middlewareScenario(t, "ReqSignIn true and request with different org provided in query string", func(sc *scenarioContext) { + middlewareScenario(t, "ReqSignIn true and request with different org provided in query string", func( + t *testing.T, sc *scenarioContext) { bus.AddHandler("test", func(query *models.GetOrgByNameQuery) error { query.Result = &models.Org{Id: orgID, Name: "test"} return nil @@ -83,24 +86,26 @@ func TestMiddlewareAuth(t *testing.T) { sc.fakeReq("GET", "/secure?orgId=2").exec() - assert.Equal(sc.t, 302, sc.resp.Code) + assert.Equal(t, 302, sc.resp.Code) location, ok := sc.resp.Header()["Location"] - assert.True(sc.t, ok) - assert.Equal(sc.t, "/login", location[0]) + assert.True(t, ok) + assert.Equal(t, "/login", location[0]) }) }) - middlewareScenario(t, "Snapshot public mode disabled and unauthenticated request should return 401", func(sc *scenarioContext) { + middlewareScenario(t, "Snapshot public mode disabled and unauthenticated request should return 401", func( + t *testing.T, sc *scenarioContext) { sc.m.Get("/api/snapshot", SnapshotPublicModeOrSignedIn(), sc.defaultHandler) sc.fakeReq("GET", "/api/snapshot").exec() - assert.Equal(sc.t, 401, sc.resp.Code) + assert.Equal(t, 401, sc.resp.Code) }) - middlewareScenario(t, "Snapshot public mode enabled and unauthenticated request should return 200", func(sc *scenarioContext) { + middlewareScenario(t, "Snapshot public mode enabled and unauthenticated request should return 200", func( + t *testing.T, sc *scenarioContext) { setting.SnapshotPublicMode = true sc.m.Get("/api/snapshot", SnapshotPublicModeOrSignedIn(), sc.defaultHandler) sc.fakeReq("GET", "/api/snapshot").exec() - assert.Equal(sc.t, 200, sc.resp.Code) + assert.Equal(t, 200, sc.resp.Code) }) } diff --git a/pkg/middleware/dashboard_redirect_test.go b/pkg/middleware/dashboard_redirect_test.go index 7efcbd801c1..30a9d27fe4c 100644 --- a/pkg/middleware/dashboard_redirect_test.go +++ b/pkg/middleware/dashboard_redirect_test.go @@ -24,7 +24,7 @@ func TestMiddlewareDashboardRedirect(t *testing.T) { fakeDash.HasAcl = false fakeDash.Uid = util.GenerateShortUID() - middlewareScenario(t, "GET dashboard by legacy url", func(sc *scenarioContext) { + middlewareScenario(t, "GET dashboard by legacy url", func(t *testing.T, sc *scenarioContext) { bus.AddHandler("test", func(query *models.GetDashboardQuery) error { query.Result = fakeDash return nil @@ -43,7 +43,7 @@ func TestMiddlewareDashboardRedirect(t *testing.T) { assert.Equal(t, 2, len(redirectURL.Query())) }) - middlewareScenario(t, "GET dashboard solo by legacy url", func(sc *scenarioContext) { + middlewareScenario(t, "GET dashboard solo by legacy url", func(t *testing.T, sc *scenarioContext) { bus.AddHandler("test", func(query *models.GetDashboardQuery) error { query.Result = fakeDash return nil @@ -65,7 +65,7 @@ func TestMiddlewareDashboardRedirect(t *testing.T) { }) }) - middlewareScenario(t, "GET dashboard by legacy edit url", func(sc *scenarioContext) { + middlewareScenario(t, "GET dashboard by legacy edit url", func(t *testing.T, sc *scenarioContext) { sc.m.Get("/d/:uid/:slug", RedirectFromLegacyPanelEditURL(), sc.defaultHandler) sc.fakeReqWithParams("GET", "/d/asd/dash?orgId=1&panelId=12&fullscreen&edit", map[string]string{}).exec() diff --git a/pkg/middleware/middleware_basic_auth_test.go b/pkg/middleware/middleware_basic_auth_test.go index 17386afcc56..447d715dd79 100644 --- a/pkg/middleware/middleware_basic_auth_test.go +++ b/pkg/middleware/middleware_basic_auth_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/grafana/grafana/pkg/bus" - authLogin "github.com/grafana/grafana/pkg/login" + "github.com/grafana/grafana/pkg/login" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" @@ -27,7 +27,7 @@ func TestMiddlewareBasicAuth(t *testing.T) { const id int64 = 12 - middlewareScenario(t, "Valid API key", func(sc *scenarioContext) { + middlewareScenario(t, "Valid API key", func(t *testing.T, sc *scenarioContext) { const orgID int64 = 2 keyhash, err := util.EncodePassword("v5nAwpMafFP6znaS4urhdWDLS5511M42", "asd") require.NoError(t, err) @@ -46,7 +46,7 @@ func TestMiddlewareBasicAuth(t *testing.T) { assert.Equal(t, models.ROLE_EDITOR, sc.context.OrgRole) }) - middlewareScenario(t, "Handle auth", func(sc *scenarioContext) { + middlewareScenario(t, "Handle auth", func(t *testing.T, sc *scenarioContext) { const password = "MyPass" const salt = "Salt" const orgID int64 = 2 @@ -78,11 +78,11 @@ func TestMiddlewareBasicAuth(t *testing.T) { assert.Equal(t, id, sc.context.UserId) }) - middlewareScenario(t, "Auth sequence", func(sc *scenarioContext) { + middlewareScenario(t, "Auth sequence", func(t *testing.T, sc *scenarioContext) { const password = "MyPass" const salt = "Salt" - authLogin.Init() + login.Init() bus.AddHandler("user-query", func(query *models.GetUserByLoginQuery) error { encoded, err := util.EncodePassword(password, salt) @@ -109,7 +109,7 @@ func TestMiddlewareBasicAuth(t *testing.T) { assert.Equal(t, id, sc.context.UserId) }) - middlewareScenario(t, "Should return error if user is not found", func(sc *scenarioContext) { + middlewareScenario(t, "Should return error if user is not found", func(t *testing.T, sc *scenarioContext) { sc.fakeReq("GET", "/") sc.req.SetBasicAuth("user", "password") sc.exec() @@ -121,7 +121,7 @@ func TestMiddlewareBasicAuth(t *testing.T) { assert.Equal(t, errStringInvalidUsernamePassword, sc.respJson["message"]) }) - middlewareScenario(t, "Should return error if user & password do not match", func(sc *scenarioContext) { + middlewareScenario(t, "Should return error if user & password do not match", func(t *testing.T, sc *scenarioContext) { bus.AddHandler("user-query", func(loginUserQuery *models.GetUserByLoginQuery) error { return nil }) diff --git a/pkg/middleware/middleware_test.go b/pkg/middleware/middleware_test.go index 69c44527890..b4dfde543b9 100644 --- a/pkg/middleware/middleware_test.go +++ b/pkg/middleware/middleware_test.go @@ -50,7 +50,7 @@ func TestMiddleWareSecurityHeaders(t *testing.T) { }) setting.ErrTemplateName = errorTemplate - middlewareScenario(t, "middleware should get correct x-xss-protection header", func(sc *scenarioContext) { + middlewareScenario(t, "middleware should get correct x-xss-protection header", func(t *testing.T, sc *scenarioContext) { origXSSProtectionHeader := setting.XSSProtectionHeader t.Cleanup(func() { setting.XSSProtectionHeader = origXSSProtectionHeader @@ -60,7 +60,7 @@ func TestMiddleWareSecurityHeaders(t *testing.T) { assert.Equal(t, "1; mode=block", sc.resp.Header().Get("X-XSS-Protection")) }) - middlewareScenario(t, "middleware should not get x-xss-protection when disabled", func(sc *scenarioContext) { + middlewareScenario(t, "middleware should not get x-xss-protection when disabled", func(t *testing.T, sc *scenarioContext) { origXSSProtectionHeader := setting.XSSProtectionHeader t.Cleanup(func() { setting.XSSProtectionHeader = origXSSProtectionHeader @@ -70,7 +70,7 @@ func TestMiddleWareSecurityHeaders(t *testing.T) { assert.Empty(t, sc.resp.Header().Get("X-XSS-Protection")) }) - middlewareScenario(t, "middleware should add correct Strict-Transport-Security header", func(sc *scenarioContext) { + middlewareScenario(t, "middleware should add correct Strict-Transport-Security header", func(t *testing.T, sc *scenarioContext) { origStrictTransportSecurity := setting.StrictTransportSecurity origProtocol := setting.Protocol origStrictTransportSecurityMaxAge := setting.StrictTransportSecurityMaxAge @@ -101,31 +101,33 @@ func TestMiddlewareContext(t *testing.T) { }) setting.ErrTemplateName = errorTemplate - middlewareScenario(t, "middleware should add context to injector", func(sc *scenarioContext) { + middlewareScenario(t, "middleware should add context to injector", func(t *testing.T, sc *scenarioContext) { sc.fakeReq("GET", "/").exec() assert.NotNil(t, sc.context) }) - middlewareScenario(t, "Default middleware should allow get request", func(sc *scenarioContext) { + middlewareScenario(t, "Default middleware should allow get request", func(t *testing.T, sc *scenarioContext) { sc.fakeReq("GET", "/").exec() assert.Equal(t, 200, sc.resp.Code) }) - middlewareScenario(t, "middleware should add Cache-Control header for requests to API", func(sc *scenarioContext) { + middlewareScenario(t, "middleware should add Cache-Control header for requests to API", func(t *testing.T, sc *scenarioContext) { sc.fakeReq("GET", "/api/search").exec() assert.Equal(t, "no-cache", sc.resp.Header().Get("Cache-Control")) assert.Equal(t, "no-cache", sc.resp.Header().Get("Pragma")) assert.Equal(t, "-1", sc.resp.Header().Get("Expires")) }) - middlewareScenario(t, "middleware should not add Cache-Control header for requests to datasource proxy API", func(sc *scenarioContext) { + middlewareScenario(t, "middleware should not add Cache-Control header for requests to datasource proxy API", func( + t *testing.T, sc *scenarioContext) { sc.fakeReq("GET", "/api/datasources/proxy/1/test").exec() assert.Empty(t, sc.resp.Header().Get("Cache-Control")) assert.Empty(t, sc.resp.Header().Get("Pragma")) assert.Empty(t, sc.resp.Header().Get("Expires")) }) - middlewareScenario(t, "middleware should add Cache-Control header for requests with html response", func(sc *scenarioContext) { + middlewareScenario(t, "middleware should add Cache-Control header for requests with html response", func( + t *testing.T, sc *scenarioContext) { sc.handler(func(c *models.ReqContext) { data := &dtos.IndexViewData{ User: &dtos.CurrentUser{}, @@ -141,12 +143,14 @@ func TestMiddlewareContext(t *testing.T) { assert.Equal(t, "-1", sc.resp.Header().Get("Expires")) }) - middlewareScenario(t, "middleware should add X-Frame-Options header with deny for request when not allowing embedding", func(sc *scenarioContext) { + middlewareScenario(t, "middleware should add X-Frame-Options header with deny for request when not allowing embedding", func( + t *testing.T, sc *scenarioContext) { sc.fakeReq("GET", "/api/search").exec() assert.Equal(t, "deny", sc.resp.Header().Get("X-Frame-Options")) }) - middlewareScenario(t, "middleware should not add X-Frame-Options header for request when allowing embedding", func(sc *scenarioContext) { + middlewareScenario(t, "middleware should not add X-Frame-Options header for request when allowing embedding", func( + t *testing.T, sc *scenarioContext) { origAllowEmbedding := setting.AllowEmbedding t.Cleanup(func() { setting.AllowEmbedding = origAllowEmbedding @@ -156,7 +160,7 @@ func TestMiddlewareContext(t *testing.T) { assert.Empty(t, sc.resp.Header().Get("X-Frame-Options")) }) - middlewareScenario(t, "Invalid api key", func(sc *scenarioContext) { + middlewareScenario(t, "Invalid api key", func(t *testing.T, sc *scenarioContext) { sc.apiKey = "invalid_key_test" sc.fakeReq("GET", "/").exec() @@ -165,7 +169,7 @@ func TestMiddlewareContext(t *testing.T) { assert.Equal(t, errStringInvalidAPIKey, sc.respJson["message"]) }) - middlewareScenario(t, "Valid api key", func(sc *scenarioContext) { + middlewareScenario(t, "Valid api key", func(t *testing.T, sc *scenarioContext) { const orgID int64 = 12 keyhash, err := util.EncodePassword("v5nAwpMafFP6znaS4urhdWDLS5511M42", "asd") require.NoError(t, err) @@ -184,7 +188,7 @@ func TestMiddlewareContext(t *testing.T) { assert.Equal(t, models.ROLE_EDITOR, sc.context.OrgRole) }) - middlewareScenario(t, "Valid api key, but does not match db hash", func(sc *scenarioContext) { + middlewareScenario(t, "Valid api key, but does not match db hash", func(t *testing.T, sc *scenarioContext) { keyhash := "Something_not_matching" bus.AddHandler("test", func(query *models.GetApiKeyByNameQuery) error { @@ -198,7 +202,7 @@ func TestMiddlewareContext(t *testing.T) { assert.Equal(t, errStringInvalidAPIKey, sc.respJson["message"]) }) - middlewareScenario(t, "Valid api key, but expired", func(sc *scenarioContext) { + middlewareScenario(t, "Valid api key, but expired", func(t *testing.T, sc *scenarioContext) { mockGetTime() defer resetGetTime() @@ -219,7 +223,8 @@ func TestMiddlewareContext(t *testing.T) { assert.Equal(t, "Expired API key", sc.respJson["message"]) }) - middlewareScenario(t, "Non-expired auth token in cookie which not are being rotated", func(sc *scenarioContext) { + middlewareScenario(t, "Non-expired auth token in cookie which not are being rotated", func( + t *testing.T, sc *scenarioContext) { const userID int64 = 12 sc.withTokenSessionCookie("token") @@ -245,7 +250,7 @@ func TestMiddlewareContext(t *testing.T) { assert.Equal(t, "", sc.resp.Header().Get("Set-Cookie")) }) - middlewareScenario(t, "Non-expired auth token in cookie which are being rotated", func(sc *scenarioContext) { + middlewareScenario(t, "Non-expired auth token in cookie which are being rotated", func(t *testing.T, sc *scenarioContext) { const userID int64 = 12 sc.withTokenSessionCookie("token") @@ -335,7 +340,7 @@ func TestMiddlewareContext(t *testing.T) { }) }) - middlewareScenario(t, "Invalid/expired auth token in cookie", func(sc *scenarioContext) { + middlewareScenario(t, "Invalid/expired auth token in cookie", func(t *testing.T, sc *scenarioContext) { sc.withTokenSessionCookie("token") sc.userAuthTokenService.LookupTokenProvider = func(ctx context.Context, unhashedToken string) (*models.UserToken, error) { @@ -349,7 +354,7 @@ func TestMiddlewareContext(t *testing.T) { assert.Nil(t, sc.context.UserToken) }) - middlewareScenario(t, "When anonymous access is enabled", func(sc *scenarioContext) { + middlewareScenario(t, "When anonymous access is enabled", func(t *testing.T, sc *scenarioContext) { const orgID int64 = 2 origAnonymousEnabled := setting.AnonymousEnabled @@ -410,7 +415,7 @@ func TestMiddlewareContext(t *testing.T) { const hdrName = "markelog" const group = "grafana-core-team" - middlewareScenario(t, "Should not sync the user if it's in the cache", func(sc *scenarioContext) { + middlewareScenario(t, "Should not sync the user if it's in the cache", func(t *testing.T, sc *scenarioContext) { bus.AddHandler("test", func(query *models.GetSignedInUserQuery) error { query.Result = &models.SignedInUser{OrgId: orgID, UserId: query.UserId} return nil @@ -430,7 +435,7 @@ func TestMiddlewareContext(t *testing.T) { assert.Equal(t, orgID, sc.context.OrgId) }) - middlewareScenario(t, "Should respect auto signup option", func(sc *scenarioContext) { + middlewareScenario(t, "Should respect auto signup option", func(t *testing.T, sc *scenarioContext) { origLDAPEnabled = setting.LDAPEnabled origAuthProxyAutoSignUp = setting.AuthProxyAutoSignUp t.Cleanup(func() { @@ -456,7 +461,7 @@ func TestMiddlewareContext(t *testing.T) { assert.Nil(t, sc.context) }) - middlewareScenario(t, "Should create an user from a header", func(sc *scenarioContext) { + middlewareScenario(t, "Should create an user from a header", func(t *testing.T, sc *scenarioContext) { origLDAPEnabled = setting.LDAPEnabled origAuthProxyAutoSignUp = setting.AuthProxyAutoSignUp t.Cleanup(func() { @@ -488,7 +493,7 @@ func TestMiddlewareContext(t *testing.T) { assert.Equal(t, orgID, sc.context.OrgId) }) - middlewareScenario(t, "Should get an existing user from header", func(sc *scenarioContext) { + middlewareScenario(t, "Should get an existing user from header", func(t *testing.T, sc *scenarioContext) { const userID int64 = 12 const orgID int64 = 2 @@ -517,7 +522,7 @@ func TestMiddlewareContext(t *testing.T) { assert.Equal(t, orgID, sc.context.OrgId) }) - middlewareScenario(t, "Should allow the request from whitelist IP", func(sc *scenarioContext) { + middlewareScenario(t, "Should allow the request from whitelist IP", func(t *testing.T, sc *scenarioContext) { origAuthProxyWhitelist = setting.AuthProxyWhitelist origLDAPEnabled = setting.LDAPEnabled t.Cleanup(func() { @@ -547,7 +552,7 @@ func TestMiddlewareContext(t *testing.T) { assert.Equal(t, orgID, sc.context.OrgId) }) - middlewareScenario(t, "Should not allow the request from whitelist IP", func(sc *scenarioContext) { + middlewareScenario(t, "Should not allow the request from whitelist IP", func(t *testing.T, sc *scenarioContext) { origAuthProxyWhitelist = setting.AuthProxyWhitelist origLDAPEnabled = setting.LDAPEnabled t.Cleanup(func() { @@ -576,7 +581,7 @@ func TestMiddlewareContext(t *testing.T) { assert.Nil(t, sc.context) }) - middlewareScenario(t, "Should return 407 status code if LDAP says no", func(sc *scenarioContext) { + middlewareScenario(t, "Should return 407 status code if LDAP says no", func(t *testing.T, sc *scenarioContext) { bus.AddHandler("LDAP", func(cmd *models.UpsertUserCommand) error { return errors.New("Do not add user") }) @@ -589,7 +594,7 @@ func TestMiddlewareContext(t *testing.T) { assert.Nil(t, sc.context) }) - middlewareScenario(t, "Should return 407 status code if there is cache mishap", func(sc *scenarioContext) { + middlewareScenario(t, "Should return 407 status code if there is cache mishap", func(t *testing.T, sc *scenarioContext) { bus.AddHandler("Do not have the user", func(query *models.GetSignedInUserQuery) error { return errors.New("Do not add user") }) @@ -653,7 +658,7 @@ func middlewareScenario(t *testing.T, desc string, fn scenarioFunc) { sc.m.Get("/", sc.defaultHandler) - fn(sc) + fn(t, sc) }) } diff --git a/pkg/middleware/org_redirect_test.go b/pkg/middleware/org_redirect_test.go index 8225b9970ed..0d1ababb4c9 100644 --- a/pkg/middleware/org_redirect_test.go +++ b/pkg/middleware/org_redirect_test.go @@ -11,7 +11,7 @@ import ( ) func TestOrgRedirectMiddleware(t *testing.T) { - middlewareScenario(t, "when setting a correct org for the user", func(sc *scenarioContext) { + middlewareScenario(t, "when setting a correct org for the user", func(t *testing.T, sc *scenarioContext) { sc.withTokenSessionCookie("token") bus.AddHandler("test", func(query *models.SetUsingOrgCommand) error { return nil @@ -35,7 +35,7 @@ func TestOrgRedirectMiddleware(t *testing.T) { assert.Equal(t, 302, sc.resp.Code) }) - middlewareScenario(t, "when setting an invalid org for user", func(sc *scenarioContext) { + middlewareScenario(t, "when setting an invalid org for user", func(t *testing.T, sc *scenarioContext) { sc.withTokenSessionCookie("token") bus.AddHandler("test", func(query *models.SetUsingOrgCommand) error { return fmt.Errorf("") diff --git a/pkg/middleware/quota_test.go b/pkg/middleware/quota_test.go index fc01ca62a3b..bf2696d5f04 100644 --- a/pkg/middleware/quota_test.go +++ b/pkg/middleware/quota_test.go @@ -42,7 +42,7 @@ func TestMiddlewareQuota(t *testing.T) { quotaFn := Quota(qs) t.Run("With user not logged in", func(t *testing.T) { - middlewareScenario(t, "and global quota not reached", func(sc *scenarioContext) { + middlewareScenario(t, "and global quota not reached", func(t *testing.T, sc *scenarioContext) { bus.AddHandler("globalQuota", func(query *models.GetGlobalQuotaByTargetQuery) error { query.Result = &models.GlobalQuotaDTO{ Target: query.Target, @@ -54,10 +54,10 @@ func TestMiddlewareQuota(t *testing.T) { sc.m.Get("/user", quotaFn("user"), sc.defaultHandler) sc.fakeReq("GET", "/user").exec() - assert.Equal(sc.t, 200, sc.resp.Code) + assert.Equal(t, 200, sc.resp.Code) }) - middlewareScenario(t, "and global quota reached", func(sc *scenarioContext) { + middlewareScenario(t, "and global quota reached", func(t *testing.T, sc *scenarioContext) { bus.AddHandler("globalQuota", func(query *models.GetGlobalQuotaByTargetQuery) error { query.Result = &models.GlobalQuotaDTO{ Target: query.Target, @@ -78,7 +78,7 @@ func TestMiddlewareQuota(t *testing.T) { assert.Equal(t, 403, sc.resp.Code) }) - middlewareScenario(t, "and global session quota not reached", func(sc *scenarioContext) { + middlewareScenario(t, "and global session quota not reached", func(t *testing.T, sc *scenarioContext) { bus.AddHandler("globalQuota", func(query *models.GetGlobalQuotaByTargetQuery) error { query.Result = &models.GlobalQuotaDTO{ Target: query.Target, @@ -99,7 +99,7 @@ func TestMiddlewareQuota(t *testing.T) { assert.Equal(t, 200, sc.resp.Code) }) - middlewareScenario(t, "and global session quota reached", func(sc *scenarioContext) { + middlewareScenario(t, "and global session quota reached", func(t *testing.T, sc *scenarioContext) { origSession := setting.Quota.Global.Session t.Cleanup(func() { setting.Quota.Global.Session = origSession @@ -108,11 +108,11 @@ func TestMiddlewareQuota(t *testing.T) { sc.m.Get("/user", quotaFn("session"), sc.defaultHandler) sc.fakeReq("GET", "/user").exec() - assert.Equal(sc.t, 403, sc.resp.Code) + assert.Equal(t, 403, sc.resp.Code) }) }) - middlewareScenario(t, "with user logged in", func(sc *scenarioContext) { + middlewareScenario(t, "with user logged in", func(t *testing.T, sc *scenarioContext) { sc.withTokenSessionCookie("token") bus.AddHandler("test", func(query *models.GetSignedInUserQuery) error { query.Result = &models.SignedInUser{OrgId: 2, UserId: 12} diff --git a/pkg/middleware/recovery_test.go b/pkg/middleware/recovery_test.go index 150167dc4bc..5507ba5aac5 100644 --- a/pkg/middleware/recovery_test.go +++ b/pkg/middleware/recovery_test.go @@ -20,7 +20,7 @@ func TestRecoveryMiddleware(t *testing.T) { t.Run("Given an API route that panics", func(t *testing.T) { apiURL := "/api/whatever" - recoveryScenario(t, "recovery middleware should return json", apiURL, func(sc *scenarioContext) { + recoveryScenario(t, "recovery middleware should return json", apiURL, func(t *testing.T, sc *scenarioContext) { sc.handlerFunc = panicHandler sc.fakeReq("GET", apiURL).exec() sc.req.Header.Add("content-type", "application/json") @@ -33,7 +33,7 @@ func TestRecoveryMiddleware(t *testing.T) { t.Run("Given a non-API route that panics", func(t *testing.T) { apiURL := "/whatever" - recoveryScenario(t, "recovery middleware should return html", apiURL, func(sc *scenarioContext) { + recoveryScenario(t, "recovery middleware should return html", apiURL, func(t *testing.T, sc *scenarioContext) { sc.handlerFunc = panicHandler sc.fakeReq("GET", apiURL).exec() @@ -85,6 +85,6 @@ func recoveryScenario(t *testing.T, desc string, url string, fn scenarioFunc) { sc.m.Get(url, sc.defaultHandler) - fn(sc) + fn(t, sc) }) } diff --git a/pkg/middleware/testing.go b/pkg/middleware/testing.go index 21a9852790d..8229fb36116 100644 --- a/pkg/middleware/testing.go +++ b/pkg/middleware/testing.go @@ -109,5 +109,5 @@ func (sc *scenarioContext) exec() { } } -type scenarioFunc func(c *scenarioContext) +type scenarioFunc func(t *testing.T, c *scenarioContext) type handlerFunc func(c *models.ReqContext) From 563478ece7b73c9fe73f52f6bd8e09a3a5c47935 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Fri, 4 Dec 2020 12:44:46 +0100 Subject: [PATCH 090/124] GraphNG: Fix issues with plugins not retrieving plot instance (#29585) * Fix issues with plugins not retrieving plot instance * Review --- .../grafana-ui/src/components/uPlot/Plot.tsx | 12 +++---- .../src/components/uPlot/context.ts | 34 ++++++++++--------- .../uPlot/geometries/EventsCanvas.tsx | 7 ++-- .../components/uPlot/geometries/XYCanvas.tsx | 7 ++-- .../uPlot/plugins/SelectionPlugin.tsx | 2 +- .../uPlot/plugins/TooltipPlugin.tsx | 2 +- .../graph3/plugins/AnnotationsPlugin.tsx | 11 +++--- .../panel/graph3/plugins/ExemplarsPlugin.tsx | 11 +++--- 8 files changed, 44 insertions(+), 42 deletions(-) diff --git a/packages/grafana-ui/src/components/uPlot/Plot.tsx b/packages/grafana-ui/src/components/uPlot/Plot.tsx index 8b202e18bd6..e030ef4bc0b 100755 --- a/packages/grafana-ui/src/components/uPlot/Plot.tsx +++ b/packages/grafana-ui/src/components/uPlot/Plot.tsx @@ -23,10 +23,6 @@ export const UPlotChart: React.FC = props => { props.config ); const getPlotInstance = useCallback(() => { - if (!plotInstance.current) { - throw new Error("Plot hasn't initialised yet"); - } - return plotInstance.current; }, []); @@ -72,13 +68,15 @@ export const UPlotChart: React.FC = props => { // Memoize plot context const plotCtx = useMemo(() => { - return buildPlotContext(Boolean(plotInstance.current), canvasRef, props.data, registerPlugin, getPlotInstance); + return buildPlotContext(canvasRef, props.data, registerPlugin, getPlotInstance); }, [plotInstance, canvasRef, props.data, registerPlugin, getPlotInstance]); return ( -
- {props.children} +
+
+ {props.children} +
); }; diff --git a/packages/grafana-ui/src/components/uPlot/context.ts b/packages/grafana-ui/src/components/uPlot/context.ts index cc6d4806b4f..558e88c2104 100644 --- a/packages/grafana-ui/src/components/uPlot/context.ts +++ b/packages/grafana-ui/src/components/uPlot/context.ts @@ -21,8 +21,7 @@ interface PlotPluginsContextType { } interface PlotContextType extends PlotPluginsContextType { - isPlotReady: boolean; - getPlotInstance: () => uPlot; + getPlotInstance: () => uPlot | undefined; getSeries: () => Series[]; getCanvas: () => PlotCanvasContextType; canvasRef: any; @@ -127,28 +126,31 @@ export const usePlotData = (): PlotDataAPI => { }; export const buildPlotContext = ( - isPlotReady: boolean, canvasRef: any, data: AlignedFrameWithGapTest, registerPlugin: any, - getPlotInstance: () => uPlot + getPlotInstance: () => uPlot | undefined ): PlotContextType => { return { - isPlotReady, canvasRef, data, registerPlugin, getPlotInstance, - getSeries: () => getPlotInstance().series, - getCanvas: () => ({ - width: getPlotInstance().width, - height: getPlotInstance().height, - plot: { - width: getPlotInstance().bbox.width / window.devicePixelRatio, - height: getPlotInstance().bbox.height / window.devicePixelRatio, - top: getPlotInstance().bbox.top / window.devicePixelRatio, - left: getPlotInstance().bbox.left / window.devicePixelRatio, - }, - }), + getSeries: () => getPlotInstance()!.series, + getCanvas: () => { + const plotInstance = getPlotInstance()!; + const bbox = plotInstance.bbox; + const pxRatio = window.devicePixelRatio; + return { + width: plotInstance.width, + height: plotInstance.height, + plot: { + width: bbox.width / pxRatio, + height: bbox.height / pxRatio, + top: bbox.top / pxRatio, + left: bbox.left / pxRatio, + }, + }; + }, }; }; diff --git a/packages/grafana-ui/src/components/uPlot/geometries/EventsCanvas.tsx b/packages/grafana-ui/src/components/uPlot/geometries/EventsCanvas.tsx index 0c5c8ac6262..cc247362a5a 100644 --- a/packages/grafana-ui/src/components/uPlot/geometries/EventsCanvas.tsx +++ b/packages/grafana-ui/src/components/uPlot/geometries/EventsCanvas.tsx @@ -18,8 +18,7 @@ export function EventsCanvas({ id, events, renderEventMarker, mapEventToXYCoo const eventMarkers = useMemo(() => { const markers: React.ReactNode[] = []; - - if (!plotCtx.isPlotReady || events.length === 0) { + if (!plotCtx.getPlotInstance() || events.length === 0) { return markers; } @@ -41,9 +40,9 @@ export function EventsCanvas({ id, events, renderEventMarker, mapEventToXYCoo } return <>{markers}; - }, [events, renderEventMarker, renderToken, plotCtx.isPlotReady]); + }, [events, renderEventMarker, renderToken, plotCtx]); - if (!plotCtx.isPlotReady) { + if (!plotCtx.getPlotInstance()) { return null; } diff --git a/packages/grafana-ui/src/components/uPlot/geometries/XYCanvas.tsx b/packages/grafana-ui/src/components/uPlot/geometries/XYCanvas.tsx index 0ded2517c74..352d34cb0df 100644 --- a/packages/grafana-ui/src/components/uPlot/geometries/XYCanvas.tsx +++ b/packages/grafana-ui/src/components/uPlot/geometries/XYCanvas.tsx @@ -10,8 +10,9 @@ interface XYCanvasProps {} */ export const XYCanvas: React.FC = ({ children }) => { const plotContext = usePlotContext(); + const plotInstance = plotContext.getPlotInstance(); - if (!plotContext.isPlotReady) { + if (!plotInstance) { return null; } @@ -20,8 +21,8 @@ export const XYCanvas: React.FC = ({ children }) => { className={css` position: absolute; overflow: visible; - left: ${plotContext.getPlotInstance().bbox.left / window.devicePixelRatio}px; - top: ${plotContext.getPlotInstance().bbox.top / window.devicePixelRatio}px; + left: ${plotInstance.bbox.left / window.devicePixelRatio}px; + top: ${plotInstance.bbox.top / window.devicePixelRatio}px; `} > {children} diff --git a/packages/grafana-ui/src/components/uPlot/plugins/SelectionPlugin.tsx b/packages/grafana-ui/src/components/uPlot/plugins/SelectionPlugin.tsx index af351aa18a2..eff8b96a341 100644 --- a/packages/grafana-ui/src/components/uPlot/plugins/SelectionPlugin.tsx +++ b/packages/grafana-ui/src/components/uPlot/plugins/SelectionPlugin.tsx @@ -75,7 +75,7 @@ export const SelectionPlugin: React.FC = ({ onSelect, onDi }; }, []); - if (!plotCtx.isPlotReady || !children || !selection) { + if (!plotCtx.getPlotInstance() || !children || !selection) { return null; } diff --git a/packages/grafana-ui/src/components/uPlot/plugins/TooltipPlugin.tsx b/packages/grafana-ui/src/components/uPlot/plugins/TooltipPlugin.tsx index be3cbb9f0d2..feed5e7fd4d 100644 --- a/packages/grafana-ui/src/components/uPlot/plugins/TooltipPlugin.tsx +++ b/packages/grafana-ui/src/components/uPlot/plugins/TooltipPlugin.tsx @@ -25,7 +25,7 @@ export const TooltipPlugin: React.FC = ({ mode = 'single', t return ( {({ focusedSeriesIdx, focusedPointIdx, coords }) => { - if (!plotContext.isPlotReady) { + if (!plotContext.getPlotInstance()) { return null; } diff --git a/public/app/plugins/panel/graph3/plugins/AnnotationsPlugin.tsx b/public/app/plugins/panel/graph3/plugins/AnnotationsPlugin.tsx index 57d615d59ab..3175239b16b 100644 --- a/public/app/plugins/panel/graph3/plugins/AnnotationsPlugin.tsx +++ b/public/app/plugins/panel/graph3/plugins/AnnotationsPlugin.tsx @@ -32,7 +32,7 @@ export const AnnotationsPlugin: React.FC = ({ annotation ); useEffect(() => { - if (plotCtx.isPlotReady) { + if (plotCtx.getPlotInstance()) { const views: Array> = []; for (const frame of annotations) { @@ -41,7 +41,7 @@ export const AnnotationsPlugin: React.FC = ({ annotation annotationsRef.current = views; } - }, [plotCtx.isPlotReady, annotations]); + }, [plotCtx, annotations]); useEffect(() => { const unregister = plotCtx.registerPlugin({ @@ -93,13 +93,14 @@ export const AnnotationsPlugin: React.FC = ({ annotation const mapAnnotationToXYCoords = useCallback( (annotation: AnnotationsDataFrameViewDTO) => { - if (!annotation.time) { + const plotInstance = plotCtx.getPlotInstance(); + if (!annotation.time || !plotInstance) { return undefined; } return { - x: plotCtx.getPlotInstance().valToPos(annotation.time / 1000, 'x'), - y: plotCtx.getPlotInstance().bbox.height / window.devicePixelRatio + 4, + x: plotInstance.valToPos(annotation.time, 'x'), + y: plotInstance.bbox.height / window.devicePixelRatio + 4, }; }, [plotCtx.getPlotInstance] diff --git a/public/app/plugins/panel/graph3/plugins/ExemplarsPlugin.tsx b/public/app/plugins/panel/graph3/plugins/ExemplarsPlugin.tsx index 2489f7ce785..14b0a5cc8aa 100644 --- a/public/app/plugins/panel/graph3/plugins/ExemplarsPlugin.tsx +++ b/public/app/plugins/panel/graph3/plugins/ExemplarsPlugin.tsx @@ -42,7 +42,7 @@ export const ExemplarsPlugin: React.FC = ({ exemplars, tim // THIS EVENT ONLY MOCKS EXEMPLAR Y VALUE!!!! TO BE REMOVED WHEN WE GET CORRECT EXEMPLARS SHAPE VIA PROPS useEffect(() => { - if (plotCtx.isPlotReady) { + if (plotCtx.getPlotInstance()) { const mocks: DataFrame[] = []; for (const frame of exemplars) { @@ -61,18 +61,19 @@ export const ExemplarsPlugin: React.FC = ({ exemplars, tim setExemplarsMock(mocks); } - }, [plotCtx.isPlotReady, exemplars]); + }, [plotCtx, exemplars]); const mapExemplarToXYCoords = useCallback( (exemplar: ExemplarsDataFrameViewDTO) => { - if (!exemplar.time) { + const plotInstance = plotCtx.getPlotInstance(); + if (!exemplar.time || !plotInstance) { return undefined; } return { - x: plotCtx.getPlotInstance().valToPos(exemplar.time / 1000, 'x'), + x: plotInstance.valToPos(exemplar.time / 1000, 'x'), // exemplar.y is a temporary mock for an examplar. This Needs to be calculated according to examplar scale! - y: Math.floor((exemplar.y * plotCtx.getPlotInstance().bbox.height) / window.devicePixelRatio), + y: Math.floor((exemplar.y * plotInstance.bbox.height) / window.devicePixelRatio), }; }, [plotCtx.getPlotInstance] From c62a0aa4d09d6c43d1d00b8d3236804bcda3df40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 4 Dec 2020 13:12:14 +0100 Subject: [PATCH 091/124] Elastic: Fixes config UI issues (#29608) * Elastic: Fixes config UI issues * Fixed index name input issue --- .../elasticsearch/configuration/DataLink.tsx | 1 + .../elasticsearch/configuration/DataLinks.tsx | 38 ++++++------- .../configuration/ElasticDetails.tsx | 54 +++++++++---------- 3 files changed, 46 insertions(+), 47 deletions(-) diff --git a/public/app/plugins/datasource/elasticsearch/configuration/DataLink.tsx b/public/app/plugins/datasource/elasticsearch/configuration/DataLink.tsx index 3d38df0fe2f..5de078ec15b 100644 --- a/public/app/plugins/datasource/elasticsearch/configuration/DataLink.tsx +++ b/public/app/plugins/datasource/elasticsearch/configuration/DataLink.tsx @@ -92,6 +92,7 @@ export const DataLink = (props: Props) => {
{ diff --git a/public/app/plugins/datasource/elasticsearch/configuration/DataLinks.tsx b/public/app/plugins/datasource/elasticsearch/configuration/DataLinks.tsx index dd7fb13a96b..c579b663754 100644 --- a/public/app/plugins/datasource/elasticsearch/configuration/DataLinks.tsx +++ b/public/app/plugins/datasource/elasticsearch/configuration/DataLinks.tsx @@ -32,9 +32,9 @@ export const DataLinks = (props: Props) => { Add links to existing fields. Links will be shown in log row details next to the field value.
-
- {value && - value.map((field, index) => { + {value && value.length > 0 && ( +
+ {value.map((field, index) => { return ( { /> ); })} -
-
-
+ )} + + ); }; diff --git a/public/app/plugins/datasource/elasticsearch/configuration/ElasticDetails.tsx b/public/app/plugins/datasource/elasticsearch/configuration/ElasticDetails.tsx index 2c26f3007db..0cc38d6f63a 100644 --- a/public/app/plugins/datasource/elasticsearch/configuration/ElasticDetails.tsx +++ b/public/app/plugins/datasource/elasticsearch/configuration/ElasticDetails.tsx @@ -34,7 +34,7 @@ export const ElasticDetails = (props: Props) => {
-
+
{ />
-
+
{
- - { - const maxConcurrentShardRequests = getMaxConcurrenShardRequestOrDefault( - value.jsonData.maxConcurrentShardRequests, - option.value! - ); - onChange({ - ...value, - jsonData: { - ...value.jsonData, - esVersion: option.value!, - maxConcurrentShardRequests, - }, - }); - }} - value={esVersions.find(version => version.value === value.jsonData.esVersion)} - /> - } - /> - + { + const maxConcurrentShardRequests = getMaxConcurrenShardRequestOrDefault( + value.jsonData.maxConcurrentShardRequests, + option.value! + ); + onChange({ + ...value, + jsonData: { + ...value.jsonData, + esVersion: option.value!, + maxConcurrentShardRequests, + }, + }); + }} + value={esVersions.find(version => version.value === value.jsonData.esVersion)} + /> + } + />
{value.jsonData.esVersion >= 56 && (
From 3d6380a0aa7c97673845ea9050c12dcc8468e07b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 4 Dec 2020 14:24:55 +0100 Subject: [PATCH 092/124] QueryGroup & DataSourceSrv & DataSourcePicker changes simplify usage, error handling and reduce duplication, support for uid (#29542) * Starting moving more stuff into data source picker * WIP progress * Progress on datasource picker rethink * Things are working now some details to figure out * Removed commented part * Complex work on getting data source lists * Fixed variable support showing correct data sources * Tried fixing dashboard import but failed * Fixes * Fixed import dashboard * Fixed unit test * Fixed explore test * Fixed test * Fix * fixed more tests * fixed more tests * fixed showing which option is default in picker * Changed query variable to use data source picker, updated tests and e2e * Fixed more tests * Updated snapshots, had wrong typescript version --- .../specs/variables/new-query-variable.ts | 3 +- packages/grafana-data/src/types/datasource.ts | 2 +- .../src/selectors/components.ts | 6 +- .../src/selectors/pages.ts | 4 +- .../src/services/dataSourceSrv.ts | 20 +- .../src/components/Layout/Layout.tsx | 4 +- .../src/components/Select/SingleValue.tsx | 9 +- pkg/api/frontendsettings.go | 1 - .../components/Select/DataSourcePicker.tsx | 125 +++++--- public/app/core/utils/richHistory.ts | 19 +- .../getAlertingValidationMessage.test.ts | 25 +- .../PanelEditor/PanelEditorQueries.tsx | 34 +-- .../app/features/explore/ExploreToolbar.tsx | 16 +- .../explore/TraceView/createSpanLink.test.ts | 12 +- .../explore/TraceView/createSpanLink.tsx | 4 +- public/app/features/explore/Wrapper.test.tsx | 6 +- .../explore/state/explorePane.test.ts | 34 +-- .../app/features/explore/state/explorePane.ts | 4 +- .../app/features/explore/state/selectors.ts | 15 - .../components/ImportDashboardForm.tsx | 11 +- .../components/ImportDashboardOverview.tsx | 3 +- .../manage-dashboards/state/actions.ts | 16 +- .../manage-dashboards/state/reducers.ts | 5 +- public/app/features/plugins/datasource_srv.ts | 173 ++++++----- .../plugins/specs/datasource_srv.test.ts | 274 +++++++++++------- .../query/components/QueryEditorRow.tsx | 30 +- .../query/components/QueryEditorRows.tsx | 13 +- .../features/query/components/QueryGroup.tsx | 200 +++++++------ .../query/components/QueryGroupOptions.tsx | 10 +- public/app/features/sandbox/TestStuffPage.tsx | 44 +-- .../features/variables/adhoc/actions.test.ts | 1 - .../variables/datasource/actions.test.ts | 7 - .../editor/VariableTextAreaField.tsx | 18 +- .../query/QueryVariableDatasourceSelect.tsx | 28 -- .../query/QueryVariableEditor.test.tsx | 7 +- .../variables/query/QueryVariableEditor.tsx | 33 ++- .../features/variables/query/actions.test.ts | 52 ++-- .../app/features/variables/query/actions.ts | 19 +- .../app/features/variables/query/reducer.ts | 3 +- .../features/variables/state/actions.test.ts | 11 +- .../variables/state/processVariable.test.ts | 55 ++-- .../elasticsearch/configuration/DataLink.tsx | 46 +-- .../loki/configuration/DerivedField.test.tsx | 20 +- .../loki/configuration/DerivedField.tsx | 52 +--- yarn.lock | 40 +-- 45 files changed, 707 insertions(+), 807 deletions(-) delete mode 100644 public/app/features/variables/query/QueryVariableDatasourceSelect.tsx diff --git a/e2e/suite1/specs/variables/new-query-variable.ts b/e2e/suite1/specs/variables/new-query-variable.ts index 3955481b03a..48485efb702 100644 --- a/e2e/suite1/specs/variables/new-query-variable.ts +++ b/e2e/suite1/specs/variables/new-query-variable.ts @@ -46,10 +46,9 @@ describe('Variables - Add variable', () => { e2e.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsDataSourceSelect() .should('be.visible') .within(select => { - e2e.components.Select.singleValue().should('have.text', ''); + e2e.components.Select.singleValue().should('have.text', 'gdev-testdata'); }); - e2e.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsQueryInput().should('not.exist'); e2e.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRefreshSelect() .should('be.visible') .within(select => { diff --git a/packages/grafana-data/src/types/datasource.ts b/packages/grafana-data/src/types/datasource.ts index f6cfcdb874f..88ea58cb799 100644 --- a/packages/grafana-data/src/types/datasource.ts +++ b/packages/grafana-data/src/types/datasource.ts @@ -567,6 +567,7 @@ export interface DataSourceInstanceSettings `Panel editor option pane tab ${title}`, }, diff --git a/packages/grafana-e2e-selectors/src/selectors/pages.ts b/packages/grafana-e2e-selectors/src/selectors/pages.ts index 2c753972131..1a238aff590 100644 --- a/packages/grafana-e2e-selectors/src/selectors/pages.ts +++ b/packages/grafana-e2e-selectors/src/selectors/pages.ts @@ -1,3 +1,5 @@ +import { Components } from './components'; + export const Pages = { Login: { url: '/login', @@ -87,7 +89,7 @@ export const Pages = { submitButton: 'Variable editor Submit button', }, QueryVariable: { - queryOptionsDataSourceSelect: 'Variable editor Form Query DataSource select', + queryOptionsDataSourceSelect: Components.DataSourcePicker.container, queryOptionsRefreshSelect: 'Variable editor Form Query Refresh select', queryOptionsRegExInput: 'Variable editor Form Query RegEx field', queryOptionsSortSelect: 'Variable editor Form Query Sort select', diff --git a/packages/grafana-runtime/src/services/dataSourceSrv.ts b/packages/grafana-runtime/src/services/dataSourceSrv.ts index 96758a0a4b2..1edc5b6f3b2 100644 --- a/packages/grafana-runtime/src/services/dataSourceSrv.ts +++ b/packages/grafana-runtime/src/services/dataSourceSrv.ts @@ -16,14 +16,9 @@ export interface DataSourceSrv { get(name?: string | null, scopedVars?: ScopedVars): Promise; /** - * Get all data sources + * Get a list of data sources */ - getAll(): DataSourceInstanceSettings[]; - - /** - * Get all data sources except for internal ones that usually should not be listed like mixed data source. - */ - getExternal(): DataSourceInstanceSettings[]; + getList(filters?: GetDataSourceListFilters): DataSourceInstanceSettings[]; /** * Get settings and plugin metadata by name or uid @@ -31,6 +26,17 @@ export interface DataSourceSrv { getInstanceSettings(nameOrUid: string | null | undefined): DataSourceInstanceSettings | undefined; } +/** @public */ +export interface GetDataSourceListFilters { + mixed?: boolean; + metrics?: boolean; + tracing?: boolean; + annotations?: boolean; + dashboard?: boolean; + variables?: boolean; + pluginId?: string; +} + let singletonInstance: DataSourceSrv; /** diff --git a/packages/grafana-ui/src/components/Layout/Layout.tsx b/packages/grafana-ui/src/components/Layout/Layout.tsx index ec2d6b36ff7..daebb434733 100644 --- a/packages/grafana-ui/src/components/Layout/Layout.tsx +++ b/packages/grafana-ui/src/components/Layout/Layout.tsx @@ -117,9 +117,11 @@ const getStyles = stylesFactory( ? 0 : `-${finalSpacing}`; + const label = orientation === Orientation.Vertical ? 'vertical-group' : 'horizontal-group'; + return { layout: css` - label: HorizontalGroup; + label: ${label}; display: flex; flex-direction: ${orientation === Orientation.Vertical ? 'column' : 'row'}; flex-wrap: ${wrap ? 'wrap' : 'nowrap'}; diff --git a/packages/grafana-ui/src/components/Select/SingleValue.tsx b/packages/grafana-ui/src/components/Select/SingleValue.tsx index 5a237b2456c..bbbb899c1b7 100644 --- a/packages/grafana-ui/src/components/Select/SingleValue.tsx +++ b/packages/grafana-ui/src/components/Select/SingleValue.tsx @@ -59,14 +59,7 @@ export const SingleValue = (props: Props) => { return ( -
+
{data.imgUrl ? ( ) : ( diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 914ac809291..b734f4dd78b 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -146,7 +146,6 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i if isDefault, _ := dsM["isDefault"].(bool); isDefault { defaultDS = n } - delete(dsM, "isDefault") meta := dsM["meta"].(*plugins.DataSourcePlugin) if meta.Preload { diff --git a/public/app/core/components/Select/DataSourcePicker.tsx b/public/app/core/components/Select/DataSourcePicker.tsx index a2e9eeb4dca..090ab2392ee 100644 --- a/public/app/core/components/Select/DataSourcePicker.tsx +++ b/public/app/core/components/Select/DataSourcePicker.tsx @@ -3,72 +3,119 @@ import React, { PureComponent } from 'react'; // Components import { HorizontalGroup, Select } from '@grafana/ui'; -import { SelectableValue, DataSourceSelectItem } from '@grafana/data'; +import { SelectableValue, DataSourceInstanceSettings } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { isUnsignedPluginSignature, PluginSignatureBadge } from '../../../features/plugins/PluginSignatureBadge'; +import { getDataSourceSrv } from '@grafana/runtime'; export interface Props { - onChange: (ds: DataSourceSelectItem) => void; - datasources: DataSourceSelectItem[]; - current?: DataSourceSelectItem | null; + onChange: (ds: DataSourceInstanceSettings) => void; + current: string | null; hideTextValue?: boolean; onBlur?: () => void; autoFocus?: boolean; openMenuOnFocus?: boolean; - showLoading?: boolean; placeholder?: string; - invalid?: boolean; + tracing?: boolean; + mixed?: boolean; + dashboard?: boolean; + metrics?: boolean; + annotations?: boolean; + variables?: boolean; + pluginId?: string; + noDefault?: boolean; } -export class DataSourcePicker extends PureComponent { +export interface State { + error?: string; +} + +export class DataSourcePicker extends PureComponent { + dataSourceSrv = getDataSourceSrv(); + static defaultProps: Partial = { autoFocus: false, openMenuOnFocus: false, placeholder: 'Select datasource', }; - searchInput: HTMLElement; + state: State = {}; constructor(props: Props) { super(props); } - onChange = (item: SelectableValue) => { - const ds = this.props.datasources.find(ds => ds.name === item.value); + componentDidMount() { + const { current } = this.props; + const dsSettings = this.dataSourceSrv.getInstanceSettings(current); + if (!dsSettings) { + this.setState({ error: 'Could not find data source ' + current }); + } + } - if (ds) { - this.props.onChange(ds); + onChange = (item: SelectableValue) => { + const dsSettings = this.dataSourceSrv.getInstanceSettings(item.value); + + if (dsSettings) { + this.props.onChange(dsSettings); + this.setState({ error: undefined }); } }; - render() { - const { - datasources, - current, - autoFocus, - hideTextValue, - onBlur, - openMenuOnFocus, - showLoading, - placeholder, - invalid, - } = this.props; + private getCurrentValue() { + const { current, hideTextValue, noDefault } = this.props; - const options = datasources.map(ds => ({ - value: ds.name, - label: ds.name, - imgUrl: ds.meta.info.logos.small, - meta: ds.meta, - })); + if (!current && noDefault) { + return null; + } - const value = current && { - label: current.name.substr(0, 37), - value: current.name, - imgUrl: current.meta.info.logos.small, - loading: showLoading, + const ds = this.dataSourceSrv.getInstanceSettings(current); + + if (ds) { + return { + label: ds.name.substr(0, 37), + value: ds.name, + imgUrl: ds.meta.info.logos.small, + hideText: hideTextValue, + meta: ds.meta, + }; + } + + return { + label: (current ?? 'no name') + ' - not found', + value: current, + imgUrl: '', hideText: hideTextValue, - meta: current.meta, }; + } + + getDataSourceOptions() { + const { tracing, metrics, mixed, dashboard, variables, annotations, pluginId } = this.props; + const options = this.dataSourceSrv + .getList({ + tracing, + metrics, + dashboard, + mixed, + variables, + annotations, + pluginId, + }) + .map(ds => ({ + value: ds.name, + label: `${ds.name}${ds.isDefault ? ' (default)' : ''}`, + imgUrl: ds.meta.info.logos.small, + meta: ds.meta, + })); + + return options; + } + + render() { + const { autoFocus, onBlur, openMenuOnFocus, placeholder } = this.props; + const { error } = this.state; + const options = this.getDataSourceOptions(); + const value = this.getCurrentValue(); return (
@@ -87,9 +134,9 @@ export class DataSourcePicker extends PureComponent { placeholder={placeholder} noOptionsMessage="No datasources found" value={value} - invalid={invalid} + invalid={!!error} getOptionLabel={o => { - if (isUnsignedPluginSignature(o.meta.signature) && o !== value) { + if (o.meta && isUnsignedPluginSignature(o.meta.signature) && o !== value) { return ( {o.label} @@ -103,5 +150,3 @@ export class DataSourcePicker extends PureComponent { ); } } - -export default DataSourcePicker; diff --git a/public/app/core/utils/richHistory.ts b/public/app/core/utils/richHistory.ts index 2ad5fb00f98..9ca3e60a699 100644 --- a/public/app/core/utils/richHistory.ts +++ b/public/app/core/utils/richHistory.ts @@ -5,11 +5,11 @@ import _ from 'lodash'; import { DataQuery, DataSourceApi, dateTimeFormat, AppEvents, urlUtil, ExploreUrlState } from '@grafana/data'; import appEvents from 'app/core/app_events'; import store from 'app/core/store'; -import { getExploreDatasources } from '../../features/explore/state/selectors'; // Types import { RichHistoryQuery } from 'app/types/explore'; import { serializeStateToUrlParam } from '@grafana/data/src/utils/url'; +import { getDataSourceSrv } from '@grafana/runtime'; const RICH_HISTORY_KEY = 'grafana.explore.richHistory'; @@ -275,22 +275,21 @@ export function mapQueriesToHeadings(query: RichHistoryQuery[], sortOrder: SortO * exploreDatasources add generic datasource image and add property isRemoved = true. */ export function createDatasourcesList(queriesDatasources: string[]) { - const exploreDatasources = getExploreDatasources(); const datasources: Array<{ label: string; value: string; imgUrl: string; isRemoved: boolean }> = []; - queriesDatasources.forEach(queryDsName => { - const index = exploreDatasources.findIndex(exploreDs => exploreDs.name === queryDsName); - if (index !== -1) { + queriesDatasources.forEach(dsName => { + const dsSettings = getDataSourceSrv().getInstanceSettings(dsName); + if (dsSettings) { datasources.push({ - label: queryDsName, - value: queryDsName, - imgUrl: exploreDatasources[index].meta.info.logos.small, + label: dsSettings.name, + value: dsSettings.name, + imgUrl: dsSettings.meta.info.logos.small, isRemoved: false, }); } else { datasources.push({ - label: queryDsName, - value: queryDsName, + label: dsName, + value: dsName, imgUrl: 'public/img/icn-datasource.svg', isRemoved: true, }); diff --git a/public/app/features/alerting/getAlertingValidationMessage.test.ts b/public/app/features/alerting/getAlertingValidationMessage.test.ts index e98c5c071a3..ed505319381 100644 --- a/public/app/features/alerting/getAlertingValidationMessage.test.ts +++ b/public/app/features/alerting/getAlertingValidationMessage.test.ts @@ -22,13 +22,10 @@ describe('getAlertingValidationMessage', () => { const getMock = jest.fn().mockResolvedValue(datasource); const datasourceSrv: DataSourceSrv = { get: getMock, - getExternal(): DataSourceInstanceSettings[] { + getList(): DataSourceInstanceSettings[] { return []; }, getInstanceSettings: (() => {}) as any, - getAll(): DataSourceInstanceSettings[] { - return []; - }, }; const targets: ElasticsearchQuery[] = [ { refId: 'A', query: '@hostname:$hostname', isLogsQuery: false }, @@ -66,10 +63,7 @@ describe('getAlertingValidationMessage', () => { return Promise.resolve(alertingDatasource); }, getInstanceSettings: (() => {}) as any, - getExternal(): DataSourceInstanceSettings[] { - return []; - }, - getAll(): DataSourceInstanceSettings[] { + getList(): DataSourceInstanceSettings[] { return []; }, }; @@ -96,10 +90,7 @@ describe('getAlertingValidationMessage', () => { const datasourceSrv: DataSourceSrv = { get: getMock, getInstanceSettings: (() => {}) as any, - getExternal(): DataSourceInstanceSettings[] { - return []; - }, - getAll(): DataSourceInstanceSettings[] { + getList(): DataSourceInstanceSettings[] { return []; }, }; @@ -128,10 +119,7 @@ describe('getAlertingValidationMessage', () => { const datasourceSrv: DataSourceSrv = { get: getMock, getInstanceSettings: (() => {}) as any, - getExternal(): DataSourceInstanceSettings[] { - return []; - }, - getAll(): DataSourceInstanceSettings[] { + getList(): DataSourceInstanceSettings[] { return []; }, }; @@ -160,10 +148,7 @@ describe('getAlertingValidationMessage', () => { const datasourceSrv: DataSourceSrv = { get: getMock, getInstanceSettings: (() => {}) as any, - getExternal(): DataSourceInstanceSettings[] { - return []; - }, - getAll(): DataSourceInstanceSettings[] { + getList(): DataSourceInstanceSettings[] { return []; }, }; diff --git a/public/app/features/dashboard/components/PanelEditor/PanelEditorQueries.tsx b/public/app/features/dashboard/components/PanelEditor/PanelEditorQueries.tsx index dfe8f45e0d1..f0f059fe786 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelEditorQueries.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelEditorQueries.tsx @@ -2,7 +2,6 @@ import React, { PureComponent } from 'react'; import { QueryGroup } from 'app/features/query/components/QueryGroup'; import { QueryGroupOptions } from 'app/features/query/components/QueryGroupOptions'; import { PanelModel } from '../../state'; -import { DataQuery, DataSourceSelectItem } from '@grafana/data'; import { getLocationSrv } from '@grafana/runtime'; interface Props { @@ -22,6 +21,10 @@ export class PanelEditorQueries extends PureComponent { buildQueryOptions({ panel }: Props): QueryGroupOptions { return { + dataSource: { + name: panel.datasource, + }, + queries: panel.targets, maxDataPoints: panel.maxDataPoints, minInterval: panel.interval, timeRange: { @@ -32,29 +35,10 @@ export class PanelEditorQueries extends PureComponent { }; } - onDataSourceChange = (ds: DataSourceSelectItem, queries: DataQuery[]) => { - const { panel } = this.props; - - panel.datasource = ds.value; - panel.targets = queries; - panel.refresh(); - - this.forceUpdate(); - }; - onRunQueries = () => { this.props.panel.refresh(); }; - onQueriesChange = (queries: DataQuery[]) => { - const { panel } = this.props; - - panel.targets = queries; - panel.refresh(); - - this.forceUpdate(); - }; - onOpenQueryInspector = () => { getLocationSrv().update({ query: { inspect: this.props.panel.id, inspectTab: 'query' }, @@ -62,9 +46,11 @@ export class PanelEditorQueries extends PureComponent { }); }; - onQueryOptionsChange = (options: QueryGroupOptions) => { + onOptionsChange = (options: QueryGroupOptions) => { const { panel } = this.props; + panel.datasource = options.dataSource.default ? null : options.dataSource.name!; + panel.targets = options.queries; panel.timeFrom = options.timeRange?.from; panel.timeShift = options.timeRange?.shift; panel.hideTimeOverride = options.timeRange?.hide; @@ -81,15 +67,11 @@ export class PanelEditorQueries extends PureComponent { return ( ); } diff --git a/public/app/features/explore/ExploreToolbar.tsx b/public/app/features/explore/ExploreToolbar.tsx index 2250c6de878..7d1726b5596 100644 --- a/public/app/features/explore/ExploreToolbar.tsx +++ b/public/app/features/explore/ExploreToolbar.tsx @@ -7,7 +7,7 @@ import { css } from 'emotion'; import { ExploreId, ExploreItemState } from 'app/types/explore'; import { Icon, IconButton, LegacyForms, SetInterval, Tooltip } from '@grafana/ui'; -import { DataQuery, RawTimeRange, TimeRange, TimeZone } from '@grafana/data'; +import { DataQuery, DataSourceInstanceSettings, RawTimeRange, TimeRange, TimeZone } from '@grafana/data'; import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker'; import { StoreState } from 'app/types/store'; import { createAndCopyShortLink } from 'app/core/utils/shortLinks'; @@ -24,7 +24,6 @@ import { LiveTailButton } from './LiveTailButton'; import { ResponsiveButton } from './ResponsiveButton'; import { RunButton } from './RunButton'; import { LiveTailControls } from './useLiveTailControls'; -import { getExploreDatasources } from './state/selectors'; import { setDashboardQueriesToUpdateOnLoad } from '../dashboard/state/reducers'; import { cancelQueries, clearQueries, runQueries } from './state/query'; @@ -81,8 +80,8 @@ interface DispatchProps { type Props = StateProps & DispatchProps & OwnProps; export class UnConnectedExploreToolbar extends PureComponent { - onChangeDatasource = async (option: { value: any }) => { - this.props.changeDatasource(this.props.exploreId, option.value, { importQueries: true }); + onChangeDatasource = async (dsSettings: DataSourceInstanceSettings) => { + this.props.changeDatasource(this.props.exploreId, dsSettings.name, { importQueries: true }); }; onClearAll = () => { @@ -141,12 +140,6 @@ export class UnConnectedExploreToolbar extends PureComponent { }); } - getSelectedDatasource = () => { - const { datasourceName } = this.props; - const exploreDatasources = getExploreDatasources(); - return datasourceName ? exploreDatasources.find(datasource => datasource.name === datasourceName) : undefined; - }; - render() { const { datasourceMissing, @@ -214,8 +207,7 @@ export class UnConnectedExploreToolbar extends PureComponent { >
diff --git a/public/app/features/explore/TraceView/createSpanLink.test.ts b/public/app/features/explore/TraceView/createSpanLink.test.ts index 46088b4e198..7ff974bb9b1 100644 --- a/public/app/features/explore/TraceView/createSpanLink.test.ts +++ b/public/app/features/explore/TraceView/createSpanLink.test.ts @@ -13,14 +13,8 @@ describe('createSpanLinkFactory', () => { it('returns undefined if there is no loki data source', () => { setDataSourceSrv({ - getExternal() { - return [ - { - meta: { - id: 'not loki', - }, - } as DataSourceInstanceSettings, - ]; + getList() { + return []; }, } as any); const splitOpenFn = jest.fn(); @@ -30,7 +24,7 @@ describe('createSpanLinkFactory', () => { it('creates correct link', () => { setDataSourceSrv({ - getExternal() { + getList() { return [ { name: 'loki1', diff --git a/public/app/features/explore/TraceView/createSpanLink.tsx b/public/app/features/explore/TraceView/createSpanLink.tsx index b9ac8dc5f7c..e71976c7e58 100644 --- a/public/app/features/explore/TraceView/createSpanLink.tsx +++ b/public/app/features/explore/TraceView/createSpanLink.tsx @@ -15,9 +15,7 @@ export function createSpanLinkFactory(splitOpenFn: (options: { datasourceUid: st } // Right now just hardcoded for first loki DS we can find - const lokiDs = getDataSourceSrv() - .getExternal() - .find(ds => ds.meta.id === 'loki'); + const lokiDs = getDataSourceSrv().getList({ pluginId: 'loki' })[0]; if (!lokiDs) { return undefined; diff --git a/public/app/features/explore/Wrapper.test.tsx b/public/app/features/explore/Wrapper.test.tsx index 104b5c0974d..ba1e8f1cfc3 100644 --- a/public/app/features/explore/Wrapper.test.tsx +++ b/public/app/features/explore/Wrapper.test.tsx @@ -220,10 +220,12 @@ function setup(options?: SetupOptions): { datasources: { [name: string]: DataSou const dsSettings = options?.datasources || defaultDatasources; setDataSourceSrv({ - getExternal(): DataSourceInstanceSettings[] { + getList(): DataSourceInstanceSettings[] { return dsSettings.map(d => d.settings); }, - + getInstanceSettings(name: string) { + return dsSettings.map(d => d.settings).find(x => x.name === name); + }, get(name?: string | null, scopedVars?: ScopedVars): Promise { return Promise.resolve((name ? dsSettings.find(d => d.api.name === name) : dsSettings[0])!.api); }, diff --git a/public/app/features/explore/state/explorePane.test.ts b/public/app/features/explore/state/explorePane.test.ts index c3b99c513bf..5856c202898 100644 --- a/public/app/features/explore/state/explorePane.test.ts +++ b/public/app/features/explore/state/explorePane.test.ts @@ -10,26 +10,9 @@ import { refreshExplore, } from './explorePane'; import { setQueriesAction } from './query'; -import * as DatasourceSrv from 'app/features/plugins/datasource_srv'; import { makeExplorePaneState, makeInitialUpdateState } from './utils'; import { reducerTester } from '../../../../test/core/redux/reducerTester'; - -jest.mock('app/features/plugins/datasource_srv'); -const getDatasourceSrvMock = (DatasourceSrv.getDatasourceSrv as any) as jest.Mock; - -beforeEach(() => { - getDatasourceSrvMock.mockClear(); - getDatasourceSrvMock.mockImplementation( - () => - ({ - getExternal: jest.fn().mockReturnValue([]), - get: jest.fn().mockReturnValue({ - testDatasource: jest.fn(), - init: jest.fn(), - }), - } as any) - ); -}); +import { setDataSourceSrv } from '@grafana/runtime'; jest.mock('../../dashboard/services/TimeSrv', () => ({ getTimeSrv: jest.fn().mockReturnValue({ @@ -47,6 +30,21 @@ const testRange = { }, }; +setDataSourceSrv({ + getList() { + return []; + }, + getInstanceSettings(name: string) { + return { name: 'hello' }; + }, + get() { + return Promise.resolve({ + testDatasource: jest.fn(), + init: jest.fn(), + }); + }, +} as any); + const setup = (updateOverides?: Partial) => { const exploreId = ExploreId.left; const containerWidth = 1920; diff --git a/public/app/features/explore/state/explorePane.ts b/public/app/features/explore/state/explorePane.ts index 17e2f5dafa0..980d3cfb0c9 100644 --- a/public/app/features/explore/state/explorePane.ts +++ b/public/app/features/explore/state/explorePane.ts @@ -32,7 +32,7 @@ import { serializeStateToUrlParam } from '@grafana/data/src/utils/url'; import { runQueries, setQueriesAction } from './query'; import { updateTime } from './time'; import { toRawTimeRange } from '../utils/time'; -import { getExploreDatasources } from './selectors'; +import { getDataSourceSrv } from '@grafana/runtime'; // // Actions and Payloads @@ -131,7 +131,7 @@ export function initializeExplore( originPanelId?: number | null ): ThunkResult { return async (dispatch, getState) => { - const exploreDatasources = getExploreDatasources(); + const exploreDatasources = getDataSourceSrv().getList(); let instance = undefined; let history: HistoryItem[] = []; diff --git a/public/app/features/explore/state/selectors.ts b/public/app/features/explore/state/selectors.ts index f14c6ad7e7d..32e097b4f62 100644 --- a/public/app/features/explore/state/selectors.ts +++ b/public/app/features/explore/state/selectors.ts @@ -1,8 +1,6 @@ import { createSelector } from 'reselect'; import { ExploreItemState } from 'app/types'; import { filterLogLevels, dedupLogRows } from 'app/core/logs_model'; -import { getDatasourceSrv } from '../../plugins/datasource_srv'; -import { DataSourceSelectItem } from '@grafana/data'; const logsRowsSelector = (state: ExploreItemState) => state.logsResult && state.logsResult.rows; const hiddenLogLevelsSelector = (state: ExploreItemState) => state.hiddenLogLevels; @@ -19,16 +17,3 @@ export const deduplicatedRowsSelector = createSelector( return dedupLogRows(filteredRows, dedupStrategy); } ); - -export const getExploreDatasources = (): DataSourceSelectItem[] => { - return getDatasourceSrv() - .getExternal() - .map( - (ds: any) => - ({ - value: ds.name, - name: ds.name, - meta: ds.meta, - } as DataSourceSelectItem) - ); -}; diff --git a/public/app/features/manage-dashboards/components/ImportDashboardForm.tsx b/public/app/features/manage-dashboards/components/ImportDashboardForm.tsx index 0262ab2b186..dc93b5effb0 100644 --- a/public/app/features/manage-dashboards/components/ImportDashboardForm.tsx +++ b/public/app/features/manage-dashboards/components/ImportDashboardForm.tsx @@ -11,11 +11,11 @@ import { Legend, } from '@grafana/ui'; import { FolderPicker } from 'app/core/components/Select/FolderPicker'; -import DataSourcePicker from 'app/core/components/Select/DataSourcePicker'; +import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker'; import { DashboardInput, DashboardInputs, DataSourceInput, ImportDashboardDTO } from '../state/reducers'; import { validateTitle, validateUid } from '../utils/validation'; -interface Props extends Omit, 'formState' | 'watch'> { +interface Props extends Omit, 'formState'> { uidReset: boolean; inputs: DashboardInputs; initialFolderId: number; @@ -36,8 +36,10 @@ export const ImportDashboardForm: FC = ({ onUidReset, onCancel, onSubmit, + watch, }) => { const [isSubmitted, setSubmitted] = useState(false); + const watchDataSources = watch('dataSources'); /* This useEffect is needed for overwriting a dashboard. It @@ -96,6 +98,7 @@ export const ImportDashboardForm: FC = ({ {inputs.dataSources && inputs.dataSources.map((input: DataSourceInput, index: number) => { const dataSourceOption = `dataSources[${index}]`; + const current = watchDataSources ?? []; return ( = ({ > { validateFieldsOnMount={['title', 'uid']} validateOn="onChange" > - {({ register, errors, control, getValues }) => ( + {({ register, errors, control, watch, getValues }) => ( { onCancel={this.onCancel} onUidReset={this.onUidReset} onSubmit={this.onSubmit} + watch={watch} initialFolderId={folder.id} /> )} diff --git a/public/app/features/manage-dashboards/state/actions.ts b/public/app/features/manage-dashboards/state/actions.ts index 1033ea18ffe..fd71b3582d6 100644 --- a/public/app/features/manage-dashboards/state/actions.ts +++ b/public/app/features/manage-dashboards/state/actions.ts @@ -1,6 +1,5 @@ -import { AppEvents, DataSourceInstanceSettings, DataSourceSelectItem, locationUtil } from '@grafana/data'; +import { AppEvents, DataSourceInstanceSettings, locationUtil } from '@grafana/data'; import { getBackendSrv } from 'app/core/services/backend_srv'; -import config from 'app/core/config'; import { clearDashboard, setInputs, @@ -13,6 +12,7 @@ import { updateLocation } from 'app/core/actions'; import { ThunkResult, FolderInfo, DashboardDTO, DashboardDataDTO } from 'app/types'; import { appEvents } from '../../../core/core'; import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher'; +import { getDataSourceSrv } from '@grafana/runtime'; export function fetchGcomDashboard(id: string): ThunkResult { return async dispatch => { @@ -73,13 +73,13 @@ export function importDashboard(importDashboardForm: ImportDashboardDTO): ThunkR const inputs = getState().importDashboard.inputs; let inputsToPersist = [] as any[]; - importDashboardForm.dataSources?.forEach((dataSource: DataSourceSelectItem, index: number) => { + importDashboardForm.dataSources?.forEach((dataSource: DataSourceInstanceSettings, index: number) => { const input = inputs.dataSources[index]; inputsToPersist.push({ name: input.name, type: input.type, pluginId: input.pluginId, - value: dataSource.value, + value: dataSource.name, }); }); @@ -105,19 +105,13 @@ export function importDashboard(importDashboardForm: ImportDashboardDTO): ThunkR } const getDataSourceOptions = (input: { pluginId: string; pluginName: string }, inputModel: any) => { - const sources = Object.values(config.datasources).filter( - (val: DataSourceInstanceSettings) => val.type === input.pluginId - ); + const sources = getDataSourceSrv().getList({ pluginId: input.pluginId }); if (sources.length === 0) { inputModel.info = 'No data sources of type ' + input.pluginName + ' found'; } else if (!inputModel.info) { inputModel.info = 'Select a ' + input.pluginName + ' data source'; } - - inputModel.options = sources.map(val => { - return { name: val.name, value: val.name, meta: val.meta }; - }); }; export function moveDashboards(dashboardUids: string[], toFolder: FolderInfo) { diff --git a/public/app/features/manage-dashboards/state/reducers.ts b/public/app/features/manage-dashboards/state/reducers.ts index ee23a8bf380..724847f00a1 100644 --- a/public/app/features/manage-dashboards/state/reducers.ts +++ b/public/app/features/manage-dashboards/state/reducers.ts @@ -1,5 +1,5 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { DataSourceSelectItem } from '@grafana/data'; +import { DataSourceInstanceSettings } from '@grafana/data'; export enum DashboardSource { Gcom = 0, @@ -11,7 +11,7 @@ export interface ImportDashboardDTO { uid: string; gnetId: string; constants: string[]; - dataSources: DataSourceSelectItem[]; + dataSources: DataSourceInstanceSettings[]; folder: { id: number; title?: string }; } @@ -30,7 +30,6 @@ export interface DashboardInput { export interface DataSourceInput extends DashboardInput { pluginId: string; - options: DataSourceSelectItem[]; } export interface DashboardInputs { diff --git a/public/app/features/plugins/datasource_srv.ts b/public/app/features/plugins/datasource_srv.ts index 76d86c6000d..e82ed4f9313 100644 --- a/public/app/features/plugins/datasource_srv.ts +++ b/public/app/features/plugins/datasource_srv.ts @@ -1,9 +1,9 @@ // Libraries -import sortBy from 'lodash/sortBy'; import coreModule from 'app/core/core_module'; // Services & Utils import { importDataSourcePlugin } from './plugin_loader'; import { + GetDataSourceListFilters, DataSourceSrv as DataSourceService, getDataSourceSrv as getDataSourceService, TemplateSrv, @@ -15,6 +15,7 @@ import { GrafanaRootScope } from 'app/routes/GrafanaCtrl'; // Pretend Datasource import { expressionDatasource } from 'app/features/expressions/ExpressionDatasource'; import { DataSourceVariableModel } from '../variables/types'; +import { cloneDeep } from 'lodash'; export class DatasourceSrv implements DataSourceService { private datasources: Record = {}; @@ -49,6 +50,20 @@ export class DatasourceSrv implements DataSourceService { return this.settingsMapByName[this.defaultName]; } + // Complex logic to support template variable data source names + // For this we just pick the current or first data source in the variable + if (nameOrUid[0] === '$') { + const interpolatedName = this.templateSrv.replace(nameOrUid, {}, variableInterpolation); + const dsSettings = this.settingsMapByUid[interpolatedName] ?? this.settingsMapByName[interpolatedName]; + if (!dsSettings) { + return undefined; + } + // The return name or uid needs preservet string containing the variable + const clone = cloneDeep(dsSettings); + clone.name = nameOrUid; + return clone; + } + return this.settingsMapByUid[nameOrUid] ?? this.settingsMapByName[nameOrUid]; } @@ -69,12 +84,7 @@ export class DatasourceSrv implements DataSourceService { } // Interpolation here is to support template variable in data source selection - nameOrUid = this.templateSrv.replace(nameOrUid, scopedVars, (value: any[]) => { - if (Array.isArray(value)) { - return value[0]; - } - return value; - }); + nameOrUid = this.templateSrv.replace(nameOrUid, scopedVars, variableInterpolation); if (nameOrUid === 'default') { return this.get(this.defaultName); @@ -130,88 +140,109 @@ export class DatasourceSrv implements DataSourceService { return Object.values(this.settingsMapByName); } - getExternal(): DataSourceInstanceSettings[] { - const datasources = this.getAll().filter(ds => !ds.meta.builtIn); - return sortBy(datasources, ['name']); - } - - getAnnotationSources() { - const sources: any[] = []; - - this.addDataSourceVariables(sources); - - Object.values(this.settingsMapByName).forEach(value => { - if (value.meta?.annotations) { - sources.push(value); + getList(filters: GetDataSourceListFilters = {}): DataSourceInstanceSettings[] { + const base = Object.values(this.settingsMapByName).filter(x => { + if (x.meta.id === 'grafana' || x.meta.id === 'mixed' || x.meta.id === 'dashboard') { + return false; } + if (filters.metrics && !x.meta.metrics) { + return false; + } + if (filters.tracing && !x.meta.tracing) { + return false; + } + if (filters.annotations && !x.meta.annotations) { + return false; + } + if (filters.pluginId && x.meta.id !== filters.pluginId) { + return false; + } + return true; }); - return sources; - } + if (filters.variables) { + for (const variable of this.templateSrv.getVariables().filter(variable => variable.type === 'datasource')) { + const dsVar = variable as DataSourceVariableModel; + const first = dsVar.current.value === 'default' ? this.defaultName : dsVar.current.value; + const dsName = (first as unknown) as string; + const dsSettings = this.settingsMapByName[dsName]; - getMetricSources(options?: { skipVariables?: boolean }) { - const metricSources: DataSourceSelectItem[] = []; - - Object.entries(this.settingsMapByName).forEach(([key, value]) => { - if (value.meta?.metrics) { - let metricSource: DataSourceSelectItem = { value: key, name: key, meta: value.meta, sort: key }; - - //Make sure grafana and mixed are sorted at the bottom - if (value.meta.id === 'grafana') { - metricSource.sort = String.fromCharCode(253); - } else if (value.meta.id === 'dashboard') { - metricSource.sort = String.fromCharCode(254); - } else if (value.meta.id === 'mixed') { - metricSource.sort = String.fromCharCode(255); - } - - metricSources.push(metricSource); - - if (key === this.defaultName) { - metricSource = { value: null, name: 'default', meta: value.meta, sort: key }; - metricSources.push(metricSource); + if (dsSettings) { + const key = `$\{${variable.name}\}`; + base.push({ + ...dsSettings, + name: key, + }); } } - }); - - if (!options || !options.skipVariables) { - this.addDataSourceVariables(metricSources); } - metricSources.sort((a, b) => { - if (a.sort.toLowerCase() > b.sort.toLowerCase()) { + const sorted = base.sort((a, b) => { + if (a.name.toLowerCase() > b.name.toLowerCase()) { return 1; } - if (a.sort.toLowerCase() < b.sort.toLowerCase()) { + if (a.name.toLowerCase() < b.name.toLowerCase()) { return -1; } return 0; }); - return metricSources; + if (!filters.pluginId) { + if (filters.mixed) { + base.push(this.getInstanceSettings('-- Mixed --')!); + } + + if (filters.dashboard) { + base.push(this.getInstanceSettings('-- Dashboard --')!); + } + + if (!filters.tracing) { + base.push(this.getInstanceSettings('-- Grafana --')!); + } + } + + return sorted; } - addDataSourceVariables(list: any[]) { - // look for data source variables - this.templateSrv - .getVariables() - .filter(variable => variable.type === 'datasource') - .forEach((variable: DataSourceVariableModel) => { - const first = variable.current.value === 'default' ? this.defaultName : variable.current.value; - const index = (first as unknown) as string; - const ds = this.settingsMapByName[index]; - - if (ds) { - const key = `$${variable.name}`; - list.push({ - name: key, - value: key, - meta: ds.meta, - sort: key, - }); - } - }); + /** + * @deprecated use getList + * */ + getExternal(): DataSourceInstanceSettings[] { + return this.getList(); } + + /** + * @deprecated use getList + * */ + getAnnotationSources() { + return this.getList({ annotations: true, variables: true }).map(x => { + return { + name: x.name, + value: x.isDefault ? null : x.name, + meta: x.meta, + }; + }); + } + + /** + * @deprecated use getList + * */ + getMetricSources(options?: { skipVariables?: boolean }): DataSourceSelectItem[] { + return this.getList({ metrics: true, variables: !options?.skipVariables }).map(x => { + return { + name: x.name, + value: x.isDefault ? null : x.name, + meta: x.meta, + }; + }); + } +} + +export function variableInterpolation(value: any[]) { + if (Array.isArray(value)) { + return value[0]; + } + return value; } export const getDatasourceSrv = (): DatasourceSrv => { diff --git a/public/app/features/plugins/specs/datasource_srv.test.ts b/public/app/features/plugins/specs/datasource_srv.test.ts index ccd3e631394..8817efa358f 100644 --- a/public/app/features/plugins/specs/datasource_srv.test.ts +++ b/public/app/features/plugins/specs/datasource_srv.test.ts @@ -1,6 +1,6 @@ import 'app/features/plugins/datasource_srv'; import { DatasourceSrv } from 'app/features/plugins/datasource_srv'; -import { DataSourceInstanceSettings, DataSourcePlugin, DataSourcePluginMeta, PluginMeta } from '@grafana/data'; +import { DataSourceInstanceSettings, DataSourcePlugin } from '@grafana/data'; // Datasource variable $datasource with current value 'BBB' const templateSrv: any = { @@ -13,7 +13,9 @@ const templateSrv: any = { }, }, ], - replace: (v: string) => v, + replace: (v: string) => { + return v.replace('${datasource}', 'BBB'); + }, }; class TestDataSource { @@ -27,120 +29,184 @@ jest.mock('../plugin_loader', () => ({ })); describe('datasource_srv', () => { - const _datasourceSrv = new DatasourceSrv({} as any, {} as any, templateSrv); - const datasources = { - buildIn: { - id: 1, - uid: '1', - type: 'b', - name: 'buildIn', - meta: { builtIn: true } as DataSourcePluginMeta, - jsonData: {}, + const dataSourceSrv = new DatasourceSrv({} as any, {} as any, templateSrv); + const dataSourceInit = { + mmm: { + type: 'test-db', + name: 'mmm', + uid: 'uid-code-mmm', + meta: { metrics: true, annotations: true } as any, }, - external1: { - id: 2, - uid: '2', - type: 'e', - name: 'external1', - meta: { builtIn: false } as DataSourcePluginMeta, - jsonData: {}, + '-- Grafana --': { + type: 'grafana', + name: '-- Grafana --', + meta: { builtIn: true, metrics: true, id: 'grafana' }, }, - external2: { - id: 3, - uid: '3', - type: 'e2', - name: 'external2', - meta: {} as PluginMeta, - jsonData: {}, + '-- Dashboard --': { + type: 'dashboard', + name: '-- Dashboard --', + meta: { builtIn: true, metrics: true, id: 'dashboard' }, + }, + '-- Mixed --': { + type: 'test-db', + name: '-- Mixed --', + meta: { builtIn: true, metrics: true, id: 'mixed' }, + }, + ZZZ: { + type: 'test-db', + name: 'ZZZ', + uid: 'uid-code-ZZZ', + meta: { metrics: true }, + }, + aaa: { + type: 'test-db', + name: 'aaa', + uid: 'uid-code-aaa', + meta: { metrics: true }, + }, + BBB: { + type: 'test-db', + name: 'BBB', + uid: 'uid-code-BBB', + meta: { metrics: true }, + }, + Jaeger: { + type: 'jaeger-db', + name: 'Jaeger', + uid: 'uid-code-Jaeger', + meta: { tracing: true, id: 'jaeger' }, }, }; - beforeEach(() => { - _datasourceSrv.init(datasources, 'external1'); - }); - - describe('when getting data source class instance', () => { - it('should load plugin and create instance and set meta', async () => { - const ds = (await _datasourceSrv.get('external1')) as any; - expect(ds.meta).toBe(datasources.external1.meta); - expect(ds.instanceSettings).toBe(datasources.external1); - - // validate that it caches instance - const ds2 = await _datasourceSrv.get('external1'); - expect(ds).toBe(ds2); - }); - - it('should be able to load data source using uid as well', async () => { - const dsByUid = await _datasourceSrv.get('2'); - const dsByName = await _datasourceSrv.get('external1'); - expect(dsByUid.meta).toBe(datasources.external1.meta); - expect(dsByUid).toBe(dsByName); - }); - }); - - describe('when getting external metric sources', () => { - it('should return list of explore sources', () => { - const externalSources = _datasourceSrv.getExternal(); - expect(externalSources.length).toBe(2); - expect(externalSources[0].name).toBe('external1'); - expect(externalSources[1].name).toBe('external2'); - }); - }); - - describe('when loading metric sources', () => { - let metricSources: any; - + describe('Given a list of data sources', () => { beforeEach(() => { - _datasourceSrv.init( - { - mmm: { - type: 'test-db', - meta: { metrics: true } as any, - }, - '--Grafana--': { - type: 'grafana', - meta: { builtIn: true, metrics: true, id: 'grafana' }, - }, - '--Mixed--': { - type: 'test-db', - meta: { builtIn: true, metrics: true, id: 'mixed' }, - }, - ZZZ: { - type: 'test-db', - meta: { metrics: true }, - }, - aaa: { - type: 'test-db', - meta: { metrics: true }, - }, - BBB: { - type: 'test-db', - meta: { metrics: true }, - }, - } as any, - 'BBB' - ); - metricSources = _datasourceSrv.getMetricSources({}); + dataSourceSrv.init(dataSourceInit as any, 'BBB'); }); - it('should return a list of sources sorted case insensitively with builtin sources last', () => { - expect(metricSources[1].name).toBe('aaa'); - expect(metricSources[2].name).toBe('BBB'); - expect(metricSources[3].name).toBe('default'); - expect(metricSources[4].name).toBe('mmm'); - expect(metricSources[5].name).toBe('ZZZ'); - expect(metricSources[6].name).toBe('--Grafana--'); - expect(metricSources[7].name).toBe('--Mixed--'); + describe('when getting data source class instance', () => { + it('should load plugin and create instance and set meta', async () => { + const ds = (await dataSourceSrv.get('mmm')) as any; + expect(ds.meta).toBe(dataSourceInit.mmm.meta); + expect(ds.instanceSettings).toBe(dataSourceInit.mmm); + + // validate that it caches instance + const ds2 = await dataSourceSrv.get('mmm'); + expect(ds).toBe(ds2); + }); + + it('should be able to load data source using uid as well', async () => { + const dsByUid = await dataSourceSrv.get('uid-code-mmm'); + const dsByName = await dataSourceSrv.get('mmm'); + expect(dsByUid.meta).toBe(dsByName.meta); + expect(dsByUid).toBe(dsByName); + }); }); - it('should set default data source', () => { - expect(metricSources[3].name).toBe('default'); - expect(metricSources[3].sort).toBe('BBB'); + describe('when getting instance settings', () => { + it('should work by name or uid', () => { + expect(dataSourceSrv.getInstanceSettings('mmm')).toBe(dataSourceSrv.getInstanceSettings('uid-code-mmm')); + }); + + it('should work with variable', () => { + const ds = dataSourceSrv.getInstanceSettings('${datasource}'); + expect(ds?.name).toBe('${datasource}'); + expect(ds?.uid).toBe('uid-code-BBB'); + }); }); - it('should set default inject the variable datasources', () => { - expect(metricSources[0].name).toBe('$datasource'); - expect(metricSources[0].sort).toBe('$datasource'); + describe('when getting external metric sources', () => { + it('should return list of explore sources', () => { + const externalSources = dataSourceSrv.getExternal(); + expect(externalSources.length).toBe(6); + }); + }); + + it('Can get list of data sources with variables: true', () => { + const list = dataSourceSrv.getList({ metrics: true, variables: true }); + expect(list[0].name).toBe('${datasource}'); + }); + + it('Can get list of data sources with tracing: true', () => { + const list = dataSourceSrv.getList({ tracing: true }); + expect(list[0].name).toBe('Jaeger'); + }); + + it('Can get list of data sources with annotation: true', () => { + const list = dataSourceSrv.getList({ annotations: true }); + expect(list[0].name).toBe('mmm'); + }); + + it('Can get get list and filter by pluginId', () => { + const list = dataSourceSrv.getList({ pluginId: 'jaeger' }); + expect(list[0].name).toBe('Jaeger'); + expect(list.length).toBe(1); + }); + + it('Can get list of data sources with metrics: true, builtIn: true, mixed: true', () => { + expect(dataSourceSrv.getList({ metrics: true, dashboard: true, mixed: true })).toMatchInlineSnapshot(` + Array [ + Object { + "meta": Object { + "metrics": true, + }, + "name": "aaa", + "type": "test-db", + "uid": "uid-code-aaa", + }, + Object { + "meta": Object { + "metrics": true, + }, + "name": "BBB", + "type": "test-db", + "uid": "uid-code-BBB", + }, + Object { + "meta": Object { + "annotations": true, + "metrics": true, + }, + "name": "mmm", + "type": "test-db", + "uid": "uid-code-mmm", + }, + Object { + "meta": Object { + "metrics": true, + }, + "name": "ZZZ", + "type": "test-db", + "uid": "uid-code-ZZZ", + }, + Object { + "meta": Object { + "builtIn": true, + "id": "mixed", + "metrics": true, + }, + "name": "-- Mixed --", + "type": "test-db", + }, + Object { + "meta": Object { + "builtIn": true, + "id": "dashboard", + "metrics": true, + }, + "name": "-- Dashboard --", + "type": "dashboard", + }, + Object { + "meta": Object { + "builtIn": true, + "id": "grafana", + "metrics": true, + }, + "name": "-- Grafana --", + "type": "grafana", + }, + ] + `); }); }); }); diff --git a/public/app/features/query/components/QueryEditorRow.tsx b/public/app/features/query/components/QueryEditorRow.tsx index ae3423136c4..e0af8a410c5 100644 --- a/public/app/features/query/components/QueryEditorRow.tsx +++ b/public/app/features/query/components/QueryEditorRow.tsx @@ -16,6 +16,7 @@ import { TimeRange, toLegacyResponseData, EventBusExtended, + DataSourceInstanceSettings, } from '@grafana/data'; import { QueryEditorRowTitle } from './QueryEditorRowTitle'; import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOperationRow'; @@ -27,8 +28,7 @@ import { PanelModel } from 'app/features/dashboard/state'; interface Props { data: PanelData; query: DataQuery; - dataSourceValue: string | null; - inMixedMode?: boolean; + dsSettings: DataSourceInstanceSettings; id: string; index: number; onAddQuery: (query?: DataQuery) => void; @@ -38,7 +38,7 @@ interface Props { } interface State { - loadedDataSourceValue: string | null | undefined; + loadedDataSourceIdentifier?: string | null; datasource: DataSourceApi | null; hasTextEditMode: boolean; data?: PanelData; @@ -52,7 +52,6 @@ export class QueryEditorRow extends PureComponent { state: State = { datasource: null, - loadedDataSourceValue: undefined, hasTextEditMode: false, data: undefined, isOpen: true, @@ -89,27 +88,31 @@ export class QueryEditorRow extends PureComponent { }; } + getQueryDataSourceIdentifier(): string | null | undefined { + const { query, dsSettings } = this.props; + return dsSettings.meta.mixed ? query.datasource : dsSettings.uid; + } + async loadDatasource() { - const { query, dataSourceValue } = this.props; const dataSourceSrv = getDatasourceSrv(); - let datasource; + let datasource: DataSourceApi; + const dataSourceIdentifier = this.getQueryDataSourceIdentifier(); try { - const datasourceName = dataSourceValue || query.datasource; - datasource = await dataSourceSrv.get(datasourceName); + datasource = await dataSourceSrv.get(dataSourceIdentifier); } catch (error) { datasource = await dataSourceSrv.get(); } this.setState({ datasource, - loadedDataSourceValue: this.props.dataSourceValue, + loadedDataSourceIdentifier: dataSourceIdentifier, hasTextEditMode: _.has(datasource, 'components.QueryCtrl.prototype.toggleEditorMode'), }); } componentDidUpdate(prevProps: Props) { - const { loadedDataSourceValue } = this.state; + const { datasource, loadedDataSourceIdentifier } = this.state; const { data, query } = this.props; if (data !== prevProps.data) { @@ -125,7 +128,7 @@ export class QueryEditorRow extends PureComponent { } // check if we need to load another datasource - if (loadedDataSourceValue !== this.props.dataSourceValue) { + if (datasource && loadedDataSourceIdentifier !== this.getQueryDataSourceIdentifier()) { if (this.angularQueryEditor) { this.angularQueryEditor.destroy(); this.angularQueryEditor = null; @@ -137,6 +140,7 @@ export class QueryEditorRow extends PureComponent { if (!this.element || this.angularQueryEditor) { return; } + this.renderAngularQueryEditor(); } @@ -259,14 +263,14 @@ export class QueryEditorRow extends PureComponent { }; renderTitle = (props: { isOpen: boolean; openRow: () => void }) => { - const { query, inMixedMode } = this.props; + const { query, dsSettings } = this.props; const { datasource } = this.state; const isDisabled = query.hide; return ( this.onToggleEditMode(e, props)} diff --git a/public/app/features/query/components/QueryEditorRows.tsx b/public/app/features/query/components/QueryEditorRows.tsx index 09d43dcacef..55d9e9805b4 100644 --- a/public/app/features/query/components/QueryEditorRows.tsx +++ b/public/app/features/query/components/QueryEditorRows.tsx @@ -2,14 +2,14 @@ import React, { PureComponent } from 'react'; // Types -import { DataQuery, PanelData, DataSourceSelectItem } from '@grafana/data'; +import { DataQuery, DataSourceInstanceSettings, PanelData } from '@grafana/data'; import { QueryEditorRow } from './QueryEditorRow'; import { DragDropContext, Droppable, DropResult } from 'react-beautiful-dnd'; interface Props { // The query configuration queries: DataQuery[]; - datasource: DataSourceSelectItem; + dsSettings: DataSourceInstanceSettings; // Query editing onQueriesChange: (queries: DataQuery[]) => void; @@ -67,7 +67,7 @@ export class QueryEditorRows extends PureComponent { }; render() { - const { props } = this; + const { dsSettings, data, queries } = this.props; return ( @@ -75,19 +75,18 @@ export class QueryEditorRows extends PureComponent { {provided => { return (
- {props.queries.map((query, index) => ( + {queries.map((query, index) => ( this.onChangeQuery(query, index)} onRemoveQuery={this.onRemoveQuery} onAddQuery={this.props.onAddQuery} onRunQuery={this.props.onRunQueries} - inMixedMode={props.datasource.meta.mixed} /> ))} {provided.placeholder} diff --git a/public/app/features/query/components/QueryGroup.tsx b/public/app/features/query/components/QueryGroup.tsx index 14086ec55a9..8c4c5b65089 100644 --- a/public/app/features/query/components/QueryGroup.tsx +++ b/public/app/features/query/components/QueryGroup.tsx @@ -2,21 +2,20 @@ import React, { PureComponent } from 'react'; // Components import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker'; -import { Button, CustomScrollbar, HorizontalGroup, Modal, stylesFactory, Field } from '@grafana/ui'; +import { Button, CustomScrollbar, HorizontalGroup, Modal, stylesFactory } from '@grafana/ui'; import { getDataSourceSrv } from '@grafana/runtime'; import { QueryEditorRows } from './QueryEditorRows'; // Services -import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { backendSrv } from 'app/core/services/backend_srv'; import config from 'app/core/config'; // Types import { DataQuery, - DataSourceSelectItem, DefaultTimeRange, LoadingState, PanelData, DataSourceApi, + DataSourceInstanceSettings, } from '@grafana/data'; import { PluginHelp } from 'app/core/components/PluginHelp/PluginHelp'; import { addQuery } from 'app/core/utils/query'; @@ -30,20 +29,15 @@ import { css } from 'emotion'; interface Props { queryRunner: PanelQueryRunner; - queries: DataQuery[]; - dataSourceName: string | null; options: QueryGroupOptions; onOpenQueryInspector?: () => void; onRunQueries: () => void; - onQueriesChange: (queries: DataQuery[]) => void; - onDataSourceChange: (ds: DataSourceSelectItem, queries: DataQuery[]) => void; onOptionsChange: (options: QueryGroupOptions) => void; } interface State { dataSource?: DataSourceApi; - dataSourceItem: DataSourceSelectItem; - dataSourceError?: string; + dsSettings?: DataSourceInstanceSettings; helpContent: React.ReactNode; isLoadingHelp: boolean; isPickerOpen: boolean; @@ -54,13 +48,12 @@ interface State { } export class QueryGroup extends PureComponent { - datasources: DataSourceSelectItem[] = getDatasourceSrv().getMetricSources(); backendSrv = backendSrv; + dataSourceSrv = getDataSourceSrv(); querySubscription: Unsubscribable | null; state: State = { isLoadingHelp: false, - dataSourceItem: this.findCurrentDataSource(this.props.dataSourceName), helpContent: null, isPickerOpen: false, isAddingMixed: false, @@ -74,19 +67,18 @@ export class QueryGroup extends PureComponent { }; async componentDidMount() { - const { queryRunner, dataSourceName: datasourceName } = this.props; + const { queryRunner, options } = this.props; this.querySubscription = queryRunner.getData({ withTransforms: false, withFieldConfig: false }).subscribe({ next: (data: PanelData) => this.onPanelDataUpdate(data), }); try { - const ds = await getDataSourceSrv().get(datasourceName); - this.setState({ dataSource: ds }); + const ds = await this.dataSourceSrv.get(options.dataSource.name); + const dsSettings = this.dataSourceSrv.getInstanceSettings(options.dataSource.name); + this.setState({ dataSource: ds, dsSettings }); } catch (error) { - const ds = await getDataSourceSrv().get(); - const dataSourceItem = this.findCurrentDataSource(ds.name); - this.setState({ dataSource: ds, dataSourceError: error?.message, dataSourceItem }); + console.log('failed to load data source', error); } } @@ -101,62 +93,73 @@ export class QueryGroup extends PureComponent { this.setState({ data }); } - findCurrentDataSource(dataSourceName: string | null): DataSourceSelectItem { - return this.datasources.find(datasource => datasource.value === dataSourceName) || this.datasources[0]; - } - - onChangeDataSource = async (newDsItem: DataSourceSelectItem) => { - let { queries } = this.props; - const { dataSourceItem } = this.state; + onChangeDataSource = async (newSettings: DataSourceInstanceSettings) => { + let { queries } = this.props.options; + const { dsSettings } = this.state; // switching to mixed - if (newDsItem.meta.mixed) { + if (newSettings.meta.mixed) { for (const query of queries) { if (query.datasource !== ExpressionDatasourceID) { - query.datasource = query.datasource; + query.datasource = dsSettings?.name; if (!query.datasource) { query.datasource = config.defaultDatasource; } } } - } else if (dataSourceItem) { + } else if (dsSettings) { // if switching from mixed - if (dataSourceItem.meta.mixed) { + if (dsSettings.meta.mixed) { // Remove the explicit datasource for (const query of queries) { if (query.datasource !== ExpressionDatasourceID) { delete query.datasource; } } - } else if (dataSourceItem.meta.id !== newDsItem.meta.id) { + } else if (dsSettings.meta.id !== newSettings.meta.id) { // we are changing data source type, clear queries queries = [{ refId: 'A' }]; } } - const dataSource = await getDataSourceSrv().get(newDsItem.value); + const dataSource = await this.dataSourceSrv.get(newSettings.name); - this.props.onDataSourceChange(newDsItem, queries); + this.onChange({ + queries, + dataSource: { + name: newSettings.name, + uid: newSettings.uid, + default: newSettings.isDefault, + }, + }); this.setState({ - dataSourceItem: newDsItem, dataSource: dataSource, - dataSourceError: undefined, + dsSettings: newSettings, }); }; onAddQueryClick = () => { - if (this.state.dataSourceItem.meta.mixed) { + if (this.state.dsSettings?.meta.mixed) { this.setState({ isAddingMixed: true }); return; } - this.props.onQueriesChange(addQuery(this.props.queries)); + this.onChange({ queries: addQuery(this.props.options.queries) }); this.onScrollBottom(); }; + onChange(changedProps: Partial) { + this.props.onOptionsChange({ + ...this.props.options, + ...changedProps, + }); + } + onAddExpressionClick = () => { - this.props.onQueriesChange(addQuery(this.props.queries, expressionDatasource.newQuery())); + this.onChange({ + queries: addQuery(this.props.options.queries, expressionDatasource.newQuery()), + }); this.onScrollBottom(); }; @@ -166,45 +169,51 @@ export class QueryGroup extends PureComponent { renderTopSection(styles: QueriesTabStyls) { const { onOpenQueryInspector, options, onOptionsChange } = this.props; - const { dataSourceItem, dataSource, dataSourceError, data } = this.state; - - if (!dataSource) { - return null; - } + const { dataSource, data } = this.state; return (
- - - -
-
-
-
- -
- {onOpenQueryInspector && ( -
- -
+ {dataSource && ( + <> +
+
+
+ +
+ {onOpenQueryInspector && ( +
+ +
+ )} + )}
@@ -220,13 +229,9 @@ export class QueryGroup extends PureComponent { }; renderMixedPicker = () => { - // We cannot filter on mixed flag as some mixed data sources like external plugin - // meta queries data source is mixed but also supports it's own queries - const filteredDsList = this.datasources.filter(ds => ds.meta.id !== 'mixed'); - return ( { }; onAddQuery = (query: Partial) => { - const { queries, onQueriesChange } = this.props; - onQueriesChange(addQuery(queries, query)); + const { queries } = this.props.options; + this.onChange({ queries: addQuery(queries, query) }); this.onScrollBottom(); }; @@ -256,20 +261,24 @@ export class QueryGroup extends PureComponent { this.setState({ scrollTop: target.scrollTop }); }; - renderQueries() { - const { onQueriesChange, queries, onRunQueries } = this.props; - const { dataSourceItem, data } = this.state; + onQueriesChange = (queries: DataQuery[]) => { + this.onChange({ queries }); + }; - if (isSharedDashboardQuery(dataSourceItem.name)) { - return ; + renderQueries(dsSettings: DataSourceInstanceSettings) { + const { options, onRunQueries } = this.props; + const { data } = this.state; + + if (isSharedDashboardQuery(dsSettings.name)) { + return ; } return (
{ ); } - renderAddQueryRow() { - const { dataSourceItem, isAddingMixed } = this.state; - const showAddButton = !(isAddingMixed || isSharedDashboardQuery(dataSourceItem.name)); + renderAddQueryRow(dsSettings: DataSourceInstanceSettings) { + const { isAddingMixed } = this.state; + const showAddButton = !(isAddingMixed || isSharedDashboardQuery(dsSettings.name)); return ( @@ -305,7 +314,7 @@ export class QueryGroup extends PureComponent { } render() { - const { scrollTop, isHelpOpen } = this.state; + const { scrollTop, isHelpOpen, dsSettings } = this.state; const styles = getStyles(); return ( @@ -318,13 +327,16 @@ export class QueryGroup extends PureComponent { >
{this.renderTopSection(styles)} -
{this.renderQueries()}
- {this.renderAddQueryRow()} - - {isHelpOpen && ( - - - + {dsSettings && ( + <> +
{this.renderQueries(dsSettings)}
+ {this.renderAddQueryRow(dsSettings)} + {isHelpOpen && ( + + + + )} + )}
diff --git a/public/app/features/query/components/QueryGroupOptions.tsx b/public/app/features/query/components/QueryGroupOptions.tsx index b4166436fa5..ca31fef73e2 100644 --- a/public/app/features/query/components/QueryGroupOptions.tsx +++ b/public/app/features/query/components/QueryGroupOptions.tsx @@ -2,7 +2,7 @@ import React, { PureComponent, ChangeEvent, FocusEvent } from 'react'; // Utils -import { rangeUtil, PanelData, DataSourceApi } from '@grafana/data'; +import { rangeUtil, PanelData, DataSourceApi, DataQuery } from '@grafana/data'; // Components import { Switch, Input, InlineField, InlineFormLabel, stylesFactory } from '@grafana/ui'; @@ -13,6 +13,8 @@ import { config } from 'app/core/config'; import { css } from 'emotion'; export interface QueryGroupOptions { + queries: DataQuery[]; + dataSource: QueryGroupDataSource; maxDataPoints?: number | null; minInterval?: string | null; cacheTimeout?: string | null; @@ -23,6 +25,12 @@ export interface QueryGroupOptions { }; } +interface QueryGroupDataSource { + name?: string | null; + uid?: string; + default?: boolean; +} + interface Props { options: QueryGroupOptions; dataSource: DataSourceApi; diff --git a/public/app/features/sandbox/TestStuffPage.tsx b/public/app/features/sandbox/TestStuffPage.tsx index cc7cff6cb73..ecb8184013b 100644 --- a/public/app/features/sandbox/TestStuffPage.tsx +++ b/public/app/features/sandbox/TestStuffPage.tsx @@ -1,12 +1,4 @@ -import { - ApplyFieldOverrideOptions, - DataQuery, - DataSourceSelectItem, - DataTransformerConfig, - dateMath, - FieldColorModeId, - PanelData, -} from '@grafana/data'; +import { ApplyFieldOverrideOptions, DataTransformerConfig, dateMath, FieldColorModeId, PanelData } from '@grafana/data'; import { GraphNG, Table } from '@grafana/ui'; import { config } from 'app/core/config'; import React, { FC, useMemo, useState } from 'react'; @@ -16,43 +8,29 @@ import { QueryGroupOptions } from '../query/components/QueryGroupOptions'; import { PanelQueryRunner } from '../query/state/PanelQueryRunner'; interface State { - queries: DataQuery[]; queryRunner: PanelQueryRunner; - dataSourceName: string | null; queryOptions: QueryGroupOptions; data?: PanelData; } export const TestStuffPage: FC = () => { const [state, setState] = useState(getDefaultState()); - const { queryOptions, queryRunner, queries, dataSourceName } = state; - - const onDataSourceChange = (ds: DataSourceSelectItem, queries: DataQuery[]) => { - setState({ - ...state, - dataSourceName: ds.value, - queries: queries, - }); - }; + const { queryOptions, queryRunner } = state; const onRunQueries = () => { const timeRange = { from: 'now-1h', to: 'now' }; queryRunner.run({ - queries, + queries: queryOptions.queries, + datasource: queryOptions.dataSource.name!, timezone: 'browser', - datasource: dataSourceName, timeRange: { from: dateMath.parse(timeRange.from)!, to: dateMath.parse(timeRange.to)!, raw: timeRange }, maxDataPoints: queryOptions.maxDataPoints ?? 100, minInterval: queryOptions.minInterval, }); }; - const onQueriesChange = (queries: DataQuery[]) => { - setState({ ...state, queries: queries }); - }; - - const onQueryOptionsChange = (queryOptions: QueryGroupOptions) => { + const onOptionsChange = (queryOptions: QueryGroupOptions) => { setState({ ...state, queryOptions }); }; @@ -68,13 +46,9 @@ export const TestStuffPage: FC = () => {
@@ -109,10 +83,12 @@ export function getDefaultState(): State { }; return { - queries: [], - dataSourceName: 'gdev-testdata', queryRunner: new PanelQueryRunner(dataConfig), queryOptions: { + queries: [], + dataSource: { + name: 'gdev-testdata', + }, maxDataPoints: 100, }, }; diff --git a/public/app/features/variables/adhoc/actions.test.ts b/public/app/features/variables/adhoc/actions.test.ts index a612e3fafb6..92deb2bd09b 100644 --- a/public/app/features/variables/adhoc/actions.test.ts +++ b/public/app/features/variables/adhoc/actions.test.ts @@ -478,6 +478,5 @@ function createDatasource(name: string, selectable = true): DataSourceSelectItem meta: { mixed: !selectable, } as DataSourcePluginMeta, - sort: '', }; } diff --git a/public/app/features/variables/datasource/actions.test.ts b/public/app/features/variables/datasource/actions.test.ts index f652545aefe..93e3a6cfdfa 100644 --- a/public/app/features/variables/datasource/actions.test.ts +++ b/public/app/features/variables/datasource/actions.test.ts @@ -27,13 +27,11 @@ describe('data source actions', () => { name: 'first-name', value: 'first-value', meta: getMockPlugin({ name: 'mock-data-name', id: 'mock-data-id' }), - sort: '', }, { name: 'second-name', value: 'second-value', meta: getMockPlugin({ name: 'mock-data-name', id: 'mock-data-id' }), - sort: '', }, ]; @@ -80,13 +78,11 @@ describe('data source actions', () => { name: 'first-name', value: 'first-value', meta: getMockPlugin({ name: 'mock-data-name', id: 'mock-data-id' }), - sort: '', }, { name: 'second-name', value: 'second-value', meta: getMockPlugin({ name: 'mock-data-name', id: 'mock-data-id' }), - sort: '', }, ]; @@ -134,13 +130,11 @@ describe('data source actions', () => { name: 'first-name', value: 'first-value', meta: getMockPlugin({ name: 'mock-data-name', id: 'mock-data-id' }), - sort: '', }, { name: 'second-name', value: 'second-value', meta: getMockPlugin({ name: 'mock-data-name', id: 'mock-data-id' }), - sort: '', }, { name: 'mixed-name', @@ -150,7 +144,6 @@ describe('data source actions', () => { id: 'mixed-data-id', mixed: true, } as unknown) as DataSourcePluginMeta), - sort: '', }, ]; diff --git a/public/app/features/variables/editor/VariableTextAreaField.tsx b/public/app/features/variables/editor/VariableTextAreaField.tsx index 30d0d01de0c..ebdc9ea89d3 100644 --- a/public/app/features/variables/editor/VariableTextAreaField.tsx +++ b/public/app/features/variables/editor/VariableTextAreaField.tsx @@ -1,5 +1,5 @@ import React, { FormEvent, PropsWithChildren, ReactElement, useCallback } from 'react'; -import { HorizontalGroup, InlineField, TextArea, useStyles } from '@grafana/ui'; +import { InlineField, TextArea, useStyles } from '@grafana/ui'; import { GrafanaTheme } from '@grafana/data'; import { css } from 'emotion'; @@ -38,16 +38,7 @@ export function VariableTextAreaField({ }, []); return ( - - - +