From bf6564169e18ee85386c1beabeff6abf4bf1b9d6 Mon Sep 17 00:00:00 2001 From: Sanyam Singhal Date: Thu, 9 Jan 2025 06:00:02 +0000 Subject: [PATCH 01/11] Implement Migration Complexity Explainability providing the summary and rationalte for complexity reported --- yb-voyager/cmd/assessMigrationCommand.go | 14 ++ yb-voyager/cmd/common.go | 17 +- yb-voyager/cmd/migration_complexity.go | 175 ++++++++++++++++-- .../migration_assessment_report.template | 1 + 4 files changed, 179 insertions(+), 28 deletions(-) diff --git a/yb-voyager/cmd/assessMigrationCommand.go b/yb-voyager/cmd/assessMigrationCommand.go index 927aba2b6..678f6322d 100644 --- a/yb-voyager/cmd/assessMigrationCommand.go +++ b/yb-voyager/cmd/assessMigrationCommand.go @@ -1602,6 +1602,14 @@ func postProcessingOfAssessmentReport() { func generateAssessmentReportJson(reportDir string) error { jsonReportFilePath := filepath.Join(reportDir, fmt.Sprintf("%s%s", ASSESSMENT_FILE_NAME, JSON_EXTENSION)) log.Infof("writing assessment report to file: %s", jsonReportFilePath) + + var err error + assessmentReport.MigrationComplexityExplainability, err = buildMigrationComplexityExplainability(assessmentReport, "text") + if err != nil { + utils.PrintAndLog("ERROR: unable to build migration complexity explainability for json report: %v", err) + } + log.Info(assessmentReport.MigrationComplexityExplainability) + strReport, err := json.MarshalIndent(assessmentReport, "", "\t") if err != nil { return fmt.Errorf("failed to marshal the assessment report: %w", err) @@ -1620,6 +1628,12 @@ func generateAssessmentReportHtml(reportDir string) error { htmlReportFilePath := filepath.Join(reportDir, fmt.Sprintf("%s%s", ASSESSMENT_FILE_NAME, HTML_EXTENSION)) log.Infof("writing assessment report to file: %s", htmlReportFilePath) + var err error + assessmentReport.MigrationComplexityExplainability, err = buildMigrationComplexityExplainability(assessmentReport, "html") + if err != nil { + utils.PrintAndLog("ERROR: unable to build migration complexity explainability for html report: %v", err) + } + file, err := os.Create(htmlReportFilePath) if err != nil { return fmt.Errorf("failed to create file for %q: %w", filepath.Base(htmlReportFilePath), err) diff --git a/yb-voyager/cmd/common.go b/yb-voyager/cmd/common.go index cc55f737a..cb1ab995f 100644 --- a/yb-voyager/cmd/common.go +++ b/yb-voyager/cmd/common.go @@ -1051,14 +1051,15 @@ func storeTableListInMSR(tableList []sqlname.NameTuple) error { // TODO: consider merging all unsupported field with single AssessmentReport struct member as AssessmentIssue type AssessmentReport struct { - VoyagerVersion string `json:"VoyagerVersion"` - TargetDBVersion *ybversion.YBVersion `json:"TargetDBVersion"` - MigrationComplexity string `json:"MigrationComplexity"` - SchemaSummary utils.SchemaSummary `json:"SchemaSummary"` - Sizing *migassessment.SizingAssessmentReport `json:"Sizing"` - Issues []AssessmentIssue `json:"-"` // disabled in reports till corresponding UI changes are done(json and html reports) - TableIndexStats *[]migassessment.TableIndexStats `json:"TableIndexStats"` - Notes []string `json:"Notes"` + VoyagerVersion string `json:"VoyagerVersion"` + TargetDBVersion *ybversion.YBVersion `json:"TargetDBVersion"` + MigrationComplexity string `json:"MigrationComplexity"` + MigrationComplexityExplainability string `json:"MigrationComplexityExplainability"` + SchemaSummary utils.SchemaSummary `json:"SchemaSummary"` + Sizing *migassessment.SizingAssessmentReport `json:"Sizing"` + Issues []AssessmentIssue `json:"-"` // disabled in reports till corresponding UI changes are done(json and html reports) + TableIndexStats *[]migassessment.TableIndexStats `json:"TableIndexStats"` + Notes []string `json:"Notes"` // fields going to be deprecated UnsupportedDataTypes []utils.TableColumnsDataTypes `json:"UnsupportedDataTypes"` diff --git a/yb-voyager/cmd/migration_complexity.go b/yb-voyager/cmd/migration_complexity.go index ae0b37975..3e6967d4e 100644 --- a/yb-voyager/cmd/migration_complexity.go +++ b/yb-voyager/cmd/migration_complexity.go @@ -16,12 +16,14 @@ limitations under the License. package cmd import ( + "bytes" "encoding/csv" "fmt" "math" "os" "path/filepath" "strings" + "text/template" "github.com/samber/lo" log "github.com/sirupsen/logrus" @@ -33,15 +35,16 @@ import ( const NOT_AVAILABLE = "NOT AVAILABLE" var ( - LEVEL_1_MEDIUM_THRESHOLD = 20 - LEVEL_1_HIGH_THRESHOLD = math.MaxInt32 - LEVEL_2_MEDIUM_THRESHOLD = 10 - LEVEL_2_HIGH_THRESHOLD = 100 - LEVEL_3_MEDIUM_THRESHOLD = 0 - LEVEL_3_HIGH_THRESHOLD = 4 + LEVEL_1_MEDIUM_THRESHOLD = 20 + LEVEL_1_HIGH_THRESHOLD = math.MaxInt32 + LEVEL_2_MEDIUM_THRESHOLD = 10 + LEVEL_2_HIGH_THRESHOLD = 100 + LEVEL_3_MEDIUM_THRESHOLD = 0 + LEVEL_3_HIGH_THRESHOLD = 4 + migrationComplexityRationale string ) -// Migration complexity calculation from the conversion issues +// Migration complexity calculation based on the detected assessment issues func calculateMigrationComplexity(sourceDBType string, schemaDirectory string, assessmentReport AssessmentReport) string { if sourceDBType != ORACLE && sourceDBType != POSTGRESQL { return NOT_AVAILABLE @@ -64,30 +67,37 @@ func calculateMigrationComplexity(sourceDBType string, schemaDirectory string, a } func calculateMigrationComplexityForPG(assessmentReport AssessmentReport) string { + if assessmentReport.MigrationComplexity != "" { + return assessmentReport.MigrationComplexity + } + counts := lo.CountValuesBy(assessmentReport.Issues, func(issue AssessmentIssue) string { return issue.Impact }) + l1IssueCount := counts[constants.IMPACT_LEVEL_1] + l2IssueCount := counts[constants.IMPACT_LEVEL_2] + l3IssueCount := counts[constants.IMPACT_LEVEL_3] - level1IssueCount := counts[constants.IMPACT_LEVEL_1] - level2IssueCount := counts[constants.IMPACT_LEVEL_2] - level3IssueCount := counts[constants.IMPACT_LEVEL_3] + log.Infof("issue counts: level-1=%d, level-2=%d, level-3=%d\n", l1IssueCount, l2IssueCount, l3IssueCount) - utils.PrintAndLog("issue counts: level-1=%d, level-2=%d, level-3=%d\n", level1IssueCount, level2IssueCount, level3IssueCount) // Determine complexity for each level - comp1 := getComplexityForLevel(constants.IMPACT_LEVEL_1, level1IssueCount) - comp2 := getComplexityForLevel(constants.IMPACT_LEVEL_2, level2IssueCount) - comp3 := getComplexityForLevel(constants.IMPACT_LEVEL_3, level3IssueCount) + comp1 := getComplexityForLevel(constants.IMPACT_LEVEL_1, l1IssueCount) + comp2 := getComplexityForLevel(constants.IMPACT_LEVEL_2, l2IssueCount) + comp3 := getComplexityForLevel(constants.IMPACT_LEVEL_3, l3IssueCount) complexities := []string{comp1, comp2, comp3} + log.Infof("complexities according to each level: %v", complexities) + finalComplexity := constants.MIGRATION_COMPLEXITY_LOW // If ANY level is HIGH => final is HIGH if slices.Contains(complexities, constants.MIGRATION_COMPLEXITY_HIGH) { - return constants.MIGRATION_COMPLEXITY_HIGH + finalComplexity = constants.MIGRATION_COMPLEXITY_HIGH + } else if slices.Contains(complexities, constants.MIGRATION_COMPLEXITY_MEDIUM) { + // Else if ANY level is MEDIUM => final is MEDIUM + finalComplexity = constants.MIGRATION_COMPLEXITY_MEDIUM } - // Else if ANY level is MEDIUM => final is MEDIUM - if slices.Contains(complexities, constants.MIGRATION_COMPLEXITY_MEDIUM) { - return constants.MIGRATION_COMPLEXITY_MEDIUM - } - return constants.MIGRATION_COMPLEXITY_LOW + + migrationComplexityRationale = buildRationale(finalComplexity, l1IssueCount, l2IssueCount, l3IssueCount) + return finalComplexity } // This is a temporary logic to get migration complexity for oracle based on the migration level from ora2pg report. @@ -200,3 +210,128 @@ func getComplexityForLevel(level string, count int) string { panic(fmt.Sprintf("unknown impact level %s for determining complexity", level)) } } + +// ======================================= Migration Complexity Explainability ========================================== + +const explainabilityTemplateHTML = ` +
+|--------------------------------|---------|---------|---------|----------|
+| Category                       | Level-1 | Level-2 | Level-3 | Total    |
+|--------------------------------|---------|---------|---------|----------|
+{{- range .Summaries }}
+| {{ printf "%-30s" .Category }} | {{ printf "%-7d" (index .ImpactCounts "LEVEL_1") }} | {{ printf "%-7d" (index .ImpactCounts "LEVEL_2") }} | {{ printf "%-7d" (index .ImpactCounts "LEVEL_3") }} | {{ printf "%-8d" (.TotalIssueCount) }} |
+{{- end }}
+|--------------------------------|---------|---------|---------|----------|
+
+Final Migration Complexity: {{ .MigrationComplexity }}
+
+Reasoning: {{ .ComplexityRationale }}
+
+` + +const explainabilityTemplateText = ` +Migration Complexity Explainability + +Categories and Issues: +{{- range .Summaries }} +Category: {{ .Category }} + Level-1 Issues: {{ index .ImpactCounts "LEVEL_1" }} + Level-2 Issues: {{ index .ImpactCounts "LEVEL_2" }} + Level-3 Issues: {{ index .ImpactCounts "LEVEL_3" }} + Total Issues: {{ .TotalIssueCount }} +{{- end }} + +Final Migration Complexity: {{ .MigrationComplexity }} +Reasoning: {{ .ComplexityRationale }} +` + +/* +{{ range .Summaries }} +--------------------------------------------------------------------- +Category: {{ .Category }} + Highest Impact: {{ .HighestImpact }} + {{- if gt (len .HighestImpactIssues) 0 }} + Examples of highest-impact issues ({{ len .HighestImpactIssues }}): + {{- range .HighestImpactIssues }} + - {{ .Name }} + {{- end }} + {{- end }} +{{ end }} +{{ end }} +*/ + +type ExplainabilityData struct { + Summaries []CategorySummary + MigrationComplexity string + ComplexityRationale string // short reasoning or explanation text +} + +type CategorySummary struct { + Category string + TotalIssueCount int + ImpactCounts map[string]int // e.g. {"Level-1": 3, "Level-2": 5, "Level-3": 2} +} + +func buildMigrationComplexityExplainability(assessmentReport AssessmentReport, reportFormat string) (string, error) { + var explainability ExplainabilityData + explainability.MigrationComplexity = assessmentReport.MigrationComplexity + explainability.ComplexityRationale = migrationComplexityRationale + + explainability.Summaries = buildCategorySummary(assessmentReport.Issues) + + var tmpl *template.Template + var err error + if reportFormat == "html" { + tmpl, err = template.New("Explainability").Parse(explainabilityTemplateHTML) + } else { + tmpl, err = template.New("Explainability").Parse(explainabilityTemplateText) + } + + if err != nil { + return "", fmt.Errorf("failed creating the explainability template: %w", err) + } + + var buf bytes.Buffer + if err := tmpl.Execute(&buf, explainability); err != nil { + return "", fmt.Errorf("failed executing the template with data: %w", err) + } + return buf.String(), nil +} + +func buildRationale(finalComplexity string, l1Count int, l2Count int, l3Count int) string { + switch finalComplexity { + case constants.MIGRATION_COMPLEXITY_HIGH: + return fmt.Sprintf("Found %d Level-2 issue(s) and %d Level-3 issue(s), resulting in HIGH complexity", l2Count, l3Count) + case constants.MIGRATION_COMPLEXITY_MEDIUM: + return fmt.Sprintf("Found %d Level-1 issue(s), %d Level-2 issue(s) and %d Level-3 issue(s), resulting in MEDIUM complexity", l1Count, l2Count, l3Count) + case constants.MIGRATION_COMPLEXITY_LOW: + return fmt.Sprintf("Found %d Level-1 issue(s) and %d Level-2 issue(s), resulting in LOW complexity", l1Count, l2Count) + } + return "" +} + +func buildCategorySummary(issues []AssessmentIssue) []CategorySummary { + summaryMap := make(map[string]*CategorySummary) + for _, issue := range issues { + if issue.Category == "" { + continue // skipping unknown category issues + } + + if _, ok := summaryMap[issue.Category]; !ok { + summaryMap[issue.Category] = &CategorySummary{ + Category: issue.Category, + TotalIssueCount: 0, + ImpactCounts: make(map[string]int), + } + } + + summaryMap[issue.Category].TotalIssueCount++ + summaryMap[issue.Category].ImpactCounts[issue.Impact]++ + } + + var result []CategorySummary + for _, summary := range summaryMap { + result = append(result, *summary) + } + return result +} diff --git a/yb-voyager/cmd/templates/migration_assessment_report.template b/yb-voyager/cmd/templates/migration_assessment_report.template index 6fe380e1e..d0bf72a94 100644 --- a/yb-voyager/cmd/templates/migration_assessment_report.template +++ b/yb-voyager/cmd/templates/migration_assessment_report.template @@ -94,6 +94,7 @@ {{else}}

Migration Complexity: {{ .MigrationComplexity }}

+

Migration Complexity Explainability: {{ .MigrationComplexityExplainability }}

{{end}}

Database Objects

From 028c168f5275db2255bddd8e35c1dd4bbc8bc407 Mon Sep 17 00:00:00 2001 From: Sanyam Singhal Date: Thu, 9 Jan 2025 07:18:35 +0000 Subject: [PATCH 02/11] changes as per feedback --- yb-voyager/cmd/assessMigrationCommand.go | 11 ++-- yb-voyager/cmd/common.go | 2 +- yb-voyager/cmd/migration_complexity.go | 64 +++++++------------ .../migration_assessment_report.template | 5 +- yb-voyager/src/utils/utils.go | 10 +++ 5 files changed, 43 insertions(+), 49 deletions(-) diff --git a/yb-voyager/cmd/assessMigrationCommand.go b/yb-voyager/cmd/assessMigrationCommand.go index 678f6322d..80d9d2da5 100644 --- a/yb-voyager/cmd/assessMigrationCommand.go +++ b/yb-voyager/cmd/assessMigrationCommand.go @@ -425,6 +425,7 @@ func assessMigration() (err error) { } log.Infof("number of assessment issues detected: %d\n", len(assessmentReport.Issues)) + utils.PrintAndLog("Migration assessment completed successfully.") completedEvent := createMigrationAssessmentCompletedEvent() controlPlane.MigrationAssessmentCompleted(completedEvent) @@ -1604,11 +1605,11 @@ func generateAssessmentReportJson(reportDir string) error { log.Infof("writing assessment report to file: %s", jsonReportFilePath) var err error - assessmentReport.MigrationComplexityExplainability, err = buildMigrationComplexityExplainability(assessmentReport, "text") + assessmentReport.MigrationComplexityExplaination, err = buildMigrationComplexityExplaination(source.DBType, assessmentReport, "") if err != nil { - utils.PrintAndLog("ERROR: unable to build migration complexity explainability for json report: %v", err) + utils.PrintAndLog("ERROR: unable to build migration complexity explanation for json report: %v", err) } - log.Info(assessmentReport.MigrationComplexityExplainability) + log.Info(assessmentReport.MigrationComplexityExplaination) strReport, err := json.MarshalIndent(assessmentReport, "", "\t") if err != nil { @@ -1629,9 +1630,9 @@ func generateAssessmentReportHtml(reportDir string) error { log.Infof("writing assessment report to file: %s", htmlReportFilePath) var err error - assessmentReport.MigrationComplexityExplainability, err = buildMigrationComplexityExplainability(assessmentReport, "html") + assessmentReport.MigrationComplexityExplaination, err = buildMigrationComplexityExplaination(source.DBType, assessmentReport, "html") if err != nil { - utils.PrintAndLog("ERROR: unable to build migration complexity explainability for html report: %v", err) + utils.PrintAndLog("ERROR: unable to build migration complexity explanation for html report: %v", err) } file, err := os.Create(htmlReportFilePath) diff --git a/yb-voyager/cmd/common.go b/yb-voyager/cmd/common.go index cb1ab995f..03c8d4f64 100644 --- a/yb-voyager/cmd/common.go +++ b/yb-voyager/cmd/common.go @@ -1054,7 +1054,7 @@ type AssessmentReport struct { VoyagerVersion string `json:"VoyagerVersion"` TargetDBVersion *ybversion.YBVersion `json:"TargetDBVersion"` MigrationComplexity string `json:"MigrationComplexity"` - MigrationComplexityExplainability string `json:"MigrationComplexityExplainability"` + MigrationComplexityExplaination string `json:"MigrationComplexityExplaination"` SchemaSummary utils.SchemaSummary `json:"SchemaSummary"` Sizing *migassessment.SizingAssessmentReport `json:"Sizing"` Issues []AssessmentIssue `json:"-"` // disabled in reports till corresponding UI changes are done(json and html reports) diff --git a/yb-voyager/cmd/migration_complexity.go b/yb-voyager/cmd/migration_complexity.go index 3e6967d4e..9d1efa000 100644 --- a/yb-voyager/cmd/migration_complexity.go +++ b/yb-voyager/cmd/migration_complexity.go @@ -211,9 +211,9 @@ func getComplexityForLevel(level string, count int) string { } } -// ======================================= Migration Complexity Explainability ========================================== +// ======================================= Migration Complexity Explanation ========================================== -const explainabilityTemplateHTML = ` +const explainTemplateHTML = `
 |--------------------------------|---------|---------|---------|----------|
 | Category                       | Level-1 | Level-2 | Level-3 | Total    |
@@ -223,44 +223,19 @@ const explainabilityTemplateHTML = `
 {{- end }}
 |--------------------------------|---------|---------|---------|----------|
 
+Level-1 Impact: Resolutions are available with minimal effort.
+Level-2 Impact: Resolutions are available requiring moderate effort.
+Level-3 Impact: Complex cases where resolutions may not be available or are highly complex.
+
 Final Migration Complexity: {{ .MigrationComplexity }}
 
 Reasoning: {{ .ComplexityRationale }}
 
` -const explainabilityTemplateText = ` -Migration Complexity Explainability - -Categories and Issues: -{{- range .Summaries }} -Category: {{ .Category }} - Level-1 Issues: {{ index .ImpactCounts "LEVEL_1" }} - Level-2 Issues: {{ index .ImpactCounts "LEVEL_2" }} - Level-3 Issues: {{ index .ImpactCounts "LEVEL_3" }} - Total Issues: {{ .TotalIssueCount }} -{{- end }} +const explainTemplateText = `Reasoning: {{ .ComplexityRationale }}` -Final Migration Complexity: {{ .MigrationComplexity }} -Reasoning: {{ .ComplexityRationale }} -` - -/* -{{ range .Summaries }} ---------------------------------------------------------------------- -Category: {{ .Category }} - Highest Impact: {{ .HighestImpact }} - {{- if gt (len .HighestImpactIssues) 0 }} - Examples of highest-impact issues ({{ len .HighestImpactIssues }}): - {{- range .HighestImpactIssues }} - - {{ .Name }} - {{- end }} - {{- end }} -{{ end }} -{{ end }} -*/ - -type ExplainabilityData struct { +type ExplainationData struct { Summaries []CategorySummary MigrationComplexity string ComplexityRationale string // short reasoning or explanation text @@ -272,27 +247,31 @@ type CategorySummary struct { ImpactCounts map[string]int // e.g. {"Level-1": 3, "Level-2": 5, "Level-3": 2} } -func buildMigrationComplexityExplainability(assessmentReport AssessmentReport, reportFormat string) (string, error) { - var explainability ExplainabilityData - explainability.MigrationComplexity = assessmentReport.MigrationComplexity - explainability.ComplexityRationale = migrationComplexityRationale +func buildMigrationComplexityExplaination(sourceDBType string, assessmentReport AssessmentReport, reportFormat string) (string, error) { + if sourceDBType != POSTGRESQL { + return "", nil + } + + var explaination ExplainationData + explaination.MigrationComplexity = assessmentReport.MigrationComplexity + explaination.ComplexityRationale = migrationComplexityRationale - explainability.Summaries = buildCategorySummary(assessmentReport.Issues) + explaination.Summaries = buildCategorySummary(assessmentReport.Issues) var tmpl *template.Template var err error if reportFormat == "html" { - tmpl, err = template.New("Explainability").Parse(explainabilityTemplateHTML) + tmpl, err = template.New("Explain").Parse(explainTemplateHTML) } else { - tmpl, err = template.New("Explainability").Parse(explainabilityTemplateText) + tmpl, err = template.New("Explain").Parse(explainTemplateText) } if err != nil { - return "", fmt.Errorf("failed creating the explainability template: %w", err) + return "", fmt.Errorf("failed creating the explaination template: %w", err) } var buf bytes.Buffer - if err := tmpl.Execute(&buf, explainability); err != nil { + if err := tmpl.Execute(&buf, explaination); err != nil { return "", fmt.Errorf("failed executing the template with data: %w", err) } return buf.String(), nil @@ -331,6 +310,7 @@ func buildCategorySummary(issues []AssessmentIssue) []CategorySummary { var result []CategorySummary for _, summary := range summaryMap { + summary.Category = utils.ToTitleCase(summary.Category) result = append(result, *summary) } return result diff --git a/yb-voyager/cmd/templates/migration_assessment_report.template b/yb-voyager/cmd/templates/migration_assessment_report.template index d0bf72a94..7e46b53b0 100644 --- a/yb-voyager/cmd/templates/migration_assessment_report.template +++ b/yb-voyager/cmd/templates/migration_assessment_report.template @@ -94,7 +94,6 @@ {{else}}

Migration Complexity: {{ .MigrationComplexity }}

-

Migration Complexity Explainability: {{ .MigrationComplexityExplainability }}

{{end}}

Database Objects

@@ -168,6 +167,10 @@ {{ end }} {{end}} + {{if ne .MigrationComplexity "NOT AVAILABLE"}} +

Migration Complexity Explaination: {{ .MigrationComplexityExplaination }}

+ {{end}} +

Unsupported Data Types

{{.UnsupportedDataTypesDesc}}

{{ if .UnsupportedDataTypes }} diff --git a/yb-voyager/src/utils/utils.go b/yb-voyager/src/utils/utils.go index b168d886c..ed002ec78 100644 --- a/yb-voyager/src/utils/utils.go +++ b/yb-voyager/src/utils/utils.go @@ -744,3 +744,13 @@ func CheckTools(tools ...string) []string { func BuildObjectName(schemaName, objName string) string { return lo.Ternary(schemaName != "", schemaName+"."+objName, objName) } + +// ToTitleCase converts a snake_case string to a title case string with spaces. +func ToTitleCase(snake string) string { + words := strings.Split(snake, "_") + for i, word := range words { + words[i] = strings.Title(word) + } + + return strings.Join(words, " ") +} \ No newline at end of file From 730da79a38eebd97594a4dbc668cc42a1c4c00c3 Mon Sep 17 00:00:00 2001 From: Sanyam Singhal Date: Thu, 9 Jan 2025 08:16:46 +0000 Subject: [PATCH 03/11] minor UI changes --- yb-voyager/cmd/migration_complexity.go | 57 ++++++++++++------- .../migration_assessment_report.template | 3 +- 2 files changed, 37 insertions(+), 23 deletions(-) diff --git a/yb-voyager/cmd/migration_complexity.go b/yb-voyager/cmd/migration_complexity.go index 9d1efa000..9a9b699ee 100644 --- a/yb-voyager/cmd/migration_complexity.go +++ b/yb-voyager/cmd/migration_complexity.go @@ -214,30 +214,44 @@ func getComplexityForLevel(level string, count int) string { // ======================================= Migration Complexity Explanation ========================================== const explainTemplateHTML = ` -
-|--------------------------------|---------|---------|---------|----------|
-| Category                       | Level-1 | Level-2 | Level-3 | Total    |
-|--------------------------------|---------|---------|---------|----------|
-{{- range .Summaries }}
-| {{ printf "%-30s" .Category }} | {{ printf "%-7d" (index .ImpactCounts "LEVEL_1") }} | {{ printf "%-7d" (index .ImpactCounts "LEVEL_2") }} | {{ printf "%-7d" (index .ImpactCounts "LEVEL_3") }} | {{ printf "%-8d" (.TotalIssueCount) }} |
-{{- end }}
-|--------------------------------|---------|---------|---------|----------|
-
-Level-1 Impact: Resolutions are available with minimal effort.
-Level-2 Impact: Resolutions are available requiring moderate effort.
-Level-3 Impact: Complex cases where resolutions may not be available or are highly complex.
-
-Final Migration Complexity: {{ .MigrationComplexity }}
-
-Reasoning: {{ .ComplexityRationale }}
-
+ + + + + + + + + + + + {{- range .Summaries }} + + + + + + + + {{- end }} + +
CategoryLevel-1Level-2Level-3Total
{{ .Category }}{{ index .ImpactCounts "LEVEL_1" }}{{ index .ImpactCounts "LEVEL_2" }}{{ index .ImpactCounts "LEVEL_3" }}{{ .TotalIssueCount }}
+ +

+ Level-1 Impact: Issues where resolutions are available with minimal effort.
+ Level-2 Impact: Issues where resolutions are available requiring moderate effort.
+ Level-3 Impact: Issues where resolutions may not be available or are highly complex. +

+ +

+ Reasoning: {{ .ComplexityRationale }} +

` const explainTemplateText = `Reasoning: {{ .ComplexityRationale }}` type ExplainationData struct { Summaries []CategorySummary - MigrationComplexity string ComplexityRationale string // short reasoning or explanation text } @@ -253,7 +267,6 @@ func buildMigrationComplexityExplaination(sourceDBType string, assessmentReport } var explaination ExplainationData - explaination.MigrationComplexity = assessmentReport.MigrationComplexity explaination.ComplexityRationale = migrationComplexityRationale explaination.Summaries = buildCategorySummary(assessmentReport.Issues) @@ -280,11 +293,11 @@ func buildMigrationComplexityExplaination(sourceDBType string, assessmentReport func buildRationale(finalComplexity string, l1Count int, l2Count int, l3Count int) string { switch finalComplexity { case constants.MIGRATION_COMPLEXITY_HIGH: - return fmt.Sprintf("Found %d Level-2 issue(s) and %d Level-3 issue(s), resulting in HIGH complexity", l2Count, l3Count) + return fmt.Sprintf("Found %d Level-2 issue(s) and %d Level-3 issue(s), resulting in HIGH migration complexity", l2Count, l3Count) case constants.MIGRATION_COMPLEXITY_MEDIUM: - return fmt.Sprintf("Found %d Level-1 issue(s), %d Level-2 issue(s) and %d Level-3 issue(s), resulting in MEDIUM complexity", l1Count, l2Count, l3Count) + return fmt.Sprintf("Found %d Level-1 issue(s), %d Level-2 issue(s) and %d Level-3 issue(s), resulting in MEDIUM migration complexity", l1Count, l2Count, l3Count) case constants.MIGRATION_COMPLEXITY_LOW: - return fmt.Sprintf("Found %d Level-1 issue(s) and %d Level-2 issue(s), resulting in LOW complexity", l1Count, l2Count) + return fmt.Sprintf("Found %d Level-1 issue(s) and %d Level-2 issue(s), resulting in LOW migration complexity", l1Count, l2Count) } return "" } diff --git a/yb-voyager/cmd/templates/migration_assessment_report.template b/yb-voyager/cmd/templates/migration_assessment_report.template index 7e46b53b0..9dc35063a 100644 --- a/yb-voyager/cmd/templates/migration_assessment_report.template +++ b/yb-voyager/cmd/templates/migration_assessment_report.template @@ -168,7 +168,8 @@ {{end}} {{if ne .MigrationComplexity "NOT AVAILABLE"}} -

Migration Complexity Explaination: {{ .MigrationComplexityExplaination }}

+

Migration Complexity Explaination

+

{{ .MigrationComplexityExplaination }}

{{end}}

Unsupported Data Types

From cbbf84f60f55cf717879971147157910953ec0db Mon Sep 17 00:00:00 2001 From: Sanyam Singhal Date: Thu, 9 Jan 2025 08:18:10 +0000 Subject: [PATCH 04/11] changing level definitions --- yb-voyager/cmd/migration_complexity.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yb-voyager/cmd/migration_complexity.go b/yb-voyager/cmd/migration_complexity.go index 9a9b699ee..3f0550cfb 100644 --- a/yb-voyager/cmd/migration_complexity.go +++ b/yb-voyager/cmd/migration_complexity.go @@ -238,9 +238,9 @@ const explainTemplateHTML = `

- Level-1 Impact: Issues where resolutions are available with minimal effort.
- Level-2 Impact: Issues where resolutions are available requiring moderate effort.
- Level-3 Impact: Issues where resolutions may not be available or are highly complex. + Level-1 Impact: Resolutions are available with minimal effort.
+ Level-2 Impact: Resolutions are available requiring moderate effort.
+ Level-3 Impact: Resolutions may not be available or are highly complex.

From 9869d5c3a6dcaa3a6e016e2923bcd03e56d4830d Mon Sep 17 00:00:00 2001 From: Sanyam Singhal Date: Thu, 9 Jan 2025 08:26:53 +0000 Subject: [PATCH 05/11] Ignoring MigrationComplexityExplanation field in the tests to be verified --- migtests/scripts/functions.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/migtests/scripts/functions.sh b/migtests/scripts/functions.sh index 4d8e1d6f4..2e6fdf825 100644 --- a/migtests/scripts/functions.sh +++ b/migtests/scripts/functions.sh @@ -891,6 +891,7 @@ normalize_json() { .OptimalSelectConnectionsPerNode? = "IGNORED" | .OptimalInsertConnectionsPerNode? = "IGNORED" | .RowCount? = "IGNORED" | + .MigrationComplexityExplaination?= "IGNORED" | # Replace newline characters in SqlStatement with spaces .SqlStatement? |= ( if type == "string" then From de90d2c1ec615c3bd96d7f2a01a9cdef3f151775 Mon Sep 17 00:00:00 2001 From: Sanyam Singhal Date: Thu, 9 Jan 2025 08:41:37 +0000 Subject: [PATCH 06/11] Fixed typo and modified upgrade test for assessment report --- migtests/scripts/functions.sh | 2 +- yb-voyager/cmd/assessMigrationCommand.go | 6 +-- yb-voyager/cmd/common.go | 18 ++++----- yb-voyager/cmd/common_test.go | 40 ++++++++++--------- yb-voyager/cmd/migration_complexity.go | 14 +++---- .../migration_assessment_report.template | 4 +- 6 files changed, 43 insertions(+), 41 deletions(-) diff --git a/migtests/scripts/functions.sh b/migtests/scripts/functions.sh index 2e6fdf825..b9830bc03 100644 --- a/migtests/scripts/functions.sh +++ b/migtests/scripts/functions.sh @@ -891,7 +891,7 @@ normalize_json() { .OptimalSelectConnectionsPerNode? = "IGNORED" | .OptimalInsertConnectionsPerNode? = "IGNORED" | .RowCount? = "IGNORED" | - .MigrationComplexityExplaination?= "IGNORED" | + .MigrationComplexityExplanation?= "IGNORED" | # Replace newline characters in SqlStatement with spaces .SqlStatement? |= ( if type == "string" then diff --git a/yb-voyager/cmd/assessMigrationCommand.go b/yb-voyager/cmd/assessMigrationCommand.go index 80d9d2da5..c9d31932c 100644 --- a/yb-voyager/cmd/assessMigrationCommand.go +++ b/yb-voyager/cmd/assessMigrationCommand.go @@ -1605,11 +1605,11 @@ func generateAssessmentReportJson(reportDir string) error { log.Infof("writing assessment report to file: %s", jsonReportFilePath) var err error - assessmentReport.MigrationComplexityExplaination, err = buildMigrationComplexityExplaination(source.DBType, assessmentReport, "") + assessmentReport.MigrationComplexityExplanation, err = buildMigrationComplexityExplanation(source.DBType, assessmentReport, "") if err != nil { utils.PrintAndLog("ERROR: unable to build migration complexity explanation for json report: %v", err) } - log.Info(assessmentReport.MigrationComplexityExplaination) + log.Info(assessmentReport.MigrationComplexityExplanation) strReport, err := json.MarshalIndent(assessmentReport, "", "\t") if err != nil { @@ -1630,7 +1630,7 @@ func generateAssessmentReportHtml(reportDir string) error { log.Infof("writing assessment report to file: %s", htmlReportFilePath) var err error - assessmentReport.MigrationComplexityExplaination, err = buildMigrationComplexityExplaination(source.DBType, assessmentReport, "html") + assessmentReport.MigrationComplexityExplanation, err = buildMigrationComplexityExplanation(source.DBType, assessmentReport, "html") if err != nil { utils.PrintAndLog("ERROR: unable to build migration complexity explanation for html report: %v", err) } diff --git a/yb-voyager/cmd/common.go b/yb-voyager/cmd/common.go index 03c8d4f64..f3cae64bf 100644 --- a/yb-voyager/cmd/common.go +++ b/yb-voyager/cmd/common.go @@ -1051,15 +1051,15 @@ func storeTableListInMSR(tableList []sqlname.NameTuple) error { // TODO: consider merging all unsupported field with single AssessmentReport struct member as AssessmentIssue type AssessmentReport struct { - VoyagerVersion string `json:"VoyagerVersion"` - TargetDBVersion *ybversion.YBVersion `json:"TargetDBVersion"` - MigrationComplexity string `json:"MigrationComplexity"` - MigrationComplexityExplaination string `json:"MigrationComplexityExplaination"` - SchemaSummary utils.SchemaSummary `json:"SchemaSummary"` - Sizing *migassessment.SizingAssessmentReport `json:"Sizing"` - Issues []AssessmentIssue `json:"-"` // disabled in reports till corresponding UI changes are done(json and html reports) - TableIndexStats *[]migassessment.TableIndexStats `json:"TableIndexStats"` - Notes []string `json:"Notes"` + VoyagerVersion string `json:"VoyagerVersion"` + TargetDBVersion *ybversion.YBVersion `json:"TargetDBVersion"` + MigrationComplexity string `json:"MigrationComplexity"` + MigrationComplexityExplanation string `json:"MigrationComplexityExplanation"` + SchemaSummary utils.SchemaSummary `json:"SchemaSummary"` + Sizing *migassessment.SizingAssessmentReport `json:"Sizing"` + Issues []AssessmentIssue `json:"-"` // disabled in reports till corresponding UI changes are done(json and html reports) + TableIndexStats *[]migassessment.TableIndexStats `json:"TableIndexStats"` + Notes []string `json:"Notes"` // fields going to be deprecated UnsupportedDataTypes []utils.TableColumnsDataTypes `json:"UnsupportedDataTypes"` diff --git a/yb-voyager/cmd/common_test.go b/yb-voyager/cmd/common_test.go index 6dd45e666..9fd9635f3 100644 --- a/yb-voyager/cmd/common_test.go +++ b/yb-voyager/cmd/common_test.go @@ -129,21 +129,22 @@ func TestAssessmentReportStructs(t *testing.T) { name: "Validate AssessmentReport Struct Definition", actualType: reflect.TypeOf(AssessmentReport{}), expectedType: struct { - VoyagerVersion string `json:"VoyagerVersion"` - TargetDBVersion *ybversion.YBVersion `json:"TargetDBVersion"` - MigrationComplexity string `json:"MigrationComplexity"` - SchemaSummary utils.SchemaSummary `json:"SchemaSummary"` - Sizing *migassessment.SizingAssessmentReport `json:"Sizing"` - Issues []AssessmentIssue `json:"-"` - TableIndexStats *[]migassessment.TableIndexStats `json:"TableIndexStats"` - Notes []string `json:"Notes"` - UnsupportedDataTypes []utils.TableColumnsDataTypes `json:"UnsupportedDataTypes"` - UnsupportedDataTypesDesc string `json:"UnsupportedDataTypesDesc"` - UnsupportedFeatures []UnsupportedFeature `json:"UnsupportedFeatures"` - UnsupportedFeaturesDesc string `json:"UnsupportedFeaturesDesc"` - UnsupportedQueryConstructs []utils.UnsupportedQueryConstruct `json:"UnsupportedQueryConstructs"` - UnsupportedPlPgSqlObjects []UnsupportedFeature `json:"UnsupportedPlPgSqlObjects"` - MigrationCaveats []UnsupportedFeature `json:"MigrationCaveats"` + VoyagerVersion string `json:"VoyagerVersion"` + TargetDBVersion *ybversion.YBVersion `json:"TargetDBVersion"` + MigrationComplexity string `json:"MigrationComplexity"` + MigrationComplexityExplanation string `json:"MigrationComplexityExplanation"` + SchemaSummary utils.SchemaSummary `json:"SchemaSummary"` + Sizing *migassessment.SizingAssessmentReport `json:"Sizing"` + Issues []AssessmentIssue `json:"-"` + TableIndexStats *[]migassessment.TableIndexStats `json:"TableIndexStats"` + Notes []string `json:"Notes"` + UnsupportedDataTypes []utils.TableColumnsDataTypes `json:"UnsupportedDataTypes"` + UnsupportedDataTypesDesc string `json:"UnsupportedDataTypesDesc"` + UnsupportedFeatures []UnsupportedFeature `json:"UnsupportedFeatures"` + UnsupportedFeaturesDesc string `json:"UnsupportedFeaturesDesc"` + UnsupportedQueryConstructs []utils.UnsupportedQueryConstruct `json:"UnsupportedQueryConstructs"` + UnsupportedPlPgSqlObjects []UnsupportedFeature `json:"UnsupportedPlPgSqlObjects"` + MigrationCaveats []UnsupportedFeature `json:"MigrationCaveats"` }{}, }, } @@ -165,9 +166,10 @@ func TestAssessmentReportJson(t *testing.T) { } assessmentReport = AssessmentReport{ - VoyagerVersion: "v1.0.0", - TargetDBVersion: newYbVersion, - MigrationComplexity: "High", + VoyagerVersion: "v1.0.0", + TargetDBVersion: newYbVersion, + MigrationComplexity: "High", + MigrationComplexityExplanation: "", SchemaSummary: utils.SchemaSummary{ Description: "Test Schema Summary", DBName: "test_db", @@ -302,12 +304,12 @@ func TestAssessmentReportJson(t *testing.T) { if err != nil { t.Fatalf("Failed to write assessment report to JSON file: %v", err) } - // expected JSON expectedJSON := `{ "VoyagerVersion": "v1.0.0", "TargetDBVersion": "2024.1.1.1", "MigrationComplexity": "High", + "MigrationComplexityExplanation": "", "SchemaSummary": { "Description": "Test Schema Summary", "DbName": "test_db", diff --git a/yb-voyager/cmd/migration_complexity.go b/yb-voyager/cmd/migration_complexity.go index 3f0550cfb..0c974d449 100644 --- a/yb-voyager/cmd/migration_complexity.go +++ b/yb-voyager/cmd/migration_complexity.go @@ -250,7 +250,7 @@ const explainTemplateHTML = ` const explainTemplateText = `Reasoning: {{ .ComplexityRationale }}` -type ExplainationData struct { +type ExplanationData struct { Summaries []CategorySummary ComplexityRationale string // short reasoning or explanation text } @@ -261,15 +261,15 @@ type CategorySummary struct { ImpactCounts map[string]int // e.g. {"Level-1": 3, "Level-2": 5, "Level-3": 2} } -func buildMigrationComplexityExplaination(sourceDBType string, assessmentReport AssessmentReport, reportFormat string) (string, error) { +func buildMigrationComplexityExplanation(sourceDBType string, assessmentReport AssessmentReport, reportFormat string) (string, error) { if sourceDBType != POSTGRESQL { return "", nil } - var explaination ExplainationData - explaination.ComplexityRationale = migrationComplexityRationale + var explanation ExplanationData + explanation.ComplexityRationale = migrationComplexityRationale - explaination.Summaries = buildCategorySummary(assessmentReport.Issues) + explanation.Summaries = buildCategorySummary(assessmentReport.Issues) var tmpl *template.Template var err error @@ -280,11 +280,11 @@ func buildMigrationComplexityExplaination(sourceDBType string, assessmentReport } if err != nil { - return "", fmt.Errorf("failed creating the explaination template: %w", err) + return "", fmt.Errorf("failed creating the explanation template: %w", err) } var buf bytes.Buffer - if err := tmpl.Execute(&buf, explaination); err != nil { + if err := tmpl.Execute(&buf, explanation); err != nil { return "", fmt.Errorf("failed executing the template with data: %w", err) } return buf.String(), nil diff --git a/yb-voyager/cmd/templates/migration_assessment_report.template b/yb-voyager/cmd/templates/migration_assessment_report.template index 9dc35063a..eeef96253 100644 --- a/yb-voyager/cmd/templates/migration_assessment_report.template +++ b/yb-voyager/cmd/templates/migration_assessment_report.template @@ -168,8 +168,8 @@ {{end}} {{if ne .MigrationComplexity "NOT AVAILABLE"}} -

Migration Complexity Explaination

-

{{ .MigrationComplexityExplaination }}

+

Migration Complexity Explanation

+

{{ .MigrationComplexityExplanation }}

{{end}}

Unsupported Data Types

From 988fae36a8fa144bcb4d5cb3ccb77ce2d63e02df Mon Sep 17 00:00:00 2001 From: Sanyam Singhal Date: Thu, 9 Jan 2025 09:56:21 +0000 Subject: [PATCH 07/11] review comments --- yb-voyager/cmd/migration_complexity.go | 20 ++++++++++---------- yb-voyager/src/utils/utils.go | 13 ++++++++----- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/yb-voyager/cmd/migration_complexity.go b/yb-voyager/cmd/migration_complexity.go index 0c974d449..d9868a082 100644 --- a/yb-voyager/cmd/migration_complexity.go +++ b/yb-voyager/cmd/migration_complexity.go @@ -240,7 +240,7 @@ const explainTemplateHTML = `

Level-1 Impact: Resolutions are available with minimal effort.
Level-2 Impact: Resolutions are available requiring moderate effort.
- Level-3 Impact: Resolutions may not be available or are highly complex. + Level-3 Impact: Resolutions may not be available or are complex.

@@ -250,12 +250,12 @@ const explainTemplateHTML = ` const explainTemplateText = `Reasoning: {{ .ComplexityRationale }}` -type ExplanationData struct { - Summaries []CategorySummary +type MigrationComplexityExplanationData struct { + Summaries []MigrationComplexityCategorySummary ComplexityRationale string // short reasoning or explanation text } -type CategorySummary struct { +type MigrationComplexityCategorySummary struct { Category string TotalIssueCount int ImpactCounts map[string]int // e.g. {"Level-1": 3, "Level-2": 5, "Level-3": 2} @@ -266,7 +266,7 @@ func buildMigrationComplexityExplanation(sourceDBType string, assessmentReport A return "", nil } - var explanation ExplanationData + var explanation MigrationComplexityExplanationData explanation.ComplexityRationale = migrationComplexityRationale explanation.Summaries = buildCategorySummary(assessmentReport.Issues) @@ -302,15 +302,15 @@ func buildRationale(finalComplexity string, l1Count int, l2Count int, l3Count in return "" } -func buildCategorySummary(issues []AssessmentIssue) []CategorySummary { - summaryMap := make(map[string]*CategorySummary) +func buildCategorySummary(issues []AssessmentIssue) []MigrationComplexityCategorySummary { + summaryMap := make(map[string]*MigrationComplexityCategorySummary) for _, issue := range issues { if issue.Category == "" { continue // skipping unknown category issues } if _, ok := summaryMap[issue.Category]; !ok { - summaryMap[issue.Category] = &CategorySummary{ + summaryMap[issue.Category] = &MigrationComplexityCategorySummary{ Category: issue.Category, TotalIssueCount: 0, ImpactCounts: make(map[string]int), @@ -321,9 +321,9 @@ func buildCategorySummary(issues []AssessmentIssue) []CategorySummary { summaryMap[issue.Category].ImpactCounts[issue.Impact]++ } - var result []CategorySummary + var result []MigrationComplexityCategorySummary for _, summary := range summaryMap { - summary.Category = utils.ToTitleCase(summary.Category) + summary.Category = utils.SnakeCaseToTitleCase(summary.Category) result = append(result, *summary) } return result diff --git a/yb-voyager/src/utils/utils.go b/yb-voyager/src/utils/utils.go index ed002ec78..c9967e455 100644 --- a/yb-voyager/src/utils/utils.go +++ b/yb-voyager/src/utils/utils.go @@ -38,6 +38,8 @@ import ( "github.com/samber/lo" log "github.com/sirupsen/logrus" "golang.org/x/exp/slices" + "golang.org/x/text/cases" + "golang.org/x/text/language" ) var DoNotPrompt bool @@ -745,12 +747,13 @@ func BuildObjectName(schemaName, objName string) string { return lo.Ternary(schemaName != "", schemaName+"."+objName, objName) } -// ToTitleCase converts a snake_case string to a title case string with spaces. -func ToTitleCase(snake string) string { +// SnakeCaseToTitleCase converts a snake_case string to a title case string with spaces. +func SnakeCaseToTitleCase(snake string) string { words := strings.Split(snake, "_") + c := cases.Title(language.English) for i, word := range words { - words[i] = strings.Title(word) + words[i] = c.String(word) } - + return strings.Join(words, " ") -} \ No newline at end of file +} From 4ac3167e4ee271e4d6a9a294886cc8df50640ce7 Mon Sep 17 00:00:00 2001 From: Sanyam Singhal Date: Thu, 9 Jan 2025 10:46:32 +0000 Subject: [PATCH 08/11] some UI changes as per feedback --- yb-voyager/cmd/migration_complexity.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/yb-voyager/cmd/migration_complexity.go b/yb-voyager/cmd/migration_complexity.go index d9868a082..173421980 100644 --- a/yb-voyager/cmd/migration_complexity.go +++ b/yb-voyager/cmd/migration_complexity.go @@ -214,6 +214,7 @@ func getComplexityForLevel(level string, count int) string { // ======================================= Migration Complexity Explanation ========================================== const explainTemplateHTML = ` +

Below is a detailed breakdown of issues by category, showing the count for each impact level.

@@ -238,13 +239,16 @@ const explainTemplateHTML = `

- Level-1 Impact: Resolutions are available with minimal effort.
- Level-2 Impact: Resolutions are available requiring moderate effort.
- Level-3 Impact: Resolutions may not be available or are complex. + Complexity: {{ .Complexity }}
+ Reasoning: {{ .ComplexityRationale }}

- Reasoning: {{ .ComplexityRationale }} +Impact Levels:
+ Level-1: Resolutions are available with minimal effort.
+ Level-2 + : Resolutions are available requiring moderate effort.
+ Level-3: Resolutions may not be available or are complex.

` @@ -252,6 +256,7 @@ const explainTemplateText = `Reasoning: {{ .ComplexityRationale }}` type MigrationComplexityExplanationData struct { Summaries []MigrationComplexityCategorySummary + Complexity string ComplexityRationale string // short reasoning or explanation text } @@ -267,6 +272,7 @@ func buildMigrationComplexityExplanation(sourceDBType string, assessmentReport A } var explanation MigrationComplexityExplanationData + explanation.Complexity = assessmentReport.MigrationComplexity explanation.ComplexityRationale = migrationComplexityRationale explanation.Summaries = buildCategorySummary(assessmentReport.Issues) From c36b10a61840b47d963e55ed95bf1b254fc762a1 Mon Sep 17 00:00:00 2001 From: Sanyam Singhal Date: Thu, 9 Jan 2025 10:59:48 +0000 Subject: [PATCH 09/11] review comments --- yb-voyager/cmd/assessMigrationCommand.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yb-voyager/cmd/assessMigrationCommand.go b/yb-voyager/cmd/assessMigrationCommand.go index c9d31932c..85642279f 100644 --- a/yb-voyager/cmd/assessMigrationCommand.go +++ b/yb-voyager/cmd/assessMigrationCommand.go @@ -1607,7 +1607,7 @@ func generateAssessmentReportJson(reportDir string) error { var err error assessmentReport.MigrationComplexityExplanation, err = buildMigrationComplexityExplanation(source.DBType, assessmentReport, "") if err != nil { - utils.PrintAndLog("ERROR: unable to build migration complexity explanation for json report: %v", err) + return fmt.Errorf("unable to build migration complexity explanation for json report: %w", err) } log.Info(assessmentReport.MigrationComplexityExplanation) @@ -1632,7 +1632,7 @@ func generateAssessmentReportHtml(reportDir string) error { var err error assessmentReport.MigrationComplexityExplanation, err = buildMigrationComplexityExplanation(source.DBType, assessmentReport, "html") if err != nil { - utils.PrintAndLog("ERROR: unable to build migration complexity explanation for html report: %v", err) + return fmt.Errorf("unable to build migration complexity explanation for html report: %w", err) } file, err := os.Create(htmlReportFilePath) From df9ea1172040daf6e6e6cf0b296cb3b28b40da7f Mon Sep 17 00:00:00 2001 From: Sanyam Singhal Date: Thu, 9 Jan 2025 11:01:04 +0000 Subject: [PATCH 10/11] added a TODO comment --- yb-voyager/cmd/migration_complexity.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yb-voyager/cmd/migration_complexity.go b/yb-voyager/cmd/migration_complexity.go index 173421980..77f4660ac 100644 --- a/yb-voyager/cmd/migration_complexity.go +++ b/yb-voyager/cmd/migration_complexity.go @@ -213,6 +213,7 @@ func getComplexityForLevel(level string, count int) string { // ======================================= Migration Complexity Explanation ========================================== +// TODO: discuss if the html should be in main report or here const explainTemplateHTML = `

Below is a detailed breakdown of issues by category, showing the count for each impact level.

@@ -246,8 +247,7 @@ const explainTemplateHTML = `

Impact Levels:
Level-1: Resolutions are available with minimal effort.
- Level-2 - : Resolutions are available requiring moderate effort.
+ Level-2: Resolutions are available requiring moderate effort.
Level-3: Resolutions may not be available or are complex.

` From 25b150b637d661c4e3f84a9974e805796397f502 Mon Sep 17 00:00:00 2001 From: Sanyam Singhal Date: Thu, 9 Jan 2025 11:22:06 +0000 Subject: [PATCH 11/11] handled html UI for zero issues cases --- yb-voyager/cmd/migration_complexity.go | 53 +++++++++++++++----------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/yb-voyager/cmd/migration_complexity.go b/yb-voyager/cmd/migration_complexity.go index 77f4660ac..69b71d1d3 100644 --- a/yb-voyager/cmd/migration_complexity.go +++ b/yb-voyager/cmd/migration_complexity.go @@ -215,29 +215,31 @@ func getComplexityForLevel(level string, count int) string { // TODO: discuss if the html should be in main report or here const explainTemplateHTML = ` -

Below is a detailed breakdown of issues by category, showing the count for each impact level.

-
- - - - - - - - - - - {{- range .Summaries }} - - - - - - - - {{- end }} - -
CategoryLevel-1Level-2Level-3Total
{{ .Category }}{{ index .ImpactCounts "LEVEL_1" }}{{ index .ImpactCounts "LEVEL_2" }}{{ index .ImpactCounts "LEVEL_3" }}{{ .TotalIssueCount }}
+{{- if .Summaries }} +

Below is a breakdown of the issues detected in different categories for each impact level.

+ + + + + + + + + + + + {{- range .Summaries }} + + + + + + + + {{- end }} + +
CategoryLevel-1Level-2Level-3Total
{{ .Category }}{{ index .ImpactCounts "LEVEL_1" }}{{ index .ImpactCounts "LEVEL_2" }}{{ index .ImpactCounts "LEVEL_3" }}{{ .TotalIssueCount }}
+{{- end }}

Complexity: {{ .Complexity }}
@@ -309,6 +311,11 @@ func buildRationale(finalComplexity string, l1Count int, l2Count int, l3Count in } func buildCategorySummary(issues []AssessmentIssue) []MigrationComplexityCategorySummary { + if len(issues) == 0 { + return nil + + } + summaryMap := make(map[string]*MigrationComplexityCategorySummary) for _, issue := range issues { if issue.Category == "" {