93c14c52da
* wip: trying to understand how to get the ds info from migrator * add datasource info provider * Use DS service to fetch DS data * add more tests cases to match with migrator cases * Add snapshots * Non-existing DS * Add different DS for snapshots * fix import * Fix tests: guard against double initialization * don't use full datasource package in test * min version should be 35 * fix test * fix conversion test * Dashboards: Support schemaVersion v35 migration in backend * Dashboards: Support schemaVersion v34 migration in backend * Dashboards: Support schemaVersion v33 migration in backend * Apply suggestions from code review Co-authored-by: Stephanie Hingtgen <stephanie.hingtgen@grafana.com> * Apply feedback * Remove unused parameters * Refactor to follow Go patterns * Update logic * Only write final migration result as output * Compare backend and frontend results * Improve snapshots to cover all possible use cases * Linter * wip make it consistent v33 * apply feedback * Return default when the ref cannot be found * Update apps/dashboard/pkg/migration/schemaversion/v33.go Co-authored-by: Stephanie Hingtgen <stephanie.hingtgen@grafana.com> * apply feedback * Use same mocks backend/frontend * restore migrations * update snapshots * Adapt migration tests to use min versions * Ensure v40-v41 works * Ensure v39-v40 works * Simplify the naming of the files * adjust jest to new input convention * Ensure every migration v36-v41 works * Improve v38 naming * Ensure v36 migrates correctly * Skip v36 refs migrations on rows * Treat rows as frontend and ensure same results for v36 * Ensure v34 runs with the same logic than the frontend * Leave empty stadistics as valid option * ensure v33 is working as the frontend * Update tests * Undo frontend changes for legend handling * Remove filtering by version in the frontend * linter * Clean up v33 input JSON --------- Co-authored-by: Todd Treece <360020+toddtreece@users.noreply.github.com> Co-authored-by: Haris Rozajac <haris.rozajac12@gmail.com> Co-authored-by: Stephanie Hingtgen <stephanie.hingtgen@grafana.com>
118 lines
3.4 KiB
Go
118 lines
3.4 KiB
Go
package schemaversion
|
|
|
|
// V39 migrates timeSeriesTable transformation configuration to support extensible per-query options.
|
|
//
|
|
// This migration addresses limitations in the original timeSeriesTable transformation design where
|
|
// each query could only be configured with a single statistic function. The original refIdToStat
|
|
// format was too restrictive for evolving use cases that require multiple configuration parameters
|
|
// per query, such as custom formatting, aggregation methods, or display preferences.
|
|
//
|
|
// The migration works by:
|
|
// 1. Locating panels with timeSeriesTable transformations (including nested panels in rows)
|
|
// 2. Extracting the existing refIdToStat mapping from transformation options
|
|
// 3. Converting each refId-statistic pair to the new nested object structure
|
|
// 4. Preserving the statistic function while enabling future option expansion
|
|
// 5. Skipping transformations that lack valid refIdToStat configuration
|
|
//
|
|
// This restructuring enables future enhancements while maintaining backward compatibility:
|
|
// - Additional per-query options can be added without breaking existing configurations
|
|
// - The stat property preserves current functionality exactly as before
|
|
// - New features like custom labels, formats, or calculations can be added seamlessly
|
|
// - The structure scales better for complex multi-query transformations
|
|
//
|
|
// Example transformation:
|
|
//
|
|
// Before migration:
|
|
//
|
|
// transformations: [{
|
|
// id: "timeSeriesTable",
|
|
// options: {
|
|
// refIdToStat: {
|
|
// "A": "mean",
|
|
// "B": "max",
|
|
// "C": "last"
|
|
// }
|
|
// }
|
|
// }]
|
|
//
|
|
// After migration:
|
|
//
|
|
// transformations: [{
|
|
// id: "timeSeriesTable",
|
|
// options: {
|
|
// "A": { stat: "mean" },
|
|
// "B": { stat: "max" },
|
|
// "C": { stat: "last" }
|
|
// }
|
|
// }]
|
|
func V39(dashboard map[string]interface{}) error {
|
|
dashboard["schemaVersion"] = int(39)
|
|
|
|
panels, ok := dashboard["panels"].([]interface{})
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
// Process all panels, including nested ones
|
|
processPanelsV39(panels)
|
|
|
|
return nil
|
|
}
|
|
|
|
// processPanelsV39 recursively processes panels, including nested panels within rows
|
|
func processPanelsV39(panels []interface{}) {
|
|
for _, panel := range panels {
|
|
p, ok := panel.(map[string]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
// Process nested panels if this is a row panel
|
|
if p["type"] == "row" {
|
|
if nestedPanels, ok := p["panels"].([]interface{}); ok {
|
|
processPanelsV39(nestedPanels)
|
|
}
|
|
continue
|
|
}
|
|
|
|
transformations, ok := p["transformations"].([]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
for _, transformation := range transformations {
|
|
t, ok := transformation.(map[string]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
// If we run into a timeSeriesTable transformation
|
|
// and it doesn't have undefined options then we migrate
|
|
if t["id"] != "timeSeriesTable" {
|
|
continue
|
|
}
|
|
|
|
options, ok := t["options"].(map[string]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
refIdStats, ok := options["refIdToStat"].(map[string]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
// For each {refIdtoStat} record which maps refId to a statistic
|
|
// we add that to the stat property of the new
|
|
// RefIdTransformerOptions interface which includes multiple settings
|
|
transformationOptions := make(map[string]interface{})
|
|
for refId, stat := range refIdStats {
|
|
transformationOptions[refId] = map[string]interface{}{"stat": stat}
|
|
}
|
|
|
|
// Update the options
|
|
t["options"] = transformationOptions
|
|
}
|
|
}
|
|
}
|