Commit 1d60c45a by David Committed by GitHub

Merge pull request #13663 from miqh/fix/label-value-suggestions

Fix typeahead behaviour for QueryField
parents da89c27c 22e0ff8b
...@@ -96,11 +96,14 @@ describe('PromQueryField typeahead handling', () => { ...@@ -96,11 +96,14 @@ describe('PromQueryField typeahead handling', () => {
it('returns label suggestions on label context but leaves out labels that already exist', () => { it('returns label suggestions on label context but leaves out labels that already exist', () => {
const instance = shallow( const instance = shallow(
<PromQueryField {...defaultProps} labelKeys={{ '{job="foo"}': ['bar', 'job'] }} /> <PromQueryField
{...defaultProps}
labelKeys={{ '{job1="foo",job2!="foo",job3=~"foo"}': ['bar', 'job1', 'job2', 'job3'] }}
/>
).instance() as PromQueryField; ).instance() as PromQueryField;
const value = Plain.deserialize('{job="foo",}'); const value = Plain.deserialize('{job1="foo",job2!="foo",job3=~"foo",}');
const range = value.selection.merge({ const range = value.selection.merge({
anchorOffset: 11, anchorOffset: 36,
}); });
const valueWithSelection = value.change().select(range).value; const valueWithSelection = value.change().select(range).value;
const result = instance.getTypeahead({ const result = instance.getTypeahead({
...@@ -113,6 +116,33 @@ describe('PromQueryField typeahead handling', () => { ...@@ -113,6 +116,33 @@ describe('PromQueryField typeahead handling', () => {
expect(result.suggestions).toEqual([{ items: [{ label: 'bar' }], label: 'Labels' }]); expect(result.suggestions).toEqual([{ items: [{ label: 'bar' }], label: 'Labels' }]);
}); });
it('returns label value suggestions inside a label value context after a negated matching operator', () => {
const instance = shallow(
<PromQueryField
{...defaultProps}
labelKeys={{ '{}': ['label'] }}
labelValues={{ '{}': { label: ['a', 'b', 'c'] } }}
/>
).instance() as PromQueryField;
const value = Plain.deserialize('{label!=}');
const range = value.selection.merge({ anchorOffset: 8 });
const valueWithSelection = value.change().select(range).value;
const result = instance.getTypeahead({
text: '!=',
prefix: '',
wrapperClasses: ['context-labels'],
labelKey: 'label',
value: valueWithSelection,
});
expect(result.context).toBe('context-label-values');
expect(result.suggestions).toEqual([
{
items: [{ label: 'a' }, { label: 'b' }, { label: 'c' }],
label: 'Label values for "label"',
},
]);
});
it('returns a refresher on label context and unavailable metric', () => { it('returns a refresher on label context and unavailable metric', () => {
const instance = shallow( const instance = shallow(
<PromQueryField {...defaultProps} labelKeys={{ '{__name__="foo"}': ['bar'] }} /> <PromQueryField {...defaultProps} labelKeys={{ '{__name__="foo"}': ['bar'] }} />
......
...@@ -111,7 +111,7 @@ export function willApplySuggestion( ...@@ -111,7 +111,7 @@ export function willApplySuggestion(
case 'context-label-values': { case 'context-label-values': {
// Always add quotes and remove existing ones instead // Always add quotes and remove existing ones instead
if (!(typeaheadText.startsWith('="') || typeaheadText.startsWith('"'))) { if (!typeaheadText.match(/^(!?=~?"|")/)) {
suggestion = `"${suggestion}`; suggestion = `"${suggestion}`;
} }
if (getNextCharacter() !== '"') { if (getNextCharacter() !== '"') {
...@@ -421,7 +421,7 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF ...@@ -421,7 +421,7 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
const containsMetric = selector.indexOf('__name__=') > -1; const containsMetric = selector.indexOf('__name__=') > -1;
const existingKeys = parsedSelector ? parsedSelector.labelKeys : []; const existingKeys = parsedSelector ? parsedSelector.labelKeys : [];
if ((text && text.startsWith('=')) || _.includes(wrapperClasses, 'attr-value')) { if ((text && text.match(/^!?=~?/)) || _.includes(wrapperClasses, 'attr-value')) {
// Label values // Label values
if (labelKey && this.state.labelValues[selector] && this.state.labelValues[selector][labelKey]) { if (labelKey && this.state.labelValues[selector] && this.state.labelValues[selector][labelKey]) {
const labelValues = this.state.labelValues[selector][labelKey]; const labelValues = this.state.labelValues[selector][labelKey];
......
...@@ -228,7 +228,13 @@ class QueryField extends React.PureComponent<TypeaheadFieldProps, TypeaheadField ...@@ -228,7 +228,13 @@ class QueryField extends React.PureComponent<TypeaheadFieldProps, TypeaheadField
const offset = range.startOffset; const offset = range.startOffset;
const text = selection.anchorNode.textContent; const text = selection.anchorNode.textContent;
let prefix = text.substr(0, offset); let prefix = text.substr(0, offset);
if (cleanText) {
// Label values could have valid characters erased if `cleanText()` is
// blindly applied, which would undesirably interfere with suggestions
const labelValueMatch = prefix.match(/(?:!?=~?"?|")(.*)/);
if (labelValueMatch) {
prefix = labelValueMatch[1];
} else if (cleanText) {
prefix = cleanText(prefix); prefix = cleanText(prefix);
} }
......
...@@ -28,7 +28,7 @@ export const cleanText = s => s.replace(/[{}[\]="(),!~+\-*/^%]/g, '').trim(); ...@@ -28,7 +28,7 @@ export const cleanText = s => s.replace(/[{}[\]="(),!~+\-*/^%]/g, '').trim();
// const cleanSelectorRegexp = /\{(\w+="[^"\n]*?")(,\w+="[^"\n]*?")*\}/; // const cleanSelectorRegexp = /\{(\w+="[^"\n]*?")(,\w+="[^"\n]*?")*\}/;
const selectorRegexp = /\{[^}]*?\}/; const selectorRegexp = /\{[^}]*?\}/;
const labelRegexp = /\b\w+="[^"\n]*?"/g; const labelRegexp = /\b(\w+)(!?=~?)("[^"\n]*?")/g;
export function parseSelector(query: string, cursorOffset = 1): { labelKeys: any[]; selector: string } { export function parseSelector(query: string, cursorOffset = 1): { labelKeys: any[]; selector: string } {
if (!query.match(selectorRegexp)) { if (!query.match(selectorRegexp)) {
// Special matcher for metrics // Special matcher for metrics
...@@ -66,11 +66,8 @@ export function parseSelector(query: string, cursorOffset = 1): { labelKeys: any ...@@ -66,11 +66,8 @@ export function parseSelector(query: string, cursorOffset = 1): { labelKeys: any
// Extract clean labels to form clean selector, incomplete labels are dropped // Extract clean labels to form clean selector, incomplete labels are dropped
const selector = query.slice(prefixOpen, suffixClose); const selector = query.slice(prefixOpen, suffixClose);
const labels = {}; const labels = {};
selector.replace(labelRegexp, match => { selector.replace(labelRegexp, (_, key, operator, value) => {
const delimiterIndex = match.indexOf('='); labels[key] = { value, operator };
const key = match.slice(0, delimiterIndex);
const value = match.slice(delimiterIndex + 1, match.length);
labels[key] = value;
return ''; return '';
}); });
...@@ -78,12 +75,12 @@ export function parseSelector(query: string, cursorOffset = 1): { labelKeys: any ...@@ -78,12 +75,12 @@ export function parseSelector(query: string, cursorOffset = 1): { labelKeys: any
const metricPrefix = query.slice(0, prefixOpen); const metricPrefix = query.slice(0, prefixOpen);
const metricMatch = metricPrefix.match(/[A-Za-z:][\w:]*$/); const metricMatch = metricPrefix.match(/[A-Za-z:][\w:]*$/);
if (metricMatch) { if (metricMatch) {
labels['__name__'] = `"${metricMatch[0]}"`; labels['__name__'] = { value: `"${metricMatch[0]}"`, operator: '=' };
} }
// Build sorted selector // Build sorted selector
const labelKeys = Object.keys(labels).sort(); const labelKeys = Object.keys(labels).sort();
const cleanSelector = labelKeys.map(key => `${key}=${labels[key]}`).join(','); const cleanSelector = labelKeys.map(key => `${key}${labels[key].operator}${labels[key].value}`).join(',');
const selectorString = ['{', cleanSelector, '}'].join(''); const selectorString = ['{', cleanSelector, '}'].join('');
......
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