# Loki LogQL Editor

A Monaco-based editor component for **Grafana Loki LogQL** query language.

## Features

- **Log queries**: stream selectors + log pipeline (line filters, parsers, label filters)
- **Metric queries**: Range Vector Aggregations (`rate`, `count_over_time`, ...)
- **Vector Aggregation Operators**: `sum`, `avg`, `topk`, `sort`, ...
- **Syntax highlighting** via Monarch tokenizer
- **Context-aware autocompletion** with async label name/value fetching
- **Variables**: `${name}` completion (only in label value context)
- **Test suite**: 87 unit tests covering lexer, parser, locate, and completions

## Usage

```tsx
import { LokiMonacoEditor } from '@fc-components/monaco-editor';

function MyComponent() {
  const [query, setQuery] = React.useState('{job="nginx"} |= "error"');

  return (
    <LokiMonacoEditor
      value={query}
      onChange={setQuery}
      onEnter={(v) => executeQuery(v)}
      enableAutocomplete
      variables={['host', 'env']}
      fetchLabelNames={async (currentMatchers) => ['job', 'app', 'level']}
      fetchLabelValues={async (name, currentMatchers) => (name === 'job' ? ['nginx', 'api'] : [])}
    />
  );
}
```

## Props

| Prop                 | Type                                                     | Default    | Description                                                                     |
| -------------------- | -------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------- |
| `value`              | `string`                                                 | `''`       | Current query value                                                             |
| `onChange`           | `(value: string) => void`                                | —          | Called on every content change                                                  |
| `onEnter`            | `(value: string) => void`                                | —          | Called when Enter is pressed (not in suggestion widget)                         |
| `onBlur`             | `(value: string) => void`                                | —          | Called on blur                                                                  |
| `onFocus`            | `(value: string) => void`                                | —          | Called on focus                                                                 |
| `theme`              | `'light' \| 'dark'`                                      | `'light'`  | Color theme                                                                     |
| `size`               | `'small' \| 'middle' \| 'large'`                         | `'middle'` | Matches antd input sizes                                                        |
| `placeholder`        | `string`                                                 | —          | Placeholder text                                                                |
| `fontSize`           | `number`                                                 | —          | Font size in px                                                                 |
| `maxHeight`          | `number \| string`                                       | —          | Max height of editor                                                            |
| `disabled`           | `boolean`                                                | `false`    | Disable editing                                                                 |
| `readOnly`           | `boolean`                                                | `false`    | Read-only mode                                                                  |
| `enableAutocomplete` | `boolean`                                                | `true`     | Enable autocompletion                                                           |
| `variables`          | `string[]`                                               | `[]`       | Variable names for `${name}` completion (only in label value context)           |
| `fetchLabelNames`    | `(currentMatchers: LabelMatcher[]) => Promise<string[]>` | —          | Async provider for label names (receives existing matchers for context)         |
| `fetchLabelValues`   | `(name: string, currentMatchers: LabelMatcher[]) => ...` | —          | Async provider for label values (receives other matchers, excludes current one) |
| `editorDidMount`     | `(editor) => void`                                       | —          | Called after editor mounts                                                      |

### LabelMatcher

```typescript
interface LabelMatcher {
  label: string; // e.g. "job"
  operator: string; // e.g. "=", "!=", "=~", "!~"
  value: string; // e.g. "nginx" (includes surrounding quotes)
}
```

## Supported Syntax

### Stream Selector

```
{job="mysql"}
{app=~"nginx|api", method!="DELETE"}
```

### Line Filters

```
|= "error"    # Contains
!= "timeout"  # Does not contain
|~ "reg.*ex"  # Regex match
!~ "reg.*ex"  # Regex not match
```

### Parsers

```
| json              # Extract all JSON fields
| logfmt            # Extract logfmt key-value pairs
| regexp "(?P<method>\\w+)"   # Extract with regex
| pattern "<method> <path>"   # Extract with pattern
| unpack            # Unpack packed log lines
```

### Label Filters

```
| status >= 400
| level = "error" and duration > 1s
| bytes_consumed > 20MB
```

### Format & Label Operations

```
| line_format "{{.label}}"
| label_format dst=src
| drop label_name
| keep label_name
| unwrap label
| decolorize
```

### Range Vector Aggregations

```
rate({job="nginx"}[5m])
rate_counter({job="nginx"}[5m])
count_over_time({job="api"}[1h])
bytes_rate({job="nginx"}[5m])
bytes_over_time({job="api"}[1h])
sum_over_time({job="nginx"}[5m])
avg_over_time({job="nginx"}[5m])
max_over_time({job="nginx"}[5m])
min_over_time({job="nginx"}[5m])
first_over_time({job="nginx"}[5m])
last_over_time({job="nginx"}[5m])
stdvar_over_time({job="nginx"}[5m])
stddev_over_time({job="nginx"}[5m])
quantile_over_time(0.95, {job="nginx"}[5m])
absent_over_time({job="nginx"}[5m])
```

### Vector Aggregation Operators

```
sum by(label) (query)
avg by(label) (query)
min by(label) (query)
max by(label) (query)
count by(label) (query)
stddev by(label) (query)
stdvar by(label) (query)
topk(5, query)
bottomk(5, query)
sort(query)
sort_desc(query)
```

### Metric Queries

```
rate({job="nginx"}[5m])
count_over_time({job="api"}[1h])
sum(rate({app="nginx"}[5m]))
```

## Completion Behavior

| Context                    | Suggestions                                        |
| -------------------------- | -------------------------------------------------- |
| `{\|`                      | Label names (from `fetchLabelNames`)               |
| `{job=\|}` or `{job="\|"}` | Variables + label values (from `fetchLabelValues`) |
| `\|`                       | Line filter operators + pipeline stage keywords    |
| Inside `\| ...`            | Label filter operators (`>=`, `<=`, `=`, ...)      |
| Inside `(...)`             | Metric & aggregation functions                     |
| Inside `[...]`             | (none — range values only)                         |
| Root level (empty editor)  | Metric & aggregation functions + stream selector   |

- **Variables** (`${host}` etc.) only appear in label value context (`IN_SELECTOR_VALUE`)
- **Label names** are inserted as plain text (user types `=` to trigger value completion)
- **Values** are auto-wrapped in quotes when outside a string, inserted raw when inside
- **Functions** insert just the function name (no template/snippet)

## Architecture

```
src/loki/
├── index.tsx                    ← React component (LokiMonacoEditor)
├── loki.ts                      ← Monaco language configuration
├── types.ts                     ← Props & types (LabelMatcher, LokiEditorProps)
├── README.md                    ← This file
├── monarch/
│   └── buildLanguage.ts         ← Monarch tokenizer (syntax highlighting)
├── parser/
│   ├── index.ts                 ← Re-exports
│   ├── types.ts                 ← Token, AST (LokiQuery, PipelineStage), Situation types
│   ├── lexer.ts                 ← Tokenizer (handles Duration, Bytes, comments, strings)
│   ├── parser.ts                ← Recursive descent parser (selector → pipeline → metric)
│   └── locate.ts                ← Cursor position analyzer (determines completion context)
├── completion/
│   ├── completions.ts           ← Completion item builder (context-aware suggestions)
│   └── getCompletionProvider.ts ← Monaco CompletionItemProvider with async data
└── __tests__/
    ├── lexer.test.ts            ← 18 tests
    ├── parser.test.ts           ← 18 tests
    ├── locate.test.ts           ← 24 tests
    └── completions.test.ts      ← 27 tests
```
