Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API-13315: Fix FilledForm answer field #158

Merged
merged 7 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 142 additions & 8 deletions agent/structures.go
Original file line number Diff line number Diff line change
Expand Up @@ -422,13 +422,8 @@ type Event struct {

// FilledForm represents LiveChat filled form event.
type FilledForm struct {
Fields []struct {
ID string `json:"id"`
Label string `json:"label"`
Type string `json:"type"`
Value string `json:"value"`
} `json:"fields"`
FormType string `json:"form_type"`
Fields []FilledFormField `json:"fields"`
FormType string `json:"form_type"`
Event
}

Expand All @@ -444,12 +439,151 @@ func (e *Event) FilledForm() *FilledForm {
if err := json.Unmarshal(e.Fields, &f.Fields); err != nil {
return nil
}
if err := json.Unmarshal(e.FormType, &f.FormType); err != nil {
if err := internal.UnmarshalOptionalRawField(e.FormType, &f.FormType); err != nil {
return nil
}
return &f
}

// FilledFormField represents a field in LiveChat filled form event.
type FilledFormField struct {
ID string `json:"id"`
Type string `json:"type"`
Label string `json:"label"`
filledFormFieldSpecific
}

type filledFormFieldSpecific struct {
Answer json.RawMessage `json:"answer,omitempty"`
Answers json.RawMessage `json:"answers,omitempty"`
}

// FilledFormFieldSingle represents a field in LiveChat filled form event with a single answer (e.g. text field).
type FilledFormFieldSingle struct {
FilledFormField
Answer string `json:"answer"`
}

// FilledFormFieldSingleChoice represents a field in LiveChat filled form event with a single choice answer (e.g. radio button).
type FilledFormFieldSingleChoice struct {
FilledFormField
Answer struct {
ID string `json:"id"`
Label string `json:"label"`
} `json:"answer"`
}

// FilledFormFieldMultiChoice represents a field in LiveChat filled form event with a multiple choice answer (e.g. checkbox).
type FilledFormFieldMultiChoice struct {
FilledFormField
Answers []struct {
ID string `json:"id"`
Label string `json:"label"`
} `json:"answer"`
}

// FilledFormEmailCheckbox represents a field in LiveChat filled form event with a checkbox for email answer.
type FilledFormEmailCheckbox struct {
FilledFormField
Answer bool `json:"answer"`
}

// FilledFormFieldGroupChooser represents a field in LiveChat filled form event with a group_chooser answer.
type FilledFormFieldGroupChooser struct {
FilledFormField
Answer struct {
ID string `json:"id"`
Label string `json:"label"`
GroupID int `json:"group_id"`
} `json:"answer"`
}

// Single method converts FilledFormField object to FilledFormFieldSingle object
// if FilledFormField's Type is one of "name", "email", "question", "textarea", "subject".
// If Type is different or FilledFormField is malformed, then it returns nil.
func (f *FilledFormField) Single() *FilledFormFieldSingle {
supportedTypes := map[string]struct{}{
"name": {},
"email": {},
"question": {},
"textarea": {},
"subject": {},
}
if _, ok := supportedTypes[f.Type]; !ok {
return nil
}
var s FilledFormFieldSingle
s.ID, s.Label = f.ID, f.Label
if err := json.Unmarshal(f.Answer, &s.Answer); err != nil {
return nil
}
return &s
}

// SingleChoice method converts FilledFormField object to FilledFormFieldSingleChoice object
// if FilledFormField's Type is "radio" or "select".
// If Type is different or FilledFormField is malformed, then it returns nil.
func (f *FilledFormField) SingleChoice() *FilledFormFieldSingleChoice {
supportedTypes := map[string]struct{}{
"radio": {},
"select": {},
}
if _, ok := supportedTypes[f.Type]; !ok {
return nil
}
var sc FilledFormFieldSingleChoice
sc.ID, sc.Label = f.ID, f.Label
if err := json.Unmarshal(f.Answer, &sc.Answer); err != nil {
return nil
}
return &sc
}

// MultiChoice method converts FilledFormField object to FilledFormFieldMultiChoice object
// if FilledFormField's Type is "checkbox".
// If Type is different or FilledFormField is malformed, then it returns nil.
func (f *FilledFormField) MultiChoice() *FilledFormFieldMultiChoice {
if f.Type != "checkbox" {
return nil
}
var mc FilledFormFieldMultiChoice
mc.ID, mc.Label = f.ID, f.Label
if err := json.Unmarshal(f.Answers, &mc.Answers); err != nil {
return nil
}
return &mc
}

// EmailCheckbox method converts FilledFormField object to FilledFormEmailCheckbox object
// if FilledFormField's Type is "checkbox_for_email".
// If Type is different or FilledFormField is malformed, then it returns nil.
func (f *FilledFormField) EmailCheckbox() *FilledFormEmailCheckbox {
if f.Type != "checkbox_for_email" {
return nil
}
var ec FilledFormEmailCheckbox
ec.ID, ec.Label = f.ID, f.Label
if err := json.Unmarshal(f.Answer, &ec.Answer); err != nil {
return nil
}
return &ec
}

// GroupChooser method converts FilledFormField object to FilledFormFieldGroupChooser object
// if FilledFormField's Type is "group_chooser".
// If Type is different or FilledFormField is malformed, then it returns nil.
func (f *FilledFormField) GroupChooser() *FilledFormFieldGroupChooser {
if f.Type != "group_chooser" {
return nil
}
var gc FilledFormFieldGroupChooser
gc.ID, gc.Label = f.ID, f.Label
if err := json.Unmarshal(f.Answer, &gc.Answer); err != nil {
return nil
}
return &gc
}

// Postback represents postback data in LiveChat message event.
type Postback struct {
ID string `json:"id"`
Expand Down
125 changes: 125 additions & 0 deletions agent/structures_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package agent_test

import (
"encoding/json"
"fmt"
"testing"

"github.com/livechat/lc-sdk-go/v6/agent"
)

func TestFilledFormTypesOK(t *testing.T) {
rawFields := json.RawMessage(`[{"id": "42", "type": "email", "answer":"[email protected]"}]`)
testCases := []struct {
name string
expectedFormType string
rawFormType json.RawMessage
}{
{name: "Missing form_type", expectedFormType: "", rawFormType: nil},
{name: "Null form_type", expectedFormType: "", rawFormType: json.RawMessage(`null`)},
{name: "Empty string form_type", expectedFormType: "", rawFormType: json.RawMessage(`""`)},
{name: "Existing form_type", expectedFormType: "prechat", rawFormType: json.RawMessage(`"prechat"`)},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var suffixFormType string
if tc.rawFormType != nil {
suffixFormType = fmt.Sprintf(`,"form_type":%s`, tc.rawFormType)
}

rawEvent := json.RawMessage(fmt.Sprintf(`{"type":"filled_form","fields":%s%s}`, rawFields, suffixFormType))
filledForm := FilledFormFromJSON(t, rawEvent)

if filledForm.FormType != tc.expectedFormType {
t.Fatalf("FilledForm() did not set FormType correctly. Expected %q, got %q", tc.expectedFormType, filledForm.FormType)
}

if len(filledForm.Fields) != 1 {
t.Fatalf("FilledForm() did not return expected number of fields: %d", len(filledForm.Fields))
}
})
}
}

func TestFilledFormFieldTypesOK(t *testing.T) {
t.Run("Single-answer filled_form field", func(t *testing.T) {
rawFormFields := json.RawMessage(`[{"id": "42", "type": "email", "answer":"[email protected]"}]`)
singleField := FilledFormFieldFromJSON(t, rawFormFields).Single()

if singleField.Answer != "[email protected]" {
t.Errorf("FilledFormField() did not return expected answer: %s", singleField.Answer)
}
})

t.Run("Single-choice filled_form field", func(t *testing.T) {
rawFormFields := json.RawMessage(`[{"id": "42", "type": "radio", "answer":{"id": "1", "label": "foo"}}]`)
singleChoiceField := FilledFormFieldFromJSON(t, rawFormFields).SingleChoice()

if singleChoiceField.Answer.ID != "1" || singleChoiceField.Answer.Label != "foo" {
t.Errorf("FilledFormField() did not return expected answer: %s", singleChoiceField.Answer)
}
})

t.Run("Multiple-choice filled_form field", func(t *testing.T) {
rawFormFields := json.RawMessage(`[{"id": "42", "type": "checkbox", "answers":[{"id": "1", "label": "foo"}, {"id": "2", "label": "bar"}]}]`)
multiChoiceField := FilledFormFieldFromJSON(t, rawFormFields).MultiChoice()

if len(multiChoiceField.Answers) != 2 {
t.Errorf("FilledFormField() did not return expected number of answers: %d", len(multiChoiceField.Answer))
}
if multiChoiceField.Answers[0].ID != "1" || multiChoiceField.Answers[0].Label != "foo" {
t.Errorf("FilledFormField() did not return expected answer: %s", multiChoiceField.Answers[0])
}
if multiChoiceField.Answers[1].ID != "2" || multiChoiceField.Answers[1].Label != "bar" {
t.Errorf("FilledFormField() did not return expected answer: %s", multiChoiceField.Answers[1])
}
})

t.Run("Email checkbox filled_form field", func(t *testing.T) {
rawFormFields := json.RawMessage(`[{"id": "42", "type": "checkbox_for_email", "answer": true}]`)
emailCheckboxField := FilledFormFieldFromJSON(t, rawFormFields).EmailCheckbox()

if !emailCheckboxField.Answer {
t.Errorf("FilledFormField() did not return expected answer: %t", emailCheckboxField.Answer)
}
})

t.Run("Group chooser filled_form field", func(t *testing.T) {
rawFormFields := json.RawMessage(`[{"id": "42", "type": "group_chooser", "answer":{"id": "1", "label": "foo", "group_id": 42}}]`)
groupChooserField := FilledFormFieldFromJSON(t, rawFormFields).GroupChooser()

if groupChooserField.Answer.ID != "1" || groupChooserField.Answer.Label != "foo" || groupChooserField.Answer.GroupID != 42 {
t.Errorf("FilledFormField() did not return expected answer: %v", groupChooserField.Answer)
}
})
}

func FilledFormFromJSON(t *testing.T, rawEvent json.RawMessage) *agent.FilledForm {
t.Helper()

var event agent.Event
if err := json.Unmarshal(rawEvent, &event); err != nil {
t.Fatalf("unmarshalling base event failed: %s", err)
}

filledForm := event.FilledForm()
if filledForm == nil {
t.Fatalf("FilledForm() returned nil")
}

return filledForm
}

func FilledFormFieldFromJSON(t *testing.T, rawFields json.RawMessage) *agent.FilledFormField {
t.Helper()

rawEvent := json.RawMessage(fmt.Sprintf(`{"type":"filled_form","fields":%s}`, rawFields))
filledForm := FilledFormFromJSON(t, rawEvent)

if len(filledForm.Fields) != 1 {
t.Fatalf("FilledForm() did not return expected number of fields: %d", len(filledForm.Fields))
}

return &filledForm.Fields[0]
}
Loading
Loading