From c92f5462fe97ce7602e14248d0beeb85a065bc9d Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Tue, 30 Oct 2018 16:14:01 +0100 Subject: [PATCH 1/2] Explore: repair logging after code restructuring this is a fix-up PR that cleans up Explore Logging after the recent restructuring. - log results need to be merged since query transactions have been introduced - logging DS has its own language provider, query field, and start page (some of them based on prometheus components) - added loader animation to log viewer - removed logging logic from prometheus components --- public/app/core/logs_model.ts | 14 ++ public/app/features/explore/Explore.tsx | 41 ++-- public/app/features/explore/Graph.tsx | 2 +- public/app/features/explore/Logs.tsx | 46 ++-- public/app/features/explore/QueryRows.tsx | 5 +- .../logging/components/LoggingCheatSheet.tsx | 29 +++ .../logging/components/LoggingQueryField.tsx | 205 +++++++++++++++++ .../logging/components/LoggingStartPage.tsx | 60 +++++ .../plugins/datasource/logging/datasource.ts | 7 +- .../datasource/logging/language_provider.ts | 211 ++++++++++++++++++ .../app/plugins/datasource/logging/module.ts | 10 +- .../prometheus/components/PromQueryField.tsx | 36 +-- .../prometheus/language_provider.ts | 38 +--- public/sass/pages/_explore.scss | 12 +- 14 files changed, 594 insertions(+), 122 deletions(-) create mode 100644 public/app/plugins/datasource/logging/components/LoggingCheatSheet.tsx create mode 100644 public/app/plugins/datasource/logging/components/LoggingQueryField.tsx create mode 100644 public/app/plugins/datasource/logging/components/LoggingStartPage.tsx create mode 100644 public/app/plugins/datasource/logging/language_provider.ts diff --git a/public/app/core/logs_model.ts b/public/app/core/logs_model.ts index 8848a929359..e6f317dbeb7 100644 --- a/public/app/core/logs_model.ts +++ b/public/app/core/logs_model.ts @@ -1,3 +1,5 @@ +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 }; +} diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index 7de20f31e69..af771bad5dd 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -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 { 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 (
@@ -858,7 +860,6 @@ export class Explore extends React.PureComponent { onClickHintFix={this.onModifyQueries} onExecuteQuery={this.onSubmit} onRemoveQueryRow={this.onRemoveQueryRow} - supportsLogs={supportsLogs} transactions={queryTransactions} />
@@ -866,23 +867,25 @@ export class Explore extends React.PureComponent { {showStartPages && } {!showStartPages && ( <> -
- {supportsGraph ? ( - - ) : null} - {supportsTable ? ( - - ) : null} - {supportsLogs ? ( - - ) : null} -
+ {viewModeCount > 1 && ( +
+ {supportsGraph ? ( + + ) : null} + {supportsTable ? ( + + ) : null} + {supportsLogs ? ( + + ) : null} +
+ )} {supportsGraph && showingGraph && ( diff --git a/public/app/features/explore/Graph.tsx b/public/app/features/explore/Graph.tsx index 1f07b9bb46a..bbb055067a2 100644 --- a/public/app/features/explore/Graph.tsx +++ b/public/app/features/explore/Graph.tsx @@ -169,7 +169,7 @@ export class Graph extends PureComponent { return (
- {loading &&
} + {loading &&
} {this.props.data && this.props.data.length > MAX_NUMBER_OF_TIME_SERIES && !this.state.showAllTimeSeries && ( diff --git a/public/app/features/explore/Logs.tsx b/public/app/features/explore/Logs.tsx index cc8f9be48fd..278c5ee016d 100644 --- a/public/app/features/explore/Logs.tsx +++ b/public/app/features/explore/Logs.tsx @@ -10,37 +10,33 @@ interface LogsProps { loading: boolean; } -const EXAMPLE_QUERY = '{job="default/prometheus"}'; - export default class Logs extends PureComponent { render() { - const { className = '', data } = this.props; + const { className = '', data, loading = false } = this.props; const hasData = data && data.rows && data.rows.length > 0; return (
- {hasData ? ( -
- {data.rows.map(row => ( - -
-
{row.timeLocal}
-
- -
- - ))} +
+ {loading &&
} +
+ {hasData && + data.rows.map(row => ( + +
+
{row.timeLocal}
+
+ +
+ + ))}
- ) : null} - {!hasData ? ( -
- Enter a query like {EXAMPLE_QUERY} -
- ) : null} + {!loading && !hasData && 'No data was returned.'} +
); } diff --git a/public/app/features/explore/QueryRows.tsx b/public/app/features/explore/QueryRows.tsx index 3a232cb658a..4aacdb22599 100644 --- a/public/app/features/explore/QueryRows.tsx +++ b/public/app/features/explore/QueryRows.tsx @@ -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 { }; 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 { onClickHintFix={this.onClickHintFix} onPressEnter={this.onPressEnter} onQueryChange={this.onChangeQuery} - supportsLogs={supportsLogs} />
diff --git a/public/app/plugins/datasource/logging/components/LoggingCheatSheet.tsx b/public/app/plugins/datasource/logging/components/LoggingCheatSheet.tsx new file mode 100644 index 00000000000..a7af48b6eda --- /dev/null +++ b/public/app/plugins/datasource/logging/components/LoggingCheatSheet.tsx @@ -0,0 +1,29 @@ +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) => ( +
+

Logging Cheat Sheet

+ {CHEAT_SHEET_ITEMS.map(item => ( +
+
{item.title}
+
props.onClickQuery(item.expression)}> + {item.expression} +
+
{item.label}
+
+ ))} +
+); diff --git a/public/app/plugins/datasource/logging/components/LoggingQueryField.tsx b/public/app/plugins/datasource/logging/components/LoggingQueryField.tsx new file mode 100644 index 00000000000..6bf1a38245a --- /dev/null +++ b/public/app/plugins/datasource/logging/components/LoggingQueryField.tsx @@ -0,0 +1,205 @@ +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 { + 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 ( +
+
+ + + +
+
+ + {error ?
{error}
: null} + {hint ? ( +
+ {hint.label}{' '} + {hint.fix ? ( + + {hint.fix.label} + + ) : null} +
+ ) : null} +
+
+ ); + } +} + +export default LoggingQueryField; diff --git a/public/app/plugins/datasource/logging/components/LoggingStartPage.tsx b/public/app/plugins/datasource/logging/components/LoggingStartPage.tsx new file mode 100644 index 00000000000..89262999637 --- /dev/null +++ b/public/app/plugins/datasource/logging/components/LoggingStartPage.tsx @@ -0,0 +1,60 @@ +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 { + state = { + active: 'start', + }; + + onClickTab = active => { + this.setState({ active }); + }; + + render() { + const { active } = this.state; + const customCss = ''; + + return ( +
+
+
+
+ +
+
+
+
+ {active === 'start' && } +
+
+ ); + } +} diff --git a/public/app/plugins/datasource/logging/datasource.ts b/public/app/plugins/datasource/logging/datasource.ts index 22edba5807a..2cc4e970233 100644 --- a/public/app/plugins/datasource/logging/datasource.ts +++ b/public/app/plugins/datasource/logging/datasource.ts @@ -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; diff --git a/public/app/plugins/datasource/logging/language_provider.ts b/public/app/plugins/datasource/logging/language_provider.ts new file mode 100644 index 00000000000..411a1f9439c --- /dev/null +++ b/public/app/plugins/datasource/logging/language_provider.ts @@ -0,0 +1,211 @@ +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); + } + } +} diff --git a/public/app/plugins/datasource/logging/module.ts b/public/app/plugins/datasource/logging/module.ts index 5e3ffb3282a..da00edbf40f 100644 --- a/public/app/plugins/datasource/logging/module.ts +++ b/public/app/plugins/datasource/logging/module.ts @@ -1,7 +1,15 @@ 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, +}; diff --git a/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx b/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx index b6f2c0f2e08..5af540965b6 100644 --- a/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx +++ b/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx @@ -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 { - 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
- {supportsLogs ? ( - - - - ) : ( - - - - )} + + +
[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 { diff --git a/public/sass/pages/_explore.scss b/public/sass/pages/_explore.scss index 0f8c335760a..be2083b6e5c 100644 --- a/public/sass/pages/_explore.scss +++ b/public/sass/pages/_explore.scss @@ -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 { From 037167ff07f865cce931a9a2e95baab59661a58a Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Tue, 30 Oct 2018 16:50:03 +0100 Subject: [PATCH 2/2] Fix TimePicker test by enforcing UTC on date string --- public/app/features/explore/TimePicker.test.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/public/app/features/explore/TimePicker.test.tsx b/public/app/features/explore/TimePicker.test.tsx index afe6b092901..cadcfbb668c 100644 --- a/public/app/features/explore/TimePicker.test.tsx +++ b/public/app/features/explore/TimePicker.test.tsx @@ -33,8 +33,8 @@ describe('', () => { 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(); expect(wrapper.state('fromRaw')).toBe('1970-01-01 00:00:00'); @@ -50,8 +50,8 @@ describe('', () => { 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();