GLQL: Add showTrends support (for stats and tables)
Add `showTrends` configuration support to GLQL, enabling percentage change indicators in both stat and table displays.
> **Note:** This feature is currently blocked by a backend dependency (gitlab#601617) to support lagged/prior-period metrics. The syntax below reflects the resolved parameterised field design from glql#130.
This is a **GLQL-level visualization feature** that works across multiple display types (stat, table) and any data type once their aggregation engines support time-series data.
**Part 6 of 6 visualization types** needed for SDLC dashboard migration:
1. Tables (gitlab#592262) - 18.11 GA ✅
2. Stats (gitlab#592780) - Post-18.11
3. Sparklines (gitlab#592781) - Post-18.11
4. Bar Charts (gitlab#592782) - Post-18.11
5. Column Charts (gitlab#592784) - Post-18.11
6. Area Charts (gitlab#592783) - Post-18.11
7. **showTrends (this issue)** - Post-18.11 - Works with stats and tables
**Aligned with research issue gitlab#589575** for GLQL visualization syntax.
**SDLC Dashboard use cases (CodeSuggestion):**
- Single stat showing total suggestions with % change vs previous period
- Table showing acceptance rate by language with % change column
- Any metric display that benefits from trend comparison
## Overview
`showTrends` is a boolean configuration option that adds percentage change indicators:
- **In stats:** Shows "▲ 12.5%" or "▼ 8.3%" below the main value
- **In tables:** Adds a "Change %" column showing trend for each row
Frontend calculates the percentage change by comparing the last two periods in the time-series data.
## Acceptance Criteria
### General
- [ ] `displayConfig.showTrends: true` is supported in both `stat` and `table` display types
- [ ] Requires time dimension with a granularity parameter
- [ ] Frontend calculates % change from time-series data (comparing last two periods)
- [ ] Up arrow (▲) shown for increases, down arrow (▼) for decreases
- [ ] Feature flag `glql_trends_display_type` gates functionality using a `gitlab_com_derisk` flag
- [ ] When flag is disabled but showTrends requested, returns error
- [ ] Works with any GLQL data type that has time-series data
- [ ] No JavaScript console errors
### For Stat Display
- [ ] Trend shows below main value as "▲ 12.5%" or "▼ 8.3%"
- [ ] Query validation: Must have 1 metric and 1 time dimension
- [ ] Frontend extracts last two data points from time-series
- [ ] Calculates: `(current - previous) / previous * 100`
### For Table Display
- [ ] Adds implicit "Change %" column after all metrics
- [ ] Shows trend for each row comparing first period to last period
- [ ] Query validation: Must have time dimension in aggregate
- [ ] Each row shows "▲ X.X%" or "▼ X.X%" based on that row's trend
### Testing
- [ ] Frontend tests cover showTrends in stat display
- [ ] Frontend tests cover showTrends in table display
- [ ] Error handling when showTrends used without time dimension
## Example GLQL Queries
### Stat with showTrends
```yaml
type: CodeSuggestion
mode: analytics
query: timestamp >= -60d
display: stat
displayConfig:
showTrends: true
dimensions: timestamp(granularity=monthly)
metrics: totalCount as 'Total Suggestions'
```
**Expected output:**
```
┌─────────────────────┐
│ Total Suggestions │
│ │
│ 1,234 │
│ ▲ 12.5% │
└─────────────────────┘
```
**Frontend calculation logic:**
- Query returns: `[{month: 1, value: 1100}, {month: 2, value: 1234}]`
- Frontend shows last value: `1,234`
- Frontend calculates: `(1234 - 1100) / 1100 * 100 = 12.18%`
- Displays: `▲ 12.5%` (rounded)
### Table with showTrends
```yaml
type: CodeSuggestion
mode: analytics
query: timestamp >= -60d
display: table
displayConfig:
showTrends: true
dimensions: language as 'Language', timestamp(granularity=monthly)
metrics: totalCount as 'Total', acceptanceRate as 'Acceptance Rate'
sort: totalCount desc
```
**Expected output:**
```
| Language | Total | Acceptance Rate | Change % |
| ---------- | ----- | --------------- | -------- |
| Ruby | 1,234 | 77.6% | ▲ 12.5% |
| JavaScript | 2,456 | 68.4% | ▼ 3.2% |
| Python | 987 | 81.2% | ▲ 8.1% |
```
**Frontend calculation logic:**
- For each row (e.g., Ruby):
- Extract time-series: `[{month: 1, total: 1100}, {month: 2, total: 1234}]`
- Compare first to last: `(1234 - 1100) / 1100 * 100 = 12.18%`
- Display: `▲ 12.5%`
### Invalid Example
```yaml
type: CodeSuggestion
mode: analytics
display: stat
displayConfig:
showTrends: true
metrics: totalCount as 'Total'
# Error: "showTrends requires a time dimension with a granularity parameter"
```
## Implementation Notes
*Files to modify:*
- `app/assets/javascripts/glql/` - Add showTrends renderer for both stat and table
*Cross-display type feature:*
- This is the first GLQL feature that works across multiple display types
- Shared logic should be extracted to common utility
- Both stat and table renderers will use the same calculation logic
*Frontend calculation (shared):*
```javascript
function calculateTrend(timeSeriesData) {
if (timeSeriesData.length < 2) return null;
const previous = timeSeriesData[timeSeriesData.length - 2].value;
const current = timeSeriesData[timeSeriesData.length - 1].value;
const percentChange = ((current - previous) / previous) * 100;
const arrow = percentChange >= 0 ? '▲' : '▼';
const formatted = Math.abs(percentChange).toFixed(1);
return `${arrow} ${formatted}%`;
}
```
*Stat-specific rendering:*
- Trend appears below the main value
- Centered alignment
- Slightly smaller font than main value
- Green color for ▲, red color for ▼
*Table-specific rendering:*
- Adds "Change %" column after all metrics
- Each row calculates trend independently
- Uses same color scheme (green ▲, red ▼)
- Column ordering: dimensions, metrics, sparklines, trends (FIFO)
*Time dimension requirement:*
- Must include a time dimension with a granularity parameter (e.g., `timestamp(granularity=daily)`)
- Examples: `timestamp(granularity=monthly)`, `timestamp(granularity=weekly)`, `timestamp(granularity=daily)`
- Frontend identifies time-series data by this dimension
*Validation:*
- Error if `showTrends: true` but no time dimension present
- Error message: "showTrends requires a time dimension with a granularity parameter"
*Feature flag behavior:*
- `glql_trends_display_type` gates this functionality
- When disabled and showTrends is requested, return error message
*Comparison logic:*
- Always compares **last two periods** in the time-series
- User controls the comparison by choosing the time range in the query
- Example: Query with `-60d` and `timestamp(granularity=monthly)` compares last 2 months
- Example: Query with `-14d` and `timestamp(granularity=daily)` compares last 2 days
*Future enhancements (document but don't implement):*
1. **Custom comparison periods:**
```yaml
displayConfig:
showTrends: true
trendComparison: -7d # Compare to specific period instead of previous
```
2. **Configurable positive direction:**
```yaml
displayConfig:
showTrends: true
positiveDirection: down # For error rates where down is good
```
3. **Multiple trend columns in tables:**
```yaml
displayConfig:
trends:
- column: '7d Change'
compareWith: -7d
- column: '30d Change'
compareWith: -30d
```
## Related Issues
- Blocked by: gitlab#601617 (Backend lagged metrics support)
- Depends on: glql#130 (Parameterised field syntax)
- Depends on: gitlab#592261 (GLQL Parser support)
- Works with: gitlab#592780 (Stat display), gitlab#592262 (Table display)
- Epic: gitlab-org&21212 (CodeSuggestion aggregations)
- Related: gitlab#592781 (Sparklines), gitlab#592782 (Bar Charts), gitlab#592783 (Area Charts), gitlab#592784 (Column Charts)
- Research: gitlab#589575 (GLQL visualization requirements and syntax)
- Note: This replaces the original "change indicators" concept, now as a cross-display feature
issue