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