mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
89b56782c6
commit
fd44c01675
@ -329,7 +329,7 @@ export interface ExploreQueryFieldProps<
|
||||
}
|
||||
|
||||
export interface ExploreStartPageProps {
|
||||
datasource?: DataSourceApi;
|
||||
datasource: DataSourceApi;
|
||||
exploreMode: ExploreMode;
|
||||
onClickExample: (query: DataQuery) => void;
|
||||
exploreId?: any;
|
||||
@ -490,7 +490,7 @@ export interface DataSourceSettings<T extends DataSourceJsonData = DataSourceJso
|
||||
isDefault: boolean;
|
||||
jsonData: T;
|
||||
secureJsonData?: S;
|
||||
secureJsonFields?: KeyValue<boolean>;
|
||||
secureJsonFields: KeyValue<boolean>;
|
||||
readOnly: boolean;
|
||||
withCredentials: boolean;
|
||||
version?: number;
|
||||
|
@ -16,7 +16,7 @@ export interface TemplateSrv {
|
||||
/**
|
||||
* Replace the values within the target string. See also {@link InterpolateFunction}
|
||||
*/
|
||||
replace(target: string, scopedVars?: ScopedVars, format?: string | Function): string;
|
||||
replace(target?: string, scopedVars?: ScopedVars, format?: string | Function): string;
|
||||
}
|
||||
|
||||
let singletonInstance: TemplateSrv;
|
||||
|
@ -28,6 +28,7 @@ const setup = (propOverrides?: object) => {
|
||||
secureJsonData: {
|
||||
password: true,
|
||||
},
|
||||
secureJsonFields: {},
|
||||
readOnly: true,
|
||||
},
|
||||
onChange: jest.fn(),
|
||||
|
@ -28,6 +28,7 @@ const settingsMock: DataSourceSettings<any, any> = {
|
||||
secureJsonData: {
|
||||
password: true,
|
||||
},
|
||||
secureJsonFields: {},
|
||||
readOnly: true,
|
||||
};
|
||||
|
||||
|
@ -26,7 +26,7 @@ export interface QueryFieldProps {
|
||||
// We have both value and local state. This is usually an antipattern but we need to keep local state
|
||||
// for perf reasons and also have outside value in for example in Explore redux that is mutable from logs
|
||||
// creating a two way binding.
|
||||
query: string | null;
|
||||
query?: string | null;
|
||||
onRunQuery?: () => void;
|
||||
onBlur?: () => void;
|
||||
onChange?: (value: string) => void;
|
||||
|
@ -12,7 +12,7 @@ import { isEqual } from 'lodash';
|
||||
import { Branding } from '../Branding/Branding';
|
||||
|
||||
interface Props {
|
||||
children: JSX.Element[] | JSX.Element;
|
||||
children: React.ReactNode;
|
||||
navModel: NavModel;
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ import PageLoader from '../PageLoader/PageLoader';
|
||||
|
||||
interface Props {
|
||||
isLoading?: boolean;
|
||||
children: JSX.Element[] | JSX.Element | null;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
class PageContents extends Component<Props> {
|
||||
|
@ -17,7 +17,7 @@ export default class TableModel implements TableData {
|
||||
rows: any[];
|
||||
type: string;
|
||||
columnMap: any;
|
||||
refId: string;
|
||||
refId?: string;
|
||||
meta?: QueryResultMeta;
|
||||
|
||||
constructor(table?: any) {
|
||||
|
@ -182,7 +182,7 @@ export function getProcessedDataFrames(results?: DataQueryResponseData[]): DataF
|
||||
return dataFrames;
|
||||
}
|
||||
|
||||
export function preProcessPanelData(data: PanelData, lastResult: PanelData): PanelData {
|
||||
export function preProcessPanelData(data: PanelData, lastResult?: PanelData): PanelData {
|
||||
const { series } = data;
|
||||
|
||||
// for loading states with no data, use last result
|
||||
@ -191,7 +191,11 @@ export function preProcessPanelData(data: PanelData, lastResult: PanelData): Pan
|
||||
lastResult = data;
|
||||
}
|
||||
|
||||
return { ...lastResult, state: LoadingState.Loading };
|
||||
return {
|
||||
...lastResult,
|
||||
state: LoadingState.Loading,
|
||||
request: data.request,
|
||||
};
|
||||
}
|
||||
|
||||
// Make sure the data frames are properly formatted
|
||||
|
@ -44,5 +44,6 @@ export const getMockDataSource = (): DataSourceSettings => {
|
||||
typeLogoUrl: 'public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png',
|
||||
url: '',
|
||||
user: '',
|
||||
secureJsonFields: {},
|
||||
};
|
||||
};
|
||||
|
@ -19,5 +19,6 @@ export function createDatasourceSettings<T>(jsonData: T): DataSourceSettings<T>
|
||||
jsonData,
|
||||
readOnly: false,
|
||||
withCredentials: false,
|
||||
secureJsonFields: {},
|
||||
};
|
||||
}
|
||||
|
@ -51,6 +51,7 @@ exports[`Render should render alpha info text 1`] = `
|
||||
"orgId": 1,
|
||||
"password": "",
|
||||
"readOnly": false,
|
||||
"secureJsonFields": Object {},
|
||||
"type": "cloudwatch",
|
||||
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
|
||||
"url": "",
|
||||
@ -242,6 +243,7 @@ exports[`Render should render is ready only message 1`] = `
|
||||
"orgId": 1,
|
||||
"password": "",
|
||||
"readOnly": true,
|
||||
"secureJsonFields": Object {},
|
||||
"type": "cloudwatch",
|
||||
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
|
||||
"url": "",
|
||||
|
@ -62,7 +62,7 @@ export const initDataSourceSettings = (
|
||||
}
|
||||
|
||||
const dataSource = dependencies.getDataSource(getState().dataSources, pageId);
|
||||
const dataSourceMeta = dependencies.getDataSourceMeta(getState().dataSources, dataSource.type);
|
||||
const dataSourceMeta = dependencies.getDataSourceMeta(getState().dataSources, dataSource!.type);
|
||||
const importedPlugin = await dependencies.importDataSourcePlugin(dataSourceMeta);
|
||||
|
||||
dispatch(initDataSourceSettingsSucceeded(importedPlugin));
|
||||
@ -118,7 +118,7 @@ export function loadDataSources(): ThunkResult<void> {
|
||||
|
||||
export function loadDataSource(id: number): ThunkResult<void> {
|
||||
return async dispatch => {
|
||||
const dataSource = await getBackendSrv().get(`/api/datasources/${id}`);
|
||||
const dataSource = (await getBackendSrv().get(`/api/datasources/${id}`)) as DataSourceSettings;
|
||||
const pluginInfo = (await getPluginSettings(dataSource.type)) as DataSourcePluginMeta;
|
||||
const plugin = await importDataSourcePlugin(pluginInfo);
|
||||
|
||||
|
@ -88,6 +88,7 @@ export function getDataSourceLoadingNav(pageName: string): NavModel {
|
||||
typeLogoUrl: 'public/img/icn-datasource.svg',
|
||||
url: '',
|
||||
user: '',
|
||||
secureJsonFields: {},
|
||||
},
|
||||
{
|
||||
meta: {
|
||||
@ -113,14 +114,14 @@ export function getDataSourceLoadingNav(pageName: string): NavModel {
|
||||
module: '',
|
||||
baseUrl: '',
|
||||
},
|
||||
} as GenericDataSourcePlugin
|
||||
} as any
|
||||
);
|
||||
|
||||
let node: NavModelItem;
|
||||
|
||||
// find active page
|
||||
for (const child of main.children) {
|
||||
if (child.id.indexOf(pageName) > 0) {
|
||||
for (const child of main.children!) {
|
||||
if (child.id!.indexOf(pageName) > 0) {
|
||||
child.active = true;
|
||||
node = child;
|
||||
break;
|
||||
@ -129,7 +130,7 @@ export function getDataSourceLoadingNav(pageName: string): NavModel {
|
||||
|
||||
return {
|
||||
main: main,
|
||||
node: node,
|
||||
node: node!,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,6 @@ import { AnyAction } from 'redux';
|
||||
import { PayloadAction } from '@reduxjs/toolkit';
|
||||
import {
|
||||
DataQuery,
|
||||
DataQueryRequest,
|
||||
DataSourceApi,
|
||||
DefaultTimeRange,
|
||||
LoadingState,
|
||||
@ -129,7 +128,6 @@ export const makeExploreItemState = (): ExploreItemState => ({
|
||||
|
||||
export const createEmptyQueryResponse = (): PanelData => ({
|
||||
state: LoadingState.NotStarted,
|
||||
request: {} as DataQueryRequest<DataQuery>,
|
||||
series: [],
|
||||
error: null,
|
||||
timeRange: DefaultTimeRange,
|
||||
|
@ -47,10 +47,10 @@ export function getLoadingNav(tabIndex: number): NavModel {
|
||||
version: 0,
|
||||
});
|
||||
|
||||
main.children[tabIndex].active = true;
|
||||
main.children![tabIndex].active = true;
|
||||
|
||||
return {
|
||||
main: main,
|
||||
node: main.children[tabIndex],
|
||||
node: main.children![tabIndex],
|
||||
};
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ interface Props {
|
||||
|
||||
export const SnapshotListTable: FC<Props> = ({ url }) => {
|
||||
const [snapshots, setSnapshots] = useState<Snapshot[]>([]);
|
||||
const [removeSnapshot, setRemoveSnapshot] = useState<Snapshot>();
|
||||
const [removeSnapshot, setRemoveSnapshot] = useState<Snapshot | undefined>();
|
||||
|
||||
const getSnapshots = useCallback(async () => {
|
||||
await getBackendSrv()
|
||||
@ -91,7 +91,7 @@ export const SnapshotListTable: FC<Props> = ({ url }) => {
|
||||
confirmText="Delete"
|
||||
onDismiss={() => setRemoveSnapshot(undefined)}
|
||||
onConfirm={() => {
|
||||
doRemoveSnapshot(removeSnapshot);
|
||||
doRemoveSnapshot(removeSnapshot!);
|
||||
setRemoveSnapshot(undefined);
|
||||
}}
|
||||
/>
|
||||
|
@ -60,7 +60,7 @@ export const UserInviteForm: FC<Props> = ({ updateLocation }) => {
|
||||
<>
|
||||
<Field
|
||||
invalid={!!errors.loginOrEmail}
|
||||
error={!!errors.loginOrEmail && 'Email or Username is required'}
|
||||
error={!!errors.loginOrEmail ? 'Email or Username is required' : undefined}
|
||||
label="Email or Username"
|
||||
>
|
||||
<Input name="loginOrEmail" placeholder="email@example.com" ref={register({ required: true })} />
|
||||
|
@ -33,7 +33,7 @@ class MetricsPanelCtrl extends PanelCtrl {
|
||||
timeInfo?: string;
|
||||
skipDataOnInit: boolean;
|
||||
dataList: LegacyResponseData[];
|
||||
querySubscription?: Unsubscribable;
|
||||
querySubscription?: Unsubscribable | null;
|
||||
useDataFrames = false;
|
||||
|
||||
constructor($scope: any, $injector: any) {
|
||||
|
@ -38,7 +38,7 @@ interface DataViewVars {
|
||||
fields?: Record<string, DisplayValue>;
|
||||
}
|
||||
|
||||
interface DataLinkScopedVars extends ScopedVars {
|
||||
interface DataLinkScopedVars {
|
||||
__series?: ScopedVar<SeriesVars>;
|
||||
__field?: ScopedVar<FieldVars>;
|
||||
__value?: ScopedVar<ValueVars>;
|
||||
@ -53,6 +53,7 @@ export const getFieldLinksSupplier = (value: FieldDisplay): LinkModelSupplier<Fi
|
||||
if (!links || links.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
getLinks: (existingScopedVars?: any) => {
|
||||
const scopedVars: DataLinkScopedVars = {
|
||||
@ -71,8 +72,8 @@ export const getFieldLinksSupplier = (value: FieldDisplay): LinkModelSupplier<Fi
|
||||
};
|
||||
|
||||
const field = value.colIndex !== undefined ? dataFrame.fields[value.colIndex] : undefined;
|
||||
|
||||
if (field) {
|
||||
console.log('Full Field Info:', field);
|
||||
scopedVars['__field'] = {
|
||||
value: {
|
||||
name: field.name,
|
||||
@ -80,19 +81,19 @@ export const getFieldLinksSupplier = (value: FieldDisplay): LinkModelSupplier<Fi
|
||||
},
|
||||
text: 'Field',
|
||||
};
|
||||
}
|
||||
|
||||
if (!isNaN(value.rowIndex)) {
|
||||
const { timeField } = getTimeField(dataFrame);
|
||||
scopedVars['__value'] = {
|
||||
value: {
|
||||
raw: field.values.get(value.rowIndex),
|
||||
numeric: value.display.numeric,
|
||||
text: formattedValueToString(value.display),
|
||||
time: timeField ? timeField.values.get(value.rowIndex) : undefined,
|
||||
},
|
||||
text: 'Value',
|
||||
};
|
||||
if (value.rowIndex !== undefined && value.rowIndex >= 0) {
|
||||
const { timeField } = getTimeField(dataFrame);
|
||||
scopedVars['__value'] = {
|
||||
value: {
|
||||
raw: field.values.get(value.rowIndex),
|
||||
numeric: value.display.numeric,
|
||||
text: formattedValueToString(value.display),
|
||||
time: timeField ? timeField.values.get(value.rowIndex) : undefined,
|
||||
},
|
||||
text: 'Value',
|
||||
};
|
||||
}
|
||||
|
||||
// Expose other values on the row
|
||||
if (value.view) {
|
||||
@ -124,13 +125,13 @@ export const getFieldLinksSupplier = (value: FieldDisplay): LinkModelSupplier<Fi
|
||||
}
|
||||
|
||||
return links.map((link: DataLink) => {
|
||||
return getLinkSrv().getDataLinkUIModel(link, scopedVars, value);
|
||||
return getLinkSrv().getDataLinkUIModel(link, scopedVars as ScopedVars, value);
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getPanelLinksSupplier = (value: PanelModel): LinkModelSupplier<PanelModel> => {
|
||||
export const getPanelLinksSupplier = (value: PanelModel): LinkModelSupplier<PanelModel> | undefined => {
|
||||
const links = value.links;
|
||||
|
||||
if (!links || links.length === 0) {
|
||||
|
@ -20,6 +20,7 @@ import {
|
||||
textUtil,
|
||||
DataLink,
|
||||
PanelPlugin,
|
||||
DataLinkClickEvent,
|
||||
} from '@grafana/data';
|
||||
|
||||
const timeRangeVars = [
|
||||
@ -126,8 +127,8 @@ const getFieldVars = (dataFrames: DataFrame[]) => {
|
||||
};
|
||||
|
||||
const getDataFrameVars = (dataFrames: DataFrame[]) => {
|
||||
let numeric: Field = undefined;
|
||||
let title: Field = undefined;
|
||||
let numeric: Field | undefined = undefined;
|
||||
let title: Field | undefined = undefined;
|
||||
const suggestions: VariableSuggestion[] = [];
|
||||
const keys: KeyValue<true> = {};
|
||||
|
||||
@ -245,7 +246,7 @@ export const getPanelOptionsVariableSuggestions = (plugin: PanelPlugin, data?: D
|
||||
};
|
||||
|
||||
export interface LinkService {
|
||||
getDataLinkUIModel: <T>(link: DataLink, scopedVars: ScopedVars, origin: T) => LinkModel<T>;
|
||||
getDataLinkUIModel: <T>(link: DataLink, scopedVars: ScopedVars | undefined, origin: T) => LinkModel<T>;
|
||||
getAnchorInfo: (link: any) => any;
|
||||
getLinkUrl: (link: any) => string;
|
||||
}
|
||||
@ -295,15 +296,17 @@ export class LinkSrv implements LinkService {
|
||||
});
|
||||
}
|
||||
|
||||
let onClick: (e: any) => void = undefined;
|
||||
let onClick: ((event: DataLinkClickEvent) => void) | undefined = undefined;
|
||||
|
||||
if (link.onClick) {
|
||||
onClick = (e: any) => {
|
||||
link.onClick({
|
||||
origin,
|
||||
scopedVars,
|
||||
e,
|
||||
});
|
||||
onClick = (e: DataLinkClickEvent) => {
|
||||
if (link.onClick) {
|
||||
link.onClick({
|
||||
origin,
|
||||
scopedVars,
|
||||
e,
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ export class QueryCtrl {
|
||||
panelCtrl: any;
|
||||
panel: any;
|
||||
hasRawMode: boolean;
|
||||
error: string;
|
||||
error?: string | null;
|
||||
isLastQuery: boolean;
|
||||
|
||||
constructor(public $scope: any, public $injector: auto.IInjectorService) {
|
||||
|
@ -22,7 +22,7 @@ interface Props {
|
||||
|
||||
interface State {
|
||||
loading: boolean;
|
||||
plugin?: AppPlugin;
|
||||
plugin?: AppPlugin | null;
|
||||
nav: NavModel;
|
||||
}
|
||||
|
||||
@ -83,7 +83,7 @@ class AppRootPage extends Component<Props, State> {
|
||||
return (
|
||||
<Page navModel={nav}>
|
||||
<Page.Contents isLoading={loading}>
|
||||
{!loading && plugin && (
|
||||
{plugin && plugin.root && (
|
||||
<plugin.root meta={plugin.meta} query={query} path={path} onNavChanged={this.onNavChanged} />
|
||||
)}
|
||||
</Page.Contents>
|
||||
|
@ -123,12 +123,14 @@ class PluginPage extends PureComponent<Props, State> {
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
const prevPage = prevProps.query.page as string;
|
||||
const page = this.props.query.page as string;
|
||||
|
||||
if (prevPage !== page) {
|
||||
const { nav, defaultPage } = this.state;
|
||||
const node = {
|
||||
...nav.node,
|
||||
children: setActivePage(page, nav.node.children, defaultPage),
|
||||
children: setActivePage(page, nav.node.children!, defaultPage),
|
||||
};
|
||||
|
||||
this.setState({
|
||||
nav: {
|
||||
node: node,
|
||||
@ -146,7 +148,7 @@ class PluginPage extends PureComponent<Props, State> {
|
||||
return <Alert severity={AppNotificationSeverity.Error} title="Plugin Not Found" />;
|
||||
}
|
||||
|
||||
const active = nav.main.children.find(tab => tab.active);
|
||||
const active = nav.main.children!.find(tab => tab.active);
|
||||
if (active) {
|
||||
// Find the current config tab
|
||||
if (plugin.configPages) {
|
||||
@ -175,7 +177,7 @@ class PluginPage extends PureComponent<Props, State> {
|
||||
showUpdateInfo = () => {
|
||||
appEvents.emit(CoreEvents.showModal, {
|
||||
src: 'public/app/features/plugins/partials/update_instructions.html',
|
||||
model: this.state.plugin.meta,
|
||||
model: this.state.plugin!.meta,
|
||||
});
|
||||
};
|
||||
|
||||
@ -190,7 +192,7 @@ class PluginPage extends PureComponent<Props, State> {
|
||||
<span>{meta.info.version}</span>
|
||||
{meta.hasUpdate && (
|
||||
<div>
|
||||
<Tooltip content={meta.latestVersion} theme="info" placement="top">
|
||||
<Tooltip content={meta.latestVersion!} theme="info" placement="top">
|
||||
<a href="#" onClick={this.showUpdateInfo}>
|
||||
Update Available!
|
||||
</a>
|
||||
@ -203,7 +205,7 @@ class PluginPage extends PureComponent<Props, State> {
|
||||
|
||||
renderSidebarIncludeBody(item: PluginInclude) {
|
||||
if (item.type === PluginIncludeType.page) {
|
||||
const pluginId = this.state.plugin.meta.id;
|
||||
const pluginId = this.state.plugin!.meta.id;
|
||||
const page = item.name.toLowerCase().replace(' ', '-');
|
||||
return (
|
||||
<a href={`plugins/${pluginId}/page/${page}`}>
|
||||
@ -220,7 +222,7 @@ class PluginPage extends PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
renderSidebarIncludes(includes: PluginInclude[]) {
|
||||
renderSidebarIncludes(includes?: PluginInclude[]) {
|
||||
if (!includes || !includes.length) {
|
||||
return null;
|
||||
}
|
||||
@ -241,7 +243,7 @@ class PluginPage extends PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
renderSidebarDependencies(dependencies: PluginDependencies) {
|
||||
renderSidebarDependencies(dependencies?: PluginDependencies) {
|
||||
if (!dependencies) {
|
||||
return null;
|
||||
}
|
||||
@ -295,10 +297,11 @@ class PluginPage extends PureComponent<Props, State> {
|
||||
const { loading, nav, plugin } = this.state;
|
||||
const { $contextSrv } = this.props;
|
||||
const isAdmin = $contextSrv.hasRole('Admin');
|
||||
|
||||
return (
|
||||
<Page navModel={nav}>
|
||||
<Page.Contents isLoading={loading}>
|
||||
{!loading && (
|
||||
{plugin && (
|
||||
<div className="sidebar-container">
|
||||
<div className="sidebar-content">
|
||||
{plugin.loadError && (
|
||||
@ -316,14 +319,12 @@ class PluginPage extends PureComponent<Props, State> {
|
||||
{this.renderBody()}
|
||||
</div>
|
||||
<aside className="page-sidebar">
|
||||
{plugin && (
|
||||
<section className="page-sidebar-section">
|
||||
{this.renderVersionInfo(plugin.meta)}
|
||||
{isAdmin && this.renderSidebarIncludes(plugin.meta.includes)}
|
||||
{this.renderSidebarDependencies(plugin.meta.dependencies)}
|
||||
{this.renderSidebarLinks(plugin.meta.info)}
|
||||
</section>
|
||||
)}
|
||||
<section className="page-sidebar-section">
|
||||
{this.renderVersionInfo(plugin.meta)}
|
||||
{isAdmin && this.renderSidebarIncludes(plugin.meta.includes)}
|
||||
{this.renderSidebarDependencies(plugin.meta.dependencies)}
|
||||
{this.renderSidebarLinks(plugin.meta.info)}
|
||||
</section>
|
||||
</aside>
|
||||
</div>
|
||||
)}
|
||||
@ -341,7 +342,7 @@ function getPluginTabsNav(
|
||||
isAdmin: boolean
|
||||
): { defaultPage: string; nav: NavModel } {
|
||||
const { meta } = plugin;
|
||||
let defaultPage: string;
|
||||
let defaultPage: string | undefined;
|
||||
const pages: NavModelItem[] = [];
|
||||
|
||||
if (true) {
|
||||
@ -377,6 +378,7 @@ function getPluginTabsNav(
|
||||
url: `${appSubUrl}${path}?page=${page.id}`,
|
||||
id: page.id,
|
||||
});
|
||||
|
||||
if (!defaultPage) {
|
||||
defaultPage = page.id;
|
||||
}
|
||||
@ -405,11 +407,11 @@ function getPluginTabsNav(
|
||||
subTitle: meta.info.author.name,
|
||||
breadcrumbs: [{ title: 'Plugins', url: 'plugins' }],
|
||||
url: `${appSubUrl}${path}`,
|
||||
children: setActivePage(query.page as string, pages, defaultPage),
|
||||
children: setActivePage(query.page as string, pages, defaultPage!),
|
||||
};
|
||||
|
||||
return {
|
||||
defaultPage,
|
||||
defaultPage: defaultPage!,
|
||||
nav: {
|
||||
node: node,
|
||||
main: node,
|
||||
@ -427,9 +429,11 @@ function setActivePage(pageId: string, pages: NavModelItem[], defaultPageId: str
|
||||
}
|
||||
return { ...p, active };
|
||||
});
|
||||
|
||||
if (!found) {
|
||||
changed[0].active = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { Badge, BadgeProps } from '@grafana/ui';
|
||||
import { PluginSignatureStatus } from '@grafana/data';
|
||||
|
||||
interface Props {
|
||||
status: PluginSignatureStatus;
|
||||
status?: PluginSignatureStatus;
|
||||
}
|
||||
|
||||
export const PluginSignatureBadge: React.FC<Props> = ({ status }) => {
|
||||
@ -11,7 +11,11 @@ export const PluginSignatureBadge: React.FC<Props> = ({ status }) => {
|
||||
return <Badge text={display.text} color={display.color} icon={display.icon} tooltip={display.tooltip} />;
|
||||
};
|
||||
|
||||
function getSignatureDisplayModel(signature: PluginSignatureStatus): BadgeProps {
|
||||
function getSignatureDisplayModel(signature?: PluginSignatureStatus): BadgeProps {
|
||||
if (!signature) {
|
||||
signature = PluginSignatureStatus.invalid;
|
||||
}
|
||||
|
||||
switch (signature) {
|
||||
case PluginSignatureStatus.internal:
|
||||
return { text: 'Core', icon: 'cube', color: 'blue', tooltip: 'Core plugin that is bundled with Grafana' };
|
||||
|
@ -126,7 +126,7 @@ export class DatasourceSrv implements DataSourceService {
|
||||
|
||||
Object.entries(config.datasources).forEach(([key, value]) => {
|
||||
if (value.meta?.metrics) {
|
||||
let metricSource = { value: key, name: key, meta: value.meta, sort: key };
|
||||
let metricSource: DataSourceSelectItem = { value: key, name: key, meta: value.meta, sort: key };
|
||||
|
||||
//Make sure grafana and mixed are sorted at the bottom
|
||||
if (value.meta.id === 'grafana') {
|
||||
|
@ -122,7 +122,7 @@ function pluginDirectiveLoader(
|
||||
'panel-ctrl': 'ctrl',
|
||||
datasource: 'ctrl.datasource',
|
||||
},
|
||||
Component: ds.components.QueryCtrl,
|
||||
Component: ds.components!.QueryCtrl,
|
||||
});
|
||||
}
|
||||
// Annotations
|
||||
@ -189,6 +189,10 @@ function pluginDirectiveLoader(
|
||||
case 'app-page': {
|
||||
const appModel = scope.ctrl.appModel;
|
||||
return importAppPlugin(appModel).then(appPlugin => {
|
||||
if (!appPlugin.angularPages) {
|
||||
throw new Error('Plugin has no page components');
|
||||
}
|
||||
|
||||
return {
|
||||
baseUrl: appModel.baseUrl,
|
||||
name: 'app-page-' + appModel.id + '-' + scope.ctrl.page.slug,
|
||||
|
@ -14,12 +14,12 @@ interface Props {
|
||||
}
|
||||
|
||||
interface State {
|
||||
angularCtrl: AngularComponent;
|
||||
angularCtrl: AngularComponent | null;
|
||||
refresh: number;
|
||||
}
|
||||
|
||||
export class AppConfigCtrlWrapper extends PureComponent<Props, State> {
|
||||
element: HTMLElement; // for angular ctrl
|
||||
element: HTMLElement | null = null;
|
||||
|
||||
// Needed for angular scope
|
||||
preUpdateHook = () => Promise.resolve();
|
||||
|
@ -77,7 +77,7 @@ export const SignupForm: FC<Props> = props => {
|
||||
<Field label="Your name">
|
||||
<Input name="name" placeholder="(optional)" ref={register} />
|
||||
</Field>
|
||||
<Field label="Email" invalid={!!errors.email} error={!!errors.email && errors.email.message}>
|
||||
<Field label="Email" invalid={!!errors.email} error={errors.email?.message}>
|
||||
<Input
|
||||
name="email"
|
||||
type="email"
|
||||
@ -91,7 +91,7 @@ export const SignupForm: FC<Props> = props => {
|
||||
})}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Password" invalid={!!errors.password} error={!!errors.password && errors.password.message}>
|
||||
<Field label="Password" invalid={!!errors.password} error={errors.password?.message}>
|
||||
<Input
|
||||
name="password"
|
||||
type="password"
|
||||
|
@ -21,6 +21,7 @@ export const DashboardListPage: FC<Props> = memo(({ navModel, uid, url }) => {
|
||||
if (!uid || !url.startsWith('/dashboards')) {
|
||||
return Promise.resolve({ pageNavModel: navModel });
|
||||
}
|
||||
|
||||
return loadFolderPage(uid!, 'manage-folder-dashboards').then(({ folder, model }) => {
|
||||
const path = locationUtil.stripBaseFromUrl(folder.url);
|
||||
|
||||
@ -33,7 +34,7 @@ export const DashboardListPage: FC<Props> = memo(({ navModel, uid, url }) => {
|
||||
}, [uid]);
|
||||
|
||||
return (
|
||||
<Page navModel={value?.pageNavModel}>
|
||||
<Page navModel={value?.pageNavModel ?? navModel}>
|
||||
<Page.Contents isLoading={loading}>
|
||||
<ManageDashboards folder={value?.folder} />
|
||||
</Page.Contents>
|
||||
|
@ -41,14 +41,14 @@ export const loadFolderPage = (uid: string, activeChildId: string) => {
|
||||
const folderUrl = folder.url;
|
||||
navModel.main.text = folderTitle;
|
||||
|
||||
const dashTab = navModel.main.children.find((child: any) => child.id === 'manage-folder-dashboards');
|
||||
const dashTab = navModel.main.children!.find((child: any) => child.id === 'manage-folder-dashboards');
|
||||
dashTab!.url = folderUrl;
|
||||
|
||||
if (folder.canAdmin) {
|
||||
const permTab = navModel.main.children.find((child: any) => child.id === 'manage-folder-permissions');
|
||||
const permTab = navModel.main.children!.find((child: any) => child.id === 'manage-folder-permissions');
|
||||
permTab!.url = folderUrl + '/permissions';
|
||||
|
||||
const settingsTab = navModel.main.children.find((child: any) => child.id === 'manage-folder-settings');
|
||||
const settingsTab = navModel.main.children!.find((child: any) => child.id === 'manage-folder-settings');
|
||||
settingsTab!.url = folderUrl + '/settings';
|
||||
} else {
|
||||
navModel.main.children = [dashTab!];
|
||||
|
@ -19,7 +19,7 @@ export interface Props {
|
||||
|
||||
interface State {
|
||||
isAdding: boolean;
|
||||
newGroupId?: string;
|
||||
newGroupId: string;
|
||||
}
|
||||
|
||||
const headerTooltip = `Sync LDAP or OAuth groups with your Grafana teams.`;
|
||||
|
@ -23,8 +23,8 @@ export interface Props {
|
||||
loadTeams: typeof loadTeams;
|
||||
deleteTeam: typeof deleteTeam;
|
||||
setSearchQuery: typeof setSearchQuery;
|
||||
editorsCanAdmin?: boolean;
|
||||
signedInUser?: User;
|
||||
editorsCanAdmin: boolean;
|
||||
signedInUser: User;
|
||||
}
|
||||
|
||||
export class TeamList extends PureComponent<Props, any> {
|
||||
|
@ -14,8 +14,8 @@ export interface Props {
|
||||
syncEnabled: boolean;
|
||||
editorsCanAdmin: boolean;
|
||||
signedInUserIsTeamAdmin: boolean;
|
||||
removeTeamMember?: typeof removeTeamMember;
|
||||
updateTeamMember?: typeof updateTeamMember;
|
||||
removeTeamMember: typeof removeTeamMember;
|
||||
updateTeamMember: typeof updateTeamMember;
|
||||
}
|
||||
|
||||
export class TeamMemberRow extends PureComponent<Props> {
|
||||
@ -31,14 +31,17 @@ export class TeamMemberRow extends PureComponent<Props> {
|
||||
|
||||
onPermissionChange = (item: SelectableValue<TeamPermissionLevel>, member: TeamMember) => {
|
||||
const permission = item.value;
|
||||
const updatedTeamMember = { ...member, permission };
|
||||
const updatedTeamMember: TeamMember = {
|
||||
...member,
|
||||
permission: permission as number,
|
||||
};
|
||||
|
||||
this.props.updateTeamMember(updatedTeamMember);
|
||||
};
|
||||
|
||||
renderPermissions(member: TeamMember) {
|
||||
const { editorsCanAdmin, signedInUserIsTeamAdmin } = this.props;
|
||||
const value = teamsPermissionLevels.find(dp => dp.value === member.permission);
|
||||
const value = teamsPermissionLevels.find(dp => dp.value === member.permission)!;
|
||||
|
||||
return (
|
||||
<WithFeatureToggle featureToggle={editorsCanAdmin}>
|
||||
|
@ -20,13 +20,13 @@ export interface Props {
|
||||
addTeamMember: typeof addTeamMember;
|
||||
setSearchMemberQuery: typeof setSearchMemberQuery;
|
||||
syncEnabled: boolean;
|
||||
editorsCanAdmin?: boolean;
|
||||
signedInUser?: SignedInUser;
|
||||
editorsCanAdmin: boolean;
|
||||
signedInUser: SignedInUser;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
isAdding: boolean;
|
||||
newTeamMember?: User;
|
||||
newTeamMember?: User | null;
|
||||
}
|
||||
|
||||
export class TeamMembers extends PureComponent<Props, State> {
|
||||
@ -48,7 +48,7 @@ export class TeamMembers extends PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
onAddUserToTeam = async () => {
|
||||
this.props.addTeamMember(this.state.newTeamMember.id);
|
||||
this.props.addTeamMember(this.state.newTeamMember!.id);
|
||||
this.setState({ newTeamMember: null });
|
||||
};
|
||||
|
||||
|
@ -23,9 +23,9 @@ export interface Props {
|
||||
teamId: number;
|
||||
pageName: string;
|
||||
navModel: NavModel;
|
||||
members?: TeamMember[];
|
||||
editorsCanAdmin?: boolean;
|
||||
signedInUser?: User;
|
||||
members: TeamMember[];
|
||||
editorsCanAdmin: boolean;
|
||||
signedInUser: User;
|
||||
}
|
||||
|
||||
interface State {
|
||||
@ -92,7 +92,7 @@ export class TeamPages extends PureComponent<Props, State> {
|
||||
return navModel;
|
||||
};
|
||||
|
||||
renderPage(isSignedInUserTeamAdmin: boolean) {
|
||||
renderPage(isSignedInUserTeamAdmin: boolean): React.ReactNode {
|
||||
const { isSyncEnabled } = this.state;
|
||||
const { members } = this.props;
|
||||
const currentPage = this.getCurrentPage();
|
||||
|
@ -54,8 +54,8 @@ export function getTeamLoadingNav(pageName: string): NavModel {
|
||||
let node: NavModelItem;
|
||||
|
||||
// find active page
|
||||
for (const child of main.children) {
|
||||
if (child.id.indexOf(pageName) > 0) {
|
||||
for (const child of main.children!) {
|
||||
if (child.id!.indexOf(pageName) > 0) {
|
||||
child.active = true;
|
||||
node = child;
|
||||
break;
|
||||
@ -64,6 +64,6 @@ export function getTeamLoadingNav(pageName: string): NavModel {
|
||||
|
||||
return {
|
||||
main: main,
|
||||
node: node,
|
||||
node: node!,
|
||||
};
|
||||
}
|
||||
|
@ -318,9 +318,9 @@ export class TemplateSrv implements BaseTemplateSrv {
|
||||
return scopedVar.value;
|
||||
}
|
||||
|
||||
replace(target: string, scopedVars?: ScopedVars, format?: string | Function): string {
|
||||
replace(target?: string, scopedVars?: ScopedVars, format?: string | Function): string {
|
||||
if (!target) {
|
||||
return target;
|
||||
return target ?? '';
|
||||
}
|
||||
|
||||
this.regex.lastIndex = 0;
|
||||
|
@ -16,7 +16,7 @@ export interface OptionsPickerState {
|
||||
id: string;
|
||||
selectedValues: VariableOption[];
|
||||
selectedTags: VariableTag[];
|
||||
queryValue: string | null;
|
||||
queryValue: string;
|
||||
highlightIndex: number;
|
||||
tags: VariableTag[];
|
||||
options: VariableOption[];
|
||||
@ -26,7 +26,7 @@ export interface OptionsPickerState {
|
||||
export const initialState: OptionsPickerState = {
|
||||
id: '',
|
||||
highlightIndex: -1,
|
||||
queryValue: null,
|
||||
queryValue: '',
|
||||
selectedTags: [],
|
||||
selectedValues: [],
|
||||
tags: [],
|
||||
|
@ -120,21 +120,34 @@ export default class CloudMonitoringMetricFindQuery {
|
||||
return [];
|
||||
}
|
||||
const metricDescriptors = await this.datasource.getMetricTypes(projectName);
|
||||
const { valueType, metricKind } = metricDescriptors.find(
|
||||
const descriptor = metricDescriptors.find(
|
||||
(m: any) => m.type === this.datasource.templateSrv.replace(selectedMetricType)
|
||||
);
|
||||
return getAlignmentOptionsByMetric(valueType, metricKind).map(this.toFindQueryResult);
|
||||
|
||||
if (!descriptor) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return getAlignmentOptionsByMetric(descriptor.valueType, descriptor.metricKind).map(this.toFindQueryResult);
|
||||
}
|
||||
|
||||
async handleAggregationQuery({ selectedMetricType, projectName }: VariableQueryData) {
|
||||
if (!selectedMetricType) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const metricDescriptors = await this.datasource.getMetricTypes(projectName);
|
||||
const { valueType, metricKind } = metricDescriptors.find(
|
||||
const descriptor = metricDescriptors.find(
|
||||
(m: any) => m.type === this.datasource.templateSrv.replace(selectedMetricType)
|
||||
);
|
||||
return getAggregationOptionsByMetric(valueType as ValueTypes, metricKind as MetricKind).map(this.toFindQueryResult);
|
||||
|
||||
if (!descriptor) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return getAggregationOptionsByMetric(descriptor.valueType as ValueTypes, descriptor.metricKind as MetricKind).map(
|
||||
this.toFindQueryResult
|
||||
);
|
||||
}
|
||||
|
||||
async handleSLOServicesQuery({ projectName }: VariableQueryData) {
|
||||
|
@ -38,7 +38,7 @@ export default class Api {
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
const responsePropName = path.match(/([^\/]*)\/*$/)[1];
|
||||
const responsePropName = path.match(/([^\/]*)\/*$/)![1];
|
||||
let res = [];
|
||||
if (response && response.data && response.data[responsePropName]) {
|
||||
res = response.data[responsePropName].map(responseMap);
|
||||
|
@ -13,7 +13,7 @@ const props: Props = {
|
||||
metricDescriptor: {
|
||||
valueType: '',
|
||||
metricKind: '',
|
||||
},
|
||||
} as any,
|
||||
crossSeriesReducer: '',
|
||||
groupBys: [],
|
||||
children: renderProps => <div />,
|
||||
@ -33,7 +33,7 @@ describe('Aggregations', () => {
|
||||
metricDescriptor: {
|
||||
valueType: ValueTypes.DOUBLE,
|
||||
metricKind: MetricKind.GAUGE,
|
||||
},
|
||||
} as any,
|
||||
};
|
||||
|
||||
it('should not have the reduce values', () => {
|
||||
@ -54,7 +54,7 @@ describe('Aggregations', () => {
|
||||
metricDescriptor: {
|
||||
valueType: ValueTypes.MONEY,
|
||||
metricKind: MetricKind.CUMULATIVE,
|
||||
},
|
||||
} as any,
|
||||
};
|
||||
|
||||
it('should have the reduce values', () => {
|
||||
|
@ -5,16 +5,14 @@ import { SelectableValue } from '@grafana/data';
|
||||
import { Segment, Icon } from '@grafana/ui';
|
||||
import { getAggregationOptionsByMetric } from '../functions';
|
||||
import { ValueTypes, MetricKind } from '../constants';
|
||||
import { MetricDescriptor } from '../types';
|
||||
|
||||
export interface Props {
|
||||
onChange: (metricDescriptor: string) => void;
|
||||
metricDescriptor: {
|
||||
valueType: string;
|
||||
metricKind: string;
|
||||
};
|
||||
metricDescriptor?: MetricDescriptor;
|
||||
crossSeriesReducer: string;
|
||||
groupBys: string[];
|
||||
children?: (renderProps: any) => JSX.Element;
|
||||
children: (displayAdvancedOptions: boolean) => React.ReactNode;
|
||||
templateVariableOptions: Array<SelectableValue<string>>;
|
||||
}
|
||||
|
||||
@ -28,7 +26,7 @@ export const Aggregations: FC<Props> = props => {
|
||||
<div className="gf-form-inline">
|
||||
<label className="gf-form-label query-keyword width-9">Aggregation</label>
|
||||
<Segment
|
||||
onChange={({ value }) => props.onChange(value)}
|
||||
onChange={({ value }) => props.onChange(value!)}
|
||||
value={selected}
|
||||
options={[
|
||||
{
|
||||
|
@ -4,11 +4,11 @@ import { QueryInlineField } from '.';
|
||||
|
||||
export interface Props {
|
||||
onChange: (alias: any) => void;
|
||||
value: string;
|
||||
value?: string;
|
||||
}
|
||||
|
||||
export const AliasBy: FunctionComponent<Props> = ({ value = '', onChange }) => {
|
||||
const [alias, setAlias] = useState(value);
|
||||
const [alias, setAlias] = useState(value ?? '');
|
||||
|
||||
const propagateOnChange = debounce(onChange, 1000);
|
||||
|
||||
|
@ -14,7 +14,7 @@ export interface Props {
|
||||
datasource: CloudMonitoringDatasource;
|
||||
projectName: string;
|
||||
metricType: string;
|
||||
children?: (renderProps: any) => JSX.Element;
|
||||
children: (metricDescriptor?: MetricDescriptor) => JSX.Element;
|
||||
}
|
||||
|
||||
interface State {
|
||||
@ -23,8 +23,8 @@ interface State {
|
||||
services: any[];
|
||||
service: string;
|
||||
metric: string;
|
||||
metricDescriptor: MetricDescriptor;
|
||||
projectName: string;
|
||||
metricDescriptor?: MetricDescriptor;
|
||||
projectName: string | null;
|
||||
}
|
||||
|
||||
export function Metrics(props: Props) {
|
||||
@ -34,7 +34,6 @@ export function Metrics(props: Props) {
|
||||
services: [],
|
||||
service: '',
|
||||
metric: '',
|
||||
metricDescriptor: null,
|
||||
projectName: null,
|
||||
});
|
||||
|
||||
@ -57,7 +56,7 @@ export function Metrics(props: Props) {
|
||||
}, [projectName]);
|
||||
|
||||
const getSelectedMetricDescriptor = (metricDescriptors: MetricDescriptor[], metricType: string) => {
|
||||
return metricDescriptors.find(md => md.type === props.templateSrv.replace(metricType));
|
||||
return metricDescriptors.find(md => md.type === props.templateSrv.replace(metricType))!;
|
||||
};
|
||||
|
||||
const getMetricsList = (metricDescriptors: MetricDescriptor[]) => {
|
||||
@ -97,9 +96,9 @@ export function Metrics(props: Props) {
|
||||
};
|
||||
|
||||
const onMetricTypeChange = ({ value }: SelectableValue<string>, extra: any = {}) => {
|
||||
const metricDescriptor = getSelectedMetricDescriptor(state.metricDescriptors, value);
|
||||
const metricDescriptor = getSelectedMetricDescriptor(state.metricDescriptors, value!);
|
||||
setState({ ...state, metricDescriptor, ...extra });
|
||||
props.onChange({ ...metricDescriptor, type: value });
|
||||
props.onChange({ ...metricDescriptor, type: value! });
|
||||
};
|
||||
|
||||
const getServicesList = (metricDescriptors: MetricDescriptor[]) => {
|
||||
|
@ -63,8 +63,8 @@ export class QueryEditor extends PureComponent<Props, State> {
|
||||
|
||||
render() {
|
||||
const { datasource, query, onRunQuery, onChange } = this.props;
|
||||
const metricQuery = { ...defaultQuery, projectName: datasource.getDefaultProject(), ...query.metricQuery };
|
||||
const sloQuery = { ...defaultSLOQuery, projectName: datasource.getDefaultProject(), ...query.sloQuery };
|
||||
const metricQuery = { ...defaultQuery, ...query.metricQuery, projectName: datasource.getDefaultProject() };
|
||||
const sloQuery = { ...defaultSLOQuery, ...query.sloQuery, projectName: datasource.getDefaultProject() };
|
||||
const queryType = query.queryType || QueryType.METRICS;
|
||||
const meta = this.props.data?.series.length ? this.props.data?.series[0].meta : {};
|
||||
const usedAlignmentPeriod = meta?.alignmentPeriod as string;
|
||||
|
@ -87,7 +87,7 @@ export class CloudMonitoringVariableQueryEditor extends PureComponent<VariableQu
|
||||
|
||||
onPropsChange = () => {
|
||||
const { metricDescriptors, labels, metricTypes, services, ...queryModel } = this.state;
|
||||
const query = this.queryTypes.find(q => q.value === this.state.selectedQueryType);
|
||||
const query = this.queryTypes.find(q => q.value === this.state.selectedQueryType)!;
|
||||
this.props.onChange(queryModel, `Google Cloud Monitoring - ${query.name}`);
|
||||
};
|
||||
|
||||
|
@ -263,8 +263,8 @@ export default class CloudMonitoringDatasource extends DataSourceApi<CloudMonito
|
||||
async getSLOServices(projectName: string): Promise<Array<SelectableValue<string>>> {
|
||||
return this.api.get(`${this.templateSrv.replace(projectName)}/services`, {
|
||||
responseMap: ({ name }: { name: string }) => ({
|
||||
value: name.match(/([^\/]*)\/*$/)[1],
|
||||
label: name.match(/([^\/]*)\/*$/)[1],
|
||||
value: name.match(/([^\/]*)\/*$/)![1],
|
||||
label: name.match(/([^\/]*)\/*$/)![1],
|
||||
}),
|
||||
});
|
||||
}
|
||||
@ -276,7 +276,7 @@ export default class CloudMonitoringDatasource extends DataSourceApi<CloudMonito
|
||||
let { projectName: p, serviceId: s } = this.interpolateProps({ projectName, serviceId });
|
||||
return this.api.get(`${p}/services/${s}/serviceLevelObjectives`, {
|
||||
responseMap: ({ name, displayName, goal }: { name: string; displayName: string; goal: number }) => ({
|
||||
value: name.match(/([^\/]*)\/*$/)[1],
|
||||
value: name.match(/([^\/]*)\/*$/)![1],
|
||||
label: displayName,
|
||||
goal,
|
||||
}),
|
||||
@ -323,7 +323,7 @@ export default class CloudMonitoringDatasource extends DataSourceApi<CloudMonito
|
||||
return false;
|
||||
}
|
||||
|
||||
if (query.queryType && query.queryType === QueryType.SLO) {
|
||||
if (query.queryType && query.queryType === QueryType.SLO && query.sloQuery) {
|
||||
const { selectorName, serviceId, sloId, projectName } = query.sloQuery;
|
||||
return !!selectorName && !!serviceId && !!sloId && !!projectName;
|
||||
}
|
||||
|
@ -93,7 +93,7 @@ export const labelsToGroupedOptions = (groupBys: string[]) => {
|
||||
};
|
||||
|
||||
export const filtersToStringArray = (filters: Filter[]) => {
|
||||
const strArr = _.flatten(filters.map(({ key, operator, value, condition }) => [key, operator, value, condition]));
|
||||
const strArr = _.flatten(filters.map(({ key, operator, value, condition }) => [key, operator, value, condition!]));
|
||||
return strArr.filter((_, i) => i !== strArr.length - 1);
|
||||
};
|
||||
|
||||
|
@ -8,7 +8,7 @@ import { CloudWatchDatasource } from '../datasource';
|
||||
|
||||
interface Props {
|
||||
query: CloudWatchLogsQuery;
|
||||
panelData: PanelData;
|
||||
panelData?: PanelData;
|
||||
datasource: CloudWatchDatasource;
|
||||
}
|
||||
|
||||
@ -18,8 +18,12 @@ interface State {
|
||||
|
||||
export default class CloudWatchLink extends Component<Props, State> {
|
||||
state: State = { href: '' };
|
||||
|
||||
async componentDidUpdate(prevProps: Props) {
|
||||
if (prevProps.panelData !== this.props.panelData && this.props.panelData.request) {
|
||||
const { panelData: panelDataNew } = this.props;
|
||||
const { panelData: panelDataOld } = prevProps;
|
||||
|
||||
if (panelDataOld !== panelDataNew && panelDataNew?.request) {
|
||||
const href = this.getExternalLink();
|
||||
this.setState({ href });
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ export const Dimensions: FunctionComponent<Props> = ({ dimensions, loadValues, l
|
||||
}, [data]);
|
||||
|
||||
const excludeUsedKeys = (options: SelectableStrings) => {
|
||||
return options.filter(({ value }) => !Object.keys(data).includes(value));
|
||||
return options.filter(({ value }) => !Object.keys(data).includes(value!));
|
||||
};
|
||||
|
||||
return (
|
||||
@ -47,7 +47,7 @@ export const Dimensions: FunctionComponent<Props> = ({ dimensions, loadValues, l
|
||||
if (newKey === removeText) {
|
||||
setData({ ...newDimensions });
|
||||
} else {
|
||||
setData({ ...newDimensions, [newKey]: '' });
|
||||
setData({ ...newDimensions, [newKey!]: '' });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@ -57,7 +57,7 @@ export const Dimensions: FunctionComponent<Props> = ({ dimensions, loadValues, l
|
||||
value={value}
|
||||
placeholder="select dimension value"
|
||||
loadOptions={() => loadValues(key)}
|
||||
onChange={({ value: newValue }) => setData({ ...data, [key]: newValue })}
|
||||
onChange={({ value: newValue }) => setData({ ...data, [key]: newValue! })}
|
||||
/>
|
||||
{Object.values(data).length > 1 && index + 1 !== Object.values(data).length && (
|
||||
<label className="gf-form-label query-keyword">AND</label>
|
||||
@ -73,7 +73,7 @@ export const Dimensions: FunctionComponent<Props> = ({ dimensions, loadValues, l
|
||||
</a>
|
||||
}
|
||||
loadOptions={() => loadKeys().then(excludeUsedKeys)}
|
||||
onChange={({ value: newKey }) => setData({ ...data, [newKey]: '' })}
|
||||
onChange={({ value: newKey }) => setData({ ...data, [newKey!]: '' })}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
@ -40,7 +40,7 @@ export interface CloudWatchLogsQueryFieldProps extends ExploreQueryFieldProps<Cl
|
||||
onLabelsRefresh?: () => void;
|
||||
ExtraFieldElement?: ReactNode;
|
||||
syntaxLoaded: boolean;
|
||||
syntax: Grammar;
|
||||
syntax: Grammar | null;
|
||||
exploreId: ExploreId;
|
||||
allowCustomValue?: boolean;
|
||||
}
|
||||
|
@ -94,7 +94,7 @@ export function MetricsQueryFieldsEditor({
|
||||
placeholder="Select region"
|
||||
options={regions}
|
||||
allowCustomValue
|
||||
onChange={({ value: region }) => onQueryChange({ ...query, region })}
|
||||
onChange={({ value: region }) => onQueryChange({ ...query, region: region! })}
|
||||
/>
|
||||
</QueryInlineField>
|
||||
|
||||
@ -106,7 +106,7 @@ export function MetricsQueryFieldsEditor({
|
||||
placeholder="Select namespace"
|
||||
allowCustomValue
|
||||
options={namespaces}
|
||||
onChange={({ value: namespace }) => onQueryChange({ ...query, namespace })}
|
||||
onChange={({ value: namespace }) => onQueryChange({ ...query, namespace: namespace! })}
|
||||
/>
|
||||
</QueryInlineField>
|
||||
|
||||
|
@ -26,7 +26,7 @@ export const Stats: FunctionComponent<Props> = ({ stats, values, onChange, varia
|
||||
onChange(
|
||||
value === removeText
|
||||
? values.filter((_, i) => i !== index)
|
||||
: values.map((v, i) => (i === index ? value : v))
|
||||
: values.map((v, i) => (i === index ? value! : v))
|
||||
)
|
||||
}
|
||||
/>
|
||||
@ -38,8 +38,8 @@ export const Stats: FunctionComponent<Props> = ({ stats, values, onChange, varia
|
||||
</a>
|
||||
}
|
||||
allowCustomValue
|
||||
onChange={({ value }) => onChange([...values, value])}
|
||||
options={[...stats.filter(({ value }) => !values.includes(value)), variableOptionGroup]}
|
||||
onChange={({ value }) => onChange([...values, value!])}
|
||||
options={[...stats.filter(({ value }) => !values.includes(value!)), variableOptionGroup]}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
@ -177,7 +177,6 @@ export class CloudWatchDatasource extends DataSourceApi<CloudWatchQuery, CloudWa
|
||||
}
|
||||
|
||||
return {
|
||||
refId: item.refId,
|
||||
intervalMs: options.intervalMs,
|
||||
maxDataPoints: options.maxDataPoints,
|
||||
datasourceId: this.id,
|
||||
|
@ -178,7 +178,7 @@ export class CloudWatchLanguageProvider extends LanguageProvider {
|
||||
private handleCommand = async (
|
||||
commandToken: Token,
|
||||
curToken: Token,
|
||||
context: TypeaheadContext
|
||||
context?: TypeaheadContext
|
||||
): Promise<TypeaheadOutput> => {
|
||||
const queryCommand = commandToken.content.toLowerCase();
|
||||
const prevToken = prevNonWhitespaceToken(curToken);
|
||||
@ -190,11 +190,11 @@ export class CloudWatchLanguageProvider extends LanguageProvider {
|
||||
|
||||
if (queryCommand === 'parse') {
|
||||
if (currentTokenIsFirstArg) {
|
||||
return await this.getFieldCompletionItems(context.logGroupNames ?? []);
|
||||
return await this.getFieldCompletionItems(context?.logGroupNames ?? []);
|
||||
}
|
||||
}
|
||||
|
||||
const currentTokenIsAfterCommandAndEmpty = isTokenType(commandToken.next, 'whitespace') && !commandToken.next.next;
|
||||
const currentTokenIsAfterCommandAndEmpty = isTokenType(commandToken.next, 'whitespace') && !commandToken.next?.next;
|
||||
const currentTokenIsAfterCommand =
|
||||
currentTokenIsAfterCommandAndEmpty || nextNonWhitespaceToken(commandToken) === curToken;
|
||||
|
||||
@ -207,7 +207,7 @@ export class CloudWatchLanguageProvider extends LanguageProvider {
|
||||
}
|
||||
|
||||
if (['display', 'fields'].includes(queryCommand)) {
|
||||
const typeaheadOutput = await this.getFieldCompletionItems(context.logGroupNames ?? []);
|
||||
const typeaheadOutput = await this.getFieldCompletionItems(context?.logGroupNames ?? []);
|
||||
typeaheadOutput.suggestions.push(...this.getFieldAndFilterFunctionCompletionItems().suggestions);
|
||||
|
||||
return typeaheadOutput;
|
||||
@ -224,7 +224,7 @@ export class CloudWatchLanguageProvider extends LanguageProvider {
|
||||
}
|
||||
|
||||
if (queryCommand === 'filter' && currentTokenIsFirstArg) {
|
||||
const sugg = await this.getFieldCompletionItems(context.logGroupNames ?? []);
|
||||
const sugg = await this.getFieldCompletionItems(context?.logGroupNames ?? []);
|
||||
const boolFuncs = this.getBoolFuncCompletionItems();
|
||||
sugg.suggestions.push(...boolFuncs.suggestions);
|
||||
return sugg;
|
||||
@ -235,10 +235,10 @@ export class CloudWatchLanguageProvider extends LanguageProvider {
|
||||
private async handleSortCommand(
|
||||
isFirstArgument: boolean,
|
||||
curToken: Token,
|
||||
context: TypeaheadContext
|
||||
context?: TypeaheadContext
|
||||
): Promise<TypeaheadOutput> {
|
||||
if (isFirstArgument) {
|
||||
return await this.getFieldCompletionItems(context.logGroupNames ?? []);
|
||||
return await this.getFieldCompletionItems(context?.logGroupNames ?? []);
|
||||
} else if (isTokenType(prevNonWhitespaceToken(curToken), 'field-name')) {
|
||||
// suggest sort options
|
||||
return {
|
||||
|
@ -52,7 +52,7 @@ export class DashboardQueryEditor extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
this.componentDidUpdate(null);
|
||||
this.componentDidUpdate(this.props);
|
||||
}
|
||||
|
||||
async componentDidUpdate(prevProps: Props) {
|
||||
@ -60,9 +60,9 @@ export class DashboardQueryEditor extends PureComponent<Props, State> {
|
||||
|
||||
if (!prevProps || prevProps.panelData !== panelData) {
|
||||
const query = this.props.panel.targets[0] as DashboardQuery;
|
||||
const defaultDS = await getDatasourceSrv().get(null);
|
||||
const defaultDS = await getDatasourceSrv().get();
|
||||
const dashboard = getDashboardSrv().getCurrent();
|
||||
const panel = dashboard.getPanelById(query.panelId);
|
||||
const panel = dashboard.getPanelById(query.panelId ?? -124134);
|
||||
|
||||
if (!panel) {
|
||||
this.setState({ defaultDatasource: defaultDS.name });
|
||||
@ -143,7 +143,7 @@ export class DashboardQueryEditor extends PureComponent<Props, State> {
|
||||
const dashboard = getDashboardSrv().getCurrent();
|
||||
const query = this.getQuery();
|
||||
|
||||
let selected: SelectableValue<number>;
|
||||
let selected: SelectableValue<number> | undefined;
|
||||
const panels: Array<SelectableValue<number>> = [];
|
||||
|
||||
for (const panel of dashboard.panels) {
|
||||
@ -188,7 +188,7 @@ export class DashboardQueryEditor extends PureComponent<Props, State> {
|
||||
isSearchable={true}
|
||||
options={panels}
|
||||
value={selected}
|
||||
onChange={item => this.onPanelChanged(item.value)}
|
||||
onChange={item => this.onPanelChanged(item.value!)}
|
||||
/>
|
||||
</div>
|
||||
<div className={css({ padding: '16px' })}>{query.panelId && this.renderQueryData(editURL)}</div>
|
||||
|
@ -23,7 +23,7 @@ export function runSharedRequest(options: QueryRunnerOptions): Observable<PanelD
|
||||
|
||||
if (!listenToPanelId) {
|
||||
subscriber.next(getQueryError('Missing panel reference ID'));
|
||||
return null;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const currentPanel = dashboard.getPanelById(options.panelId);
|
||||
@ -31,7 +31,7 @@ export function runSharedRequest(options: QueryRunnerOptions): Observable<PanelD
|
||||
|
||||
if (!listenToPanel) {
|
||||
subscriber.next(getQueryError('Unknown Panel: ' + listenToPanelId));
|
||||
return null;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const listenToRunner = listenToPanel.getQueryRunner();
|
||||
|
@ -152,7 +152,7 @@ const DataSourceSection = (props: DataSourceSectionProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
function useInternalLink(datasourceUid: string): [boolean, Dispatch<SetStateAction<boolean>>] {
|
||||
function useInternalLink(datasourceUid?: string): [boolean, Dispatch<SetStateAction<boolean>>] {
|
||||
const [showInternalLink, setShowInternalLink] = useState<boolean>(!!datasourceUid);
|
||||
const previousUid = usePrevious(datasourceUid);
|
||||
|
||||
|
@ -86,13 +86,13 @@ export const ElasticDetails = (props: Props) => {
|
||||
onChange={option => {
|
||||
const maxConcurrentShardRequests = getMaxConcurrenShardRequestOrDefault(
|
||||
value.jsonData.maxConcurrentShardRequests,
|
||||
option.value
|
||||
option.value!
|
||||
);
|
||||
onChange({
|
||||
...value,
|
||||
jsonData: {
|
||||
...value.jsonData,
|
||||
esVersion: option.value,
|
||||
esVersion: option.value!,
|
||||
maxConcurrentShardRequests,
|
||||
},
|
||||
});
|
||||
@ -179,10 +179,12 @@ const intervalHandler = (value: Props['value'], onChange: Props['onChange']) =>
|
||||
|
||||
if (!database || database.length === 0 || database.startsWith('[logstash-]')) {
|
||||
let newDatabase = '';
|
||||
|
||||
if (newInterval !== undefined) {
|
||||
const pattern = indexPatternTypes.find(pattern => pattern.value === newInterval);
|
||||
|
||||
if (pattern) {
|
||||
newDatabase = pattern.example;
|
||||
newDatabase = pattern.example ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
@ -205,7 +207,7 @@ const intervalHandler = (value: Props['value'], onChange: Props['onChange']) =>
|
||||
}
|
||||
};
|
||||
|
||||
function getMaxConcurrenShardRequestOrDefault(maxConcurrentShardRequests: number, version: number): number {
|
||||
function getMaxConcurrenShardRequestOrDefault(maxConcurrentShardRequests: number | undefined, version: number): number {
|
||||
if (maxConcurrentShardRequests === 5 && version < 70) {
|
||||
return 256;
|
||||
}
|
||||
|
@ -20,15 +20,15 @@ import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||
import { DataLinkConfig, ElasticsearchOptions, ElasticsearchQuery } from './types';
|
||||
|
||||
export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, ElasticsearchOptions> {
|
||||
basicAuth: string;
|
||||
withCredentials: boolean;
|
||||
basicAuth?: string;
|
||||
withCredentials?: boolean;
|
||||
url: string;
|
||||
name: string;
|
||||
index: string;
|
||||
timeField: string;
|
||||
esVersion: number;
|
||||
interval: string;
|
||||
maxConcurrentShardRequests: number;
|
||||
maxConcurrentShardRequests?: number;
|
||||
queryBuilder: ElasticQueryBuilder;
|
||||
indexPattern: IndexPattern;
|
||||
logMessageField?: string;
|
||||
@ -44,9 +44,9 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
|
||||
super(instanceSettings);
|
||||
this.basicAuth = instanceSettings.basicAuth;
|
||||
this.withCredentials = instanceSettings.withCredentials;
|
||||
this.url = instanceSettings.url;
|
||||
this.url = instanceSettings.url!;
|
||||
this.name = instanceSettings.name;
|
||||
this.index = instanceSettings.database;
|
||||
this.index = instanceSettings.database ?? '';
|
||||
const settingsData = instanceSettings.jsonData || ({} as ElasticsearchOptions);
|
||||
|
||||
this.timeField = settingsData.timeField;
|
||||
@ -63,11 +63,11 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
|
||||
this.dataLinks = settingsData.dataLinks || [];
|
||||
|
||||
if (this.logMessageField === '') {
|
||||
this.logMessageField = null;
|
||||
this.logMessageField = undefined;
|
||||
}
|
||||
|
||||
if (this.logLevelField === '') {
|
||||
this.logLevelField = null;
|
||||
this.logLevelField = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@ -335,9 +335,11 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
|
||||
ignore_unavailable: true,
|
||||
index: this.indexPattern.getIndexList(timeFrom, timeTo),
|
||||
};
|
||||
|
||||
if (this.esVersion >= 56 && this.esVersion < 70) {
|
||||
queryHeader['max_concurrent_shard_requests'] = this.maxConcurrentShardRequests;
|
||||
}
|
||||
|
||||
return angular.toJson(queryHeader);
|
||||
}
|
||||
|
||||
@ -402,6 +404,7 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
|
||||
|
||||
return this.post(url, payload).then((res: any) => {
|
||||
const er = new ElasticResponse(sentTargets, res);
|
||||
|
||||
if (sentTargets.some(target => target.isLogsQuery)) {
|
||||
const response = er.getLogs(this.logMessageField, this.logLevelField);
|
||||
for (const dataFrame of response.data) {
|
||||
|
@ -12,7 +12,8 @@ export class ElasticResponse {
|
||||
}
|
||||
|
||||
processMetrics(esAgg: any, target: any, seriesList: any, props: any) {
|
||||
let metric, y, i, newSeries, bucket, value;
|
||||
let metric, y, i, bucket, value;
|
||||
let newSeries: any;
|
||||
|
||||
for (y = 0; y < target.metrics.length; y++) {
|
||||
metric = target.metrics[y];
|
||||
@ -486,7 +487,7 @@ const flattenHits = (hits: Doc[]): { docs: Array<Record<string, any>>; propNames
|
||||
let propNames: string[] = [];
|
||||
|
||||
for (const hit of hits) {
|
||||
const flattened = hit._source ? flatten(hit._source, null) : {};
|
||||
const flattened = hit._source ? flatten(hit._source) : {};
|
||||
const doc = {
|
||||
_id: hit._id,
|
||||
_type: hit._type,
|
||||
|
@ -9,7 +9,7 @@ const intervalMap: any = {
|
||||
};
|
||||
|
||||
export class IndexPattern {
|
||||
constructor(private pattern: any, private interval: string | null) {}
|
||||
constructor(private pattern: any, private interval?: string) {}
|
||||
|
||||
getIndexForToday() {
|
||||
if (this.interval) {
|
||||
|
@ -89,7 +89,7 @@ export class ElasticMetricAggCtrl {
|
||||
}
|
||||
return memo;
|
||||
},
|
||||
[]
|
||||
[] as string[]
|
||||
);
|
||||
|
||||
$scope.settingsLinkText = 'Stats: ' + stats.join(', ');
|
||||
|
@ -3,7 +3,7 @@ import { DataQuery, DataSourceJsonData } from '@grafana/data';
|
||||
export interface ElasticsearchOptions extends DataSourceJsonData {
|
||||
timeField: string;
|
||||
esVersion: number;
|
||||
interval: string;
|
||||
interval?: string;
|
||||
timeInterval: string;
|
||||
maxConcurrentShardRequests?: number;
|
||||
logMessageField?: string;
|
||||
|
@ -423,7 +423,7 @@ describe('AppInsightsDatasource', () => {
|
||||
});
|
||||
|
||||
it('should return a list of metric names', () => {
|
||||
return ctx.ds.metricFindQuery('appInsightsMetricNames()').then((results: any) => {
|
||||
return ctx.ds.metricFindQueryInternal('appInsightsMetricNames()').then((results: any) => {
|
||||
expect(results.length).toBe(2);
|
||||
expect(results[0].text).toBe('exceptions/server');
|
||||
expect(results[0].value).toBe('exceptions/server');
|
||||
@ -461,7 +461,7 @@ describe('AppInsightsDatasource', () => {
|
||||
});
|
||||
|
||||
it('should return a list of group bys', () => {
|
||||
return ctx.ds.metricFindQuery('appInsightsGroupBys(requests/count)').then((results: any) => {
|
||||
return ctx.ds.metricFindQueryInternal('appInsightsGroupBys(requests/count)').then((results: any) => {
|
||||
expect(results[0].text).toContain('client/os');
|
||||
expect(results[0].value).toContain('client/os');
|
||||
expect(results[1].text).toContain('client/city');
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ScopedVars } from '@grafana/data';
|
||||
import { ScopedVars, MetricFindValue } from '@grafana/data';
|
||||
import { DataQueryRequest, DataSourceInstanceSettings } from '@grafana/data';
|
||||
import { getBackendSrv, getTemplateSrv, DataSourceWithBackend } from '@grafana/runtime';
|
||||
import _, { isString } from 'lodash';
|
||||
@ -122,7 +122,13 @@ export default class AppInsightsDatasource extends DataSourceWithBackend<AzureMo
|
||||
};
|
||||
}
|
||||
|
||||
metricFindQuery(query: string) {
|
||||
/**
|
||||
* This is named differently than DataSourceApi.metricFindQuery
|
||||
* because it's not exposed to Grafana like the main AzureMonitorDataSource.
|
||||
* And some of the azure internal data sources return null in this function, which the
|
||||
* external interface does not support
|
||||
*/
|
||||
metricFindQueryInternal(query: string): Promise<MetricFindValue[]> | null {
|
||||
const appInsightsMetricNameQuery = query.match(/^AppInsightsMetricNames\(\)/i);
|
||||
if (appInsightsMetricNameQuery) {
|
||||
return this.getMetricNames();
|
||||
@ -134,7 +140,7 @@ export default class AppInsightsDatasource extends DataSourceWithBackend<AzureMo
|
||||
return this.getGroupBys(getTemplateSrv().replace(metricName));
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return null;
|
||||
}
|
||||
|
||||
testDatasource() {
|
||||
|
@ -2,13 +2,7 @@ import _ from 'lodash';
|
||||
import LogAnalyticsQuerystringBuilder from '../log_analytics/querystring_builder';
|
||||
import ResponseParser from './response_parser';
|
||||
import { AzureMonitorQuery, AzureDataSourceJsonData, AzureLogsVariable, AzureQueryType } from '../types';
|
||||
import {
|
||||
DataQueryResponse,
|
||||
ScopedVars,
|
||||
DataSourceInstanceSettings,
|
||||
QueryResultMeta,
|
||||
MetricFindValue,
|
||||
} from '@grafana/data';
|
||||
import { DataQueryResponse, ScopedVars, DataSourceInstanceSettings, MetricFindValue } from '@grafana/data';
|
||||
import { getBackendSrv, getTemplateSrv, DataSourceWithBackend } from '@grafana/runtime';
|
||||
|
||||
export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
|
||||
@ -140,7 +134,7 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
|
||||
for (const df of res.data) {
|
||||
const encodedQuery = df.meta?.custom?.encodedQuery;
|
||||
if (encodedQuery && encodedQuery.length > 0) {
|
||||
const url = await this.buildDeepLink(df.meta);
|
||||
const url = await this.buildDeepLink(df.meta.custom);
|
||||
if (url?.length) {
|
||||
for (const field of df.fields) {
|
||||
field.config.links = [
|
||||
@ -158,10 +152,10 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
|
||||
return res;
|
||||
}
|
||||
|
||||
private async buildDeepLink(meta: QueryResultMeta) {
|
||||
const base64Enc = encodeURIComponent(meta.custom.encodedQuery);
|
||||
const workspaceId = meta.custom.workspace;
|
||||
const subscription = meta.custom.subscription;
|
||||
private async buildDeepLink(customMeta: Record<string, any>) {
|
||||
const base64Enc = encodeURIComponent(customMeta.encodedQuery);
|
||||
const workspaceId = customMeta.workspace;
|
||||
const subscription = customMeta.subscription;
|
||||
|
||||
const details = await this.getWorkspaceDetails(workspaceId);
|
||||
if (!details.workspace || !details.resourceGroup) {
|
||||
@ -200,7 +194,13 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
|
||||
};
|
||||
}
|
||||
|
||||
metricFindQuery(query: string): Promise<MetricFindValue[]> {
|
||||
/**
|
||||
* This is named differently than DataSourceApi.metricFindQuery
|
||||
* because it's not exposed to Grafana like the main AzureMonitorDataSource.
|
||||
* And some of the azure internal data sources return null in this function, which the
|
||||
* external interface does not support
|
||||
*/
|
||||
metricFindQueryInternal(query: string): Promise<MetricFindValue[]> {
|
||||
const workspacesQuery = query.match(/^workspaces\(\)/i);
|
||||
if (workspacesQuery) {
|
||||
return this.getWorkspaces(this.subscriptionId);
|
||||
@ -233,7 +233,7 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
|
||||
throw { message: err.error.data.error.message };
|
||||
}
|
||||
});
|
||||
}) as Promise<MetricFindValue[]>; // ??
|
||||
}) as Promise<MetricFindValue[]>;
|
||||
}
|
||||
|
||||
private buildQuery(query: string, options: any, workspace: any) {
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
AzureMonitorResourceGroupsResponse,
|
||||
AzureQueryType,
|
||||
} from '../types';
|
||||
import { DataSourceInstanceSettings, ScopedVars } from '@grafana/data';
|
||||
import { DataSourceInstanceSettings, ScopedVars, MetricFindValue } from '@grafana/data';
|
||||
import { getBackendSrv, DataSourceWithBackend, getTemplateSrv } from '@grafana/runtime';
|
||||
|
||||
const defaultDropdownValue = 'select';
|
||||
@ -32,8 +32,7 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
|
||||
this.subscriptionId = instanceSettings.jsonData.subscriptionId;
|
||||
this.cloudName = instanceSettings.jsonData.cloudName || 'azuremonitor';
|
||||
this.baseUrl = `/${this.cloudName}/subscriptions`;
|
||||
this.url = instanceSettings.url;
|
||||
|
||||
this.url = instanceSettings.url!;
|
||||
this.supportedMetricNamespaces = new SupportedNamespaces(this.cloudName).get();
|
||||
}
|
||||
|
||||
@ -44,13 +43,9 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
|
||||
filterQuery(item: AzureMonitorQuery): boolean {
|
||||
return (
|
||||
item.hide !== true &&
|
||||
item.azureMonitor.resourceGroup &&
|
||||
item.azureMonitor.resourceGroup !== defaultDropdownValue &&
|
||||
item.azureMonitor.resourceName &&
|
||||
item.azureMonitor.resourceName !== defaultDropdownValue &&
|
||||
item.azureMonitor.metricDefinition &&
|
||||
item.azureMonitor.metricDefinition !== defaultDropdownValue &&
|
||||
item.azureMonitor.metricName &&
|
||||
item.azureMonitor.metricName !== defaultDropdownValue
|
||||
);
|
||||
}
|
||||
@ -77,7 +72,7 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
|
||||
const dimensionsFilters = item.dimensionFilters
|
||||
.filter(f => f.dimension && f.dimension !== 'None')
|
||||
.map(f => {
|
||||
const filter = templateSrv.replace(f.filter, scopedVars);
|
||||
const filter = templateSrv.replace(f.filter ?? '', scopedVars);
|
||||
return {
|
||||
dimension: templateSrv.replace(f.dimension, scopedVars),
|
||||
operator: f.operator || 'eq',
|
||||
@ -107,7 +102,13 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
|
||||
};
|
||||
}
|
||||
|
||||
metricFindQuery(query: string) {
|
||||
/**
|
||||
* This is named differently than DataSourceApi.metricFindQuery
|
||||
* because it's not exposed to Grafana like the main AzureMonitorDataSource.
|
||||
* And some of the azure internal data sources return null in this function, which the
|
||||
* external interface does not support
|
||||
*/
|
||||
metricFindQueryInternal(query: string): Promise<MetricFindValue[]> | null {
|
||||
const subscriptionsQuery = query.match(/^Subscriptions\(\)/i);
|
||||
if (subscriptionsQuery) {
|
||||
return this.getSubscriptions();
|
||||
@ -196,7 +197,7 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
|
||||
return this.getMetricNames(subscription, resourceGroup, metricDefinition, resourceName, metricNamespace);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return null;
|
||||
}
|
||||
|
||||
toVariable(metric: string) {
|
||||
@ -395,7 +396,7 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
|
||||
});
|
||||
}
|
||||
|
||||
isValidConfigField(field: string) {
|
||||
isValidConfigField(field?: string) {
|
||||
return field && field.length > 0;
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,7 @@ export class AnalyticsConfig extends PureComponent<Props, State> {
|
||||
onAzureLogAnalyticsSameAsChange = () => {
|
||||
const { options, onUpdateDatasourceOptions, makeSameAs } = this.props;
|
||||
|
||||
if (!options.jsonData.azureLogAnalyticsSameAs && options.secureJsonData.clientSecret) {
|
||||
if (!options.jsonData.azureLogAnalyticsSameAs && options.secureJsonData!.clientSecret) {
|
||||
makeSameAs();
|
||||
} else if (!options.jsonData.azureLogAnalyticsSameAs) {
|
||||
// if currently off, clear monitor secret
|
||||
@ -94,7 +94,7 @@ export class AnalyticsConfig extends PureComponent<Props, State> {
|
||||
jsonData.tenantId &&
|
||||
jsonData.clientId &&
|
||||
jsonData.subscriptionId &&
|
||||
(secureJsonData.clientSecret || secureJsonFields.clientSecret)
|
||||
(secureJsonData!.clientSecret || secureJsonFields.clientSecret)
|
||||
);
|
||||
}
|
||||
|
||||
@ -104,7 +104,7 @@ export class AnalyticsConfig extends PureComponent<Props, State> {
|
||||
jsonData.logAnalyticsClientId &&
|
||||
jsonData.logAnalyticsClientId.length &&
|
||||
jsonData.logAnalyticsSubscriptionId &&
|
||||
(secureJsonFields.logAnalyticsClientSecret || secureJsonData.logAnalyticsClientSecret)
|
||||
(secureJsonFields.logAnalyticsClientSecret || secureJsonData!.logAnalyticsClientSecret)
|
||||
);
|
||||
};
|
||||
|
||||
@ -132,14 +132,14 @@ export class AnalyticsConfig extends PureComponent<Props, State> {
|
||||
jsonData.azureLogAnalyticsSameAs &&
|
||||
secureJsonFields &&
|
||||
!secureJsonFields.clientSecret &&
|
||||
!secureJsonData.clientSecret;
|
||||
!secureJsonData!.clientSecret;
|
||||
|
||||
return (
|
||||
<>
|
||||
<h3 className="page-heading">Azure Log Analytics API Details</h3>
|
||||
<Switch
|
||||
label="Same details as Azure Monitor API"
|
||||
checked={jsonData.azureLogAnalyticsSameAs}
|
||||
checked={jsonData.azureLogAnalyticsSameAs ?? false}
|
||||
onChange={this.onAzureLogAnalyticsSameAsChange}
|
||||
{...addtlAttrs}
|
||||
/>
|
||||
@ -156,7 +156,7 @@ export class AnalyticsConfig extends PureComponent<Props, State> {
|
||||
selectedSubscription={jsonData.logAnalyticsSubscriptionId}
|
||||
tenantId={jsonData.logAnalyticsTenantId}
|
||||
clientId={jsonData.logAnalyticsClientId}
|
||||
clientSecret={secureJsonData.logAnalyticsClientSecret}
|
||||
clientSecret={secureJsonData!.logAnalyticsClientSecret}
|
||||
clientSecretConfigured={secureJsonFields.logAnalyticsClientSecret}
|
||||
onSubscriptionSelectChange={this.onLogAnalyticsSubscriptionSelect}
|
||||
onTenantIdChange={this.onLogAnalyticsTenantIdChange}
|
||||
|
@ -7,10 +7,10 @@ export interface Props {
|
||||
selectedAzureCloud?: string;
|
||||
selectedSubscription?: string;
|
||||
azureCloudOptions?: SelectableValue[];
|
||||
tenantId: string;
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
clientSecretConfigured: boolean;
|
||||
tenantId?: string;
|
||||
clientId?: string;
|
||||
clientSecret?: string;
|
||||
clientSecretConfigured?: boolean;
|
||||
subscriptionOptions?: SelectableValue[];
|
||||
onAzureCloudChange?: (value: SelectableValue<string>) => void;
|
||||
onSubscriptionSelectChange?: (value: SelectableValue<string>) => void;
|
||||
@ -124,7 +124,7 @@ export class AzureCredentialsForm extends PureComponent<Props> {
|
||||
<InlineFormLabel className="width-12">Default Subscription</InlineFormLabel>
|
||||
<div className="width-25">
|
||||
<Select
|
||||
value={subscriptionOptions.find(subscription => subscription.value === selectedSubscription)}
|
||||
value={subscriptionOptions!.find(subscription => subscription.value === selectedSubscription)}
|
||||
options={subscriptionOptions}
|
||||
defaultValue={selectedSubscription}
|
||||
onChange={onSubscriptionSelectChange}
|
||||
|
@ -9,8 +9,7 @@ import {
|
||||
} from '@grafana/data';
|
||||
import { MonitorConfig } from './MonitorConfig';
|
||||
import { AnalyticsConfig } from './AnalyticsConfig';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { getBackendSrv, TemplateSrv, getTemplateSrv } from '@grafana/runtime';
|
||||
import { InsightsConfig } from './InsightsConfig';
|
||||
import ResponseParser from '../azure_monitor/response_parser';
|
||||
import { AzureDataSourceJsonData, AzureDataSourceSecureJsonData, AzureDataSourceSettings } from '../types';
|
||||
@ -27,6 +26,9 @@ export interface State {
|
||||
}
|
||||
|
||||
export class ConfigEditor extends PureComponent<Props, State> {
|
||||
initPromise: CancelablePromise<any> | null = null;
|
||||
templateSrv: TemplateSrv = getTemplateSrv();
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
@ -38,15 +40,11 @@ export class ConfigEditor extends PureComponent<Props, State> {
|
||||
logAnalyticsSubscriptionId: '',
|
||||
};
|
||||
|
||||
this.templateSrv = new TemplateSrv();
|
||||
if (this.props.options.id) {
|
||||
updateDatasourcePluginOption(this.props, 'url', '/api/datasources/proxy/' + this.props.options.id);
|
||||
}
|
||||
}
|
||||
|
||||
initPromise: CancelablePromise<any> = null;
|
||||
templateSrv: TemplateSrv = null;
|
||||
|
||||
componentDidMount() {
|
||||
this.initPromise = makePromiseCancelable(this.init());
|
||||
this.initPromise.promise.catch(({ isCanceled }) => {
|
||||
@ -57,7 +55,7 @@ export class ConfigEditor extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.initPromise.cancel();
|
||||
this.initPromise!.cancel();
|
||||
}
|
||||
|
||||
init = async () => {
|
||||
@ -94,7 +92,7 @@ export class ConfigEditor extends PureComponent<Props, State> {
|
||||
|
||||
makeSameAs = (updatedClientSecret?: string) => {
|
||||
const { options } = this.props;
|
||||
const clientSecret = updatedClientSecret || options.secureJsonData.clientSecret;
|
||||
const clientSecret = updatedClientSecret || options.secureJsonData!.clientSecret;
|
||||
|
||||
this.props.onOptionsChange({
|
||||
...options,
|
||||
@ -114,7 +112,7 @@ export class ConfigEditor extends PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
hasNecessaryCredentials = () => {
|
||||
if (!this.props.options.secureJsonFields.clientSecret && !this.props.options.secureJsonData.clientSecret) {
|
||||
if (!this.props.options.secureJsonFields.clientSecret && !this.props.options.secureJsonData!.clientSecret) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -128,7 +126,7 @@ export class ConfigEditor extends PureComponent<Props, State> {
|
||||
logAnalyticsHasNecessaryCredentials = () => {
|
||||
if (
|
||||
!this.props.options.secureJsonFields.logAnalyticsClientSecret &&
|
||||
!this.props.options.secureJsonData.logAnalyticsClientSecret
|
||||
!this.props.options.secureJsonData!.logAnalyticsClientSecret
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@ -174,7 +172,7 @@ export class ConfigEditor extends PureComponent<Props, State> {
|
||||
const azureCloud = cloudName || 'azuremonitor';
|
||||
azureMonitorUrl = `/${azureCloud}/subscriptions`;
|
||||
} else {
|
||||
subscriptionId = logAnalyticsSubscriptionId;
|
||||
subscriptionId = logAnalyticsSubscriptionId!;
|
||||
azureMonitorUrl = `/workspacesloganalytics/subscriptions`;
|
||||
}
|
||||
|
||||
@ -231,14 +229,14 @@ export class ConfigEditor extends PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
getWorkspaces = async () => {
|
||||
const sameAs = this.props.options.jsonData.azureLogAnalyticsSameAs && this.props.options.jsonData.subscriptionId;
|
||||
if (!sameAs && !this.props.options.jsonData.logAnalyticsSubscriptionId) {
|
||||
const { subscriptionId, azureLogAnalyticsSameAs, logAnalyticsSubscriptionId } = this.props.options.jsonData;
|
||||
const subscriptionIdToUse = azureLogAnalyticsSameAs ? subscriptionId : logAnalyticsSubscriptionId;
|
||||
|
||||
if (!subscriptionIdToUse) {
|
||||
return;
|
||||
}
|
||||
|
||||
const logAnalyticsWorkspaces = await this.loadWorkspaces(
|
||||
sameAs ? this.props.options.jsonData.subscriptionId : this.props.options.jsonData.logAnalyticsSubscriptionId
|
||||
);
|
||||
const logAnalyticsWorkspaces = await this.loadWorkspaces(subscriptionIdToUse);
|
||||
|
||||
if (logAnalyticsWorkspaces.length > 0) {
|
||||
this.setState({ logAnalyticsWorkspaces });
|
||||
@ -255,6 +253,7 @@ export class ConfigEditor extends PureComponent<Props, State> {
|
||||
const { options } = this.props;
|
||||
|
||||
options.jsonData.cloudName = options.jsonData.cloudName || 'azuremonitor';
|
||||
// This is bad, causes so many messy typing issues everwhere..
|
||||
options.secureJsonData = (options.secureJsonData || {}) as AzureDataSourceSecureJsonData;
|
||||
|
||||
return (
|
||||
|
@ -46,7 +46,7 @@ export class InsightsConfig extends PureComponent<Props> {
|
||||
<Input
|
||||
className="width-30"
|
||||
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||
value={options.secureJsonData.appInsightsApiKey || ''}
|
||||
value={options.secureJsonData!.appInsightsApiKey || ''}
|
||||
onChange={onUpdateSecureJsonDataOption('appInsightsApiKey')}
|
||||
/>
|
||||
</div>
|
||||
|
@ -63,7 +63,7 @@ export class MonitorConfig extends PureComponent<Props> {
|
||||
selectedSubscription={options.jsonData.subscriptionId}
|
||||
tenantId={options.jsonData.tenantId}
|
||||
clientId={options.jsonData.clientId}
|
||||
clientSecret={options.secureJsonData.clientSecret}
|
||||
clientSecret={options.secureJsonData?.clientSecret}
|
||||
clientSecretConfigured={options.secureJsonFields.clientSecret}
|
||||
onAzureCloudChange={this.onAzureCloudSelect}
|
||||
onSubscriptionSelectChange={this.onSubscriptionSelect}
|
||||
|
@ -118,17 +118,17 @@ export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDa
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
const aiResult = this.appInsightsDatasource.metricFindQuery(query);
|
||||
const aiResult = this.appInsightsDatasource.metricFindQueryInternal(query);
|
||||
if (aiResult) {
|
||||
return aiResult;
|
||||
}
|
||||
|
||||
const amResult = this.azureMonitorDatasource.metricFindQuery(query);
|
||||
const amResult = this.azureMonitorDatasource.metricFindQueryInternal(query);
|
||||
if (amResult) {
|
||||
return amResult;
|
||||
}
|
||||
|
||||
const alaResult = this.azureLogAnalyticsDatasource.metricFindQuery(query);
|
||||
const alaResult = this.azureLogAnalyticsDatasource.metricFindQueryInternal(query);
|
||||
if (alaResult) {
|
||||
return alaResult;
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ interface SuggestionGroup {
|
||||
|
||||
interface KustoSchema {
|
||||
Databases: {
|
||||
Default?: KustoDBSchema;
|
||||
Default: KustoDBSchema;
|
||||
};
|
||||
Plugins?: any[];
|
||||
}
|
||||
@ -65,7 +65,8 @@ export default class KustoQueryField extends QueryField {
|
||||
|
||||
onTypeahead = (force = false) => {
|
||||
const selection = window.getSelection();
|
||||
if (selection.anchorNode) {
|
||||
|
||||
if (selection && selection.anchorNode) {
|
||||
const wrapperNode = selection.anchorNode.parentElement;
|
||||
if (wrapperNode === null) {
|
||||
return;
|
||||
@ -408,7 +409,7 @@ export default class KustoQueryField extends QueryField {
|
||||
if (match && match.length > 1 && match[0] && match[1]) {
|
||||
return match[1];
|
||||
} else {
|
||||
return null;
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,13 +103,6 @@ class QueryField extends React.Component<any, any> {
|
||||
});
|
||||
};
|
||||
|
||||
request = (url?: string) => {
|
||||
if (this.props.request) {
|
||||
return this.props.request(url);
|
||||
}
|
||||
return fetch(url);
|
||||
};
|
||||
|
||||
onChangeQuery = () => {
|
||||
// Send text change to parent
|
||||
const { onQueryChange } = this.props;
|
||||
@ -256,13 +249,14 @@ class QueryField extends React.Component<any, any> {
|
||||
const { suggestions } = this.state;
|
||||
const menu = this.menuEl;
|
||||
const selection = window.getSelection();
|
||||
const node = selection.anchorNode;
|
||||
|
||||
// No menu, nothing to do
|
||||
if (!menu) {
|
||||
if (!menu || !selection) {
|
||||
return;
|
||||
}
|
||||
|
||||
const node = selection.anchorNode;
|
||||
|
||||
// No suggestions or blur, remove menu
|
||||
const hasSuggesstions = suggestions && suggestions.length > 0;
|
||||
if (!hasSuggesstions) {
|
||||
|
@ -13,8 +13,8 @@ const FunctionDescription = React.lazy(async () => {
|
||||
// @ts-ignore
|
||||
const { default: rst2html } = await import(/* webpackChunkName: "rst2html" */ 'rst2html');
|
||||
return {
|
||||
default: (props: { description: string }) => (
|
||||
<div dangerouslySetInnerHTML={{ __html: rst2html(props.description) }} />
|
||||
default: (props: { description?: string }) => (
|
||||
<div dangerouslySetInnerHTML={{ __html: rst2html(props.description ?? '') }} />
|
||||
),
|
||||
};
|
||||
});
|
||||
@ -77,7 +77,7 @@ class FunctionEditor extends React.PureComponent<FunctionEditorProps, FunctionEd
|
||||
{(showPopper, hidePopper, popperProps) => {
|
||||
return (
|
||||
<>
|
||||
{this.triggerRef && (
|
||||
{this.triggerRef.current && (
|
||||
<Popover
|
||||
{...popperProps}
|
||||
referenceElement={this.triggerRef.current}
|
||||
|
@ -20,7 +20,7 @@ export interface FunctionEditorControlsProps {
|
||||
onRemove: (func: FunctionDescriptor) => void;
|
||||
}
|
||||
|
||||
const FunctionHelpButton = (props: { description: string; name: string; onDescriptionShow: () => void }) => {
|
||||
const FunctionHelpButton = (props: { description?: string; name: string; onDescriptionShow: () => void }) => {
|
||||
if (props.description) {
|
||||
return <Icon className="pointer" name="question-circle" onClick={props.onDescriptionShow} />;
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import {
|
||||
QueryResultMetaStat,
|
||||
ScopedVars,
|
||||
toDataFrame,
|
||||
TimeRange,
|
||||
} from '@grafana/data';
|
||||
import { isVersionGtOrEq, SemVersion } from 'app/core/utils/version';
|
||||
import gfunc from './gfunc';
|
||||
@ -29,7 +30,7 @@ export class GraphiteDatasource extends DataSourceApi<GraphiteQuery, GraphiteOpt
|
||||
cacheTimeout: any;
|
||||
withCredentials: boolean;
|
||||
funcDefs: any = null;
|
||||
funcDefsPromise: Promise<any> = null;
|
||||
funcDefsPromise: Promise<any> | null = null;
|
||||
_seriesRefLetters: string;
|
||||
|
||||
/** @ngInject */
|
||||
@ -64,8 +65,8 @@ export class GraphiteDatasource extends DataSourceApi<GraphiteQuery, GraphiteOpt
|
||||
|
||||
async query(options: DataQueryRequest<GraphiteQuery>): Promise<DataQueryResponse> {
|
||||
const graphOptions = {
|
||||
from: this.translateTime(options.rangeRaw.from, false, options.timezone),
|
||||
until: this.translateTime(options.rangeRaw.to, true, options.timezone),
|
||||
from: this.translateTime(options.range.raw.from, false, options.timezone),
|
||||
until: this.translateTime(options.range.raw.to, true, options.timezone),
|
||||
targets: options.targets,
|
||||
format: (options as any).format,
|
||||
cacheTimeout: options.cacheTimeout || this.cacheTimeout,
|
||||
@ -199,7 +200,7 @@ export class GraphiteDatasource extends DataSourceApi<GraphiteQuery, GraphiteOpt
|
||||
const expandedQuery = {
|
||||
...query,
|
||||
datasource: this.name,
|
||||
target: this.templateSrv.replace(query.target, scopedVars),
|
||||
target: this.templateSrv.replace(query.target ?? '', scopedVars),
|
||||
};
|
||||
return expandedQuery;
|
||||
});
|
||||
@ -212,7 +213,7 @@ export class GraphiteDatasource extends DataSourceApi<GraphiteQuery, GraphiteOpt
|
||||
if (options.annotation.target) {
|
||||
const target = this.templateSrv.replace(options.annotation.target, {}, 'glob');
|
||||
const graphiteQuery = ({
|
||||
rangeRaw: options.rangeRaw,
|
||||
range: options.range,
|
||||
targets: [{ target: target }],
|
||||
format: 'json',
|
||||
maxDataPoints: 100,
|
||||
@ -245,7 +246,7 @@ export class GraphiteDatasource extends DataSourceApi<GraphiteQuery, GraphiteOpt
|
||||
} else {
|
||||
// Graphite event as annotation
|
||||
const tags = this.templateSrv.replace(options.annotation.tags);
|
||||
return this.events({ range: options.rangeRaw, tags: tags }).then((results: any) => {
|
||||
return this.events({ range: options.range, tags: tags }).then((results: any) => {
|
||||
const list = [];
|
||||
for (let i = 0; i < results.data.length; i++) {
|
||||
const e = results.data[i];
|
||||
@ -269,7 +270,7 @@ export class GraphiteDatasource extends DataSourceApi<GraphiteQuery, GraphiteOpt
|
||||
}
|
||||
}
|
||||
|
||||
events(options: { range: any; tags: any; timezone?: any }) {
|
||||
events(options: { range: TimeRange; tags: any; timezone?: any }) {
|
||||
try {
|
||||
let tags = '';
|
||||
if (options.tags) {
|
||||
@ -279,9 +280,9 @@ export class GraphiteDatasource extends DataSourceApi<GraphiteQuery, GraphiteOpt
|
||||
method: 'GET',
|
||||
url:
|
||||
'/events/get_data?from=' +
|
||||
this.translateTime(options.range.from, false, options.timezone) +
|
||||
this.translateTime(options.range.raw.from, false, options.timezone) +
|
||||
'&until=' +
|
||||
this.translateTime(options.range.to, true, options.timezone) +
|
||||
this.translateTime(options.range.raw.to, true, options.timezone) +
|
||||
tags,
|
||||
});
|
||||
} catch (err) {
|
||||
@ -290,7 +291,7 @@ export class GraphiteDatasource extends DataSourceApi<GraphiteQuery, GraphiteOpt
|
||||
}
|
||||
|
||||
targetContainsTemplate(target: GraphiteQuery) {
|
||||
return this.templateSrv.variableExists(target.target);
|
||||
return this.templateSrv.variableExists(target.target ?? '');
|
||||
}
|
||||
|
||||
translateTime(date: any, roundUp: any, timezone: any) {
|
||||
|
@ -975,7 +975,7 @@ export class FuncInstance {
|
||||
text: any;
|
||||
added: boolean;
|
||||
|
||||
constructor(funcDef: any, options: { withDefaultParams: any }) {
|
||||
constructor(funcDef: any, options?: { withDefaultParams: any }) {
|
||||
this.def = funcDef;
|
||||
this.params = [];
|
||||
|
||||
|
@ -104,7 +104,7 @@ describe('graphiteDatasource', () => {
|
||||
const query = {
|
||||
panelId: 3,
|
||||
dashboardId: 5,
|
||||
rangeRaw: { from: 'now-1h', to: 'now' },
|
||||
range: { raw: { from: 'now-1h', to: 'now' } },
|
||||
targets: [{ target: 'prod1.count' }, { target: 'prod2.count' }],
|
||||
maxDataPoints: 500,
|
||||
};
|
||||
@ -179,8 +179,8 @@ describe('graphiteDatasource', () => {
|
||||
range: {
|
||||
from: dateTime(1432288354),
|
||||
to: dateTime(1432288401),
|
||||
raw: { from: 'now-24h', to: 'now' },
|
||||
},
|
||||
rangeRaw: { from: 'now-24h', to: 'now' },
|
||||
};
|
||||
|
||||
describe('and tags are returned as string', () => {
|
||||
|
@ -13,15 +13,15 @@ export interface Props extends ExploreQueryFieldProps<InfluxDatasource, InfluxQu
|
||||
|
||||
export interface State {
|
||||
measurements: CascaderOption[];
|
||||
measurement: string;
|
||||
field: string;
|
||||
error: string;
|
||||
measurement: string | null;
|
||||
field: string | null;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
interface ChooserOptions {
|
||||
measurement: string;
|
||||
field: string;
|
||||
error: string;
|
||||
measurement: string | null;
|
||||
field: string | null;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
// Helper function for determining if a collection of pairs are valid
|
||||
@ -51,9 +51,9 @@ export class InfluxLogsQueryField extends React.PureComponent<Props, State> {
|
||||
templateSrv: TemplateSrv = new TemplateSrv();
|
||||
state: State = {
|
||||
measurements: [],
|
||||
measurement: (null as unknown) as string,
|
||||
field: (null as unknown) as string,
|
||||
error: (null as unknown) as string,
|
||||
measurement: null,
|
||||
field: null,
|
||||
error: null,
|
||||
};
|
||||
|
||||
async componentDidMount() {
|
||||
@ -115,10 +115,10 @@ export class InfluxLogsQueryField extends React.PureComponent<Props, State> {
|
||||
...query,
|
||||
resultFormat: 'table',
|
||||
groupBy: [],
|
||||
select: [[{ type: 'field', params: [field] }]],
|
||||
select: [[{ type: 'field', params: [field ?? ''] }]],
|
||||
tags: pairs,
|
||||
limit: '1000',
|
||||
measurement,
|
||||
measurement: measurement ?? '',
|
||||
},
|
||||
this.templateSrv
|
||||
);
|
||||
@ -143,7 +143,7 @@ export class InfluxLogsQueryField extends React.PureComponent<Props, State> {
|
||||
<ButtonCascader
|
||||
options={measurements}
|
||||
disabled={!hasMeasurement}
|
||||
value={[measurement, field]}
|
||||
value={[measurement ?? '', field ?? '']}
|
||||
onChange={this.onMeasurementsChange}
|
||||
>
|
||||
{cascadeText}
|
||||
|
@ -34,8 +34,9 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
|
||||
|
||||
constructor(instanceSettings: DataSourceInstanceSettings<InfluxOptions>) {
|
||||
super(instanceSettings);
|
||||
|
||||
this.type = 'influxdb';
|
||||
this.urls = _.map(instanceSettings.url.split(','), url => {
|
||||
this.urls = (instanceSettings.url ?? '').split(',').map(url => {
|
||||
return url.trim();
|
||||
});
|
||||
|
||||
@ -67,7 +68,7 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
|
||||
applyTemplateVariables(query: InfluxQuery, scopedVars: ScopedVars): Record<string, any> {
|
||||
return {
|
||||
...query,
|
||||
query: getTemplateSrv().replace(query.query, scopedVars), // The raw query text
|
||||
query: getTemplateSrv().replace(query.query ?? '', scopedVars), // The raw query text
|
||||
};
|
||||
}
|
||||
|
||||
@ -79,7 +80,7 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
|
||||
const scopedVars = options.scopedVars;
|
||||
const targets = _.cloneDeep(options.targets);
|
||||
const queryTargets: any[] = [];
|
||||
let queryModel: InfluxQueryModel;
|
||||
|
||||
let i, y;
|
||||
const templateSrv = getTemplateSrv();
|
||||
|
||||
@ -93,8 +94,7 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
|
||||
// backward compatibility
|
||||
scopedVars.interval = scopedVars.__interval;
|
||||
|
||||
queryModel = new InfluxQueryModel(target, templateSrv, scopedVars);
|
||||
return queryModel.render(true);
|
||||
return new InfluxQueryModel(target, templateSrv, scopedVars).render(true);
|
||||
}).reduce((acc, current) => {
|
||||
if (current !== '') {
|
||||
acc += ';' + current;
|
||||
@ -109,7 +109,8 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
|
||||
// add global adhoc filters to timeFilter
|
||||
const adhocFilters = (templateSrv as any).getAdhocFilters(this.name);
|
||||
if (adhocFilters.length > 0) {
|
||||
timeFilter += ' AND ' + queryModel.renderAdhocFilters(adhocFilters);
|
||||
const tmpQuery = new InfluxQueryModel({ refId: 'A' }, templateSrv, scopedVars);
|
||||
timeFilter += ' AND ' + tmpQuery.renderAdhocFilters(adhocFilters);
|
||||
}
|
||||
|
||||
// replace grafana variables
|
||||
@ -175,7 +176,7 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
|
||||
|
||||
const timeFilter = this.getTimeFilter({ rangeRaw: options.rangeRaw, timezone: options.timezone });
|
||||
let query = options.annotation.query.replace('$timeFilter', timeFilter);
|
||||
query = getTemplateSrv().replace(query, null, 'regex');
|
||||
query = getTemplateSrv().replace(query, undefined, 'regex');
|
||||
|
||||
return this._seriesQuery(query, options).then((data: any) => {
|
||||
if (!data || !data.results || !data.results[0]) {
|
||||
@ -233,7 +234,7 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
|
||||
const expandedTags = query.tags.map(tag => {
|
||||
const expandedTag = {
|
||||
...tag,
|
||||
value: templateSrv.replace(tag.value, null, 'regex'),
|
||||
value: templateSrv.replace(tag.value, undefined, 'regex'),
|
||||
};
|
||||
return expandedTag;
|
||||
});
|
||||
@ -246,7 +247,7 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
|
||||
}
|
||||
|
||||
metricFindQuery(query: string, options?: any) {
|
||||
const interpolated = getTemplateSrv().replace(query, null, 'regex');
|
||||
const interpolated = getTemplateSrv().replace(query, undefined, 'regex');
|
||||
|
||||
return this._seriesQuery(interpolated, options).then(resp => {
|
||||
return this.responseParser.parse(query, resp);
|
||||
@ -292,7 +293,7 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
|
||||
memo.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
|
||||
return memo;
|
||||
},
|
||||
[]
|
||||
[] as string[]
|
||||
).join('&');
|
||||
}
|
||||
|
||||
@ -351,7 +352,7 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
|
||||
}
|
||||
|
||||
_influxRequest(method: string, url: string, data: any, options?: any) {
|
||||
const currentUrl = this.urls.shift();
|
||||
const currentUrl = this.urls.shift()!;
|
||||
this.urls.push(currentUrl);
|
||||
|
||||
const params: any = {};
|
||||
|
@ -12,7 +12,7 @@ export default class InfluxQueryModel {
|
||||
groupByParts: any;
|
||||
templateSrv: any;
|
||||
scopedVars: any;
|
||||
refId: string;
|
||||
refId?: string;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(target: InfluxQuery, templateSrv?: TemplateSrv, scopedVars?: ScopedVars) {
|
||||
@ -111,12 +111,12 @@ export default class InfluxQueryModel {
|
||||
});
|
||||
}
|
||||
|
||||
this.target.groupBy.splice(index, 1);
|
||||
this.target.groupBy!.splice(index, 1);
|
||||
this.updateProjection();
|
||||
}
|
||||
|
||||
removeSelect(index: number) {
|
||||
this.target.select.splice(index, 1);
|
||||
this.target.select!.splice(index, 1);
|
||||
this.updateProjection();
|
||||
}
|
||||
|
||||
@ -141,7 +141,7 @@ export default class InfluxQueryModel {
|
||||
this.updatePersistedParts();
|
||||
}
|
||||
|
||||
private renderTagCondition(tag: InfluxQueryTag, index: number, interpolate: boolean) {
|
||||
private renderTagCondition(tag: InfluxQueryTag, index: number, interpolate?: boolean) {
|
||||
let str = '';
|
||||
let operator = tag.operator;
|
||||
let value = tag.value;
|
||||
|
@ -3,7 +3,7 @@ import TableModel from 'app/core/table_model';
|
||||
import { FieldType, QueryResultMeta, TimeSeries, TableData } from '@grafana/data';
|
||||
|
||||
export default class InfluxSeries {
|
||||
refId: string;
|
||||
refId?: string;
|
||||
series: any;
|
||||
alias: any;
|
||||
annotation: any;
|
||||
|
@ -28,8 +28,8 @@ function renderTagCondition(tag: { operator: any; value: string; condition: any;
|
||||
export class InfluxQueryBuilder {
|
||||
constructor(private target: { measurement: any; tags: any; policy?: any }, private database?: string) {}
|
||||
|
||||
buildExploreQuery(type: string, withKey?: string, withMeasurementFilter?: string) {
|
||||
let query;
|
||||
buildExploreQuery(type: string, withKey?: string, withMeasurementFilter?: string): string {
|
||||
let query = '';
|
||||
let measurement;
|
||||
let policy;
|
||||
|
||||
@ -99,19 +99,21 @@ export class InfluxQueryBuilder {
|
||||
memo.push(renderTagCondition(tag, memo.length));
|
||||
return memo;
|
||||
},
|
||||
[]
|
||||
[] as string[]
|
||||
);
|
||||
|
||||
if (whereConditions.length > 0) {
|
||||
query += ' WHERE ' + whereConditions.join(' ');
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'MEASUREMENTS') {
|
||||
query += ' LIMIT 100';
|
||||
//Solve issue #2524 by limiting the number of measurements returned
|
||||
//LIMIT must be after WITH MEASUREMENT and WHERE clauses
|
||||
//This also could be used for TAG KEYS and TAG VALUES, if desired
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
}
|
||||
|
@ -104,7 +104,7 @@ export class InfluxQueryCtrl extends QueryCtrl {
|
||||
memo.push(menu);
|
||||
return memo;
|
||||
},
|
||||
[]
|
||||
[] as any
|
||||
);
|
||||
}
|
||||
|
||||
@ -384,7 +384,7 @@ export class InfluxQueryCtrl extends QueryCtrl {
|
||||
rebuildTargetTagConditions() {
|
||||
const tags: any[] = [];
|
||||
let tagIndex = 0;
|
||||
let tagOperator = '';
|
||||
let tagOperator: string | null = '';
|
||||
|
||||
_.each(this.tagSegments, (segment2, index) => {
|
||||
if (segment2.type === 'key') {
|
||||
@ -411,7 +411,7 @@ export class InfluxQueryCtrl extends QueryCtrl {
|
||||
this.panelCtrl.refresh();
|
||||
}
|
||||
|
||||
getTagValueOperator(tagValue: string, tagOperator: string): string {
|
||||
getTagValueOperator(tagValue: string, tagOperator: string): string | null {
|
||||
if (tagOperator !== '=~' && tagOperator !== '!~' && /^\/.*\/$/.test(tagValue)) {
|
||||
return '=~';
|
||||
} else if ((tagOperator === '=~' || tagOperator === '!~') && /^(?!\/.*\/$)/.test(tagValue)) {
|
||||
|
@ -124,7 +124,7 @@ export class JaegerQueryField extends React.PureComponent<Props, State> {
|
||||
this.setState(state => {
|
||||
// Place new traces into the correct service/operation sub-tree
|
||||
const serviceOptions = state.serviceOptions.map(serviceOption => {
|
||||
if (serviceOption.value === service) {
|
||||
if (serviceOption.value === service && serviceOption.children) {
|
||||
const operationOptions = serviceOption.children.map(operationOption => {
|
||||
if (operationOption.value === operationValue) {
|
||||
return {
|
||||
|
@ -102,7 +102,7 @@ export class JaegerDatasource extends DataSourceApi<JaegerQuery> {
|
||||
|
||||
function getTime(date: string | DateTime, roundUp: boolean) {
|
||||
if (typeof date === 'string') {
|
||||
date = dateMath.parse(date, roundUp);
|
||||
date = dateMath.parse(date, roundUp)!;
|
||||
}
|
||||
return date.valueOf() * 1000;
|
||||
}
|
||||
|
@ -40,7 +40,6 @@ export const LokiAnnotationsQueryEditor = memo(function LokiAnnotationQueryEdito
|
||||
onChange={(query: LokiQuery) => onChange(query.expr)}
|
||||
onRunQuery={() => {}}
|
||||
history={[]}
|
||||
data={null}
|
||||
onLoadOptions={setActiveOption}
|
||||
onLabelsRefresh={refreshLabels}
|
||||
absoluteRange={absolute}
|
||||
|
@ -15,7 +15,7 @@ export function LokiExploreQueryEditor(props: Props) {
|
||||
const { query, data, datasource, exploreMode, history, onChange, onRunQuery } = props;
|
||||
|
||||
let absolute: AbsoluteTimeRange;
|
||||
if (data && !_.isEmpty(data.request)) {
|
||||
if (data && data.request) {
|
||||
const { range } = data.request;
|
||||
|
||||
absolute = {
|
||||
|
@ -27,11 +27,11 @@ export class LokiQueryEditor extends PureComponent<Props, State> {
|
||||
// Query target properties that are fully controlled inputs
|
||||
this.state = {
|
||||
// Fully controlled text inputs
|
||||
legendFormat: query.legendFormat,
|
||||
legendFormat: query.legendFormat ?? '',
|
||||
};
|
||||
}
|
||||
|
||||
calcAbsoluteRange = (data: PanelData): AbsoluteTimeRange => {
|
||||
calcAbsoluteRange = (data: PanelData | undefined): AbsoluteTimeRange => {
|
||||
if (data && data.request) {
|
||||
const { range } = data.request;
|
||||
return {
|
||||
|
@ -63,7 +63,7 @@ function willApplySuggestion(suggestion: string, { typeaheadContext, typeaheadTe
|
||||
|
||||
export interface LokiQueryFieldFormProps extends ExploreQueryFieldProps<LokiDatasource, LokiQuery, LokiOptions> {
|
||||
history: LokiHistoryItem[];
|
||||
syntax: Grammar;
|
||||
syntax: Grammar | null;
|
||||
logLabelOptions: CascaderOption[];
|
||||
syntaxLoaded: boolean;
|
||||
absoluteRange: AbsoluteTimeRange;
|
||||
|
@ -21,7 +21,7 @@ export const useLokiLabels = (
|
||||
const mounted = useRefMounted();
|
||||
|
||||
// State
|
||||
const [logLabelOptions, setLogLabelOptions] = useState([]);
|
||||
const [logLabelOptions, setLogLabelOptions] = useState<any>([]);
|
||||
const [shouldTryRefreshLabels, setRefreshLabels] = useState(false);
|
||||
/**
|
||||
* Holds information about currently selected option from rc-cascader to perform effect
|
||||
@ -56,7 +56,7 @@ export const useLokiLabels = (
|
||||
if (languageProviderInitialised) {
|
||||
const targetOption = activeOption[activeOption.length - 1];
|
||||
if (targetOption) {
|
||||
const nextOptions = logLabelOptions.map(option => {
|
||||
const nextOptions = logLabelOptions.map((option: any) => {
|
||||
if (option.value === targetOption.value) {
|
||||
return {
|
||||
...option,
|
||||
|
@ -39,7 +39,7 @@ const useInitLanguageProvider = (languageProvider: LokiLanguageProvider, absolut
|
||||
*/
|
||||
const useLokiSyntax = (languageProvider: LokiLanguageProvider, languageProviderInitialized: boolean) => {
|
||||
// State
|
||||
const [syntax, setSyntax] = useState<Grammar>(null);
|
||||
const [syntax, setSyntax] = useState<Grammar | null>(null);
|
||||
|
||||
// Effects
|
||||
useEffect(() => {
|
||||
|
@ -18,6 +18,7 @@ describe('DerivedFields', () => {
|
||||
|
||||
it('renders correctly when no fields', async () => {
|
||||
let wrapper: any;
|
||||
//@ts-ignore
|
||||
await act(async () => {
|
||||
wrapper = await mount(<DerivedFields onChange={() => {}} />);
|
||||
});
|
||||
@ -42,6 +43,7 @@ describe('DerivedFields', () => {
|
||||
it('adds new field', async () => {
|
||||
const onChangeMock = jest.fn();
|
||||
let wrapper: any;
|
||||
//@ts-ignore
|
||||
await act(async () => {
|
||||
wrapper = await mount(<DerivedFields onChange={onChangeMock} />);
|
||||
});
|
||||
@ -53,6 +55,7 @@ describe('DerivedFields', () => {
|
||||
it('removes field', async () => {
|
||||
const onChangeMock = jest.fn();
|
||||
let wrapper: any;
|
||||
//@ts-ignore
|
||||
await act(async () => {
|
||||
wrapper = await mount(<DerivedFields value={testValue} onChange={onChangeMock} />);
|
||||
});
|
||||
|
@ -31,7 +31,7 @@ export function makeMockLokiDatasource(labelsAndValues: Labels, series?: SeriesF
|
||||
const seriesMatch = url.match(lokiSeriesEndpointRegex);
|
||||
if (labelsMatch) {
|
||||
return labelsAndValues[labelsMatch[1]] || [];
|
||||
} else if (seriesMatch) {
|
||||
} else if (seriesMatch && series && params) {
|
||||
return series[params.match] || [];
|
||||
} else {
|
||||
throw new Error(`Unexpected url error, ${url}`);
|
||||
|
@ -142,20 +142,20 @@ describe('enhanceDataFrame', () => {
|
||||
});
|
||||
expect(df.fields.length).toBe(3);
|
||||
const fc = new FieldCache(df);
|
||||
expect(fc.getFieldByName('trace1').values.toArray()).toEqual([null, '1234', null]);
|
||||
expect(fc.getFieldByName('trace1').config.links[0]).toEqual({
|
||||
expect(fc.getFieldByName('trace1')!.values.toArray()).toEqual([null, '1234', null]);
|
||||
expect(fc.getFieldByName('trace1')!.config.links![0]).toEqual({
|
||||
url: 'http://localhost/${__value.raw}',
|
||||
title: '',
|
||||
});
|
||||
|
||||
expect(fc.getFieldByName('trace2').values.toArray()).toEqual([null, null, 'foo']);
|
||||
expect(fc.getFieldByName('trace2').config.links.length).toBe(2);
|
||||
expect(fc.getFieldByName('trace2').config.links[0]).toEqual({
|
||||
expect(fc.getFieldByName('trace2')!.values.toArray()).toEqual([null, null, 'foo']);
|
||||
expect(fc.getFieldByName('trace2')!.config.links!.length).toBe(2);
|
||||
expect(fc.getFieldByName('trace2')!.config.links![0]).toEqual({
|
||||
title: '',
|
||||
internal: { datasourceUid: 'uid', query: { query: 'test' } },
|
||||
url: '',
|
||||
});
|
||||
expect(fc.getFieldByName('trace2').config.links[1]).toEqual({
|
||||
expect(fc.getFieldByName('trace2')!.config.links![1]).toEqual({
|
||||
title: '',
|
||||
internal: { datasourceUid: 'uid2', query: { query: 'test' } },
|
||||
url: '',
|
||||
|
@ -91,7 +91,7 @@ export interface LokiTailResponse {
|
||||
dropped_entries?: Array<{
|
||||
labels: Record<string, string>;
|
||||
timestamp: string;
|
||||
}>;
|
||||
}> | null;
|
||||
}
|
||||
|
||||
export type LokiResult = LokiVectorResult | LokiMatrixResult | LokiStreamResult;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user