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
This commit is contained in:
Torkel Ödegaard 2020-07-08 11:05:20 +02:00 committed by GitHub
parent 89b56782c6
commit fd44c01675
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
103 changed files with 386 additions and 331 deletions

View File

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

View File

@ -16,7 +16,7 @@ export interface TemplateSrv {
/** /**
* Replace the values within the target string. See also {@link InterpolateFunction} * 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; let singletonInstance: TemplateSrv;

View File

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

View File

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

View File

@ -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 // 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 // for perf reasons and also have outside value in for example in Explore redux that is mutable from logs
// creating a two way binding. // creating a two way binding.
query: string | null; query?: string | null;
onRunQuery?: () => void; onRunQuery?: () => void;
onBlur?: () => void; onBlur?: () => void;
onChange?: (value: string) => void; onChange?: (value: string) => void;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -62,7 +62,7 @@ export const initDataSourceSettings = (
} }
const dataSource = dependencies.getDataSource(getState().dataSources, pageId); 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); const importedPlugin = await dependencies.importDataSourcePlugin(dataSourceMeta);
dispatch(initDataSourceSettingsSucceeded(importedPlugin)); dispatch(initDataSourceSettingsSucceeded(importedPlugin));
@ -118,7 +118,7 @@ export function loadDataSources(): ThunkResult<void> {
export function loadDataSource(id: number): ThunkResult<void> { export function loadDataSource(id: number): ThunkResult<void> {
return async dispatch => { 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 pluginInfo = (await getPluginSettings(dataSource.type)) as DataSourcePluginMeta;
const plugin = await importDataSourcePlugin(pluginInfo); const plugin = await importDataSourcePlugin(pluginInfo);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -126,7 +126,7 @@ export class DatasourceSrv implements DataSourceService {
Object.entries(config.datasources).forEach(([key, value]) => { Object.entries(config.datasources).forEach(([key, value]) => {
if (value.meta?.metrics) { 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 //Make sure grafana and mixed are sorted at the bottom
if (value.meta.id === 'grafana') { if (value.meta.id === 'grafana') {

View File

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

View File

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

View File

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

View File

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

View File

@ -41,14 +41,14 @@ export const loadFolderPage = (uid: string, activeChildId: string) => {
const folderUrl = folder.url; const folderUrl = folder.url;
navModel.main.text = folderTitle; 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; dashTab!.url = folderUrl;
if (folder.canAdmin) { 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'; 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'; settingsTab!.url = folderUrl + '/settings';
} else { } else {
navModel.main.children = [dashTab!]; navModel.main.children = [dashTab!];

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -120,21 +120,34 @@ export default class CloudMonitoringMetricFindQuery {
return []; return [];
} }
const metricDescriptors = await this.datasource.getMetricTypes(projectName); 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) (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) { async handleAggregationQuery({ selectedMetricType, projectName }: VariableQueryData) {
if (!selectedMetricType) { if (!selectedMetricType) {
return []; return [];
} }
const metricDescriptors = await this.datasource.getMetricTypes(projectName); 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) (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) { async handleSLOServicesQuery({ projectName }: VariableQueryData) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -87,7 +87,7 @@ export class CloudMonitoringVariableQueryEditor extends PureComponent<VariableQu
onPropsChange = () => { onPropsChange = () => {
const { metricDescriptors, labels, metricTypes, services, ...queryModel } = this.state; 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}`); this.props.onChange(queryModel, `Google Cloud Monitoring - ${query.name}`);
}; };

View File

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

View File

@ -93,7 +93,7 @@ export const labelsToGroupedOptions = (groupBys: string[]) => {
}; };
export const filtersToStringArray = (filters: Filter[]) => { 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); return strArr.filter((_, i) => i !== strArr.length - 1);
}; };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 [showInternalLink, setShowInternalLink] = useState<boolean>(!!datasourceUid);
const previousUid = usePrevious(datasourceUid); const previousUid = usePrevious(datasourceUid);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -423,7 +423,7 @@ describe('AppInsightsDatasource', () => {
}); });
it('should return a list of metric names', () => { 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.length).toBe(2);
expect(results[0].text).toBe('exceptions/server'); expect(results[0].text).toBe('exceptions/server');
expect(results[0].value).toBe('exceptions/server'); expect(results[0].value).toBe('exceptions/server');
@ -461,7 +461,7 @@ describe('AppInsightsDatasource', () => {
}); });
it('should return a list of group bys', () => { 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].text).toContain('client/os');
expect(results[0].value).toContain('client/os'); expect(results[0].value).toContain('client/os');
expect(results[1].text).toContain('client/city'); expect(results[1].text).toContain('client/city');

View File

@ -1,4 +1,4 @@
import { ScopedVars } from '@grafana/data'; import { ScopedVars, MetricFindValue } from '@grafana/data';
import { DataQueryRequest, DataSourceInstanceSettings } from '@grafana/data'; import { DataQueryRequest, DataSourceInstanceSettings } from '@grafana/data';
import { getBackendSrv, getTemplateSrv, DataSourceWithBackend } from '@grafana/runtime'; import { getBackendSrv, getTemplateSrv, DataSourceWithBackend } from '@grafana/runtime';
import _, { isString } from 'lodash'; 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); const appInsightsMetricNameQuery = query.match(/^AppInsightsMetricNames\(\)/i);
if (appInsightsMetricNameQuery) { if (appInsightsMetricNameQuery) {
return this.getMetricNames(); return this.getMetricNames();
@ -134,7 +140,7 @@ export default class AppInsightsDatasource extends DataSourceWithBackend<AzureMo
return this.getGroupBys(getTemplateSrv().replace(metricName)); return this.getGroupBys(getTemplateSrv().replace(metricName));
} }
return undefined; return null;
} }
testDatasource() { testDatasource() {

View File

@ -2,13 +2,7 @@ import _ from 'lodash';
import LogAnalyticsQuerystringBuilder from '../log_analytics/querystring_builder'; import LogAnalyticsQuerystringBuilder from '../log_analytics/querystring_builder';
import ResponseParser from './response_parser'; import ResponseParser from './response_parser';
import { AzureMonitorQuery, AzureDataSourceJsonData, AzureLogsVariable, AzureQueryType } from '../types'; import { AzureMonitorQuery, AzureDataSourceJsonData, AzureLogsVariable, AzureQueryType } from '../types';
import { import { DataQueryResponse, ScopedVars, DataSourceInstanceSettings, MetricFindValue } from '@grafana/data';
DataQueryResponse,
ScopedVars,
DataSourceInstanceSettings,
QueryResultMeta,
MetricFindValue,
} from '@grafana/data';
import { getBackendSrv, getTemplateSrv, DataSourceWithBackend } from '@grafana/runtime'; import { getBackendSrv, getTemplateSrv, DataSourceWithBackend } from '@grafana/runtime';
export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend< export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
@ -140,7 +134,7 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
for (const df of res.data) { for (const df of res.data) {
const encodedQuery = df.meta?.custom?.encodedQuery; const encodedQuery = df.meta?.custom?.encodedQuery;
if (encodedQuery && encodedQuery.length > 0) { if (encodedQuery && encodedQuery.length > 0) {
const url = await this.buildDeepLink(df.meta); const url = await this.buildDeepLink(df.meta.custom);
if (url?.length) { if (url?.length) {
for (const field of df.fields) { for (const field of df.fields) {
field.config.links = [ field.config.links = [
@ -158,10 +152,10 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
return res; return res;
} }
private async buildDeepLink(meta: QueryResultMeta) { private async buildDeepLink(customMeta: Record<string, any>) {
const base64Enc = encodeURIComponent(meta.custom.encodedQuery); const base64Enc = encodeURIComponent(customMeta.encodedQuery);
const workspaceId = meta.custom.workspace; const workspaceId = customMeta.workspace;
const subscription = meta.custom.subscription; const subscription = customMeta.subscription;
const details = await this.getWorkspaceDetails(workspaceId); const details = await this.getWorkspaceDetails(workspaceId);
if (!details.workspace || !details.resourceGroup) { 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); const workspacesQuery = query.match(/^workspaces\(\)/i);
if (workspacesQuery) { if (workspacesQuery) {
return this.getWorkspaces(this.subscriptionId); return this.getWorkspaces(this.subscriptionId);
@ -233,7 +233,7 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
throw { message: err.error.data.error.message }; throw { message: err.error.data.error.message };
} }
}); });
}) as Promise<MetricFindValue[]>; // ?? }) as Promise<MetricFindValue[]>;
} }
private buildQuery(query: string, options: any, workspace: any) { private buildQuery(query: string, options: any, workspace: any) {

View File

@ -10,7 +10,7 @@ import {
AzureMonitorResourceGroupsResponse, AzureMonitorResourceGroupsResponse,
AzureQueryType, AzureQueryType,
} from '../types'; } from '../types';
import { DataSourceInstanceSettings, ScopedVars } from '@grafana/data'; import { DataSourceInstanceSettings, ScopedVars, MetricFindValue } from '@grafana/data';
import { getBackendSrv, DataSourceWithBackend, getTemplateSrv } from '@grafana/runtime'; import { getBackendSrv, DataSourceWithBackend, getTemplateSrv } from '@grafana/runtime';
const defaultDropdownValue = 'select'; const defaultDropdownValue = 'select';
@ -32,8 +32,7 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
this.subscriptionId = instanceSettings.jsonData.subscriptionId; this.subscriptionId = instanceSettings.jsonData.subscriptionId;
this.cloudName = instanceSettings.jsonData.cloudName || 'azuremonitor'; this.cloudName = instanceSettings.jsonData.cloudName || 'azuremonitor';
this.baseUrl = `/${this.cloudName}/subscriptions`; this.baseUrl = `/${this.cloudName}/subscriptions`;
this.url = instanceSettings.url; this.url = instanceSettings.url!;
this.supportedMetricNamespaces = new SupportedNamespaces(this.cloudName).get(); this.supportedMetricNamespaces = new SupportedNamespaces(this.cloudName).get();
} }
@ -44,13 +43,9 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
filterQuery(item: AzureMonitorQuery): boolean { filterQuery(item: AzureMonitorQuery): boolean {
return ( return (
item.hide !== true && item.hide !== true &&
item.azureMonitor.resourceGroup &&
item.azureMonitor.resourceGroup !== defaultDropdownValue && item.azureMonitor.resourceGroup !== defaultDropdownValue &&
item.azureMonitor.resourceName &&
item.azureMonitor.resourceName !== defaultDropdownValue && item.azureMonitor.resourceName !== defaultDropdownValue &&
item.azureMonitor.metricDefinition &&
item.azureMonitor.metricDefinition !== defaultDropdownValue && item.azureMonitor.metricDefinition !== defaultDropdownValue &&
item.azureMonitor.metricName &&
item.azureMonitor.metricName !== defaultDropdownValue item.azureMonitor.metricName !== defaultDropdownValue
); );
} }
@ -77,7 +72,7 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
const dimensionsFilters = item.dimensionFilters const dimensionsFilters = item.dimensionFilters
.filter(f => f.dimension && f.dimension !== 'None') .filter(f => f.dimension && f.dimension !== 'None')
.map(f => { .map(f => {
const filter = templateSrv.replace(f.filter, scopedVars); const filter = templateSrv.replace(f.filter ?? '', scopedVars);
return { return {
dimension: templateSrv.replace(f.dimension, scopedVars), dimension: templateSrv.replace(f.dimension, scopedVars),
operator: f.operator || 'eq', 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); const subscriptionsQuery = query.match(/^Subscriptions\(\)/i);
if (subscriptionsQuery) { if (subscriptionsQuery) {
return this.getSubscriptions(); return this.getSubscriptions();
@ -196,7 +197,7 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
return this.getMetricNames(subscription, resourceGroup, metricDefinition, resourceName, metricNamespace); return this.getMetricNames(subscription, resourceGroup, metricDefinition, resourceName, metricNamespace);
} }
return undefined; return null;
} }
toVariable(metric: string) { 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; return field && field.length > 0;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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