예제 #1
0
// 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
}
예제 #2
0
// 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
}
예제 #3
0
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)
		}
	}
}
예제 #4
0
// 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
}