Merge pull request #13844 from grafana/davkal/explore-empty-page
Explore: Pluggable components from datasource plugins
This commit is contained in:
@@ -4,6 +4,7 @@ import Select from 'react-select';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import { ExploreState, ExploreUrlState, HistoryItem, Query, QueryTransaction, ResultType } from 'app/types/explore';
|
import { ExploreState, ExploreUrlState, HistoryItem, Query, QueryTransaction, ResultType } from 'app/types/explore';
|
||||||
|
import { RawTimeRange } from 'app/types/series';
|
||||||
import kbn from 'app/core/utils/kbn';
|
import kbn from 'app/core/utils/kbn';
|
||||||
import colors from 'app/core/utils/colors';
|
import colors from 'app/core/utils/colors';
|
||||||
import store from 'app/core/store';
|
import store from 'app/core/store';
|
||||||
@@ -16,14 +17,14 @@ import IndicatorsContainer from 'app/core/components/Picker/IndicatorsContainer'
|
|||||||
import NoOptionsMessage from 'app/core/components/Picker/NoOptionsMessage';
|
import NoOptionsMessage from 'app/core/components/Picker/NoOptionsMessage';
|
||||||
import TableModel, { mergeTablesIntoModel } from 'app/core/table_model';
|
import TableModel, { mergeTablesIntoModel } from 'app/core/table_model';
|
||||||
|
|
||||||
import ErrorBoundary from './ErrorBoundary';
|
|
||||||
import QueryRows from './QueryRows';
|
import QueryRows from './QueryRows';
|
||||||
import Graph from './Graph';
|
import Graph from './Graph';
|
||||||
import Logs from './Logs';
|
import Logs from './Logs';
|
||||||
import Table from './Table';
|
import Table from './Table';
|
||||||
|
import ErrorBoundary from './ErrorBoundary';
|
||||||
import TimePicker from './TimePicker';
|
import TimePicker from './TimePicker';
|
||||||
import { ensureQueries, generateQueryKey, hasQuery } from './utils/query';
|
import { ensureQueries, generateQueryKey, hasQuery } from './utils/query';
|
||||||
import { RawTimeRange } from 'app/types/series';
|
import { DataSource } from 'app/types/datasources';
|
||||||
|
|
||||||
const MAX_HISTORY_ITEMS = 100;
|
const MAX_HISTORY_ITEMS = 100;
|
||||||
|
|
||||||
@@ -148,7 +149,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async setDatasource(datasource) {
|
async setDatasource(datasource: DataSource) {
|
||||||
const supportsGraph = datasource.meta.metrics;
|
const supportsGraph = datasource.meta.metrics;
|
||||||
const supportsLogs = datasource.meta.logs;
|
const supportsLogs = datasource.meta.logs;
|
||||||
const supportsTable = datasource.meta.metrics;
|
const supportsTable = datasource.meta.metrics;
|
||||||
@@ -176,8 +177,12 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
query: this.queryExpressions[i],
|
query: this.queryExpressions[i],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// Custom components
|
||||||
|
const StartPage = datasource.pluginExports.ExploreStartPage;
|
||||||
|
|
||||||
this.setState(
|
this.setState(
|
||||||
{
|
{
|
||||||
|
StartPage,
|
||||||
datasource,
|
datasource,
|
||||||
datasourceError,
|
datasourceError,
|
||||||
history,
|
history,
|
||||||
@@ -330,6 +335,13 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Use this in help pages to set page to a single query
|
||||||
|
onClickQuery = query => {
|
||||||
|
const nextQueries = [{ query, key: generateQueryKey() }];
|
||||||
|
this.queryExpressions = nextQueries.map(q => q.query);
|
||||||
|
this.setState({ queries: nextQueries }, this.onSubmit);
|
||||||
|
};
|
||||||
|
|
||||||
onClickSplit = () => {
|
onClickSplit = () => {
|
||||||
const { onChangeSplit } = this.props;
|
const { onChangeSplit } = this.props;
|
||||||
if (onChangeSplit) {
|
if (onChangeSplit) {
|
||||||
@@ -721,6 +733,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
render() {
|
render() {
|
||||||
const { position, split } = this.props;
|
const { position, split } = this.props;
|
||||||
const {
|
const {
|
||||||
|
StartPage,
|
||||||
datasource,
|
datasource,
|
||||||
datasourceError,
|
datasourceError,
|
||||||
datasourceLoading,
|
datasourceLoading,
|
||||||
@@ -760,6 +773,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
queryTransactions.filter(qt => qt.resultType === 'Logs' && qt.done && qt.result).map(qt => qt.result)
|
queryTransactions.filter(qt => qt.resultType === 'Logs' && qt.done && qt.result).map(qt => qt.result)
|
||||||
);
|
);
|
||||||
const loading = queryTransactions.some(qt => !qt.done);
|
const loading = queryTransactions.some(qt => !qt.done);
|
||||||
|
const showStartPages = StartPage && queryTransactions.length === 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={exploreClass} ref={this.getRef}>
|
<div className={exploreClass} ref={this.getRef}>
|
||||||
@@ -847,43 +861,48 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
supportsLogs={supportsLogs}
|
supportsLogs={supportsLogs}
|
||||||
transactions={queryTransactions}
|
transactions={queryTransactions}
|
||||||
/>
|
/>
|
||||||
<div className="result-options">
|
|
||||||
{supportsGraph ? (
|
|
||||||
<button className={`btn toggle-btn ${graphButtonActive}`} onClick={this.onClickGraphButton}>
|
|
||||||
Graph
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
{supportsTable ? (
|
|
||||||
<button className={`btn toggle-btn ${tableButtonActive}`} onClick={this.onClickTableButton}>
|
|
||||||
Table
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
{supportsLogs ? (
|
|
||||||
<button className={`btn toggle-btn ${logsButtonActive}`} onClick={this.onClickLogsButton}>
|
|
||||||
Logs
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<main className="m-t-2">
|
<main className="m-t-2">
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
{supportsGraph &&
|
{showStartPages && <StartPage onClickQuery={this.onClickQuery} />}
|
||||||
showingGraph && (
|
{!showStartPages && (
|
||||||
<Graph
|
<>
|
||||||
data={graphResult}
|
<div className="result-options">
|
||||||
height={graphHeight}
|
{supportsGraph ? (
|
||||||
loading={graphLoading}
|
<button className={`btn toggle-btn ${graphButtonActive}`} onClick={this.onClickGraphButton}>
|
||||||
id={`explore-graph-${position}`}
|
Graph
|
||||||
range={graphRange}
|
</button>
|
||||||
split={split}
|
) : null}
|
||||||
/>
|
{supportsTable ? (
|
||||||
)}
|
<button className={`btn toggle-btn ${tableButtonActive}`} onClick={this.onClickTableButton}>
|
||||||
{supportsTable && showingTable ? (
|
Table
|
||||||
<div className="panel-container m-t-2">
|
</button>
|
||||||
<Table data={tableResult} loading={tableLoading} onClickCell={this.onClickTableCell} />
|
) : null}
|
||||||
</div>
|
{supportsLogs ? (
|
||||||
) : null}
|
<button className={`btn toggle-btn ${logsButtonActive}`} onClick={this.onClickLogsButton}>
|
||||||
{supportsLogs && showingLogs ? <Logs data={logsResult} loading={logsLoading} /> : null}
|
Logs
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{supportsGraph &&
|
||||||
|
showingGraph && (
|
||||||
|
<Graph
|
||||||
|
data={graphResult}
|
||||||
|
height={graphHeight}
|
||||||
|
loading={graphLoading}
|
||||||
|
id={`explore-graph-${position}`}
|
||||||
|
range={graphRange}
|
||||||
|
split={split}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{supportsTable && showingTable ? (
|
||||||
|
<div className="panel-container m-t-2">
|
||||||
|
<Table data={tableResult} loading={tableLoading} onClickCell={this.onClickTableCell} />
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{supportsLogs && showingLogs ? <Logs data={logsResult} loading={logsLoading} /> : null}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ function hasSuggestions(suggestions: CompletionItemGroup[]): boolean {
|
|||||||
return suggestions && suggestions.length > 0;
|
return suggestions && suggestions.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TypeaheadFieldProps {
|
interface QueryFieldProps {
|
||||||
additionalPlugins?: any[];
|
additionalPlugins?: any[];
|
||||||
cleanText?: (text: string) => string;
|
cleanText?: (text: string) => string;
|
||||||
initialValue: string | null;
|
initialValue: string | null;
|
||||||
@@ -35,14 +35,14 @@ interface TypeaheadFieldProps {
|
|||||||
onFocus?: () => void;
|
onFocus?: () => void;
|
||||||
onTypeahead?: (typeahead: TypeaheadInput) => TypeaheadOutput;
|
onTypeahead?: (typeahead: TypeaheadInput) => TypeaheadOutput;
|
||||||
onValueChanged?: (value: Value) => void;
|
onValueChanged?: (value: Value) => void;
|
||||||
onWillApplySuggestion?: (suggestion: string, state: TypeaheadFieldState) => string;
|
onWillApplySuggestion?: (suggestion: string, state: QueryFieldState) => string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
portalOrigin?: string;
|
portalOrigin?: string;
|
||||||
syntax?: string;
|
syntax?: string;
|
||||||
syntaxLoaded?: boolean;
|
syntaxLoaded?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TypeaheadFieldState {
|
export interface QueryFieldState {
|
||||||
suggestions: CompletionItemGroup[];
|
suggestions: CompletionItemGroup[];
|
||||||
typeaheadContext: string | null;
|
typeaheadContext: string | null;
|
||||||
typeaheadIndex: number;
|
typeaheadIndex: number;
|
||||||
@@ -60,7 +60,7 @@ export interface TypeaheadInput {
|
|||||||
wrapperNode: Element;
|
wrapperNode: Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
class QueryField extends React.PureComponent<TypeaheadFieldProps, TypeaheadFieldState> {
|
export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldState> {
|
||||||
menuEl: HTMLElement | null;
|
menuEl: HTMLElement | null;
|
||||||
placeholdersBuffer: PlaceholdersBuffer;
|
placeholdersBuffer: PlaceholdersBuffer;
|
||||||
plugins: any[];
|
plugins: any[];
|
||||||
@@ -72,7 +72,7 @@ class QueryField extends React.PureComponent<TypeaheadFieldProps, TypeaheadField
|
|||||||
this.placeholdersBuffer = new PlaceholdersBuffer(props.initialValue || '');
|
this.placeholdersBuffer = new PlaceholdersBuffer(props.initialValue || '');
|
||||||
|
|
||||||
// Base plugins
|
// Base plugins
|
||||||
this.plugins = [ClearPlugin(), NewlinePlugin(), ...props.additionalPlugins];
|
this.plugins = [ClearPlugin(), NewlinePlugin(), ...props.additionalPlugins].filter(p => p);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
suggestions: [],
|
suggestions: [],
|
||||||
@@ -102,7 +102,7 @@ class QueryField extends React.PureComponent<TypeaheadFieldProps, TypeaheadField
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps: TypeaheadFieldProps) {
|
componentWillReceiveProps(nextProps: QueryFieldProps) {
|
||||||
if (nextProps.syntaxLoaded && !this.props.syntaxLoaded) {
|
if (nextProps.syntaxLoaded && !this.props.syntaxLoaded) {
|
||||||
// Need a bogus edit to re-render the editor after syntax has fully loaded
|
// Need a bogus edit to re-render the editor after syntax has fully loaded
|
||||||
const change = this.state.value
|
const change = this.state.value
|
||||||
@@ -434,19 +434,21 @@ class QueryField extends React.PureComponent<TypeaheadFieldProps, TypeaheadField
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="slate-query-field">
|
<div className="slate-query-field-wrapper">
|
||||||
{this.renderMenu()}
|
<div className="slate-query-field">
|
||||||
<Editor
|
{this.renderMenu()}
|
||||||
autoCorrect={false}
|
<Editor
|
||||||
onBlur={this.handleBlur}
|
autoCorrect={false}
|
||||||
onKeyDown={this.onKeyDown}
|
onBlur={this.handleBlur}
|
||||||
onChange={this.onChange}
|
onKeyDown={this.onKeyDown}
|
||||||
onFocus={this.handleFocus}
|
onChange={this.onChange}
|
||||||
placeholder={this.props.placeholder}
|
onFocus={this.handleFocus}
|
||||||
plugins={this.plugins}
|
placeholder={this.props.placeholder}
|
||||||
spellCheck={false}
|
plugins={this.plugins}
|
||||||
value={this.state.value}
|
spellCheck={false}
|
||||||
/>
|
value={this.state.value}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import React, { PureComponent } from 'react';
|
|||||||
|
|
||||||
import { QueryTransaction, HistoryItem, Query, QueryHint } from 'app/types/explore';
|
import { QueryTransaction, HistoryItem, Query, QueryHint } from 'app/types/explore';
|
||||||
|
|
||||||
// TODO make this datasource-plugin-dependent
|
import DefaultQueryField from './QueryField';
|
||||||
import QueryField from './PromQueryField';
|
import QueryTransactionStatus from './QueryTransactionStatus';
|
||||||
import QueryTransactions from './QueryTransactions';
|
import { DataSource } from 'app/types';
|
||||||
|
|
||||||
function getFirstHintFromTransactions(transactions: QueryTransaction[]): QueryHint {
|
function getFirstHintFromTransactions(transactions: QueryTransaction[]): QueryHint {
|
||||||
const transaction = transactions.find(qt => qt.hints && qt.hints.length > 0);
|
const transaction = transactions.find(qt => qt.hints && qt.hints.length > 0);
|
||||||
@@ -24,7 +24,7 @@ interface QueryRowEventHandlers {
|
|||||||
|
|
||||||
interface QueryRowCommonProps {
|
interface QueryRowCommonProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
datasource: any;
|
datasource: DataSource;
|
||||||
history: HistoryItem[];
|
history: HistoryItem[];
|
||||||
// Temporarily
|
// Temporarily
|
||||||
supportsLogs?: boolean;
|
supportsLogs?: boolean;
|
||||||
@@ -82,10 +82,11 @@ class QueryRow extends PureComponent<QueryRowProps> {
|
|||||||
const transactionWithError = transactions.find(t => t.error !== undefined);
|
const transactionWithError = transactions.find(t => t.error !== undefined);
|
||||||
const hint = getFirstHintFromTransactions(transactions);
|
const hint = getFirstHintFromTransactions(transactions);
|
||||||
const queryError = transactionWithError ? transactionWithError.error : null;
|
const queryError = transactionWithError ? transactionWithError.error : null;
|
||||||
|
const QueryField = datasource.pluginExports.ExploreQueryField || DefaultQueryField;
|
||||||
return (
|
return (
|
||||||
<div className="query-row">
|
<div className="query-row">
|
||||||
<div className="query-row-status">
|
<div className="query-row-status">
|
||||||
<QueryTransactions transactions={transactions} />
|
<QueryTransactionStatus transactions={transactions} />
|
||||||
</div>
|
</div>
|
||||||
<div className="query-row-field">
|
<div className="query-row-field">
|
||||||
<QueryField
|
<QueryField
|
||||||
|
|||||||
+8
-8
@@ -1,17 +1,17 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
|
|
||||||
import { QueryTransaction as QueryTransactionModel } from 'app/types/explore';
|
import { QueryTransaction } from 'app/types/explore';
|
||||||
import ElapsedTime from './ElapsedTime';
|
import ElapsedTime from './ElapsedTime';
|
||||||
|
|
||||||
function formatLatency(value) {
|
function formatLatency(value) {
|
||||||
return `${(value / 1000).toFixed(1)}s`;
|
return `${(value / 1000).toFixed(1)}s`;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface QueryTransactionProps {
|
interface QueryTransactionStatusItemProps {
|
||||||
transaction: QueryTransactionModel;
|
transaction: QueryTransaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
class QueryTransaction extends PureComponent<QueryTransactionProps> {
|
class QueryTransactionStatusItem extends PureComponent<QueryTransactionStatusItemProps> {
|
||||||
render() {
|
render() {
|
||||||
const { transaction } = this.props;
|
const { transaction } = this.props;
|
||||||
const className = transaction.done ? 'query-transaction' : 'query-transaction query-transaction--loading';
|
const className = transaction.done ? 'query-transaction' : 'query-transaction query-transaction--loading';
|
||||||
@@ -26,16 +26,16 @@ class QueryTransaction extends PureComponent<QueryTransactionProps> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface QueryTransactionsProps {
|
interface QueryTransactionStatusProps {
|
||||||
transactions: QueryTransactionModel[];
|
transactions: QueryTransaction[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class QueryTransactions extends PureComponent<QueryTransactionsProps> {
|
export default class QueryTransactionStatus extends PureComponent<QueryTransactionStatusProps> {
|
||||||
render() {
|
render() {
|
||||||
const { transactions } = this.props;
|
const { transactions } = this.props;
|
||||||
return (
|
return (
|
||||||
<div className="query-transactions">
|
<div className="query-transactions">
|
||||||
{transactions.map((t, i) => <QueryTransaction key={`${t.query}:${t.resultType}`} transaction={t} />)}
|
{transactions.map((t, i) => <QueryTransactionStatusItem key={`${t.query}:${t.resultType}`} transaction={t} />)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -8,9 +8,10 @@ import { importPluginModule } from './plugin_loader';
|
|||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { DataSourceApi } from 'app/types/series';
|
import { DataSourceApi } from 'app/types/series';
|
||||||
|
import { DataSource } from 'app/types';
|
||||||
|
|
||||||
export class DatasourceSrv {
|
export class DatasourceSrv {
|
||||||
datasources: any;
|
datasources: { [name: string]: DataSource };
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(private $q, private $injector, private $rootScope, private templateSrv) {
|
constructor(private $q, private $injector, private $rootScope, private templateSrv) {
|
||||||
@@ -61,9 +62,10 @@ export class DatasourceSrv {
|
|||||||
throw new Error('Plugin module is missing Datasource constructor');
|
throw new Error('Plugin module is missing Datasource constructor');
|
||||||
}
|
}
|
||||||
|
|
||||||
const instance = this.$injector.instantiate(plugin.Datasource, { instanceSettings: dsConfig });
|
const instance: DataSource = this.$injector.instantiate(plugin.Datasource, { instanceSettings: dsConfig });
|
||||||
instance.meta = pluginDef;
|
instance.meta = pluginDef;
|
||||||
instance.name = name;
|
instance.name = name;
|
||||||
|
instance.pluginExports = plugin;
|
||||||
this.datasources[name] = instance;
|
this.datasources[name] = instance;
|
||||||
deferred.resolve(instance);
|
deferred.resolve(instance);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const CHEAT_SHEET_ITEMS = [
|
||||||
|
{
|
||||||
|
title: 'Request Rate',
|
||||||
|
expression: 'rate(http_request_total[5m])',
|
||||||
|
label:
|
||||||
|
'Given an HTTP request counter, this query calculates the per-second average request rate over the last 5 minutes.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '95th Percentile of Request Latencies',
|
||||||
|
expression: 'histogram_quantile(0.95, sum(rate(prometheus_http_request_duration_seconds_bucket[5m])) by (le))',
|
||||||
|
label: 'Calculates the 95th percentile of HTTP request rate over 5 minute windows.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Alerts Firing',
|
||||||
|
expression: 'sort_desc(sum(sum_over_time(ALERTS{alertstate="firing"}[24h])) by (alertname))',
|
||||||
|
label: 'Sums up the alerts that have been firing over the last 24 hours.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default (props: any) => (
|
||||||
|
<div>
|
||||||
|
<h1>PromQL Cheat Sheet</h1>
|
||||||
|
{CHEAT_SHEET_ITEMS.map(item => (
|
||||||
|
<div className="cheat-sheet-item" key={item.expression}>
|
||||||
|
<div className="cheat-sheet-item__title">{item.title}</div>
|
||||||
|
<div className="cheat-sheet-item__expression" onClick={e => props.onClickQuery(item.expression)}>
|
||||||
|
<code>{item.expression}</code>
|
||||||
|
</div>
|
||||||
|
<div className="cheat-sheet-item__label">{item.label}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
+16
-22
@@ -7,11 +7,10 @@ import Prism from 'prismjs';
|
|||||||
import { TypeaheadOutput } from 'app/types/explore';
|
import { TypeaheadOutput } from 'app/types/explore';
|
||||||
|
|
||||||
// dom also includes Element polyfills
|
// dom also includes Element polyfills
|
||||||
import { getNextCharacter, getPreviousCousin } from './utils/dom';
|
import { getNextCharacter, getPreviousCousin } from 'app/features/explore/utils/dom';
|
||||||
import BracesPlugin from './slate-plugins/braces';
|
import BracesPlugin from 'app/features/explore/slate-plugins/braces';
|
||||||
import RunnerPlugin from './slate-plugins/runner';
|
import RunnerPlugin from 'app/features/explore/slate-plugins/runner';
|
||||||
|
import TypeaheadField, { TypeaheadInput, QueryFieldState } from 'app/features/explore/QueryField';
|
||||||
import TypeaheadField, { TypeaheadInput, TypeaheadFieldState } from './QueryField';
|
|
||||||
|
|
||||||
const HISTOGRAM_GROUP = '__histograms__';
|
const HISTOGRAM_GROUP = '__histograms__';
|
||||||
const METRIC_MARK = 'metric';
|
const METRIC_MARK = 'metric';
|
||||||
@@ -51,10 +50,7 @@ export function groupMetricsByPrefix(metrics: string[], delimiter = '_'): Cascad
|
|||||||
return [...options, ...metricsOptions];
|
return [...options, ...metricsOptions];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function willApplySuggestion(
|
export function willApplySuggestion(suggestion: string, { typeaheadContext, typeaheadText }: QueryFieldState): string {
|
||||||
suggestion: string,
|
|
||||||
{ typeaheadContext, typeaheadText }: TypeaheadFieldState
|
|
||||||
): string {
|
|
||||||
// Modify suggestion based on context
|
// Modify suggestion based on context
|
||||||
switch (typeaheadContext) {
|
switch (typeaheadContext) {
|
||||||
case 'context-labels': {
|
case 'context-labels': {
|
||||||
@@ -261,19 +257,17 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="prom-query-field-wrapper">
|
<div className="prom-query-field-wrapper">
|
||||||
<div className="slate-query-field-wrapper">
|
<TypeaheadField
|
||||||
<TypeaheadField
|
additionalPlugins={this.plugins}
|
||||||
additionalPlugins={this.plugins}
|
cleanText={cleanText}
|
||||||
cleanText={cleanText}
|
initialValue={initialQuery}
|
||||||
initialValue={initialQuery}
|
onTypeahead={this.onTypeahead}
|
||||||
onTypeahead={this.onTypeahead}
|
onWillApplySuggestion={willApplySuggestion}
|
||||||
onWillApplySuggestion={willApplySuggestion}
|
onValueChanged={this.onChangeQuery}
|
||||||
onValueChanged={this.onChangeQuery}
|
placeholder="Enter a PromQL query"
|
||||||
placeholder="Enter a PromQL query"
|
portalOrigin="prometheus"
|
||||||
portalOrigin="prometheus"
|
syntaxLoaded={syntaxLoaded}
|
||||||
syntaxLoaded={syntaxLoaded}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{error ? <div className="prom-query-field-info text-error">{error}</div> : null}
|
{error ? <div className="prom-query-field-info text-error">{error}</div> : null}
|
||||||
{hint ? (
|
{hint ? (
|
||||||
<div className="prom-query-field-info text-warning">
|
<div className="prom-query-field-info text-warning">
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import PromCheatSheet from './PromCheatSheet';
|
||||||
|
|
||||||
|
const TAB_MENU_ITEMS = [
|
||||||
|
{
|
||||||
|
text: 'Start',
|
||||||
|
id: 'start',
|
||||||
|
icon: 'fa fa-rocket',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default class PromStart extends PureComponent<any, { active: string }> {
|
||||||
|
state = {
|
||||||
|
active: 'start',
|
||||||
|
};
|
||||||
|
|
||||||
|
onClickTab = active => {
|
||||||
|
this.setState({ active });
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { active } = this.state;
|
||||||
|
const customCss = '';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ margin: '45px 0', border: '1px solid #ddd', borderRadius: 5 }}>
|
||||||
|
<div className="page-header-canvas">
|
||||||
|
<div className="page-container">
|
||||||
|
<div className="page-header">
|
||||||
|
<nav>
|
||||||
|
<ul className={`gf-tabs ${customCss}`}>
|
||||||
|
{TAB_MENU_ITEMS.map((tab, idx) => {
|
||||||
|
const tabClasses = classNames({
|
||||||
|
'gf-tabs-link': true,
|
||||||
|
active: tab.id === active,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li className="gf-tabs-item" key={tab.id}>
|
||||||
|
<a className={tabClasses} onClick={() => this.onClickTab(tab.id)}>
|
||||||
|
<i className={tab.icon} />
|
||||||
|
{tab.text}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="page-container page-body">
|
||||||
|
{active === 'start' && <PromCheatSheet onClickQuery={this.props.onClickQuery} />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,9 @@ import { PrometheusDatasource } from './datasource';
|
|||||||
import { PrometheusQueryCtrl } from './query_ctrl';
|
import { PrometheusQueryCtrl } from './query_ctrl';
|
||||||
import { PrometheusConfigCtrl } from './config_ctrl';
|
import { PrometheusConfigCtrl } from './config_ctrl';
|
||||||
|
|
||||||
|
import PrometheusStartPage from './components/PromStart';
|
||||||
|
import PromQueryField from './components/PromQueryField';
|
||||||
|
|
||||||
class PrometheusAnnotationsQueryCtrl {
|
class PrometheusAnnotationsQueryCtrl {
|
||||||
static templateUrl = 'partials/annotations.editor.html';
|
static templateUrl = 'partials/annotations.editor.html';
|
||||||
}
|
}
|
||||||
@@ -11,4 +14,6 @@ export {
|
|||||||
PrometheusQueryCtrl as QueryCtrl,
|
PrometheusQueryCtrl as QueryCtrl,
|
||||||
PrometheusConfigCtrl as ConfigCtrl,
|
PrometheusConfigCtrl as ConfigCtrl,
|
||||||
PrometheusAnnotationsQueryCtrl as AnnotationsQueryCtrl,
|
PrometheusAnnotationsQueryCtrl as AnnotationsQueryCtrl,
|
||||||
|
PromQueryField as ExploreQueryField,
|
||||||
|
PrometheusStartPage as ExploreStartPage,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { LayoutMode } from '../core/components/LayoutSelector/LayoutSelector';
|
import { LayoutMode } from '../core/components/LayoutSelector/LayoutSelector';
|
||||||
import { Plugin } from './plugins';
|
import { Plugin, PluginExports, PluginMeta } from './plugins';
|
||||||
|
|
||||||
export interface DataSource {
|
export interface DataSource {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -16,6 +16,10 @@ export interface DataSource {
|
|||||||
isDefault: boolean;
|
isDefault: boolean;
|
||||||
jsonData: { authType: string; defaultRegion: string };
|
jsonData: { authType: string; defaultRegion: string };
|
||||||
readOnly: boolean;
|
readOnly: boolean;
|
||||||
|
meta?: PluginMeta;
|
||||||
|
pluginExports?: PluginExports;
|
||||||
|
init?: () => void;
|
||||||
|
testDatasource?: () => Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DataSourcesState {
|
export interface DataSourcesState {
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ export interface TextMatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ExploreState {
|
export interface ExploreState {
|
||||||
|
StartPage?: any;
|
||||||
datasource: any;
|
datasource: any;
|
||||||
datasourceError: any;
|
datasourceError: any;
|
||||||
datasourceLoading: boolean | null;
|
datasourceLoading: boolean | null;
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ export interface PluginExports {
|
|||||||
ConfigCtrl?: any;
|
ConfigCtrl?: any;
|
||||||
AnnotationsQueryCtrl?: any;
|
AnnotationsQueryCtrl?: any;
|
||||||
PanelOptions?: any;
|
PanelOptions?: any;
|
||||||
|
ExploreQueryField?: any;
|
||||||
|
ExploreStartPage?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PanelPlugin {
|
export interface PanelPlugin {
|
||||||
@@ -25,6 +27,12 @@ export interface PluginMeta {
|
|||||||
name: string;
|
name: string;
|
||||||
info: PluginMetaInfo;
|
info: PluginMetaInfo;
|
||||||
includes: PluginInclude[];
|
includes: PluginInclude[];
|
||||||
|
|
||||||
|
// Datasource-specific
|
||||||
|
metrics?: boolean;
|
||||||
|
logs?: boolean;
|
||||||
|
explore?: boolean;
|
||||||
|
annotations?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PluginInclude {
|
export interface PluginInclude {
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.result-options {
|
.result-options {
|
||||||
margin-top: 2 * $panel-margin;
|
margin: 2 * $panel-margin 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-series-disclaimer {
|
.time-series-disclaimer {
|
||||||
@@ -322,3 +322,19 @@
|
|||||||
.ReactTable .rt-tr .rt-td:last-child {
|
.ReactTable .rt-tr .rt-td:last-child {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO Experimental
|
||||||
|
|
||||||
|
.cheat-sheet-item {
|
||||||
|
margin: 2*$panel-margin 0;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cheat-sheet-item__title {
|
||||||
|
font-size: $font-size-h3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cheat-sheet-item__expression {
|
||||||
|
margin: $panel-margin/2 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ module.exports = {
|
|||||||
publicPath: "public/build/",
|
publicPath: "public/build/",
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.ts', '.tsx', '.es6', '.js', '.json'],
|
extensions: ['.ts', '.tsx', '.es6', '.js', '.json', '.svg'],
|
||||||
alias: {
|
alias: {
|
||||||
},
|
},
|
||||||
modules: [
|
modules: [
|
||||||
|
|||||||
Reference in New Issue
Block a user