Commit e99c1b48 by David Committed by GitHub

Merge pull request #13904 from grafana/davkal/explore-logging

Explore: repair logging after recent Explore restructuring
parents 3d452e5a 037167ff
import _ from 'lodash';
export enum LogLevel {
crit = 'crit',
warn = 'warn',
......@@ -27,3 +29,15 @@ export interface LogRow {
export interface LogsModel {
rows: LogRow[];
}
export function mergeStreams(streams: LogsModel[], limit?: number): LogsModel {
const combinedEntries = streams.reduce((acc, stream) => {
return [...acc, ...stream.rows];
}, []);
const sortedEntries = _.chain(combinedEntries)
.sortBy('timestamp')
.reverse()
.slice(0, limit || combinedEntries.length)
.value();
return { rows: sortedEntries };
}
......@@ -25,6 +25,7 @@ import ErrorBoundary from './ErrorBoundary';
import TimePicker from './TimePicker';
import { ensureQueries, generateQueryKey, hasQuery } from './utils/query';
import { DataSource } from 'app/types/datasources';
import { mergeStreams } from 'app/core/logs_model';
const MAX_HISTORY_ITEMS = 100;
......@@ -769,11 +770,12 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
new TableModel(),
...queryTransactions.filter(qt => qt.resultType === 'Table' && qt.done && qt.result).map(qt => qt.result)
);
const logsResult = _.flatten(
const logsResult = mergeStreams(
queryTransactions.filter(qt => qt.resultType === 'Logs' && qt.done && qt.result).map(qt => qt.result)
);
const loading = queryTransactions.some(qt => !qt.done);
const showStartPages = StartPage && queryTransactions.length === 0;
const viewModeCount = [supportsGraph, supportsLogs, supportsTable].filter(m => m).length;
return (
<div className={exploreClass} ref={this.getRef}>
......@@ -858,7 +860,6 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
onClickHintFix={this.onModifyQueries}
onExecuteQuery={this.onSubmit}
onRemoveQueryRow={this.onRemoveQueryRow}
supportsLogs={supportsLogs}
transactions={queryTransactions}
/>
<main className="m-t-2">
......@@ -866,23 +867,25 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
{showStartPages && <StartPage onClickQuery={this.onClickQuery} />}
{!showStartPages && (
<>
<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>
{viewModeCount > 1 && (
<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>
)}
{supportsGraph &&
showingGraph && (
......
......@@ -169,7 +169,7 @@ export class Graph extends PureComponent<GraphProps, GraphState> {
return (
<div className="panel-container">
{loading && <div className="explore-graph__loader" />}
{loading && <div className="explore-panel__loader" />}
{this.props.data &&
this.props.data.length > MAX_NUMBER_OF_TIME_SERIES &&
!this.state.showAllTimeSeries && (
......
......@@ -10,37 +10,33 @@ interface LogsProps {
loading: boolean;
}
const EXAMPLE_QUERY = '{job="default/prometheus"}';
export default class Logs extends PureComponent<LogsProps, {}> {
render() {
const { className = '', data } = this.props;
const { className = '', data, loading = false } = this.props;
const hasData = data && data.rows && data.rows.length > 0;
return (
<div className={`${className} logs`}>
{hasData ? (
<div className="logs-entries panel-container">
{data.rows.map(row => (
<Fragment key={row.key}>
<div className={row.logLevel ? `logs-row-level logs-row-level-${row.logLevel}` : ''} />
<div title={`${row.timestamp} (${row.timeFromNow})`}>{row.timeLocal}</div>
<div>
<Highlighter
textToHighlight={row.entry}
searchWords={row.searchWords}
findChunks={findHighlightChunksInText}
highlightClassName="logs-row-match-highlight"
/>
</div>
</Fragment>
))}
</div>
) : null}
{!hasData ? (
<div className="panel-container">
Enter a query like <code>{EXAMPLE_QUERY}</code>
<div className="panel-container">
{loading && <div className="explore-panel__loader" />}
<div className="logs-entries">
{hasData &&
data.rows.map(row => (
<Fragment key={row.key}>
<div className={row.logLevel ? `logs-row-level logs-row-level-${row.logLevel}` : ''} />
<div title={`${row.timestamp} (${row.timeFromNow})`}>{row.timeLocal}</div>
<div>
<Highlighter
textToHighlight={row.entry}
searchWords={row.searchWords}
findChunks={findHighlightChunksInText}
highlightClassName="logs-row-match-highlight"
/>
</div>
</Fragment>
))}
</div>
) : null}
{!loading && !hasData && 'No data was returned.'}
</div>
</div>
);
}
......
......@@ -26,8 +26,6 @@ interface QueryRowCommonProps {
className?: string;
datasource: DataSource;
history: HistoryItem[];
// Temporarily
supportsLogs?: boolean;
transactions: QueryTransaction[];
}
......@@ -78,7 +76,7 @@ class QueryRow extends PureComponent<QueryRowProps> {
};
render() {
const { datasource, history, query, supportsLogs, transactions } = this.props;
const { datasource, history, query, transactions } = this.props;
const transactionWithError = transactions.find(t => t.error !== undefined);
const hint = getFirstHintFromTransactions(transactions);
const queryError = transactionWithError ? transactionWithError.error : null;
......@@ -98,7 +96,6 @@ class QueryRow extends PureComponent<QueryRowProps> {
onClickHintFix={this.onClickHintFix}
onPressEnter={this.onPressEnter}
onQueryChange={this.onChangeQuery}
supportsLogs={supportsLogs}
/>
</div>
<div className="query-row-tools">
......
......@@ -33,8 +33,8 @@ describe('<TimePicker />', () => {
to: '1000',
};
const rangeString = rangeUtil.describeTimeRange({
from: parseTime(range.from),
to: parseTime(range.to),
from: parseTime(range.from, true),
to: parseTime(range.to, true),
});
const wrapper = shallow(<TimePicker range={range} isUtc isOpen />);
expect(wrapper.state('fromRaw')).toBe('1970-01-01 00:00:00');
......@@ -50,8 +50,8 @@ describe('<TimePicker />', () => {
to: '4000',
};
const rangeString = rangeUtil.describeTimeRange({
from: parseTime(range.from),
to: parseTime(range.to),
from: parseTime(range.from, true),
to: parseTime(range.to, true),
});
const onChangeTime = sinon.spy();
......
import React from 'react';
const CHEAT_SHEET_ITEMS = [
{
title: 'Logs From a Job',
expression: '{job="default/prometheus"}',
label: 'Returns all log lines emitted by instances of this job.',
},
{
title: 'Search For Text',
expression: '{app="cassandra"} Maximum memory usage',
label: 'Returns all log lines for the selector and highlights the given text in the results.',
},
];
export default (props: any) => (
<div>
<h1>Logging 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>
);
import _ from 'lodash';
import React from 'react';
import Cascader from 'rc-cascader';
import PluginPrism from 'slate-prism';
import Prism from 'prismjs';
import { TypeaheadOutput } from 'app/types/explore';
// dom also includes Element polyfills
import { getNextCharacter, getPreviousCousin } from 'app/features/explore/utils/dom';
import BracesPlugin from 'app/features/explore/slate-plugins/braces';
import RunnerPlugin from 'app/features/explore/slate-plugins/runner';
import TypeaheadField, { TypeaheadInput, QueryFieldState } from 'app/features/explore/QueryField';
const PRISM_SYNTAX = 'promql';
export function willApplySuggestion(suggestion: string, { typeaheadContext, typeaheadText }: QueryFieldState): string {
// Modify suggestion based on context
switch (typeaheadContext) {
case 'context-labels': {
const nextChar = getNextCharacter();
if (!nextChar || nextChar === '}' || nextChar === ',') {
suggestion += '=';
}
break;
}
case 'context-label-values': {
// Always add quotes and remove existing ones instead
if (!typeaheadText.match(/^(!?=~?"|")/)) {
suggestion = `"${suggestion}`;
}
if (getNextCharacter() !== '"') {
suggestion = `${suggestion}"`;
}
break;
}
default:
}
return suggestion;
}
interface CascaderOption {
label: string;
value: string;
children?: CascaderOption[];
disabled?: boolean;
}
interface LoggingQueryFieldProps {
datasource: any;
error?: string | JSX.Element;
hint?: any;
history?: any[];
initialQuery?: string | null;
onClickHintFix?: (action: any) => void;
onPressEnter?: () => void;
onQueryChange?: (value: string, override?: boolean) => void;
}
interface LoggingQueryFieldState {
logLabelOptions: any[];
syntaxLoaded: boolean;
}
class LoggingQueryField extends React.PureComponent<LoggingQueryFieldProps, LoggingQueryFieldState> {
plugins: any[];
languageProvider: any;
constructor(props: LoggingQueryFieldProps, context) {
super(props, context);
if (props.datasource.languageProvider) {
this.languageProvider = props.datasource.languageProvider;
}
this.plugins = [
BracesPlugin(),
RunnerPlugin({ handler: props.onPressEnter }),
PluginPrism({
onlyIn: node => node.type === 'code_block',
getSyntax: node => 'promql',
}),
];
this.state = {
logLabelOptions: [],
syntaxLoaded: false,
};
}
componentDidMount() {
if (this.languageProvider) {
this.languageProvider.start().then(() => this.onReceiveMetrics());
}
}
onChangeLogLabels = (values: string[], selectedOptions: CascaderOption[]) => {
let query;
if (selectedOptions.length === 1) {
if (selectedOptions[0].children.length === 0) {
query = selectedOptions[0].value;
} else {
// Ignore click on group
return;
}
} else {
const key = selectedOptions[0].value;
const value = selectedOptions[1].value;
query = `{${key}="${value}"}`;
}
this.onChangeQuery(query, true);
};
onChangeQuery = (value: string, override?: boolean) => {
// Send text change to parent
const { onQueryChange } = this.props;
if (onQueryChange) {
onQueryChange(value, override);
}
};
onClickHintFix = () => {
const { hint, onClickHintFix } = this.props;
if (onClickHintFix && hint && hint.fix) {
onClickHintFix(hint.fix.action);
}
};
onReceiveMetrics = () => {
Prism.languages[PRISM_SYNTAX] = this.languageProvider.getSyntax();
const { logLabelOptions } = this.languageProvider;
this.setState({
logLabelOptions,
syntaxLoaded: true,
});
};
onTypeahead = (typeahead: TypeaheadInput): TypeaheadOutput => {
if (!this.languageProvider) {
return { suggestions: [] };
}
const { history } = this.props;
const { prefix, text, value, wrapperNode } = typeahead;
// Get DOM-dependent context
const wrapperClasses = Array.from(wrapperNode.classList);
const labelKeyNode = getPreviousCousin(wrapperNode, '.attr-name');
const labelKey = labelKeyNode && labelKeyNode.textContent;
const nextChar = getNextCharacter();
const result = this.languageProvider.provideCompletionItems(
{ text, value, prefix, wrapperClasses, labelKey },
{ history }
);
console.log('handleTypeahead', wrapperClasses, text, prefix, nextChar, labelKey, result.context);
return result;
};
render() {
const { error, hint, initialQuery } = this.props;
const { logLabelOptions, syntaxLoaded } = this.state;
const cleanText = this.languageProvider ? this.languageProvider.cleanText : undefined;
return (
<div className="prom-query-field">
<div className="prom-query-field-tools">
<Cascader options={logLabelOptions} onChange={this.onChangeLogLabels}>
<button className="btn navbar-button navbar-button--tight">Log labels</button>
</Cascader>
</div>
<div className="prom-query-field-wrapper">
<TypeaheadField
additionalPlugins={this.plugins}
cleanText={cleanText}
initialValue={initialQuery}
onTypeahead={this.onTypeahead}
onWillApplySuggestion={willApplySuggestion}
onValueChanged={this.onChangeQuery}
placeholder="Enter a PromQL query"
portalOrigin="prometheus"
syntaxLoaded={syntaxLoaded}
/>
{error ? <div className="prom-query-field-info text-error">{error}</div> : null}
{hint ? (
<div className="prom-query-field-info text-warning">
{hint.label}{' '}
{hint.fix ? (
<a className="text-link muted" onClick={this.onClickHintFix}>
{hint.fix.label}
</a>
) : null}
</div>
) : null}
</div>
</div>
);
}
}
export default LoggingQueryField;
import React, { PureComponent } from 'react';
import classNames from 'classnames';
import LoggingCheatSheet from './LoggingCheatSheet';
const TAB_MENU_ITEMS = [
{
text: 'Start',
id: 'start',
icon: 'fa fa-rocket',
},
];
export default class LoggingStartPage 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' && <LoggingCheatSheet onClickQuery={this.props.onClickQuery} />}
</div>
</div>
);
}
}
......@@ -2,6 +2,7 @@ import _ from 'lodash';
import * as dateMath from 'app/core/utils/datemath';
import LanguageProvider from './language_provider';
import { processStreams } from './result_transformer';
const DEFAULT_LIMIT = 100;
......@@ -48,8 +49,12 @@ function serializeParams(data: any) {
}
export default class LoggingDatasource {
languageProvider: LanguageProvider;
/** @ngInject */
constructor(private instanceSettings, private backendSrv, private templateSrv) {}
constructor(private instanceSettings, private backendSrv, private templateSrv) {
this.languageProvider = new LanguageProvider(this);
}
_request(apiUrl: string, data?, options?: any) {
const baseUrl = this.instanceSettings.url;
......
import _ from 'lodash';
import moment from 'moment';
import {
CompletionItem,
CompletionItemGroup,
LanguageProvider,
TypeaheadInput,
TypeaheadOutput,
} from 'app/types/explore';
import { parseSelector } from 'app/plugins/datasource/prometheus/language_utils';
import PromqlSyntax from 'app/plugins/datasource/prometheus/promql';
const DEFAULT_KEYS = ['job', 'instance'];
const EMPTY_SELECTOR = '{}';
const HISTORY_ITEM_COUNT = 5;
const HISTORY_COUNT_CUTOFF = 1000 * 60 * 60 * 24; // 24h
const wrapLabel = (label: string) => ({ label });
export function addHistoryMetadata(item: CompletionItem, history: any[]): CompletionItem {
const cutoffTs = Date.now() - HISTORY_COUNT_CUTOFF;
const historyForItem = history.filter(h => h.ts > cutoffTs && h.query === item.label);
const count = historyForItem.length;
const recent = historyForItem[0];
let hint = `Queried ${count} times in the last 24h.`;
if (recent) {
const lastQueried = moment(recent.ts).fromNow();
hint = `${hint} Last queried ${lastQueried}.`;
}
return {
...item,
documentation: hint,
};
}
export default class LoggingLanguageProvider extends LanguageProvider {
labelKeys?: { [index: string]: string[] }; // metric -> [labelKey,...]
labelValues?: { [index: string]: { [index: string]: string[] } }; // metric -> labelKey -> [labelValue,...]
logLabelOptions: any[];
started: boolean;
constructor(datasource: any, initialValues?: any) {
super();
this.datasource = datasource;
this.labelKeys = {};
this.labelValues = {};
this.started = false;
Object.assign(this, initialValues);
}
// Strip syntax chars
cleanText = s => s.replace(/[{}[\]="(),!~+\-*/^%]/g, '').trim();
getSyntax() {
return PromqlSyntax;
}
request = url => {
return this.datasource.metadataRequest(url);
};
start = () => {
if (!this.started) {
this.started = true;
return Promise.all([this.fetchLogLabels()]);
}
return Promise.resolve([]);
};
// Keep this DOM-free for testing
provideCompletionItems({ prefix, wrapperClasses, text }: TypeaheadInput, context?: any): TypeaheadOutput {
// Syntax spans have 3 classes by default. More indicate a recognized token
const tokenRecognized = wrapperClasses.length > 3;
// Determine candidates by CSS context
if (_.includes(wrapperClasses, 'context-labels')) {
// Suggestions for metric{|} and metric{foo=|}, as well as metric-independent label queries like {|}
return this.getLabelCompletionItems.apply(this, arguments);
} else if (
// Show default suggestions in a couple of scenarios
(prefix && !tokenRecognized) || // Non-empty prefix, but not inside known token
(prefix === '' && !text.match(/^[\]})\s]+$/)) || // Empty prefix, but not following a closing brace
text.match(/[+\-*/^%]/) // Anything after binary operator
) {
return this.getEmptyCompletionItems(context || {});
}
return {
suggestions: [],
};
}
getEmptyCompletionItems(context: any): TypeaheadOutput {
const { history } = context;
const suggestions: CompletionItemGroup[] = [];
if (history && history.length > 0) {
const historyItems = _.chain(history)
.uniqBy('query')
.take(HISTORY_ITEM_COUNT)
.map(h => h.query)
.map(wrapLabel)
.map(item => addHistoryMetadata(item, history))
.value();
suggestions.push({
prefixMatch: true,
skipSort: true,
label: 'History',
items: historyItems,
});
}
return { suggestions };
}
getLabelCompletionItems({ text, wrapperClasses, labelKey, value }: TypeaheadInput): TypeaheadOutput {
let context: string;
const suggestions: CompletionItemGroup[] = [];
const line = value.anchorBlock.getText();
const cursorOffset: number = value.anchorOffset;
// Get normalized selector
let selector;
let parsedSelector;
try {
parsedSelector = parseSelector(line, cursorOffset);
selector = parsedSelector.selector;
} catch {
selector = EMPTY_SELECTOR;
}
const containsMetric = selector.indexOf('__name__=') > -1;
const existingKeys = parsedSelector ? parsedSelector.labelKeys : [];
if ((text && text.match(/^!?=~?/)) || _.includes(wrapperClasses, 'attr-value')) {
// Label values
if (labelKey && this.labelValues[selector] && this.labelValues[selector][labelKey]) {
const labelValues = this.labelValues[selector][labelKey];
context = 'context-label-values';
suggestions.push({
label: `Label values for "${labelKey}"`,
items: labelValues.map(wrapLabel),
});
}
} else {
// Label keys
const labelKeys = this.labelKeys[selector] || (containsMetric ? null : DEFAULT_KEYS);
if (labelKeys) {
const possibleKeys = _.difference(labelKeys, existingKeys);
if (possibleKeys.length > 0) {
context = 'context-labels';
suggestions.push({ label: `Labels`, items: possibleKeys.map(wrapLabel) });
}
}
}
return { context, suggestions };
}
async fetchLogLabels() {
const url = '/api/prom/label';
try {
const res = await this.request(url);
const body = await (res.data || res.json());
const labelKeys = body.data.slice().sort();
const labelKeysBySelector = {
...this.labelKeys,
[EMPTY_SELECTOR]: labelKeys,
};
const labelValuesByKey = {};
this.logLabelOptions = [];
for (const key of labelKeys) {
const valuesUrl = `/api/prom/label/${key}/values`;
const res = await this.request(valuesUrl);
const body = await (res.data || res.json());
const values = body.data.slice().sort();
labelValuesByKey[key] = values;
this.logLabelOptions.push({
label: key,
value: key,
children: values.map(value => ({ label: value, value })),
});
}
this.labelValues = { [EMPTY_SELECTOR]: labelValuesByKey };
this.labelKeys = labelKeysBySelector;
} catch (e) {
console.error(e);
}
}
async fetchLabelValues(key: string) {
const url = `/api/prom/label/${key}/values`;
try {
const res = await this.request(url);
const body = await (res.data || res.json());
const exisingValues = this.labelValues[EMPTY_SELECTOR];
const values = {
...exisingValues,
[key]: body.data,
};
this.labelValues = {
...this.labelValues,
[EMPTY_SELECTOR]: values,
};
} catch (e) {
console.error(e);
}
}
}
import Datasource from './datasource';
import LoggingStartPage from './components/LoggingStartPage';
import LoggingQueryField from './components/LoggingQueryField';
export class LoggingConfigCtrl {
static templateUrl = 'partials/config.html';
}
export { Datasource, LoggingConfigCtrl as ConfigCtrl };
export {
Datasource,
LoggingConfigCtrl as ConfigCtrl,
LoggingQueryField as ExploreQueryField,
LoggingStartPage as ExploreStartPage,
};
......@@ -94,11 +94,9 @@ interface PromQueryFieldProps {
onClickHintFix?: (action: any) => void;
onPressEnter?: () => void;
onQueryChange?: (value: string, override?: boolean) => void;
supportsLogs?: boolean; // To be removed after Logging gets its own query field
}
interface PromQueryFieldState {
logLabelOptions: any[];
metricsOptions: any[];
metricsByPrefix: CascaderOption[];
syntaxLoaded: boolean;
......@@ -125,7 +123,6 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
];
this.state = {
logLabelOptions: [],
metricsByPrefix: [],
metricsOptions: [],
syntaxLoaded: false,
......@@ -138,23 +135,6 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
}
}
onChangeLogLabels = (values: string[], selectedOptions: CascaderOption[]) => {
let query;
if (selectedOptions.length === 1) {
if (selectedOptions[0].children.length === 0) {
query = selectedOptions[0].value;
} else {
// Ignore click on group
return;
}
} else {
const key = selectedOptions[0].value;
const value = selectedOptions[1].value;
query = `{${key}="${value}"}`;
}
this.onChangeQuery(query, true);
};
onChangeMetrics = (values: string[], selectedOptions: CascaderOption[]) => {
let query;
if (selectedOptions.length === 1) {
......@@ -239,22 +219,16 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
};
render() {
const { error, hint, initialQuery, supportsLogs } = this.props;
const { logLabelOptions, metricsOptions, syntaxLoaded } = this.state;
const { error, hint, initialQuery } = this.props;
const { metricsOptions, syntaxLoaded } = this.state;
const cleanText = this.languageProvider ? this.languageProvider.cleanText : undefined;
return (
<div className="prom-query-field">
<div className="prom-query-field-tools">
{supportsLogs ? (
<Cascader options={logLabelOptions} onChange={this.onChangeLogLabels}>
<button className="btn navbar-button navbar-button--tight">Log labels</button>
</Cascader>
) : (
<Cascader options={metricsOptions} onChange={this.onChangeMetrics}>
<button className="btn navbar-button navbar-button--tight">Metrics</button>
</Cascader>
)}
<Cascader options={metricsOptions} onChange={this.onChangeMetrics}>
<button className="btn navbar-button navbar-button--tight">Metrics</button>
</Cascader>
</div>
<div className="prom-query-field-wrapper">
<TypeaheadField
......
......@@ -46,8 +46,6 @@ export default class PromQlLanguageProvider extends LanguageProvider {
labelKeys?: { [index: string]: string[] }; // metric -> [labelKey,...]
labelValues?: { [index: string]: { [index: string]: string[] } }; // metric -> labelKey -> [labelValue,...]
metrics?: string[];
logLabelOptions: any[];
supportsLogs?: boolean;
started: boolean;
constructor(datasource: any, initialValues?: any) {
......@@ -58,7 +56,6 @@ export default class PromQlLanguageProvider extends LanguageProvider {
this.labelKeys = {};
this.labelValues = {};
this.metrics = [];
this.supportsLogs = false;
this.started = false;
Object.assign(this, initialValues);
......@@ -243,8 +240,7 @@ export default class PromQlLanguageProvider extends LanguageProvider {
}
// Query labels for selector
// Temporarily add skip for logging
if (selector && !this.labelValues[selector] && !this.supportsLogs) {
if (selector && !this.labelValues[selector]) {
if (selector === EMPTY_SELECTOR) {
// Query label values for default labels
refresher = Promise.all(DEFAULT_KEYS.map(key => this.fetchLabelValues(key)));
......@@ -275,38 +271,6 @@ export default class PromQlLanguageProvider extends LanguageProvider {
}
}
// Temporarily here while reusing this field for logging
async fetchLogLabels() {
const url = '/api/prom/label';
try {
const res = await this.request(url);
const body = await (res.data || res.json());
const labelKeys = body.data.slice().sort();
const labelKeysBySelector = {
...this.labelKeys,
[EMPTY_SELECTOR]: labelKeys,
};
const labelValuesByKey = {};
this.logLabelOptions = [];
for (const key of labelKeys) {
const valuesUrl = `/api/prom/label/${key}/values`;
const res = await this.request(valuesUrl);
const body = await (res.data || res.json());
const values = body.data.slice().sort();
labelValuesByKey[key] = values;
this.logLabelOptions.push({
label: key,
value: key,
children: values.map(value => ({ label: value, value })),
});
}
this.labelValues = { [EMPTY_SELECTOR]: labelValuesByKey };
this.labelKeys = labelKeysBySelector;
} catch (e) {
console.error(e);
}
}
async fetchLabelValues(key: string) {
const url = `/api/v1/label/${key}/values`;
try {
......
......@@ -87,7 +87,7 @@
flex-wrap: wrap;
}
.explore-graph__loader {
.explore-panel__loader {
height: 2px;
position: relative;
overflow: hidden;
......@@ -95,7 +95,7 @@
margin: $panel-margin / 2;
}
.explore-graph__loader:after {
.explore-panel__loader:after {
content: ' ';
display: block;
width: 25%;
......@@ -219,7 +219,13 @@
}
.logs-row-match-highlight {
background-color: lighten($blue, 20%);
// Undoing mark styling
background: inherit;
padding: inherit;
color: $typeahead-selected-color;
border-bottom: 1px solid $typeahead-selected-color;
background-color: lighten($typeahead-selected-color, 60%);
}
.logs-row-level {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment