-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: export questionnaire answers to xslx format spreadsheet (#134)
closes #125
- Loading branch information
1 parent
75aab92
commit fac5630
Showing
9 changed files
with
188 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,60 @@ | ||
package controller | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"strings" | ||
|
||
"github.com/CMS-Enterprise/ztmf/backend/cmd/api/internal/auth" | ||
"github.com/CMS-Enterprise/ztmf/backend/cmd/api/internal/model" | ||
"github.com/CMS-Enterprise/ztmf/backend/cmd/api/internal/spreadsheet" | ||
"github.com/gorilla/mux" | ||
) | ||
|
||
func ListDataCalls(w http.ResponseWriter, r *http.Request) { | ||
datacalls, err := model.FindDataCalls(r.Context()) | ||
respond(w, r, datacalls, err) | ||
} | ||
|
||
func GetDatacallExport(w http.ResponseWriter, r *http.Request) { | ||
user := auth.UserFromContext(r.Context()) | ||
input := model.FindAnswersInput{} | ||
|
||
if !user.IsAdmin() { | ||
input.UserID = &user.UserID | ||
} | ||
|
||
vars := mux.Vars(r) | ||
if v, ok := vars["datacallid"]; ok { | ||
fmt.Sscan(v, &input.DataCallID) | ||
} | ||
|
||
qVars := r.URL.Query() | ||
if qVars.Has("fsids") { | ||
for _, v := range qVars["fsids"] { | ||
var fismaSystemID int32 | ||
fmt.Sscan(v, &fismaSystemID) | ||
if !user.IsAdmin() && !user.IsAssignedFismaSystem(fismaSystemID) { | ||
respond(w, r, nil, ErrForbidden) | ||
return | ||
} | ||
input.FismaSystemIDs = append(input.FismaSystemIDs, &fismaSystemID) | ||
} | ||
} | ||
|
||
answers, err := model.FindAnswers(r.Context(), input) | ||
if err != nil { | ||
respond(w, r, nil, err) | ||
return | ||
} | ||
|
||
file, err := spreadsheet.Excel(answers) | ||
if err != nil { | ||
respond(w, r, nil, err) | ||
return | ||
} | ||
|
||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.xlsx", strings.ReplaceAll(answers[0].DataCall, " ", ""))) | ||
w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") | ||
file.Write(w) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package model | ||
|
||
import ( | ||
"context" | ||
"log" | ||
|
||
"github.com/Masterminds/squirrel" | ||
"github.com/jackc/pgx/v5" | ||
) | ||
|
||
type Answer struct { | ||
DataCall string | ||
FismaSystemID int32 | ||
FismaAcronym string | ||
DataCenterEnvironment string | ||
Pillar string | ||
Question string | ||
Function string | ||
Description string | ||
OptionName string | ||
Score int | ||
Notes string | ||
} | ||
|
||
type FindAnswersInput struct { | ||
FismaSystemIDs []*int32 | ||
DataCallID int32 | ||
UserID *string | ||
} | ||
|
||
// FindAnswers queries the DB and returns a fully comprehensive set of fields and values | ||
// leveraging all the necessary joins that would otherwise require multiple DB calls | ||
// if using lower-level methods such as FindFismaSystems, FindScores, FindQuestions, etc | ||
// this is primarily meant for use in exporting to spreadsheets | ||
func FindAnswers(ctx context.Context, input FindAnswersInput) ([]*Answer, error) { | ||
sqlb := sqlBuilder.Select("datacalls.datacall, fismasystems.fismasystemid, fismasystems.fismaacronym, fismasystems.datacenterenvironment, pillars.pillar, questions.question, functions.function, functions.description, functionoptions.optionname, functionoptions.score, scores.notes"). | ||
From("scores"). | ||
InnerJoin("datacalls ON datacalls.datacallid=scores.datacallid AND datacalls.datacallid=?", input.DataCallID). | ||
InnerJoin("fismasystems ON fismasystems.fismasystemid=scores.fismasystemid"). | ||
InnerJoin("functionoptions ON functionoptions.functionoptionid=scores.functionoptionid"). | ||
InnerJoin("functions ON functions.functionid=functionoptions.functionid"). | ||
InnerJoin("questions ON questions.questionid=functions.questionid"). | ||
InnerJoin("pillars ON pillars.pillarid=functions.pillarid"). | ||
OrderBy("fismasystems.fismasystemid, pillars.ordr, questions.ordr ASC") | ||
|
||
if input.UserID != nil { | ||
sqlb = sqlb.InnerJoin("users_fismasystems ON users_fismasystems.userid=? AND users_fismasystems.fismasystemid=fismasystems.fismasystemid", input.UserID) | ||
} | ||
|
||
if len(input.FismaSystemIDs) > 0 { | ||
sqlb = sqlb.Where(squirrel.Eq{"fismasystems.fismasystemid": input.FismaSystemIDs}) | ||
} | ||
|
||
sql, boundArgs, _ := sqlb.ToSql() | ||
rows, err := query(ctx, sql, boundArgs...) | ||
|
||
if err != nil { | ||
log.Println(err, sql) | ||
return nil, trapError(err) | ||
} | ||
|
||
return pgx.CollectRows(rows, func(row pgx.CollectableRow) (*Answer, error) { | ||
answer := Answer{} | ||
err := row.Scan(&answer.DataCall, &answer.FismaSystemID, &answer.FismaAcronym, &answer.DataCenterEnvironment, &answer.Pillar, &answer.Question, &answer.Function, &answer.Description, &answer.OptionName, &answer.Score, &answer.Notes) | ||
return &answer, trapError(err) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package spreadsheet | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/CMS-Enterprise/ztmf/backend/cmd/api/internal/model" | ||
"github.com/xuri/excelize/v2" | ||
) | ||
|
||
func Excel(answers []*model.Answer) (*excelize.File, error) { | ||
|
||
sheet := "Sheet1" | ||
|
||
f := excelize.NewFile() | ||
|
||
f.SetCellValue(sheet, "A1", "Fisma Acronym") | ||
f.SetCellValue(sheet, "B1", "Data Center Environment") | ||
f.SetCellValue(sheet, "C1", "Pillar") | ||
f.SetCellValue(sheet, "D1", "Function") | ||
f.SetCellValue(sheet, "E1", "Function Description") | ||
f.SetCellValue(sheet, "F1", "Question") | ||
f.SetCellValue(sheet, "G1", "Answer") | ||
f.SetCellValue(sheet, "H1", "Score") | ||
f.SetCellValue(sheet, "I1", "ADO Answer Details") | ||
|
||
for i, a := range answers { | ||
row := i + 2 // i starts at 0 and headers are in row 1 | ||
f.SetCellValue(sheet, fmt.Sprintf("A%d", row), a.FismaAcronym) | ||
f.SetCellValue(sheet, fmt.Sprintf("B%d", row), a.DataCenterEnvironment) | ||
f.SetCellValue(sheet, fmt.Sprintf("C%d", row), a.Pillar) | ||
f.SetCellValue(sheet, fmt.Sprintf("D%d", row), a.Function) | ||
f.SetCellValue(sheet, fmt.Sprintf("E%d", row), a.Description) | ||
f.SetCellValue(sheet, fmt.Sprintf("F%d", row), a.Question) | ||
f.SetCellValue(sheet, fmt.Sprintf("G%d", row), a.OptionName) | ||
f.SetCellValue(sheet, fmt.Sprintf("H%d", row), a.Score) | ||
f.SetCellValue(sheet, fmt.Sprintf("I%d", row), a.Notes) | ||
} | ||
|
||
return f, nil | ||
} |
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters