Chore: Fix typescript strict null fixes now at 298 (#26125)

* Chore: Fix typescript strict null errors

* Added new limit

* Fixed ts issue

* fixed tests

* trying to fix type inference

* Fixing more ts errors

* Revert tsconfig option

* Fix

* Fixed code

* More fixes

* fix tests

* Updated snapshot

* Chore: More ts strict null fixes

* More fixes in some really messed up azure config components

* More fixes, current count: 441

* 419

* More fixes

* Fixed invalid initial state in explore

* Fixing tests

* Fixed tests

* Explore fix

* More fixes

* Progress

* Sub 300

* Fixed incorrect type

* removed unused import
pull/26137/head^2
Torkel Ödegaard 5 years ago committed by GitHub
parent 89b56782c6
commit fd44c01675
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      packages/grafana-data/src/types/datasource.ts
  2. 2
      packages/grafana-runtime/src/services/templateSrv.ts
  3. 1
      packages/grafana-ui/src/components/DataSourceSettings/CustomHeadersSettings.test.tsx
  4. 1
      packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.story.tsx
  5. 2
      packages/grafana-ui/src/components/QueryField/QueryField.tsx
  6. 2
      public/app/core/components/Page/Page.tsx
  7. 2
      public/app/core/components/Page/PageContents.tsx
  8. 2
      public/app/core/table_model.ts
  9. 8
      public/app/features/dashboard/state/runRequest.ts
  10. 1
      public/app/features/datasources/__mocks__/dataSourcesMocks.ts
  11. 1
      public/app/features/datasources/mocks.ts
  12. 2
      public/app/features/datasources/settings/__snapshots__/DataSourceSettingsPage.test.tsx.snap
  13. 4
      public/app/features/datasources/state/actions.ts
  14. 9
      public/app/features/datasources/state/navModel.ts
  15. 2
      public/app/features/explore/state/reducers.ts
  16. 4
      public/app/features/folders/state/navModel.ts
  17. 4
      public/app/features/manage-dashboards/components/SnapshotListTable.tsx
  18. 2
      public/app/features/org/UserInviteForm.tsx
  19. 2
      public/app/features/panel/metrics_panel_ctrl.ts
  20. 33
      public/app/features/panel/panellinks/linkSuppliers.ts
  21. 23
      public/app/features/panel/panellinks/link_srv.ts
  22. 2
      public/app/features/panel/query_ctrl.ts
  23. 4
      public/app/features/plugins/AppRootPage.tsx
  24. 42
      public/app/features/plugins/PluginPage.tsx
  25. 8
      public/app/features/plugins/PluginSignatureBadge.tsx
  26. 2
      public/app/features/plugins/datasource_srv.ts
  27. 6
      public/app/features/plugins/plugin_component.ts
  28. 4
      public/app/features/plugins/wrappers/AppConfigWrapper.tsx
  29. 4
      public/app/features/profile/SignupForm.tsx
  30. 3
      public/app/features/search/components/DashboardListPage.tsx
  31. 6
      public/app/features/search/loaders.ts
  32. 2
      public/app/features/teams/TeamGroupSync.tsx
  33. 4
      public/app/features/teams/TeamList.tsx
  34. 11
      public/app/features/teams/TeamMemberRow.tsx
  35. 8
      public/app/features/teams/TeamMembers.tsx
  36. 8
      public/app/features/teams/TeamPages.tsx
  37. 6
      public/app/features/teams/state/navModel.ts
  38. 4
      public/app/features/templating/template_srv.ts
  39. 4
      public/app/features/variables/pickers/OptionsPicker/reducer.ts
  40. 21
      public/app/plugins/datasource/cloud-monitoring/CloudMonitoringMetricFindQuery.ts
  41. 2
      public/app/plugins/datasource/cloud-monitoring/api.ts
  42. 6
      public/app/plugins/datasource/cloud-monitoring/components/Aggregations.test.tsx
  43. 10
      public/app/plugins/datasource/cloud-monitoring/components/Aggregations.tsx
  44. 4
      public/app/plugins/datasource/cloud-monitoring/components/AliasBy.tsx
  45. 13
      public/app/plugins/datasource/cloud-monitoring/components/Metrics.tsx
  46. 4
      public/app/plugins/datasource/cloud-monitoring/components/QueryEditor.tsx
  47. 2
      public/app/plugins/datasource/cloud-monitoring/components/VariableQueryEditor.tsx
  48. 8
      public/app/plugins/datasource/cloud-monitoring/datasource.ts
  49. 2
      public/app/plugins/datasource/cloud-monitoring/functions.ts
  50. 8
      public/app/plugins/datasource/cloudwatch/components/CloudWatchLink.tsx
  51. 8
      public/app/plugins/datasource/cloudwatch/components/Dimensions.tsx
  52. 2
      public/app/plugins/datasource/cloudwatch/components/LogsQueryField.tsx
  53. 4
      public/app/plugins/datasource/cloudwatch/components/MetricsQueryFieldsEditor.tsx
  54. 6
      public/app/plugins/datasource/cloudwatch/components/Stats.tsx
  55. 1
      public/app/plugins/datasource/cloudwatch/datasource.ts
  56. 14
      public/app/plugins/datasource/cloudwatch/language_provider.ts
  57. 10
      public/app/plugins/datasource/dashboard/DashboardQueryEditor.tsx
  58. 4
      public/app/plugins/datasource/dashboard/runSharedRequest.ts
  59. 2
      public/app/plugins/datasource/elasticsearch/configuration/DataLink.tsx
  60. 10
      public/app/plugins/datasource/elasticsearch/configuration/ElasticDetails.tsx
  61. 17
      public/app/plugins/datasource/elasticsearch/datasource.ts
  62. 5
      public/app/plugins/datasource/elasticsearch/elastic_response.ts
  63. 2
      public/app/plugins/datasource/elasticsearch/index_pattern.ts
  64. 2
      public/app/plugins/datasource/elasticsearch/metric_agg.ts
  65. 2
      public/app/plugins/datasource/elasticsearch/types.ts
  66. 4
      public/app/plugins/datasource/grafana-azure-monitor-datasource/app_insights/app_insights_datasource.test.ts
  67. 12
      public/app/plugins/datasource/grafana-azure-monitor-datasource/app_insights/app_insights_datasource.ts
  68. 28
      public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_log_analytics/azure_log_analytics_datasource.ts
  69. 23
      public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/azure_monitor_datasource.ts
  70. 12
      public/app/plugins/datasource/grafana-azure-monitor-datasource/components/AnalyticsConfig.tsx
  71. 10
      public/app/plugins/datasource/grafana-azure-monitor-datasource/components/AzureCredentialsForm.tsx
  72. 31
      public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ConfigEditor.tsx
  73. 2
      public/app/plugins/datasource/grafana-azure-monitor-datasource/components/InsightsConfig.tsx
  74. 2
      public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MonitorConfig.tsx
  75. 6
      public/app/plugins/datasource/grafana-azure-monitor-datasource/datasource.ts
  76. 7
      public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx
  77. 12
      public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx
  78. 6
      public/app/plugins/datasource/graphite/FunctionEditor.tsx
  79. 2
      public/app/plugins/datasource/graphite/FunctionEditorControls.tsx
  80. 21
      public/app/plugins/datasource/graphite/datasource.ts
  81. 2
      public/app/plugins/datasource/graphite/gfunc.ts
  82. 4
      public/app/plugins/datasource/graphite/specs/datasource.test.ts
  83. 24
      public/app/plugins/datasource/influxdb/components/InfluxLogsQueryField.tsx
  84. 23
      public/app/plugins/datasource/influxdb/datasource.ts
  85. 8
      public/app/plugins/datasource/influxdb/influx_query_model.ts
  86. 2
      public/app/plugins/datasource/influxdb/influx_series.ts
  87. 8
      public/app/plugins/datasource/influxdb/query_builder.ts
  88. 6
      public/app/plugins/datasource/influxdb/query_ctrl.ts
  89. 2
      public/app/plugins/datasource/jaeger/QueryField.tsx
  90. 2
      public/app/plugins/datasource/jaeger/datasource.ts
  91. 1
      public/app/plugins/datasource/loki/components/AnnotationsQueryEditor.tsx
  92. 2
      public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.tsx
  93. 4
      public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx
  94. 2
      public/app/plugins/datasource/loki/components/LokiQueryFieldForm.tsx
  95. 4
      public/app/plugins/datasource/loki/components/useLokiLabels.ts
  96. 2
      public/app/plugins/datasource/loki/components/useLokiSyntaxAndLabels.ts
  97. 3
      public/app/plugins/datasource/loki/configuration/DerivedFields.test.tsx
  98. 2
      public/app/plugins/datasource/loki/mocks.ts
  99. 12
      public/app/plugins/datasource/loki/result_transformer.test.ts
  100. 2
      public/app/plugins/datasource/loki/types.ts
  101. Some files were not shown because too many files have changed in this diff Show More

@ -329,7 +329,7 @@ export interface ExploreQueryFieldProps<
}
export interface ExploreStartPageProps {
datasource?: DataSourceApi;
datasource: DataSourceApi;
exploreMode: ExploreMode;
onClickExample: (query: DataQuery) => void;
exploreId?: any;
@ -490,7 +490,7 @@ export interface DataSourceSettings<T extends DataSourceJsonData = DataSourceJso
isDefault: boolean;
jsonData: T;
secureJsonData?: S;
secureJsonFields?: KeyValue<boolean>;
secureJsonFields: KeyValue<boolean>;
readOnly: boolean;
withCredentials: boolean;
version?: number;

@ -16,7 +16,7 @@ export interface TemplateSrv {
/**
* Replace the values within the target string. See also {@link InterpolateFunction}
*/
replace(target: string, scopedVars?: ScopedVars, format?: string | Function): string;
replace(target?: string, scopedVars?: ScopedVars, format?: string | Function): string;
}
let singletonInstance: TemplateSrv;

@ -28,6 +28,7 @@ const setup = (propOverrides?: object) => {
secureJsonData: {
password: true,
},
secureJsonFields: {},
readOnly: true,
},
onChange: jest.fn(),

@ -28,6 +28,7 @@ const settingsMock: DataSourceSettings<any, any> = {
secureJsonData: {
password: true,
},
secureJsonFields: {},
readOnly: true,
};

@ -26,7 +26,7 @@ export interface QueryFieldProps {
// We have both value and local state. This is usually an antipattern but we need to keep local state
// for perf reasons and also have outside value in for example in Explore redux that is mutable from logs
// creating a two way binding.
query: string | null;
query?: string | null;
onRunQuery?: () => void;
onBlur?: () => void;
onChange?: (value: string) => void;

@ -12,7 +12,7 @@ import { isEqual } from 'lodash';
import { Branding } from '../Branding/Branding';
interface Props {
children: JSX.Element[] | JSX.Element;
children: React.ReactNode;
navModel: NavModel;
}

@ -6,7 +6,7 @@ import PageLoader from '../PageLoader/PageLoader';
interface Props {
isLoading?: boolean;
children: JSX.Element[] | JSX.Element | null;
children: React.ReactNode;
}
class PageContents extends Component<Props> {

@ -17,7 +17,7 @@ export default class TableModel implements TableData {
rows: any[];
type: string;
columnMap: any;
refId: string;
refId?: string;
meta?: QueryResultMeta;
constructor(table?: any) {

@ -182,7 +182,7 @@ export function getProcessedDataFrames(results?: DataQueryResponseData[]): DataF
return dataFrames;
}
export function preProcessPanelData(data: PanelData, lastResult: PanelData): PanelData {
export function preProcessPanelData(data: PanelData, lastResult?: PanelData): PanelData {
const { series } = data;
// for loading states with no data, use last result
@ -191,7 +191,11 @@ export function preProcessPanelData(data: PanelData, lastResult: PanelData): Pan
lastResult = data;
}
return { ...lastResult, state: LoadingState.Loading };
return {
...lastResult,
state: LoadingState.Loading,
request: data.request,
};
}
// Make sure the data frames are properly formatted

@ -44,5 +44,6 @@ export const getMockDataSource = (): DataSourceSettings => {
typeLogoUrl: 'public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png',
url: '',
user: '',
secureJsonFields: {},
};
};

@ -19,5 +19,6 @@ export function createDatasourceSettings<T>(jsonData: T): DataSourceSettings<T>
jsonData,
readOnly: false,
withCredentials: false,
secureJsonFields: {},
};
}

@ -51,6 +51,7 @@ exports[`Render should render alpha info text 1`] = `
"orgId": 1,
"password": "",
"readOnly": false,
"secureJsonFields": Object {},
"type": "cloudwatch",
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
"url": "",
@ -242,6 +243,7 @@ exports[`Render should render is ready only message 1`] = `
"orgId": 1,
"password": "",
"readOnly": true,
"secureJsonFields": Object {},
"type": "cloudwatch",
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
"url": "",

@ -62,7 +62,7 @@ export const initDataSourceSettings = (
}
const dataSource = dependencies.getDataSource(getState().dataSources, pageId);
const dataSourceMeta = dependencies.getDataSourceMeta(getState().dataSources, dataSource.type);
const dataSourceMeta = dependencies.getDataSourceMeta(getState().dataSources, dataSource!.type);
const importedPlugin = await dependencies.importDataSourcePlugin(dataSourceMeta);
dispatch(initDataSourceSettingsSucceeded(importedPlugin));
@ -118,7 +118,7 @@ export function loadDataSources(): ThunkResult<void> {
export function loadDataSource(id: number): ThunkResult<void> {
return async dispatch => {
const dataSource = await getBackendSrv().get(`/api/datasources/${id}`);
const dataSource = (await getBackendSrv().get(`/api/datasources/${id}`)) as DataSourceSettings;
const pluginInfo = (await getPluginSettings(dataSource.type)) as DataSourcePluginMeta;
const plugin = await importDataSourcePlugin(pluginInfo);

@ -88,6 +88,7 @@ export function getDataSourceLoadingNav(pageName: string): NavModel {
typeLogoUrl: 'public/img/icn-datasource.svg',
url: '',
user: '',
secureJsonFields: {},
},
{
meta: {
@ -113,14 +114,14 @@ export function getDataSourceLoadingNav(pageName: string): NavModel {
module: '',
baseUrl: '',
},
} as GenericDataSourcePlugin
} as any
);
let node: NavModelItem;
// find active page
for (const child of main.children) {
if (child.id.indexOf(pageName) > 0) {
for (const child of main.children!) {
if (child.id!.indexOf(pageName) > 0) {
child.active = true;
node = child;
break;
@ -129,7 +130,7 @@ export function getDataSourceLoadingNav(pageName: string): NavModel {
return {
main: main,
node: node,
node: node!,
};
}

@ -3,7 +3,6 @@ import { AnyAction } from 'redux';
import { PayloadAction } from '@reduxjs/toolkit';
import {
DataQuery,
DataQueryRequest,
DataSourceApi,
DefaultTimeRange,
LoadingState,
@ -129,7 +128,6 @@ export const makeExploreItemState = (): ExploreItemState => ({
export const createEmptyQueryResponse = (): PanelData => ({
state: LoadingState.NotStarted,
request: {} as DataQueryRequest<DataQuery>,
series: [],
error: null,
timeRange: DefaultTimeRange,

@ -47,10 +47,10 @@ export function getLoadingNav(tabIndex: number): NavModel {
version: 0,
});
main.children[tabIndex].active = true;
main.children![tabIndex].active = true;
return {
main: main,
node: main.children[tabIndex],
node: main.children![tabIndex],
};
}

@ -10,7 +10,7 @@ interface Props {
export const SnapshotListTable: FC<Props> = ({ url }) => {
const [snapshots, setSnapshots] = useState<Snapshot[]>([]);
const [removeSnapshot, setRemoveSnapshot] = useState<Snapshot>();
const [removeSnapshot, setRemoveSnapshot] = useState<Snapshot | undefined>();
const getSnapshots = useCallback(async () => {
await getBackendSrv()
@ -91,7 +91,7 @@ export const SnapshotListTable: FC<Props> = ({ url }) => {
confirmText="Delete"
onDismiss={() => setRemoveSnapshot(undefined)}
onConfirm={() => {
doRemoveSnapshot(removeSnapshot);
doRemoveSnapshot(removeSnapshot!);
setRemoveSnapshot(undefined);
}}
/>

@ -60,7 +60,7 @@ export const UserInviteForm: FC<Props> = ({ updateLocation }) => {
<>
<Field
invalid={!!errors.loginOrEmail}
error={!!errors.loginOrEmail && 'Email or Username is required'}
error={!!errors.loginOrEmail ? 'Email or Username is required' : undefined}
label="Email or Username"
>
<Input name="loginOrEmail" placeholder="email@example.com" ref={register({ required: true })} />

@ -33,7 +33,7 @@ class MetricsPanelCtrl extends PanelCtrl {
timeInfo?: string;
skipDataOnInit: boolean;
dataList: LegacyResponseData[];
querySubscription?: Unsubscribable;
querySubscription?: Unsubscribable | null;
useDataFrames = false;
constructor($scope: any, $injector: any) {

@ -38,7 +38,7 @@ interface DataViewVars {
fields?: Record<string, DisplayValue>;
}
interface DataLinkScopedVars extends ScopedVars {
interface DataLinkScopedVars {
__series?: ScopedVar<SeriesVars>;
__field?: ScopedVar<FieldVars>;
__value?: ScopedVar<ValueVars>;
@ -53,6 +53,7 @@ export const getFieldLinksSupplier = (value: FieldDisplay): LinkModelSupplier<Fi
if (!links || links.length === 0) {
return undefined;
}
return {
getLinks: (existingScopedVars?: any) => {
const scopedVars: DataLinkScopedVars = {
@ -71,8 +72,8 @@ export const getFieldLinksSupplier = (value: FieldDisplay): LinkModelSupplier<Fi
};
const field = value.colIndex !== undefined ? dataFrame.fields[value.colIndex] : undefined;
if (field) {
console.log('Full Field Info:', field);
scopedVars['__field'] = {
value: {
name: field.name,
@ -80,19 +81,19 @@ export const getFieldLinksSupplier = (value: FieldDisplay): LinkModelSupplier<Fi
},
text: 'Field',
};
}
if (!isNaN(value.rowIndex)) {
const { timeField } = getTimeField(dataFrame);
scopedVars['__value'] = {
value: {
raw: field.values.get(value.rowIndex),
numeric: value.display.numeric,
text: formattedValueToString(value.display),
time: timeField ? timeField.values.get(value.rowIndex) : undefined,
},
text: 'Value',
};
if (value.rowIndex !== undefined && value.rowIndex >= 0) {
const { timeField } = getTimeField(dataFrame);
scopedVars['__value'] = {
value: {
raw: field.values.get(value.rowIndex),
numeric: value.display.numeric,
text: formattedValueToString(value.display),
time: timeField ? timeField.values.get(value.rowIndex) : undefined,
},
text: 'Value',
};
}
// Expose other values on the row
if (value.view) {
@ -124,13 +125,13 @@ export const getFieldLinksSupplier = (value: FieldDisplay): LinkModelSupplier<Fi
}
return links.map((link: DataLink) => {
return getLinkSrv().getDataLinkUIModel(link, scopedVars, value);
return getLinkSrv().getDataLinkUIModel(link, scopedVars as ScopedVars, value);
});
},
};
};
export const getPanelLinksSupplier = (value: PanelModel): LinkModelSupplier<PanelModel> => {
export const getPanelLinksSupplier = (value: PanelModel): LinkModelSupplier<PanelModel> | undefined => {
const links = value.links;
if (!links || links.length === 0) {

@ -20,6 +20,7 @@ import {
textUtil,
DataLink,
PanelPlugin,
DataLinkClickEvent,
} from '@grafana/data';
const timeRangeVars = [
@ -126,8 +127,8 @@ const getFieldVars = (dataFrames: DataFrame[]) => {
};
const getDataFrameVars = (dataFrames: DataFrame[]) => {
let numeric: Field = undefined;
let title: Field = undefined;
let numeric: Field | undefined = undefined;
let title: Field | undefined = undefined;
const suggestions: VariableSuggestion[] = [];
const keys: KeyValue<true> = {};
@ -245,7 +246,7 @@ export const getPanelOptionsVariableSuggestions = (plugin: PanelPlugin, data?: D
};
export interface LinkService {
getDataLinkUIModel: <T>(link: DataLink, scopedVars: ScopedVars, origin: T) => LinkModel<T>;
getDataLinkUIModel: <T>(link: DataLink, scopedVars: ScopedVars | undefined, origin: T) => LinkModel<T>;
getAnchorInfo: (link: any) => any;
getLinkUrl: (link: any) => string;
}
@ -295,15 +296,17 @@ export class LinkSrv implements LinkService {
});
}
let onClick: (e: any) => void = undefined;
let onClick: ((event: DataLinkClickEvent) => void) | undefined = undefined;
if (link.onClick) {
onClick = (e: any) => {
link.onClick({
origin,
scopedVars,
e,
});
onClick = (e: DataLinkClickEvent) => {
if (link.onClick) {
link.onClick({
origin,
scopedVars,
e,
});
}
};
}

@ -7,7 +7,7 @@ export class QueryCtrl {
panelCtrl: any;
panel: any;
hasRawMode: boolean;
error: string;
error?: string | null;
isLastQuery: boolean;
constructor(public $scope: any, public $injector: auto.IInjectorService) {

@ -22,7 +22,7 @@ interface Props {
interface State {
loading: boolean;
plugin?: AppPlugin;
plugin?: AppPlugin | null;
nav: NavModel;
}
@ -83,7 +83,7 @@ class AppRootPage extends Component<Props, State> {
return (
<Page navModel={nav}>
<Page.Contents isLoading={loading}>
{!loading && plugin && (
{plugin && plugin.root && (
<plugin.root meta={plugin.meta} query={query} path={path} onNavChanged={this.onNavChanged} />
)}
</Page.Contents>

@ -123,12 +123,14 @@ class PluginPage extends PureComponent<Props, State> {
componentDidUpdate(prevProps: Props) {
const prevPage = prevProps.query.page as string;
const page = this.props.query.page as string;
if (prevPage !== page) {
const { nav, defaultPage } = this.state;
const node = {
...nav.node,
children: setActivePage(page, nav.node.children, defaultPage),
children: setActivePage(page, nav.node.children!, defaultPage),
};
this.setState({
nav: {
node: node,
@ -146,7 +148,7 @@ class PluginPage extends PureComponent<Props, State> {
return <Alert severity={AppNotificationSeverity.Error} title="Plugin Not Found" />;
}
const active = nav.main.children.find(tab => tab.active);
const active = nav.main.children!.find(tab => tab.active);
if (active) {
// Find the current config tab
if (plugin.configPages) {
@ -175,7 +177,7 @@ class PluginPage extends PureComponent<Props, State> {
showUpdateInfo = () => {
appEvents.emit(CoreEvents.showModal, {
src: 'public/app/features/plugins/partials/update_instructions.html',
model: this.state.plugin.meta,
model: this.state.plugin!.meta,
});
};
@ -190,7 +192,7 @@ class PluginPage extends PureComponent<Props, State> {
<span>{meta.info.version}</span>
{meta.hasUpdate && (
<div>
<Tooltip content={meta.latestVersion} theme="info" placement="top">
<Tooltip content={meta.latestVersion!} theme="info" placement="top">
<a href="#" onClick={this.showUpdateInfo}>
Update Available!
</a>
@ -203,7 +205,7 @@ class PluginPage extends PureComponent<Props, State> {
renderSidebarIncludeBody(item: PluginInclude) {
if (item.type === PluginIncludeType.page) {
const pluginId = this.state.plugin.meta.id;
const pluginId = this.state.plugin!.meta.id;
const page = item.name.toLowerCase().replace(' ', '-');
return (
<a href={`plugins/${pluginId}/page/${page}`}>
@ -220,7 +222,7 @@ class PluginPage extends PureComponent<Props, State> {
);
}
renderSidebarIncludes(includes: PluginInclude[]) {
renderSidebarIncludes(includes?: PluginInclude[]) {
if (!includes || !includes.length) {
return null;
}
@ -241,7 +243,7 @@ class PluginPage extends PureComponent<Props, State> {
);
}
renderSidebarDependencies(dependencies: PluginDependencies) {
renderSidebarDependencies(dependencies?: PluginDependencies) {
if (!dependencies) {
return null;
}
@ -295,10 +297,11 @@ class PluginPage extends PureComponent<Props, State> {
const { loading, nav, plugin } = this.state;
const { $contextSrv } = this.props;
const isAdmin = $contextSrv.hasRole('Admin');
return (
<Page navModel={nav}>
<Page.Contents isLoading={loading}>
{!loading && (
{plugin && (
<div className="sidebar-container">
<div className="sidebar-content">
{plugin.loadError && (
@ -316,14 +319,12 @@ class PluginPage extends PureComponent<Props, State> {
{this.renderBody()}
</div>
<aside className="page-sidebar">
{plugin && (
<section className="page-sidebar-section">
{this.renderVersionInfo(plugin.meta)}
{isAdmin && this.renderSidebarIncludes(plugin.meta.includes)}
{this.renderSidebarDependencies(plugin.meta.dependencies)}
{this.renderSidebarLinks(plugin.meta.info)}
</section>
)}
<section className="page-sidebar-section">
{this.renderVersionInfo(plugin.meta)}
{isAdmin && this.renderSidebarIncludes(plugin.meta.includes)}
{this.renderSidebarDependencies(plugin.meta.dependencies)}
{this.renderSidebarLinks(plugin.meta.info)}
</section>
</aside>
</div>
)}
@ -341,7 +342,7 @@ function getPluginTabsNav(
isAdmin: boolean
): { defaultPage: string; nav: NavModel } {
const { meta } = plugin;
let defaultPage: string;
let defaultPage: string | undefined;
const pages: NavModelItem[] = [];
if (true) {
@ -377,6 +378,7 @@ function getPluginTabsNav(
url: `${appSubUrl}${path}?page=${page.id}`,
id: page.id,
});
if (!defaultPage) {
defaultPage = page.id;
}
@ -405,11 +407,11 @@ function getPluginTabsNav(
subTitle: meta.info.author.name,
breadcrumbs: [{ title: 'Plugins', url: 'plugins' }],
url: `${appSubUrl}${path}`,
children: setActivePage(query.page as string, pages, defaultPage),
children: setActivePage(query.page as string, pages, defaultPage!),
};
return {
defaultPage,
defaultPage: defaultPage!,
nav: {
node: node,
main: node,
@ -427,9 +429,11 @@ function setActivePage(pageId: string, pages: NavModelItem[], defaultPageId: str
}
return { ...p, active };
});
if (!found) {
changed[0].active = true;
}
return changed;
}

@ -3,7 +3,7 @@ import { Badge, BadgeProps } from '@grafana/ui';
import { PluginSignatureStatus } from '@grafana/data';
interface Props {
status: PluginSignatureStatus;
status?: PluginSignatureStatus;
}
export const PluginSignatureBadge: React.FC<Props> = ({ status }) => {
@ -11,7 +11,11 @@ export const PluginSignatureBadge: React.FC<Props> = ({ status }) => {
return <Badge text={display.text} color={display.color} icon={display.icon} tooltip={display.tooltip} />;
};
function getSignatureDisplayModel(signature: PluginSignatureStatus): BadgeProps {
function getSignatureDisplayModel(signature?: PluginSignatureStatus): BadgeProps {
if (!signature) {
signature = PluginSignatureStatus.invalid;
}
switch (signature) {
case PluginSignatureStatus.internal:
return { text: 'Core', icon: 'cube', color: 'blue', tooltip: 'Core plugin that is bundled with Grafana' };

@ -126,7 +126,7 @@ export class DatasourceSrv implements DataSourceService {
Object.entries(config.datasources).forEach(([key, value]) => {
if (value.meta?.metrics) {
let metricSource = { value: key, name: key, meta: value.meta, sort: key };
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') {

@ -122,7 +122,7 @@ function pluginDirectiveLoader(
'panel-ctrl': 'ctrl',
datasource: 'ctrl.datasource',
},
Component: ds.components.QueryCtrl,
Component: ds.components!.QueryCtrl,
});
}
// Annotations
@ -189,6 +189,10 @@ function pluginDirectiveLoader(
case 'app-page': {
const appModel = scope.ctrl.appModel;
return importAppPlugin(appModel).then(appPlugin => {
if (!appPlugin.angularPages) {
throw new Error('Plugin has no page components');
}
return {
baseUrl: appModel.baseUrl,
name: 'app-page-' + appModel.id + '-' + scope.ctrl.page.slug,

@ -14,12 +14,12 @@ interface Props {
}
interface State {
angularCtrl: AngularComponent;
angularCtrl: AngularComponent | null;
refresh: number;
}
export class AppConfigCtrlWrapper extends PureComponent<Props, State> {
element: HTMLElement; // for angular ctrl
element: HTMLElement | null = null;
// Needed for angular scope
preUpdateHook = () => Promise.resolve();

@ -77,7 +77,7 @@ export const SignupForm: FC<Props> = props => {
<Field label="Your name">
<Input name="name" placeholder="(optional)" ref={register} />
</Field>
<Field label="Email" invalid={!!errors.email} error={!!errors.email && errors.email.message}>
<Field label="Email" invalid={!!errors.email} error={errors.email?.message}>
<Input
name="email"
type="email"
@ -91,7 +91,7 @@ export const SignupForm: FC<Props> = props => {
})}
/>
</Field>
<Field label="Password" invalid={!!errors.password} error={!!errors.password && errors.password.message}>
<Field label="Password" invalid={!!errors.password} error={errors.password?.message}>
<Input
name="password"
type="password"

@ -21,6 +21,7 @@ export const DashboardListPage: FC<Props> = memo(({ navModel, uid, url }) => {
if (!uid || !url.startsWith('/dashboards')) {
return Promise.resolve({ pageNavModel: navModel });
}
return loadFolderPage(uid!, 'manage-folder-dashboards').then(({ folder, model }) => {
const path = locationUtil.stripBaseFromUrl(folder.url);
@ -33,7 +34,7 @@ export const DashboardListPage: FC<Props> = memo(({ navModel, uid, url }) => {
}, [uid]);
return (
<Page navModel={value?.pageNavModel}>
<Page navModel={value?.pageNavModel ?? navModel}>
<Page.Contents isLoading={loading}>
<ManageDashboards folder={value?.folder} />
</Page.Contents>

@ -41,14 +41,14 @@ export const loadFolderPage = (uid: string, activeChildId: string) => {
const folderUrl = folder.url;
navModel.main.text = folderTitle;
const dashTab = navModel.main.children.find((child: any) => child.id === 'manage-folder-dashboards');
const dashTab = navModel.main.children!.find((child: any) => child.id === 'manage-folder-dashboards');
dashTab!.url = folderUrl;
if (folder.canAdmin) {
const permTab = navModel.main.children.find((child: any) => child.id === 'manage-folder-permissions');
const permTab = navModel.main.children!.find((child: any) => child.id === 'manage-folder-permissions');
permTab!.url = folderUrl + '/permissions';
const settingsTab = navModel.main.children.find((child: any) => child.id === 'manage-folder-settings');
const settingsTab = navModel.main.children!.find((child: any) => child.id === 'manage-folder-settings');
settingsTab!.url = folderUrl + '/settings';
} else {
navModel.main.children = [dashTab!];

@ -19,7 +19,7 @@ export interface Props {
interface State {
isAdding: boolean;
newGroupId?: string;
newGroupId: string;
}
const headerTooltip = `Sync LDAP or OAuth groups with your Grafana teams.`;

@ -23,8 +23,8 @@ export interface Props {
loadTeams: typeof loadTeams;
deleteTeam: typeof deleteTeam;
setSearchQuery: typeof setSearchQuery;
editorsCanAdmin?: boolean;
signedInUser?: User;
editorsCanAdmin: boolean;
signedInUser: User;
}
export class TeamList extends PureComponent<Props, any> {

@ -14,8 +14,8 @@ export interface Props {
syncEnabled: boolean;
editorsCanAdmin: boolean;
signedInUserIsTeamAdmin: boolean;
removeTeamMember?: typeof removeTeamMember;
updateTeamMember?: typeof updateTeamMember;
removeTeamMember: typeof removeTeamMember;
updateTeamMember: typeof updateTeamMember;
}
export class TeamMemberRow extends PureComponent<Props> {
@ -31,14 +31,17 @@ export class TeamMemberRow extends PureComponent<Props> {
onPermissionChange = (item: SelectableValue<TeamPermissionLevel>, member: TeamMember) => {
const permission = item.value;
const updatedTeamMember = { ...member, permission };
const updatedTeamMember: TeamMember = {
...member,
permission: permission as number,
};
this.props.updateTeamMember(updatedTeamMember);
};
renderPermissions(member: TeamMember) {
const { editorsCanAdmin, signedInUserIsTeamAdmin } = this.props;
const value = teamsPermissionLevels.find(dp => dp.value === member.permission);
const value = teamsPermissionLevels.find(dp => dp.value === member.permission)!;
return (
<WithFeatureToggle featureToggle={editorsCanAdmin}>

@ -20,13 +20,13 @@ export interface Props {
addTeamMember: typeof addTeamMember;
setSearchMemberQuery: typeof setSearchMemberQuery;
syncEnabled: boolean;
editorsCanAdmin?: boolean;
signedInUser?: SignedInUser;
editorsCanAdmin: boolean;
signedInUser: SignedInUser;
}
export interface State {
isAdding: boolean;
newTeamMember?: User;
newTeamMember?: User | null;
}
export class TeamMembers extends PureComponent<Props, State> {
@ -48,7 +48,7 @@ export class TeamMembers extends PureComponent<Props, State> {
};
onAddUserToTeam = async () => {
this.props.addTeamMember(this.state.newTeamMember.id);
this.props.addTeamMember(this.state.newTeamMember!.id);
this.setState({ newTeamMember: null });
};

@ -23,9 +23,9 @@ export interface Props {
teamId: number;
pageName: string;
navModel: NavModel;
members?: TeamMember[];
editorsCanAdmin?: boolean;
signedInUser?: User;
members: TeamMember[];
editorsCanAdmin: boolean;
signedInUser: User;
}
interface State {
@ -92,7 +92,7 @@ export class TeamPages extends PureComponent<Props, State> {
return navModel;
};
renderPage(isSignedInUserTeamAdmin: boolean) {
renderPage(isSignedInUserTeamAdmin: boolean): React.ReactNode {
const { isSyncEnabled } = this.state;
const { members } = this.props;
const currentPage = this.getCurrentPage();

@ -54,8 +54,8 @@ export function getTeamLoadingNav(pageName: string): NavModel {
let node: NavModelItem;
// find active page
for (const child of main.children) {
if (child.id.indexOf(pageName) > 0) {
for (const child of main.children!) {
if (child.id!.indexOf(pageName) > 0) {
child.active = true;
node = child;
break;
@ -64,6 +64,6 @@ export function getTeamLoadingNav(pageName: string): NavModel {
return {
main: main,
node: node,
node: node!,
};
}

@ -318,9 +318,9 @@ export class TemplateSrv implements BaseTemplateSrv {
return scopedVar.value;
}
replace(target: string, scopedVars?: ScopedVars, format?: string | Function): string {
replace(target?: string, scopedVars?: ScopedVars, format?: string | Function): string {
if (!target) {
return target;
return target ?? '';
}
this.regex.lastIndex = 0;

@ -16,7 +16,7 @@ export interface OptionsPickerState {
id: string;
selectedValues: VariableOption[];
selectedTags: VariableTag[];
queryValue: string | null;
queryValue: string;
highlightIndex: number;
tags: VariableTag[];
options: VariableOption[];
@ -26,7 +26,7 @@ export interface OptionsPickerState {
export const initialState: OptionsPickerState = {
id: '',
highlightIndex: -1,
queryValue: null,
queryValue: '',
selectedTags: [],
selectedValues: [],
tags: [],

@ -120,21 +120,34 @@ export default class CloudMonitoringMetricFindQuery {
return [];
}
const metricDescriptors = await this.datasource.getMetricTypes(projectName);
const { valueType, metricKind } = metricDescriptors.find(
const descriptor = metricDescriptors.find(
(m: any) => m.type === this.datasource.templateSrv.replace(selectedMetricType)
);
return getAlignmentOptionsByMetric(valueType, metricKind).map(this.toFindQueryResult);
if (!descriptor) {
return [];
}
return getAlignmentOptionsByMetric(descriptor.valueType, descriptor.metricKind).map(this.toFindQueryResult);
}
async handleAggregationQuery({ selectedMetricType, projectName }: VariableQueryData) {
if (!selectedMetricType) {
return [];
}
const metricDescriptors = await this.datasource.getMetricTypes(projectName);
const { valueType, metricKind } = metricDescriptors.find(
const descriptor = metricDescriptors.find(
(m: any) => m.type === this.datasource.templateSrv.replace(selectedMetricType)
);
return getAggregationOptionsByMetric(valueType as ValueTypes, metricKind as MetricKind).map(this.toFindQueryResult);
if (!descriptor) {
return [];
}
return getAggregationOptionsByMetric(descriptor.valueType as ValueTypes, descriptor.metricKind as MetricKind).map(
this.toFindQueryResult
);
}
async handleSLOServicesQuery({ projectName }: VariableQueryData) {

@ -38,7 +38,7 @@ export default class Api {
method: 'GET',
});
const responsePropName = path.match(/([^\/]*)\/*$/)[1];
const responsePropName = path.match(/([^\/]*)\/*$/)![1];
let res = [];
if (response && response.data && response.data[responsePropName]) {
res = response.data[responsePropName].map(responseMap);

@ -13,7 +13,7 @@ const props: Props = {
metricDescriptor: {
valueType: '',
metricKind: '',
},
} as any,
crossSeriesReducer: '',
groupBys: [],
children: renderProps => <div />,
@ -33,7 +33,7 @@ describe('Aggregations', () => {
metricDescriptor: {
valueType: ValueTypes.DOUBLE,
metricKind: MetricKind.GAUGE,
},
} as any,
};
it('should not have the reduce values', () => {
@ -54,7 +54,7 @@ describe('Aggregations', () => {
metricDescriptor: {
valueType: ValueTypes.MONEY,
metricKind: MetricKind.CUMULATIVE,
},
} as any,
};
it('should have the reduce values', () => {

@ -5,16 +5,14 @@ import { SelectableValue } from '@grafana/data';
import { Segment, Icon } from '@grafana/ui';
import { getAggregationOptionsByMetric } from '../functions';
import { ValueTypes, MetricKind } from '../constants';
import { MetricDescriptor } from '../types';
export interface Props {
onChange: (metricDescriptor: string) => void;
metricDescriptor: {
valueType: string;
metricKind: string;
};
metricDescriptor?: MetricDescriptor;
crossSeriesReducer: string;
groupBys: string[];
children?: (renderProps: any) => JSX.Element;
children: (displayAdvancedOptions: boolean) => React.ReactNode;
templateVariableOptions: Array<SelectableValue<string>>;
}
@ -28,7 +26,7 @@ export const Aggregations: FC<Props> = props => {
<div className="gf-form-inline">
<label className="gf-form-label query-keyword width-9">Aggregation</label>
<Segment
onChange={({ value }) => props.onChange(value)}
onChange={({ value }) => props.onChange(value!)}
value={selected}
options={[
{

@ -4,11 +4,11 @@ import { QueryInlineField } from '.';
export interface Props {
onChange: (alias: any) => void;
value: string;
value?: string;
}
export const AliasBy: FunctionComponent<Props> = ({ value = '', onChange }) => {
const [alias, setAlias] = useState(value);
const [alias, setAlias] = useState(value ?? '');
const propagateOnChange = debounce(onChange, 1000);

@ -14,7 +14,7 @@ export interface Props {
datasource: CloudMonitoringDatasource;
projectName: string;
metricType: string;
children?: (renderProps: any) => JSX.Element;
children: (metricDescriptor?: MetricDescriptor) => JSX.Element;
}
interface State {
@ -23,8 +23,8 @@ interface State {
services: any[];
service: string;
metric: string;
metricDescriptor: MetricDescriptor;
projectName: string;
metricDescriptor?: MetricDescriptor;
projectName: string | null;
}
export function Metrics(props: Props) {
@ -34,7 +34,6 @@ export function Metrics(props: Props) {
services: [],
service: '',
metric: '',
metricDescriptor: null,
projectName: null,
});
@ -57,7 +56,7 @@ export function Metrics(props: Props) {
}, [projectName]);
const getSelectedMetricDescriptor = (metricDescriptors: MetricDescriptor[], metricType: string) => {
return metricDescriptors.find(md => md.type === props.templateSrv.replace(metricType));
return metricDescriptors.find(md => md.type === props.templateSrv.replace(metricType))!;
};
const getMetricsList = (metricDescriptors: MetricDescriptor[]) => {
@ -97,9 +96,9 @@ export function Metrics(props: Props) {
};
const onMetricTypeChange = ({ value }: SelectableValue<string>, extra: any = {}) => {
const metricDescriptor = getSelectedMetricDescriptor(state.metricDescriptors, value);
const metricDescriptor = getSelectedMetricDescriptor(state.metricDescriptors, value!);
setState({ ...state, metricDescriptor, ...extra });
props.onChange({ ...metricDescriptor, type: value });
props.onChange({ ...metricDescriptor, type: value! });
};
const getServicesList = (metricDescriptors: MetricDescriptor[]) => {

@ -63,8 +63,8 @@ export class QueryEditor extends PureComponent<Props, State> {
render() {
const { datasource, query, onRunQuery, onChange } = this.props;
const metricQuery = { ...defaultQuery, projectName: datasource.getDefaultProject(), ...query.metricQuery };
const sloQuery = { ...defaultSLOQuery, projectName: datasource.getDefaultProject(), ...query.sloQuery };
const metricQuery = { ...defaultQuery, ...query.metricQuery, projectName: datasource.getDefaultProject() };
const sloQuery = { ...defaultSLOQuery, ...query.sloQuery, projectName: datasource.getDefaultProject() };
const queryType = query.queryType || QueryType.METRICS;
const meta = this.props.data?.series.length ? this.props.data?.series[0].meta : {};
const usedAlignmentPeriod = meta?.alignmentPeriod as string;

@ -87,7 +87,7 @@ export class CloudMonitoringVariableQueryEditor extends PureComponent<VariableQu
onPropsChange = () => {
const { metricDescriptors, labels, metricTypes, services, ...queryModel } = this.state;
const query = this.queryTypes.find(q => q.value === this.state.selectedQueryType);
const query = this.queryTypes.find(q => q.value === this.state.selectedQueryType)!;
this.props.onChange(queryModel, `Google Cloud Monitoring - ${query.name}`);
};

@ -263,8 +263,8 @@ export default class CloudMonitoringDatasource extends DataSourceApi<CloudMonito
async getSLOServices(projectName: string): Promise<Array<SelectableValue<string>>> {
return this.api.get(`${this.templateSrv.replace(projectName)}/services`, {
responseMap: ({ name }: { name: string }) => ({
value: name.match(/([^\/]*)\/*$/)[1],
label: name.match(/([^\/]*)\/*$/)[1],
value: name.match(/([^\/]*)\/*$/)![1],
label: name.match(/([^\/]*)\/*$/)![1],
}),
});
}
@ -276,7 +276,7 @@ export default class CloudMonitoringDatasource extends DataSourceApi<CloudMonito
let { projectName: p, serviceId: s } = this.interpolateProps({ projectName, serviceId });
return this.api.get(`${p}/services/${s}/serviceLevelObjectives`, {
responseMap: ({ name, displayName, goal }: { name: string; displayName: string; goal: number }) => ({
value: name.match(/([^\/]*)\/*$/)[1],
value: name.match(/([^\/]*)\/*$/)![1],
label: displayName,
goal,
}),
@ -323,7 +323,7 @@ export default class CloudMonitoringDatasource extends DataSourceApi<CloudMonito
return false;
}
if (query.queryType && query.queryType === QueryType.SLO) {
if (query.queryType && query.queryType === QueryType.SLO && query.sloQuery) {
const { selectorName, serviceId, sloId, projectName } = query.sloQuery;
return !!selectorName && !!serviceId && !!sloId && !!projectName;
}

@ -93,7 +93,7 @@ export const labelsToGroupedOptions = (groupBys: string[]) => {
};
export const filtersToStringArray = (filters: Filter[]) => {
const strArr = _.flatten(filters.map(({ key, operator, value, condition }) => [key, operator, value, condition]));
const strArr = _.flatten(filters.map(({ key, operator, value, condition }) => [key, operator, value, condition!]));
return strArr.filter((_, i) => i !== strArr.length - 1);
};

@ -8,7 +8,7 @@ import { CloudWatchDatasource } from '../datasource';
interface Props {
query: CloudWatchLogsQuery;
panelData: PanelData;
panelData?: PanelData;
datasource: CloudWatchDatasource;
}
@ -18,8 +18,12 @@ interface State {
export default class CloudWatchLink extends Component<Props, State> {
state: State = { href: '' };
async componentDidUpdate(prevProps: Props) {
if (prevProps.panelData !== this.props.panelData && this.props.panelData.request) {
const { panelData: panelDataNew } = this.props;
const { panelData: panelDataOld } = prevProps;
if (panelDataOld !== panelDataNew && panelDataNew?.request) {
const href = this.getExternalLink();
this.setState({ href });
}

@ -31,7 +31,7 @@ export const Dimensions: FunctionComponent<Props> = ({ dimensions, loadValues, l
}, [data]);
const excludeUsedKeys = (options: SelectableStrings) => {
return options.filter(({ value }) => !Object.keys(data).includes(value));
return options.filter(({ value }) => !Object.keys(data).includes(value!));
};
return (
@ -47,7 +47,7 @@ export const Dimensions: FunctionComponent<Props> = ({ dimensions, loadValues, l
if (newKey === removeText) {
setData({ ...newDimensions });
} else {
setData({ ...newDimensions, [newKey]: '' });
setData({ ...newDimensions, [newKey!]: '' });
}
}}
/>
@ -57,7 +57,7 @@ export const Dimensions: FunctionComponent<Props> = ({ dimensions, loadValues, l
value={value}
placeholder="select dimension value"
loadOptions={() => loadValues(key)}
onChange={({ value: newValue }) => setData({ ...data, [key]: newValue })}
onChange={({ value: newValue }) => setData({ ...data, [key]: newValue! })}
/>
{Object.values(data).length > 1 && index + 1 !== Object.values(data).length && (
<label className="gf-form-label query-keyword">AND</label>
@ -73,7 +73,7 @@ export const Dimensions: FunctionComponent<Props> = ({ dimensions, loadValues, l
</a>
}
loadOptions={() => loadKeys().then(excludeUsedKeys)}
onChange={({ value: newKey }) => setData({ ...data, [newKey]: '' })}
onChange={({ value: newKey }) => setData({ ...data, [newKey!]: '' })}
/>
)}
</>

@ -40,7 +40,7 @@ export interface CloudWatchLogsQueryFieldProps extends ExploreQueryFieldProps<Cl
onLabelsRefresh?: () => void;
ExtraFieldElement?: ReactNode;
syntaxLoaded: boolean;
syntax: Grammar;
syntax: Grammar | null;
exploreId: ExploreId;
allowCustomValue?: boolean;
}

@ -94,7 +94,7 @@ export function MetricsQueryFieldsEditor({
placeholder="Select region"
options={regions}
allowCustomValue
onChange={({ value: region }) => onQueryChange({ ...query, region })}
onChange={({ value: region }) => onQueryChange({ ...query, region: region! })}
/>
</QueryInlineField>
@ -106,7 +106,7 @@ export function MetricsQueryFieldsEditor({
placeholder="Select namespace"
allowCustomValue
options={namespaces}
onChange={({ value: namespace }) => onQueryChange({ ...query, namespace })}
onChange={({ value: namespace }) => onQueryChange({ ...query, namespace: namespace! })}
/>
</QueryInlineField>

@ -26,7 +26,7 @@ export const Stats: FunctionComponent<Props> = ({ stats, values, onChange, varia
onChange(
value === removeText
? values.filter((_, i) => i !== index)
: values.map((v, i) => (i === index ? value : v))
: values.map((v, i) => (i === index ? value! : v))
)
}
/>
@ -38,8 +38,8 @@ export const Stats: FunctionComponent<Props> = ({ stats, values, onChange, varia
</a>
}
allowCustomValue
onChange={({ value }) => onChange([...values, value])}
options={[...stats.filter(({ value }) => !values.includes(value)), variableOptionGroup]}
onChange={({ value }) => onChange([...values, value!])}
options={[...stats.filter(({ value }) => !values.includes(value!)), variableOptionGroup]}
/>
</>
);

@ -177,7 +177,6 @@ export class CloudWatchDatasource extends DataSourceApi<CloudWatchQuery, CloudWa
}
return {
refId: item.refId,
intervalMs: options.intervalMs,
maxDataPoints: options.maxDataPoints,
datasourceId: this.id,

@ -178,7 +178,7 @@ export class CloudWatchLanguageProvider extends LanguageProvider {
private handleCommand = async (
commandToken: Token,
curToken: Token,
context: TypeaheadContext
context?: TypeaheadContext
): Promise<TypeaheadOutput> => {
const queryCommand = commandToken.content.toLowerCase();
const prevToken = prevNonWhitespaceToken(curToken);
@ -190,11 +190,11 @@ export class CloudWatchLanguageProvider extends LanguageProvider {
if (queryCommand === 'parse') {
if (currentTokenIsFirstArg) {
return await this.getFieldCompletionItems(context.logGroupNames ?? []);
return await this.getFieldCompletionItems(context?.logGroupNames ?? []);
}
}
const currentTokenIsAfterCommandAndEmpty = isTokenType(commandToken.next, 'whitespace') && !commandToken.next.next;
const currentTokenIsAfterCommandAndEmpty = isTokenType(commandToken.next, 'whitespace') && !commandToken.next?.next;
const currentTokenIsAfterCommand =
currentTokenIsAfterCommandAndEmpty || nextNonWhitespaceToken(commandToken) === curToken;
@ -207,7 +207,7 @@ export class CloudWatchLanguageProvider extends LanguageProvider {
}
if (['display', 'fields'].includes(queryCommand)) {
const typeaheadOutput = await this.getFieldCompletionItems(context.logGroupNames ?? []);
const typeaheadOutput = await this.getFieldCompletionItems(context?.logGroupNames ?? []);
typeaheadOutput.suggestions.push(...this.getFieldAndFilterFunctionCompletionItems().suggestions);
return typeaheadOutput;
@ -224,7 +224,7 @@ export class CloudWatchLanguageProvider extends LanguageProvider {
}
if (queryCommand === 'filter' && currentTokenIsFirstArg) {
const sugg = await this.getFieldCompletionItems(context.logGroupNames ?? []);
const sugg = await this.getFieldCompletionItems(context?.logGroupNames ?? []);
const boolFuncs = this.getBoolFuncCompletionItems();
sugg.suggestions.push(...boolFuncs.suggestions);
return sugg;
@ -235,10 +235,10 @@ export class CloudWatchLanguageProvider extends LanguageProvider {
private async handleSortCommand(
isFirstArgument: boolean,
curToken: Token,
context: TypeaheadContext
context?: TypeaheadContext
): Promise<TypeaheadOutput> {
if (isFirstArgument) {
return await this.getFieldCompletionItems(context.logGroupNames ?? []);
return await this.getFieldCompletionItems(context?.logGroupNames ?? []);
} else if (isTokenType(prevNonWhitespaceToken(curToken), 'field-name')) {
// suggest sort options
return {

@ -52,7 +52,7 @@ export class DashboardQueryEditor extends PureComponent<Props, State> {
}
async componentDidMount() {
this.componentDidUpdate(null);
this.componentDidUpdate(this.props);
}
async componentDidUpdate(prevProps: Props) {
@ -60,9 +60,9 @@ export class DashboardQueryEditor extends PureComponent<Props, State> {
if (!prevProps || prevProps.panelData !== panelData) {
const query = this.props.panel.targets[0] as DashboardQuery;
const defaultDS = await getDatasourceSrv().get(null);
const defaultDS = await getDatasourceSrv().get();
const dashboard = getDashboardSrv().getCurrent();
const panel = dashboard.getPanelById(query.panelId);
const panel = dashboard.getPanelById(query.panelId ?? -124134);
if (!panel) {
this.setState({ defaultDatasource: defaultDS.name });
@ -143,7 +143,7 @@ export class DashboardQueryEditor extends PureComponent<Props, State> {
const dashboard = getDashboardSrv().getCurrent();
const query = this.getQuery();
let selected: SelectableValue<number>;
let selected: SelectableValue<number> | undefined;
const panels: Array<SelectableValue<number>> = [];
for (const panel of dashboard.panels) {
@ -188,7 +188,7 @@ export class DashboardQueryEditor extends PureComponent<Props, State> {
isSearchable={true}
options={panels}
value={selected}
onChange={item => this.onPanelChanged(item.value)}
onChange={item => this.onPanelChanged(item.value!)}
/>
</div>
<div className={css({ padding: '16px' })}>{query.panelId && this.renderQueryData(editURL)}</div>

@ -23,7 +23,7 @@ export function runSharedRequest(options: QueryRunnerOptions): Observable<PanelD
if (!listenToPanelId) {
subscriber.next(getQueryError('Missing panel reference ID'));
return null;
return undefined;
}
const currentPanel = dashboard.getPanelById(options.panelId);
@ -31,7 +31,7 @@ export function runSharedRequest(options: QueryRunnerOptions): Observable<PanelD
if (!listenToPanel) {
subscriber.next(getQueryError('Unknown Panel: ' + listenToPanelId));
return null;
return undefined;
}
const listenToRunner = listenToPanel.getQueryRunner();

@ -152,7 +152,7 @@ const DataSourceSection = (props: DataSourceSectionProps) => {
);
};
function useInternalLink(datasourceUid: string): [boolean, Dispatch<SetStateAction<boolean>>] {
function useInternalLink(datasourceUid?: string): [boolean, Dispatch<SetStateAction<boolean>>] {
const [showInternalLink, setShowInternalLink] = useState<boolean>(!!datasourceUid);
const previousUid = usePrevious(datasourceUid);

@ -86,13 +86,13 @@ export const ElasticDetails = (props: Props) => {
onChange={option => {
const maxConcurrentShardRequests = getMaxConcurrenShardRequestOrDefault(
value.jsonData.maxConcurrentShardRequests,
option.value
option.value!
);
onChange({
...value,
jsonData: {
...value.jsonData,
esVersion: option.value,
esVersion: option.value!,
maxConcurrentShardRequests,
},
});
@ -179,10 +179,12 @@ const intervalHandler = (value: Props['value'], onChange: Props['onChange']) =>
if (!database || database.length === 0 || database.startsWith('[logstash-]')) {
let newDatabase = '';
if (newInterval !== undefined) {
const pattern = indexPatternTypes.find(pattern => pattern.value === newInterval);
if (pattern) {
newDatabase = pattern.example;
newDatabase = pattern.example ?? '';
}
}
@ -205,7 +207,7 @@ const intervalHandler = (value: Props['value'], onChange: Props['onChange']) =>
}
};
function getMaxConcurrenShardRequestOrDefault(maxConcurrentShardRequests: number, version: number): number {
function getMaxConcurrenShardRequestOrDefault(maxConcurrentShardRequests: number | undefined, version: number): number {
if (maxConcurrentShardRequests === 5 && version < 70) {
return 256;
}

@ -20,15 +20,15 @@ import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { DataLinkConfig, ElasticsearchOptions, ElasticsearchQuery } from './types';
export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, ElasticsearchOptions> {
basicAuth: string;
withCredentials: boolean;
basicAuth?: string;
withCredentials?: boolean;
url: string;
name: string;
index: string;
timeField: string;
esVersion: number;
interval: string;
maxConcurrentShardRequests: number;
maxConcurrentShardRequests?: number;
queryBuilder: ElasticQueryBuilder;
indexPattern: IndexPattern;
logMessageField?: string;
@ -44,9 +44,9 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
super(instanceSettings);
this.basicAuth = instanceSettings.basicAuth;
this.withCredentials = instanceSettings.withCredentials;
this.url = instanceSettings.url;
this.url = instanceSettings.url!;
this.name = instanceSettings.name;
this.index = instanceSettings.database;
this.index = instanceSettings.database ?? '';
const settingsData = instanceSettings.jsonData || ({} as ElasticsearchOptions);
this.timeField = settingsData.timeField;
@ -63,11 +63,11 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
this.dataLinks = settingsData.dataLinks || [];
if (this.logMessageField === '') {
this.logMessageField = null;
this.logMessageField = undefined;
}
if (this.logLevelField === '') {
this.logLevelField = null;
this.logLevelField = undefined;
}
}
@ -335,9 +335,11 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
ignore_unavailable: true,
index: this.indexPattern.getIndexList(timeFrom, timeTo),
};
if (this.esVersion >= 56 && this.esVersion < 70) {
queryHeader['max_concurrent_shard_requests'] = this.maxConcurrentShardRequests;
}
return angular.toJson(queryHeader);
}
@ -402,6 +404,7 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
return this.post(url, payload).then((res: any) => {
const er = new ElasticResponse(sentTargets, res);
if (sentTargets.some(target => target.isLogsQuery)) {
const response = er.getLogs(this.logMessageField, this.logLevelField);
for (const dataFrame of response.data) {

@ -12,7 +12,8 @@ export class ElasticResponse {
}
processMetrics(esAgg: any, target: any, seriesList: any, props: any) {
let metric, y, i, newSeries, bucket, value;
let metric, y, i, bucket, value;
let newSeries: any;
for (y = 0; y < target.metrics.length; y++) {
metric = target.metrics[y];
@ -486,7 +487,7 @@ const flattenHits = (hits: Doc[]): { docs: Array<Record<string, any>>; propNames
let propNames: string[] = [];
for (const hit of hits) {
const flattened = hit._source ? flatten(hit._source, null) : {};
const flattened = hit._source ? flatten(hit._source) : {};
const doc = {
_id: hit._id,
_type: hit._type,

@ -9,7 +9,7 @@ const intervalMap: any = {
};
export class IndexPattern {
constructor(private pattern: any, private interval: string | null) {}
constructor(private pattern: any, private interval?: string) {}
getIndexForToday() {
if (this.interval) {

@ -89,7 +89,7 @@ export class ElasticMetricAggCtrl {
}
return memo;
},
[]
[] as string[]
);
$scope.settingsLinkText = 'Stats: ' + stats.join(', ');

@ -3,7 +3,7 @@ import { DataQuery, DataSourceJsonData } from '@grafana/data';
export interface ElasticsearchOptions extends DataSourceJsonData {
timeField: string;
esVersion: number;
interval: string;
interval?: string;
timeInterval: string;
maxConcurrentShardRequests?: number;
logMessageField?: string;

@ -423,7 +423,7 @@ describe('AppInsightsDatasource', () => {
});
it('should return a list of metric names', () => {
return ctx.ds.metricFindQuery('appInsightsMetricNames()').then((results: any) => {
return ctx.ds.metricFindQueryInternal('appInsightsMetricNames()').then((results: any) => {
expect(results.length).toBe(2);
expect(results[0].text).toBe('exceptions/server');
expect(results[0].value).toBe('exceptions/server');
@ -461,7 +461,7 @@ describe('AppInsightsDatasource', () => {
});
it('should return a list of group bys', () => {
return ctx.ds.metricFindQuery('appInsightsGroupBys(requests/count)').then((results: any) => {
return ctx.ds.metricFindQueryInternal('appInsightsGroupBys(requests/count)').then((results: any) => {
expect(results[0].text).toContain('client/os');
expect(results[0].value).toContain('client/os');
expect(results[1].text).toContain('client/city');

@ -1,4 +1,4 @@
import { ScopedVars } from '@grafana/data';
import { ScopedVars, MetricFindValue } from '@grafana/data';
import { DataQueryRequest, DataSourceInstanceSettings } from '@grafana/data';
import { getBackendSrv, getTemplateSrv, DataSourceWithBackend } from '@grafana/runtime';
import _, { isString } from 'lodash';
@ -122,7 +122,13 @@ export default class AppInsightsDatasource extends DataSourceWithBackend<AzureMo
};
}
metricFindQuery(query: string) {
/**
* This is named differently than DataSourceApi.metricFindQuery
* because it's not exposed to Grafana like the main AzureMonitorDataSource.
* And some of the azure internal data sources return null in this function, which the
* external interface does not support
*/
metricFindQueryInternal(query: string): Promise<MetricFindValue[]> | null {
const appInsightsMetricNameQuery = query.match(/^AppInsightsMetricNames\(\)/i);
if (appInsightsMetricNameQuery) {
return this.getMetricNames();
@ -134,7 +140,7 @@ export default class AppInsightsDatasource extends DataSourceWithBackend<AzureMo
return this.getGroupBys(getTemplateSrv().replace(metricName));
}
return undefined;
return null;
}
testDatasource() {

@ -2,13 +2,7 @@ import _ from 'lodash';
import LogAnalyticsQuerystringBuilder from '../log_analytics/querystring_builder';
import ResponseParser from './response_parser';
import { AzureMonitorQuery, AzureDataSourceJsonData, AzureLogsVariable, AzureQueryType } from '../types';
import {
DataQueryResponse,
ScopedVars,
DataSourceInstanceSettings,
QueryResultMeta,
MetricFindValue,
} from '@grafana/data';
import { DataQueryResponse, ScopedVars, DataSourceInstanceSettings, MetricFindValue } from '@grafana/data';
import { getBackendSrv, getTemplateSrv, DataSourceWithBackend } from '@grafana/runtime';
export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
@ -140,7 +134,7 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
for (const df of res.data) {
const encodedQuery = df.meta?.custom?.encodedQuery;
if (encodedQuery && encodedQuery.length > 0) {
const url = await this.buildDeepLink(df.meta);
const url = await this.buildDeepLink(df.meta.custom);
if (url?.length) {
for (const field of df.fields) {
field.config.links = [
@ -158,10 +152,10 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
return res;
}
private async buildDeepLink(meta: QueryResultMeta) {
const base64Enc = encodeURIComponent(meta.custom.encodedQuery);
const workspaceId = meta.custom.workspace;
const subscription = meta.custom.subscription;
private async buildDeepLink(customMeta: Record<string, any>) {
const base64Enc = encodeURIComponent(customMeta.encodedQuery);
const workspaceId = customMeta.workspace;
const subscription = customMeta.subscription;
const details = await this.getWorkspaceDetails(workspaceId);
if (!details.workspace || !details.resourceGroup) {
@ -200,7 +194,13 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
};
}
metricFindQuery(query: string): Promise<MetricFindValue[]> {
/**
* This is named differently than DataSourceApi.metricFindQuery
* because it's not exposed to Grafana like the main AzureMonitorDataSource.
* And some of the azure internal data sources return null in this function, which the
* external interface does not support
*/
metricFindQueryInternal(query: string): Promise<MetricFindValue[]> {
const workspacesQuery = query.match(/^workspaces\(\)/i);
if (workspacesQuery) {
return this.getWorkspaces(this.subscriptionId);
@ -233,7 +233,7 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
throw { message: err.error.data.error.message };
}
});
}) as Promise<MetricFindValue[]>; // ??
}) as Promise<MetricFindValue[]>;
}
private buildQuery(query: string, options: any, workspace: any) {

@ -10,7 +10,7 @@ import {
AzureMonitorResourceGroupsResponse,
AzureQueryType,
} from '../types';
import { DataSourceInstanceSettings, ScopedVars } from '@grafana/data';
import { DataSourceInstanceSettings, ScopedVars, MetricFindValue } from '@grafana/data';
import { getBackendSrv, DataSourceWithBackend, getTemplateSrv } from '@grafana/runtime';
const defaultDropdownValue = 'select';
@ -32,8 +32,7 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
this.subscriptionId = instanceSettings.jsonData.subscriptionId;
this.cloudName = instanceSettings.jsonData.cloudName || 'azuremonitor';
this.baseUrl = `/${this.cloudName}/subscriptions`;
this.url = instanceSettings.url;
this.url = instanceSettings.url!;
this.supportedMetricNamespaces = new SupportedNamespaces(this.cloudName).get();
}
@ -44,13 +43,9 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
filterQuery(item: AzureMonitorQuery): boolean {
return (
item.hide !== true &&
item.azureMonitor.resourceGroup &&
item.azureMonitor.resourceGroup !== defaultDropdownValue &&
item.azureMonitor.resourceName &&
item.azureMonitor.resourceName !== defaultDropdownValue &&
item.azureMonitor.metricDefinition &&
item.azureMonitor.metricDefinition !== defaultDropdownValue &&
item.azureMonitor.metricName &&
item.azureMonitor.metricName !== defaultDropdownValue
);
}
@ -77,7 +72,7 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
const dimensionsFilters = item.dimensionFilters
.filter(f => f.dimension && f.dimension !== 'None')
.map(f => {
const filter = templateSrv.replace(f.filter, scopedVars);
const filter = templateSrv.replace(f.filter ?? '', scopedVars);
return {
dimension: templateSrv.replace(f.dimension, scopedVars),
operator: f.operator || 'eq',
@ -107,7 +102,13 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
};
}
metricFindQuery(query: string) {
/**
* This is named differently than DataSourceApi.metricFindQuery
* because it's not exposed to Grafana like the main AzureMonitorDataSource.
* And some of the azure internal data sources return null in this function, which the
* external interface does not support
*/
metricFindQueryInternal(query: string): Promise<MetricFindValue[]> | null {
const subscriptionsQuery = query.match(/^Subscriptions\(\)/i);
if (subscriptionsQuery) {
return this.getSubscriptions();
@ -196,7 +197,7 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
return this.getMetricNames(subscription, resourceGroup, metricDefinition, resourceName, metricNamespace);
}
return undefined;
return null;
}
toVariable(metric: string) {
@ -395,7 +396,7 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
});
}
isValidConfigField(field: string) {
isValidConfigField(field?: string) {
return field && field.length > 0;
}

@ -53,7 +53,7 @@ export class AnalyticsConfig extends PureComponent<Props, State> {
onAzureLogAnalyticsSameAsChange = () => {
const { options, onUpdateDatasourceOptions, makeSameAs } = this.props;
if (!options.jsonData.azureLogAnalyticsSameAs && options.secureJsonData.clientSecret) {
if (!options.jsonData.azureLogAnalyticsSameAs && options.secureJsonData!.clientSecret) {
makeSameAs();
} else if (!options.jsonData.azureLogAnalyticsSameAs) {
// if currently off, clear monitor secret
@ -94,7 +94,7 @@ export class AnalyticsConfig extends PureComponent<Props, State> {
jsonData.tenantId &&
jsonData.clientId &&
jsonData.subscriptionId &&
(secureJsonData.clientSecret || secureJsonFields.clientSecret)
(secureJsonData!.clientSecret || secureJsonFields.clientSecret)
);
}
@ -104,7 +104,7 @@ export class AnalyticsConfig extends PureComponent<Props, State> {
jsonData.logAnalyticsClientId &&
jsonData.logAnalyticsClientId.length &&
jsonData.logAnalyticsSubscriptionId &&
(secureJsonFields.logAnalyticsClientSecret || secureJsonData.logAnalyticsClientSecret)
(secureJsonFields.logAnalyticsClientSecret || secureJsonData!.logAnalyticsClientSecret)
);
};
@ -132,14 +132,14 @@ export class AnalyticsConfig extends PureComponent<Props, State> {
jsonData.azureLogAnalyticsSameAs &&
secureJsonFields &&
!secureJsonFields.clientSecret &&
!secureJsonData.clientSecret;
!secureJsonData!.clientSecret;
return (
<>
<h3 className="page-heading">Azure Log Analytics API Details</h3>
<Switch
label="Same details as Azure Monitor API"
checked={jsonData.azureLogAnalyticsSameAs}
checked={jsonData.azureLogAnalyticsSameAs ?? false}
onChange={this.onAzureLogAnalyticsSameAsChange}
{...addtlAttrs}
/>
@ -156,7 +156,7 @@ export class AnalyticsConfig extends PureComponent<Props, State> {
selectedSubscription={jsonData.logAnalyticsSubscriptionId}
tenantId={jsonData.logAnalyticsTenantId}
clientId={jsonData.logAnalyticsClientId}
clientSecret={secureJsonData.logAnalyticsClientSecret}
clientSecret={secureJsonData!.logAnalyticsClientSecret}
clientSecretConfigured={secureJsonFields.logAnalyticsClientSecret}
onSubscriptionSelectChange={this.onLogAnalyticsSubscriptionSelect}
onTenantIdChange={this.onLogAnalyticsTenantIdChange}

@ -7,10 +7,10 @@ export interface Props {
selectedAzureCloud?: string;
selectedSubscription?: string;
azureCloudOptions?: SelectableValue[];
tenantId: string;
clientId: string;
clientSecret: string;
clientSecretConfigured: boolean;
tenantId?: string;
clientId?: string;
clientSecret?: string;
clientSecretConfigured?: boolean;
subscriptionOptions?: SelectableValue[];
onAzureCloudChange?: (value: SelectableValue<string>) => void;
onSubscriptionSelectChange?: (value: SelectableValue<string>) => void;
@ -124,7 +124,7 @@ export class AzureCredentialsForm extends PureComponent<Props> {
<InlineFormLabel className="width-12">Default Subscription</InlineFormLabel>
<div className="width-25">
<Select
value={subscriptionOptions.find(subscription => subscription.value === selectedSubscription)}
value={subscriptionOptions!.find(subscription => subscription.value === selectedSubscription)}
options={subscriptionOptions}
defaultValue={selectedSubscription}
onChange={onSubscriptionSelectChange}

@ -9,8 +9,7 @@ import {
} from '@grafana/data';
import { MonitorConfig } from './MonitorConfig';
import { AnalyticsConfig } from './AnalyticsConfig';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { getBackendSrv } from '@grafana/runtime';
import { getBackendSrv, TemplateSrv, getTemplateSrv } from '@grafana/runtime';
import { InsightsConfig } from './InsightsConfig';
import ResponseParser from '../azure_monitor/response_parser';
import { AzureDataSourceJsonData, AzureDataSourceSecureJsonData, AzureDataSourceSettings } from '../types';
@ -27,6 +26,9 @@ export interface State {
}
export class ConfigEditor extends PureComponent<Props, State> {
initPromise: CancelablePromise<any> | null = null;
templateSrv: TemplateSrv = getTemplateSrv();
constructor(props: Props) {
super(props);
@ -38,15 +40,11 @@ export class ConfigEditor extends PureComponent<Props, State> {
logAnalyticsSubscriptionId: '',
};
this.templateSrv = new TemplateSrv();
if (this.props.options.id) {
updateDatasourcePluginOption(this.props, 'url', '/api/datasources/proxy/' + this.props.options.id);
}
}
initPromise: CancelablePromise<any> = null;
templateSrv: TemplateSrv = null;
componentDidMount() {
this.initPromise = makePromiseCancelable(this.init());
this.initPromise.promise.catch(({ isCanceled }) => {
@ -57,7 +55,7 @@ export class ConfigEditor extends PureComponent<Props, State> {
}
componentWillUnmount() {
this.initPromise.cancel();
this.initPromise!.cancel();
}
init = async () => {
@ -94,7 +92,7 @@ export class ConfigEditor extends PureComponent<Props, State> {
makeSameAs = (updatedClientSecret?: string) => {
const { options } = this.props;
const clientSecret = updatedClientSecret || options.secureJsonData.clientSecret;
const clientSecret = updatedClientSecret || options.secureJsonData!.clientSecret;
this.props.onOptionsChange({
...options,
@ -114,7 +112,7 @@ export class ConfigEditor extends PureComponent<Props, State> {
};
hasNecessaryCredentials = () => {
if (!this.props.options.secureJsonFields.clientSecret && !this.props.options.secureJsonData.clientSecret) {
if (!this.props.options.secureJsonFields.clientSecret && !this.props.options.secureJsonData!.clientSecret) {
return false;
}
@ -128,7 +126,7 @@ export class ConfigEditor extends PureComponent<Props, State> {
logAnalyticsHasNecessaryCredentials = () => {
if (
!this.props.options.secureJsonFields.logAnalyticsClientSecret &&
!this.props.options.secureJsonData.logAnalyticsClientSecret
!this.props.options.secureJsonData!.logAnalyticsClientSecret
) {
return false;
}
@ -174,7 +172,7 @@ export class ConfigEditor extends PureComponent<Props, State> {
const azureCloud = cloudName || 'azuremonitor';
azureMonitorUrl = `/${azureCloud}/subscriptions`;
} else {
subscriptionId = logAnalyticsSubscriptionId;
subscriptionId = logAnalyticsSubscriptionId!;
azureMonitorUrl = `/workspacesloganalytics/subscriptions`;
}
@ -231,14 +229,14 @@ export class ConfigEditor extends PureComponent<Props, State> {
};
getWorkspaces = async () => {
const sameAs = this.props.options.jsonData.azureLogAnalyticsSameAs && this.props.options.jsonData.subscriptionId;
if (!sameAs && !this.props.options.jsonData.logAnalyticsSubscriptionId) {
const { subscriptionId, azureLogAnalyticsSameAs, logAnalyticsSubscriptionId } = this.props.options.jsonData;
const subscriptionIdToUse = azureLogAnalyticsSameAs ? subscriptionId : logAnalyticsSubscriptionId;
if (!subscriptionIdToUse) {
return;
}
const logAnalyticsWorkspaces = await this.loadWorkspaces(
sameAs ? this.props.options.jsonData.subscriptionId : this.props.options.jsonData.logAnalyticsSubscriptionId
);
const logAnalyticsWorkspaces = await this.loadWorkspaces(subscriptionIdToUse);
if (logAnalyticsWorkspaces.length > 0) {
this.setState({ logAnalyticsWorkspaces });
@ -255,6 +253,7 @@ export class ConfigEditor extends PureComponent<Props, State> {
const { options } = this.props;
options.jsonData.cloudName = options.jsonData.cloudName || 'azuremonitor';
// This is bad, causes so many messy typing issues everwhere..
options.secureJsonData = (options.secureJsonData || {}) as AzureDataSourceSecureJsonData;
return (

@ -46,7 +46,7 @@ export class InsightsConfig extends PureComponent<Props> {
<Input
className="width-30"
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
value={options.secureJsonData.appInsightsApiKey || ''}
value={options.secureJsonData!.appInsightsApiKey || ''}
onChange={onUpdateSecureJsonDataOption('appInsightsApiKey')}
/>
</div>

@ -63,7 +63,7 @@ export class MonitorConfig extends PureComponent<Props> {
selectedSubscription={options.jsonData.subscriptionId}
tenantId={options.jsonData.tenantId}
clientId={options.jsonData.clientId}
clientSecret={options.secureJsonData.clientSecret}
clientSecret={options.secureJsonData?.clientSecret}
clientSecretConfigured={options.secureJsonFields.clientSecret}
onAzureCloudChange={this.onAzureCloudSelect}
onSubscriptionSelectChange={this.onSubscriptionSelect}

@ -118,17 +118,17 @@ export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDa
return Promise.resolve([]);
}
const aiResult = this.appInsightsDatasource.metricFindQuery(query);
const aiResult = this.appInsightsDatasource.metricFindQueryInternal(query);
if (aiResult) {
return aiResult;
}
const amResult = this.azureMonitorDatasource.metricFindQuery(query);
const amResult = this.azureMonitorDatasource.metricFindQueryInternal(query);
if (amResult) {
return amResult;
}
const alaResult = this.azureLogAnalyticsDatasource.metricFindQuery(query);
const alaResult = this.azureLogAnalyticsDatasource.metricFindQueryInternal(query);
if (alaResult) {
return alaResult;
}

@ -26,7 +26,7 @@ interface SuggestionGroup {
interface KustoSchema {
Databases: {
Default?: KustoDBSchema;
Default: KustoDBSchema;
};
Plugins?: any[];
}
@ -65,7 +65,8 @@ export default class KustoQueryField extends QueryField {
onTypeahead = (force = false) => {
const selection = window.getSelection();
if (selection.anchorNode) {
if (selection && selection.anchorNode) {
const wrapperNode = selection.anchorNode.parentElement;
if (wrapperNode === null) {
return;
@ -408,7 +409,7 @@ export default class KustoQueryField extends QueryField {
if (match && match.length > 1 && match[0] && match[1]) {
return match[1];
} else {
return null;
return undefined;
}
}

@ -103,13 +103,6 @@ class QueryField extends React.Component<any, any> {
});
};
request = (url?: string) => {
if (this.props.request) {
return this.props.request(url);
}
return fetch(url);
};
onChangeQuery = () => {
// Send text change to parent
const { onQueryChange } = this.props;
@ -256,13 +249,14 @@ class QueryField extends React.Component<any, any> {
const { suggestions } = this.state;
const menu = this.menuEl;
const selection = window.getSelection();
const node = selection.anchorNode;
// No menu, nothing to do
if (!menu) {
if (!menu || !selection) {
return;
}
const node = selection.anchorNode;
// No suggestions or blur, remove menu
const hasSuggesstions = suggestions && suggestions.length > 0;
if (!hasSuggesstions) {

@ -13,8 +13,8 @@ const FunctionDescription = React.lazy(async () => {
// @ts-ignore
const { default: rst2html } = await import(/* webpackChunkName: "rst2html" */ 'rst2html');
return {
default: (props: { description: string }) => (
<div dangerouslySetInnerHTML={{ __html: rst2html(props.description) }} />
default: (props: { description?: string }) => (
<div dangerouslySetInnerHTML={{ __html: rst2html(props.description ?? '') }} />
),
};
});
@ -77,7 +77,7 @@ class FunctionEditor extends React.PureComponent<FunctionEditorProps, FunctionEd
{(showPopper, hidePopper, popperProps) => {
return (
<>
{this.triggerRef && (
{this.triggerRef.current && (
<Popover
{...popperProps}
referenceElement={this.triggerRef.current}

@ -20,7 +20,7 @@ export interface FunctionEditorControlsProps {
onRemove: (func: FunctionDescriptor) => void;
}
const FunctionHelpButton = (props: { description: string; name: string; onDescriptionShow: () => void }) => {
const FunctionHelpButton = (props: { description?: string; name: string; onDescriptionShow: () => void }) => {
if (props.description) {
return <Icon className="pointer" name="question-circle" onClick={props.onDescriptionShow} />;
}

@ -8,6 +8,7 @@ import {
QueryResultMetaStat,
ScopedVars,
toDataFrame,
TimeRange,
} from '@grafana/data';
import { isVersionGtOrEq, SemVersion } from 'app/core/utils/version';
import gfunc from './gfunc';
@ -29,7 +30,7 @@ export class GraphiteDatasource extends DataSourceApi<GraphiteQuery, GraphiteOpt
cacheTimeout: any;
withCredentials: boolean;
funcDefs: any = null;
funcDefsPromise: Promise<any> = null;
funcDefsPromise: Promise<any> | null = null;
_seriesRefLetters: string;
/** @ngInject */
@ -64,8 +65,8 @@ export class GraphiteDatasource extends DataSourceApi<GraphiteQuery, GraphiteOpt
async query(options: DataQueryRequest<GraphiteQuery>): Promise<DataQueryResponse> {
const graphOptions = {
from: this.translateTime(options.rangeRaw.from, false, options.timezone),
until: this.translateTime(options.rangeRaw.to, true, options.timezone),
from: this.translateTime(options.range.raw.from, false, options.timezone),
until: this.translateTime(options.range.raw.to, true, options.timezone),
targets: options.targets,
format: (options as any).format,
cacheTimeout: options.cacheTimeout || this.cacheTimeout,
@ -199,7 +200,7 @@ export class GraphiteDatasource extends DataSourceApi<GraphiteQuery, GraphiteOpt
const expandedQuery = {
...query,
datasource: this.name,
target: this.templateSrv.replace(query.target, scopedVars),
target: this.templateSrv.replace(query.target ?? '', scopedVars),
};
return expandedQuery;
});
@ -212,7 +213,7 @@ export class GraphiteDatasource extends DataSourceApi<GraphiteQuery, GraphiteOpt
if (options.annotation.target) {
const target = this.templateSrv.replace(options.annotation.target, {}, 'glob');
const graphiteQuery = ({
rangeRaw: options.rangeRaw,
range: options.range,
targets: [{ target: target }],
format: 'json',
maxDataPoints: 100,
@ -245,7 +246,7 @@ export class GraphiteDatasource extends DataSourceApi<GraphiteQuery, GraphiteOpt
} else {
// Graphite event as annotation
const tags = this.templateSrv.replace(options.annotation.tags);
return this.events({ range: options.rangeRaw, tags: tags }).then((results: any) => {
return this.events({ range: options.range, tags: tags }).then((results: any) => {
const list = [];
for (let i = 0; i < results.data.length; i++) {
const e = results.data[i];
@ -269,7 +270,7 @@ export class GraphiteDatasource extends DataSourceApi<GraphiteQuery, GraphiteOpt
}
}
events(options: { range: any; tags: any; timezone?: any }) {
events(options: { range: TimeRange; tags: any; timezone?: any }) {
try {
let tags = '';
if (options.tags) {
@ -279,9 +280,9 @@ export class GraphiteDatasource extends DataSourceApi<GraphiteQuery, GraphiteOpt
method: 'GET',
url:
'/events/get_data?from=' +
this.translateTime(options.range.from, false, options.timezone) +
this.translateTime(options.range.raw.from, false, options.timezone) +
'&until=' +
this.translateTime(options.range.to, true, options.timezone) +
this.translateTime(options.range.raw.to, true, options.timezone) +
tags,
});
} catch (err) {
@ -290,7 +291,7 @@ export class GraphiteDatasource extends DataSourceApi<GraphiteQuery, GraphiteOpt
}
targetContainsTemplate(target: GraphiteQuery) {
return this.templateSrv.variableExists(target.target);
return this.templateSrv.variableExists(target.target ?? '');
}
translateTime(date: any, roundUp: any, timezone: any) {

@ -975,7 +975,7 @@ export class FuncInstance {
text: any;
added: boolean;
constructor(funcDef: any, options: { withDefaultParams: any }) {
constructor(funcDef: any, options?: { withDefaultParams: any }) {
this.def = funcDef;
this.params = [];

@ -104,7 +104,7 @@ describe('graphiteDatasource', () => {
const query = {
panelId: 3,
dashboardId: 5,
rangeRaw: { from: 'now-1h', to: 'now' },
range: { raw: { from: 'now-1h', to: 'now' } },
targets: [{ target: 'prod1.count' }, { target: 'prod2.count' }],
maxDataPoints: 500,
};
@ -179,8 +179,8 @@ describe('graphiteDatasource', () => {
range: {
from: dateTime(1432288354),
to: dateTime(1432288401),
raw: { from: 'now-24h', to: 'now' },
},
rangeRaw: { from: 'now-24h', to: 'now' },
};
describe('and tags are returned as string', () => {

@ -13,15 +13,15 @@ export interface Props extends ExploreQueryFieldProps<InfluxDatasource, InfluxQu
export interface State {
measurements: CascaderOption[];
measurement: string;
field: string;
error: string;
measurement: string | null;
field: string | null;
error: string | null;
}
interface ChooserOptions {
measurement: string;
field: string;
error: string;
measurement: string | null;
field: string | null;
error: string | null;
}
// Helper function for determining if a collection of pairs are valid
@ -51,9 +51,9 @@ export class InfluxLogsQueryField extends React.PureComponent<Props, State> {
templateSrv: TemplateSrv = new TemplateSrv();
state: State = {
measurements: [],
measurement: (null as unknown) as string,
field: (null as unknown) as string,
error: (null as unknown) as string,
measurement: null,
field: null,
error: null,
};
async componentDidMount() {
@ -115,10 +115,10 @@ export class InfluxLogsQueryField extends React.PureComponent<Props, State> {
...query,
resultFormat: 'table',
groupBy: [],
select: [[{ type: 'field', params: [field] }]],
select: [[{ type: 'field', params: [field ?? ''] }]],
tags: pairs,
limit: '1000',
measurement,
measurement: measurement ?? '',
},
this.templateSrv
);
@ -143,7 +143,7 @@ export class InfluxLogsQueryField extends React.PureComponent<Props, State> {
<ButtonCascader
options={measurements}
disabled={!hasMeasurement}
value={[measurement, field]}
value={[measurement ?? '', field ?? '']}
onChange={this.onMeasurementsChange}
>
{cascadeText}

@ -34,8 +34,9 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
constructor(instanceSettings: DataSourceInstanceSettings<InfluxOptions>) {
super(instanceSettings);
this.type = 'influxdb';
this.urls = _.map(instanceSettings.url.split(','), url => {
this.urls = (instanceSettings.url ?? '').split(',').map(url => {
return url.trim();
});
@ -67,7 +68,7 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
applyTemplateVariables(query: InfluxQuery, scopedVars: ScopedVars): Record<string, any> {
return {
...query,
query: getTemplateSrv().replace(query.query, scopedVars), // The raw query text
query: getTemplateSrv().replace(query.query ?? '', scopedVars), // The raw query text
};
}
@ -79,7 +80,7 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
const scopedVars = options.scopedVars;
const targets = _.cloneDeep(options.targets);
const queryTargets: any[] = [];
let queryModel: InfluxQueryModel;
let i, y;
const templateSrv = getTemplateSrv();
@ -93,8 +94,7 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
// backward compatibility
scopedVars.interval = scopedVars.__interval;
queryModel = new InfluxQueryModel(target, templateSrv, scopedVars);
return queryModel.render(true);
return new InfluxQueryModel(target, templateSrv, scopedVars).render(true);
}).reduce((acc, current) => {
if (current !== '') {
acc += ';' + current;
@ -109,7 +109,8 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
// add global adhoc filters to timeFilter
const adhocFilters = (templateSrv as any).getAdhocFilters(this.name);
if (adhocFilters.length > 0) {
timeFilter += ' AND ' + queryModel.renderAdhocFilters(adhocFilters);
const tmpQuery = new InfluxQueryModel({ refId: 'A' }, templateSrv, scopedVars);
timeFilter += ' AND ' + tmpQuery.renderAdhocFilters(adhocFilters);
}
// replace grafana variables
@ -175,7 +176,7 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
const timeFilter = this.getTimeFilter({ rangeRaw: options.rangeRaw, timezone: options.timezone });
let query = options.annotation.query.replace('$timeFilter', timeFilter);
query = getTemplateSrv().replace(query, null, 'regex');
query = getTemplateSrv().replace(query, undefined, 'regex');
return this._seriesQuery(query, options).then((data: any) => {
if (!data || !data.results || !data.results[0]) {
@ -233,7 +234,7 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
const expandedTags = query.tags.map(tag => {
const expandedTag = {
...tag,
value: templateSrv.replace(tag.value, null, 'regex'),
value: templateSrv.replace(tag.value, undefined, 'regex'),
};
return expandedTag;
});
@ -246,7 +247,7 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
}
metricFindQuery(query: string, options?: any) {
const interpolated = getTemplateSrv().replace(query, null, 'regex');
const interpolated = getTemplateSrv().replace(query, undefined, 'regex');
return this._seriesQuery(interpolated, options).then(resp => {
return this.responseParser.parse(query, resp);
@ -292,7 +293,7 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
memo.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
return memo;
},
[]
[] as string[]
).join('&');
}
@ -351,7 +352,7 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
}
_influxRequest(method: string, url: string, data: any, options?: any) {
const currentUrl = this.urls.shift();
const currentUrl = this.urls.shift()!;
this.urls.push(currentUrl);
const params: any = {};

@ -12,7 +12,7 @@ export default class InfluxQueryModel {
groupByParts: any;
templateSrv: any;
scopedVars: any;
refId: string;
refId?: string;
/** @ngInject */
constructor(target: InfluxQuery, templateSrv?: TemplateSrv, scopedVars?: ScopedVars) {
@ -111,12 +111,12 @@ export default class InfluxQueryModel {
});
}
this.target.groupBy.splice(index, 1);
this.target.groupBy!.splice(index, 1);
this.updateProjection();
}
removeSelect(index: number) {
this.target.select.splice(index, 1);
this.target.select!.splice(index, 1);
this.updateProjection();
}
@ -141,7 +141,7 @@ export default class InfluxQueryModel {
this.updatePersistedParts();
}
private renderTagCondition(tag: InfluxQueryTag, index: number, interpolate: boolean) {
private renderTagCondition(tag: InfluxQueryTag, index: number, interpolate?: boolean) {
let str = '';
let operator = tag.operator;
let value = tag.value;

@ -3,7 +3,7 @@ import TableModel from 'app/core/table_model';
import { FieldType, QueryResultMeta, TimeSeries, TableData } from '@grafana/data';
export default class InfluxSeries {
refId: string;
refId?: string;
series: any;
alias: any;
annotation: any;

@ -28,8 +28,8 @@ function renderTagCondition(tag: { operator: any; value: string; condition: any;
export class InfluxQueryBuilder {
constructor(private target: { measurement: any; tags: any; policy?: any }, private database?: string) {}
buildExploreQuery(type: string, withKey?: string, withMeasurementFilter?: string) {
let query;
buildExploreQuery(type: string, withKey?: string, withMeasurementFilter?: string): string {
let query = '';
let measurement;
let policy;
@ -99,19 +99,21 @@ export class InfluxQueryBuilder {
memo.push(renderTagCondition(tag, memo.length));
return memo;
},
[]
[] as string[]
);
if (whereConditions.length > 0) {
query += ' WHERE ' + whereConditions.join(' ');
}
}
if (type === 'MEASUREMENTS') {
query += ' LIMIT 100';
//Solve issue #2524 by limiting the number of measurements returned
//LIMIT must be after WITH MEASUREMENT and WHERE clauses
//This also could be used for TAG KEYS and TAG VALUES, if desired
}
return query;
}
}

@ -104,7 +104,7 @@ export class InfluxQueryCtrl extends QueryCtrl {
memo.push(menu);
return memo;
},
[]
[] as any
);
}
@ -384,7 +384,7 @@ export class InfluxQueryCtrl extends QueryCtrl {
rebuildTargetTagConditions() {
const tags: any[] = [];
let tagIndex = 0;
let tagOperator = '';
let tagOperator: string | null = '';
_.each(this.tagSegments, (segment2, index) => {
if (segment2.type === 'key') {
@ -411,7 +411,7 @@ export class InfluxQueryCtrl extends QueryCtrl {
this.panelCtrl.refresh();
}
getTagValueOperator(tagValue: string, tagOperator: string): string {
getTagValueOperator(tagValue: string, tagOperator: string): string | null {
if (tagOperator !== '=~' && tagOperator !== '!~' && /^\/.*\/$/.test(tagValue)) {
return '=~';
} else if ((tagOperator === '=~' || tagOperator === '!~') && /^(?!\/.*\/$)/.test(tagValue)) {

@ -124,7 +124,7 @@ export class JaegerQueryField extends React.PureComponent<Props, State> {
this.setState(state => {
// Place new traces into the correct service/operation sub-tree
const serviceOptions = state.serviceOptions.map(serviceOption => {
if (serviceOption.value === service) {
if (serviceOption.value === service && serviceOption.children) {
const operationOptions = serviceOption.children.map(operationOption => {
if (operationOption.value === operationValue) {
return {

@ -102,7 +102,7 @@ export class JaegerDatasource extends DataSourceApi<JaegerQuery> {
function getTime(date: string | DateTime, roundUp: boolean) {
if (typeof date === 'string') {
date = dateMath.parse(date, roundUp);
date = dateMath.parse(date, roundUp)!;
}
return date.valueOf() * 1000;
}

@ -40,7 +40,6 @@ export const LokiAnnotationsQueryEditor = memo(function LokiAnnotationQueryEdito
onChange={(query: LokiQuery) => onChange(query.expr)}
onRunQuery={() => {}}
history={[]}
data={null}
onLoadOptions={setActiveOption}
onLabelsRefresh={refreshLabels}
absoluteRange={absolute}

@ -15,7 +15,7 @@ export function LokiExploreQueryEditor(props: Props) {
const { query, data, datasource, exploreMode, history, onChange, onRunQuery } = props;
let absolute: AbsoluteTimeRange;
if (data && !_.isEmpty(data.request)) {
if (data && data.request) {
const { range } = data.request;
absolute = {

@ -27,11 +27,11 @@ export class LokiQueryEditor extends PureComponent<Props, State> {
// Query target properties that are fully controlled inputs
this.state = {
// Fully controlled text inputs
legendFormat: query.legendFormat,
legendFormat: query.legendFormat ?? '',
};
}
calcAbsoluteRange = (data: PanelData): AbsoluteTimeRange => {
calcAbsoluteRange = (data: PanelData | undefined): AbsoluteTimeRange => {
if (data && data.request) {
const { range } = data.request;
return {

@ -63,7 +63,7 @@ function willApplySuggestion(suggestion: string, { typeaheadContext, typeaheadTe
export interface LokiQueryFieldFormProps extends ExploreQueryFieldProps<LokiDatasource, LokiQuery, LokiOptions> {
history: LokiHistoryItem[];
syntax: Grammar;
syntax: Grammar | null;
logLabelOptions: CascaderOption[];
syntaxLoaded: boolean;
absoluteRange: AbsoluteTimeRange;

@ -21,7 +21,7 @@ export const useLokiLabels = (
const mounted = useRefMounted();
// State
const [logLabelOptions, setLogLabelOptions] = useState([]);
const [logLabelOptions, setLogLabelOptions] = useState<any>([]);
const [shouldTryRefreshLabels, setRefreshLabels] = useState(false);
/**
* Holds information about currently selected option from rc-cascader to perform effect
@ -56,7 +56,7 @@ export const useLokiLabels = (
if (languageProviderInitialised) {
const targetOption = activeOption[activeOption.length - 1];
if (targetOption) {
const nextOptions = logLabelOptions.map(option => {
const nextOptions = logLabelOptions.map((option: any) => {
if (option.value === targetOption.value) {
return {
...option,

@ -39,7 +39,7 @@ const useInitLanguageProvider = (languageProvider: LokiLanguageProvider, absolut
*/
const useLokiSyntax = (languageProvider: LokiLanguageProvider, languageProviderInitialized: boolean) => {
// State
const [syntax, setSyntax] = useState<Grammar>(null);
const [syntax, setSyntax] = useState<Grammar | null>(null);
// Effects
useEffect(() => {

@ -18,6 +18,7 @@ describe('DerivedFields', () => {
it('renders correctly when no fields', async () => {
let wrapper: any;
//@ts-ignore
await act(async () => {
wrapper = await mount(<DerivedFields onChange={() => {}} />);
});
@ -42,6 +43,7 @@ describe('DerivedFields', () => {
it('adds new field', async () => {
const onChangeMock = jest.fn();
let wrapper: any;
//@ts-ignore
await act(async () => {
wrapper = await mount(<DerivedFields onChange={onChangeMock} />);
});
@ -53,6 +55,7 @@ describe('DerivedFields', () => {
it('removes field', async () => {
const onChangeMock = jest.fn();
let wrapper: any;
//@ts-ignore
await act(async () => {
wrapper = await mount(<DerivedFields value={testValue} onChange={onChangeMock} />);
});

@ -31,7 +31,7 @@ export function makeMockLokiDatasource(labelsAndValues: Labels, series?: SeriesF
const seriesMatch = url.match(lokiSeriesEndpointRegex);
if (labelsMatch) {
return labelsAndValues[labelsMatch[1]] || [];
} else if (seriesMatch) {
} else if (seriesMatch && series && params) {
return series[params.match] || [];
} else {
throw new Error(`Unexpected url error, ${url}`);

@ -142,20 +142,20 @@ describe('enhanceDataFrame', () => {
});
expect(df.fields.length).toBe(3);
const fc = new FieldCache(df);
expect(fc.getFieldByName('trace1').values.toArray()).toEqual([null, '1234', null]);
expect(fc.getFieldByName('trace1').config.links[0]).toEqual({
expect(fc.getFieldByName('trace1')!.values.toArray()).toEqual([null, '1234', null]);
expect(fc.getFieldByName('trace1')!.config.links![0]).toEqual({
url: 'http://localhost/${__value.raw}',
title: '',
});
expect(fc.getFieldByName('trace2').values.toArray()).toEqual([null, null, 'foo']);
expect(fc.getFieldByName('trace2').config.links.length).toBe(2);
expect(fc.getFieldByName('trace2').config.links[0]).toEqual({
expect(fc.getFieldByName('trace2')!.values.toArray()).toEqual([null, null, 'foo']);
expect(fc.getFieldByName('trace2')!.config.links!.length).toBe(2);
expect(fc.getFieldByName('trace2')!.config.links![0]).toEqual({
title: '',
internal: { datasourceUid: 'uid', query: { query: 'test' } },
url: '',
});
expect(fc.getFieldByName('trace2').config.links[1]).toEqual({
expect(fc.getFieldByName('trace2')!.config.links![1]).toEqual({
title: '',
internal: { datasourceUid: 'uid2', query: { query: 'test' } },
url: '',

@ -91,7 +91,7 @@ export interface LokiTailResponse {
dropped_entries?: Array<{
labels: Record<string, string>;
timestamp: string;
}>;
}> | null;
}
export type LokiResult = LokiVectorResult | LokiMatrixResult | LokiStreamResult;

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save