mirror of
https://github.com/grafana/grafana.git
synced 2025-09-19 21:24:56 +08:00

* Make search popover be tabbed onto Co-Authored-By: Sonia Aguilar <33540275+soniaAguilarPeiron@users.noreply.github.com> * Allow tokens in rule description to be tabbed onto Co-Authored-By: Sonia Aguilar <33540275+soniaAguilarPeiron@users.noreply.github.com> --------- Co-authored-by: Sonia Aguilar <33540275+soniaAguilarPeiron@users.noreply.github.com>
154 lines
3.8 KiB
TypeScript
154 lines
3.8 KiB
TypeScript
import { css } from '@emotion/css';
|
|
import React from 'react';
|
|
|
|
import { GrafanaTheme2 } from '@grafana/data';
|
|
import { Badge, useStyles2 } from '@grafana/ui';
|
|
|
|
import { HoverCard } from './HoverCard';
|
|
import { keywords as KEYWORDS, builtinFunctions as FUNCTIONS } from './receivers/editor/language';
|
|
|
|
const VARIABLES = ['$', '.', '"'];
|
|
|
|
interface TokenizerProps {
|
|
input: string;
|
|
delimiter?: [string, string];
|
|
}
|
|
|
|
function Tokenize({ input, delimiter = ['{{', '}}'] }: TokenizerProps) {
|
|
const styles = useStyles2(getStyles);
|
|
|
|
const [open, close] = delimiter;
|
|
|
|
/**
|
|
* This RegExp uses 2 named capture groups, text that comes before the token and the token itself
|
|
*
|
|
* <before> open <token> close
|
|
* ───────── ── ─────────── ──
|
|
* Some text {{ $labels.foo }}
|
|
*/
|
|
const regex = new RegExp(`(?<before>.*?)(${open}(?<token>.*?)${close}|$)`, 'gm');
|
|
const lines = input.split('\n');
|
|
|
|
const output: React.ReactElement[] = [];
|
|
|
|
lines.forEach((line, lineIndex) => {
|
|
const matches = Array.from(line.matchAll(regex));
|
|
|
|
matches.forEach((match, matchIndex) => {
|
|
const before = match.groups?.before;
|
|
const token = match.groups?.token?.trim();
|
|
|
|
if (before) {
|
|
output.push(<span key={`${lineIndex}-${matchIndex}-before`}>{before}</span>);
|
|
}
|
|
|
|
if (token) {
|
|
const type = tokenType(token);
|
|
const description = type === TokenType.Variable ? token : '';
|
|
const tokenContent = `${open} ${token} ${close}`;
|
|
|
|
output.push(
|
|
<Token
|
|
key={`${lineIndex}-${matchIndex}-token`}
|
|
content={tokenContent}
|
|
type={type}
|
|
description={description}
|
|
/>
|
|
);
|
|
}
|
|
});
|
|
|
|
output.push(<br key={`${lineIndex}-newline`} />);
|
|
});
|
|
|
|
return <span className={styles.wrapper}>{output}</span>;
|
|
}
|
|
|
|
enum TokenType {
|
|
Variable = 'variable',
|
|
Function = 'function',
|
|
Keyword = 'keyword',
|
|
Unknown = 'unknown',
|
|
}
|
|
|
|
interface TokenProps {
|
|
content: string;
|
|
type?: TokenType;
|
|
description?: string;
|
|
}
|
|
|
|
function Token({ content, description, type }: TokenProps) {
|
|
const styles = useStyles2(getStyles);
|
|
|
|
const disableCard = Boolean(type) === false;
|
|
|
|
return (
|
|
<HoverCard
|
|
placement="top-start"
|
|
disabled={disableCard}
|
|
content={
|
|
<div className={styles.hoverTokenItem}>
|
|
<Badge tabIndex={0} text={<>{type}</>} color={'blue'} /> {description && <code>{description}</code>}
|
|
</div>
|
|
}
|
|
>
|
|
<span>
|
|
<Badge tabIndex={0} className={styles.token} text={content} color={'blue'} />
|
|
</span>
|
|
</HoverCard>
|
|
);
|
|
}
|
|
|
|
function isVariable(input: string) {
|
|
return VARIABLES.some((character) => input.startsWith(character));
|
|
}
|
|
|
|
function isKeyword(input: string) {
|
|
return KEYWORDS.some((keyword) => input.startsWith(keyword));
|
|
}
|
|
|
|
function isFunction(input: string) {
|
|
return FUNCTIONS.some((functionName) => input.startsWith(functionName));
|
|
}
|
|
|
|
function tokenType(input: string) {
|
|
let tokenType;
|
|
if (isVariable(input)) {
|
|
tokenType = TokenType.Variable;
|
|
} else if (isKeyword(input)) {
|
|
tokenType = TokenType.Keyword;
|
|
} else if (isFunction(input)) {
|
|
tokenType = TokenType.Function;
|
|
} else {
|
|
tokenType = TokenType.Unknown;
|
|
}
|
|
|
|
return tokenType;
|
|
}
|
|
|
|
const getStyles = (theme: GrafanaTheme2) => ({
|
|
wrapper: css`
|
|
white-space: pre-wrap;
|
|
`,
|
|
token: css`
|
|
cursor: default;
|
|
font-family: ${theme.typography.fontFamilyMonospace};
|
|
`,
|
|
popover: css`
|
|
border-radius: ${theme.shape.borderRadius()};
|
|
box-shadow: ${theme.shadows.z3};
|
|
background: ${theme.colors.background.primary};
|
|
border: 1px solid ${theme.colors.border.medium};
|
|
|
|
padding: ${theme.spacing(1)};
|
|
`,
|
|
hoverTokenItem: css`
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
gap: ${theme.spacing(1)};
|
|
`,
|
|
});
|
|
|
|
export { Tokenize, Token };
|