// Search retrieves a set of FormSubmission's based on the search params // provided in the query string. // 200 Success, 400 Bad Request, 404 Not Found, 500 Internal func (formSubmissionHandle) Search(c *web.Context) error { formID := c.Params["form_id"] limit, err := strconv.Atoi(c.Request.URL.Query().Get("limit")) if err != nil { limit = 0 } skip, err := strconv.Atoi(c.Request.URL.Query().Get("skip")) if err != nil { skip = 0 } opts := submission.SearchOpts{ Query: c.Request.URL.Query().Get("search"), FilterBy: c.Request.URL.Query().Get("filterby"), } if c.Request.URL.Query().Get("orderby") == "dsc" { opts.DscOrder = true } results, err := submission.Search(c.SessionID, c.Ctx["DB"].(*db.DB), formID, limit, skip, opts) if err != nil { return err } c.Respond(results, http.StatusOK) return nil }
// Download retrieves a set of FormSubmission's based on the search params // provided in the query string and generates a CSV with the replies. // 200 Success, 400 Bad Request, 404 Not Found, 500 Internal func (formSubmissionHandle) Download(c *web.Context) error { if c.Request.URL.Query().Get("download") != "true" { // Returns a URL To the CSV file. results := submission.SearchResults{} results.CSVURL = fmt.Sprintf("http://%v%v?download=true", c.Request.Host, c.Request.URL.Path) c.Respond(results, http.StatusOK) return nil } // Generates and returns the CSV file. // It will only arrive to this handler if the formID exists. formID := c.Params["form_id"] var ( limit int skip int ) opts := submission.SearchOpts{ Query: c.Request.URL.Query().Get("search"), FilterBy: c.Request.URL.Query().Get("filterby"), } if c.Request.URL.Query().Get("orderby") == "dsc" { opts.DscOrder = true } results, err := submission.Search(c.SessionID, c.Ctx["DB"].(*db.DB), formID, limit, skip, opts) if err != nil { return err } // Convert into [][]string to encode the CSV. csvData, err := encodeSubmissionsToCSV(results.Submissions) if err != nil { return err } // Set the content type. c.Header().Set("Content-Type", "text/csv") c.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"ask_%s_%s.csv\"", formID, time.Now().String())) c.WriteHeader(http.StatusOK) c.Status = http.StatusOK c.Write(csvData) return nil }
func Test_Search(t *testing.T) { subs, db := setup(t, "submission") defer teardown(t, db) t.Log("Given the need to create and delete submissions.") { t.Log("\tWhen starting from an empty submissions collection") { //---------------------------------------------------------------------- // Create the submissions. for _, sub := range subs { if err := submission.Create(tests.Context, db, sub.FormID.Hex(), &sub); err != nil { t.Fatalf("\t%s\tShould be able to create a submission : %s", tests.Failed, err) } } t.Logf("\t%s\tShould be able to create submissions.", tests.Success) //---------------------------------------------------------------------- // Search the submissions. results, err := submission.Search(tests.Context, db, subs[0].FormID.Hex(), len(subs), 0, submission.SearchOpts{}) if err != nil { t.Fatalf("\t%s\tShould be able to list submissions : %s", tests.Failed, err) } t.Logf("\t%s\tShould be able to list submissions", tests.Success) //---------------------------------------------------------------------- // Ensure that the counts make sense. if results.Counts.TotalSearch != len(subs) { t.Fatalf("\t%s\tShould have the same total search count : Only found %d results, expected %d", tests.Failed, results.Counts.TotalSearch, len(subs)) } t.Logf("\t%s\tShould have the same total search count", tests.Success) if results.Counts.TotalSubmissions != len(subs) { t.Fatalf("\t%s\tShould have the same total submission count : Only found %d results, expected %d", tests.Failed, results.Counts.TotalSubmissions, len(subs)) } t.Logf("\t%s\tShould have the same total submission count", tests.Success) //---------------------------------------------------------------------- // Verify that the docs exist inside the results. matchSubmissions(t, subs, results.Submissions) //---------------------------------------------------------------------- // Search the submissions with a query. results, err = submission.Search(tests.Context, db, subs[0].FormID.Hex(), len(subs), 0, submission.SearchOpts{ Query: "Option 1", }) if err != nil { t.Fatalf("\t%s\tShould be able to list submissions : %s", tests.Failed, err) } t.Logf("\t%s\tShould be able to list submissions", tests.Success) //---------------------------------------------------------------------- // Ensure that the counts make sense. if results.Counts.TotalSearch != len(subs) { t.Fatalf("\t%s\tShould have the same total search count : Only found %d results, expected %d", tests.Failed, results.Counts.TotalSearch, len(subs)) } t.Logf("\t%s\tShould have the same total search count", tests.Success) if results.Counts.TotalSubmissions != len(subs) { t.Fatalf("\t%s\tShould have the same total submission count : Only found %d results, expected %d", tests.Failed, results.Counts.TotalSubmissions, len(subs)) } t.Logf("\t%s\tShould have the same total submission count", tests.Success) //---------------------------------------------------------------------- // Verify that the docs exist inside the results. matchSubmissions(t, subs, results.Submissions) //---------------------------------------------------------------------- // Search the submissions with a filter. results, err = submission.Search(tests.Context, db, subs[0].FormID.Hex(), len(subs), 0, submission.SearchOpts{ FilterBy: "flagged", }) if err != nil { t.Fatalf("\t%s\tShould be able to list submissions : %s", tests.Failed, err) } t.Logf("\t%s\tShould be able to list submissions", tests.Success) //---------------------------------------------------------------------- // Ensure that the counts make sense. if results.Counts.TotalSearch != len(subs) { t.Fatalf("\t%s\tShould have the same total search count : Only found %d results, expected %d", tests.Failed, results.Counts.TotalSearch, len(subs)) } t.Logf("\t%s\tShould have the same total search count", tests.Success) if results.Counts.TotalSubmissions != len(subs) { t.Fatalf("\t%s\tShould have the same total submission count : Only found %d results, expected %d", tests.Failed, results.Counts.TotalSubmissions, len(subs)) } t.Logf("\t%s\tShould have the same total submission count", tests.Success) //---------------------------------------------------------------------- // Verify that the docs exist inside the results. matchSubmissions(t, subs, results.Submissions) //---------------------------------------------------------------------- // Search the submissions with a negating filter. results, err = submission.Search(tests.Context, db, subs[0].FormID.Hex(), len(subs), 0, submission.SearchOpts{ FilterBy: "-flagged", }) if err != nil { t.Fatalf("\t%s\tShould be able to list submissions : %s", tests.Failed, err) } t.Logf("\t%s\tShould be able to list submissions", tests.Success) //---------------------------------------------------------------------- // Verify that the results do not contain any results (as all results // contain the flagged flag). if results.Counts.TotalSearch != 0 { t.Fatalf("\t%s\tShould have the same total search count : Only found %d results, expected %d", tests.Failed, results.Counts.TotalSearch, 0) } t.Logf("\t%s\tShould have the same total search count", tests.Success) if len(results.Submissions) != 0 { t.Fatalf("\t%s\tShould not list any results unmatched : Found %d, expected 0", tests.Failed, len(results.Submissions)) } t.Logf("\t%s\tShould not list any results unmatched", tests.Failed) } } }
// GroupSubmissions organizes submissions by Group. It looks for questions with the group by flag // and creates Group structs. func GroupSubmissions(context interface{}, db *db.DB, formID string, limit int, skip int, opts submission.SearchOpts) (map[Group][]submission.Submission, error) { // Validate the formID. if !bson.IsObjectIdHex(formID) { log.Error(context, "TextAggregate", ErrInvalidID, "Completed") return nil, ErrInvalidID } // Load the form so that we can find which questions are flagged groupBy. form, err := Retrieve(context, db, formID) if err != nil { return nil, err } // Create a container for questions flagged as groupSubmissions. groupBys := make(map[string]bool) // Find the widgets to groupBy and add them to the map. for _, step := range form.Steps { for _, widget := range step.Widgets { // Type the props value. props, ok := widget.Props.(bson.M) if !ok { continue } // If groupSubmissions is set, add the ID to the map of questions to include. if props["groupSubmissions"] == true { groupBys[widget.ID] = true } } } // Get the submissions for the form.Collection. subs, err := submission.Search(context, db, formID, limit, skip, opts) if err != nil { return nil, err } groups := make(map[Group][]submission.Submission) // Scan all the submissions and answers. for _, sub := range subs.Submissions { // Add all submissions to the [all,all] group. group := Group{ ID: "all", Question: "all", Answer: "all", } tmp := groups[group] tmp = append(tmp, sub) groups[group] = tmp // Look for answers that may contain sub groups. for _, ans := range sub.Answers { // Skip nil answers. if ans.Answer == nil { continue } // Only include answers of questions flagged with "includeInGroups". _, ok := groupBys[ans.WidgetID] if !ok { continue } // Unpack the answer object. a, ok := ans.Answer.(bson.M) if !ok { continue } // Retrieve the options param from the answer. options, ok := a["options"] // Options == nil points to a non MultipleChoice answer. if !ok || options == nil { continue } // This map of interfaces represent each checkbox the user clicked. opts, ok := options.([]interface{}) if !ok { continue } for _, opt := range opts { // Unpack the option. op := opt.(bson.M) // Use the title of the option as the map key. selection, ok := op["title"].(string) if !ok { continue } // Hash the answer text for a unique key, as no actual key exists. hasher := md5.New() hasher.Write([]byte(selection)) optKeyStr := hex.EncodeToString(hasher.Sum(nil)) // Add the submission to this subgroup. group := Group{ ID: optKeyStr, Question: ans.Question, Answer: selection, } tmp := groups[group] tmp = append(tmp, sub) groups[group] = tmp } } } return groups, nil }