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