Skip to content

Commit

Permalink
Allow formatting PromQL expressions in the UI (#11039)
Browse files Browse the repository at this point in the history
* Allow formatting PromQL expressions in the UI

Signed-off-by: Julius Volz <julius.volz@gmail.com>

* Improve error handling, also catch HTTP errors

Signed-off-by: Julius Volz <julius.volz@gmail.com>

* Remove now-unneeded async property

Signed-off-by: Julius Volz <julius.volz@gmail.com>

* Disable format button when already formatted

Signed-off-by: Julius Volz <julius.volz@gmail.com>

* Disable format button when there are linter errors

Signed-off-by: Julius Volz <julius.volz@gmail.com>

* Remove disabling of format button again for linter errors

Signed-off-by: Julius Volz <julius.volz@gmail.com>
  • Loading branch information
juliusv committed Jul 21, 2022
1 parent b57deb6 commit b8af463
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 6 deletions.
73 changes: 69 additions & 4 deletions web/ui/react-app/src/pages/graph/ExpressionInput.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { FC, useState, useEffect, useRef } from 'react';
import { Button, InputGroup, InputGroupAddon, InputGroupText } from 'reactstrap';
import { Alert, Button, InputGroup, InputGroupAddon, InputGroupText } from 'reactstrap';

import { EditorView, highlightSpecialChars, keymap, ViewUpdate, placeholder } from '@codemirror/view';
import { EditorState, Prec, Compartment } from '@codemirror/state';
Expand All @@ -18,12 +18,13 @@ import {
import { baseTheme, lightTheme, darkTheme, promqlHighlighter } from './CMTheme';

import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSearch, faSpinner, faGlobeEurope } from '@fortawesome/free-solid-svg-icons';
import { faSearch, faSpinner, faGlobeEurope, faIndent, faCheck } from '@fortawesome/free-solid-svg-icons';
import MetricsExplorer from './MetricsExplorer';
import { usePathPrefix } from '../../contexts/PathPrefixContext';
import { useTheme } from '../../contexts/ThemeContext';
import { CompleteStrategy, PromQLExtension } from '@prometheus-io/codemirror-promql';
import { newCompleteStrategy } from '@prometheus-io/codemirror-promql/dist/esm/complete';
import { API_PATH } from '../../constants/constants';

const promqlExtension = new PromQLExtension();

Expand Down Expand Up @@ -98,6 +99,10 @@ const ExpressionInput: FC<CMExpressionInputProps> = ({
const pathPrefix = usePathPrefix();
const { theme } = useTheme();

const [formatError, setFormatError] = useState<string | null>(null);
const [isFormatting, setIsFormatting] = useState<boolean>(false);
const [exprFormatted, setExprFormatted] = useState<boolean>(false);

// (Re)initialize editor based on settings / setting changes.
useEffect(() => {
// Build the dynamic part of the config.
Expand Down Expand Up @@ -169,7 +174,10 @@ const ExpressionInput: FC<CMExpressionInputProps> = ({
])
),
EditorView.updateListener.of((update: ViewUpdate): void => {
onExpressionChange(update.state.doc.toString());
if (update.docChanged) {
onExpressionChange(update.state.doc.toString());
setExprFormatted(false);
}
}),
],
});
Expand Down Expand Up @@ -209,6 +217,47 @@ const ExpressionInput: FC<CMExpressionInputProps> = ({
);
};

const formatExpression = () => {
setFormatError(null);
setIsFormatting(true);

fetch(
`${pathPrefix}/${API_PATH}/format_query?${new URLSearchParams({
query: value,
})}`,
{
cache: 'no-store',
credentials: 'same-origin',
}
)
.then((resp) => {
if (!resp.ok && resp.status !== 400) {
throw new Error(`format HTTP request failed: ${resp.statusText}`);
}

return resp.json();
})
.then((json) => {
if (json.status !== 'success') {
throw new Error(json.error || 'invalid response JSON');
}

const view = viewRef.current;
if (view === null) {
return;
}

view.dispatch(view.state.update({ changes: { from: 0, to: view.state.doc.length, insert: json.data } }));
setExprFormatted(true);
})
.catch((err) => {
setFormatError(err.message);
})
.finally(() => {
setIsFormatting(false);
});
};

return (
<>
<InputGroup className="expression-input">
Expand All @@ -220,7 +269,21 @@ const ExpressionInput: FC<CMExpressionInputProps> = ({
<div ref={containerRef} className="cm-expression-input" />
<InputGroupAddon addonType="append">
<Button
className="metrics-explorer-btn"
className="expression-input-action-btn"
title={isFormatting ? 'Formatting expression' : exprFormatted ? 'Expression formatted' : 'Format expression'}
onClick={formatExpression}
disabled={isFormatting || exprFormatted}
>
{isFormatting ? (
<FontAwesomeIcon icon={faSpinner} spin />
) : exprFormatted ? (
<FontAwesomeIcon icon={faCheck} />
) : (
<FontAwesomeIcon icon={faIndent} />
)}
</Button>
<Button
className="expression-input-action-btn"
title="Open metrics explorer"
onClick={() => setShowMetricsExplorer(true)}
>
Expand All @@ -232,6 +295,8 @@ const ExpressionInput: FC<CMExpressionInputProps> = ({
</InputGroupAddon>
</InputGroup>

{formatError && <Alert color="danger">Error formatting expression: {formatError}</Alert>}

<MetricsExplorer
show={showMetricsExplorer}
updateShow={setShowMetricsExplorer}
Expand Down
4 changes: 2 additions & 2 deletions web/ui/react-app/src/themes/_shared.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
Example:
$foo = '#000';
2. Add a style rule in _shared.scss (this file) that uses new variable.
Example:
Expand Down Expand Up @@ -58,7 +58,7 @@
.metrics-explorer .metric:hover {
background: $metrics-explorer-bg;
}
button.metrics-explorer-btn {
button.expression-input-action-btn {
color: $input-group-addon-color;
background-color: $input-group-addon-bg;
border: $input-border-width solid $input-group-addon-border-color;
Expand Down

0 comments on commit b8af463

Please sign in to comment.