diff --git a/.changelog/9336.txt b/.changelog/9336.txt new file mode 100644 index 0000000000..9972f21095 --- /dev/null +++ b/.changelog/9336.txt @@ -0,0 +1,6 @@ +```release-note:enhancement +dialogflowcx: added `is_default_welcome_intent` and `is_default_negative_intent` fields to `google_dialogflow_cx_intent` resource to allow management of default intent resources via Terraform +``` +```release-note:enhancement +dialogflowcx: added `is_default_start_flow` field to `google_dialogflow_cx_flow` resource to allow management of default flow resources via Terraform +``` diff --git a/google-beta/services/dialogflowcx/resource_dialogflow_cx_flow.go b/google-beta/services/dialogflowcx/resource_dialogflow_cx_flow.go index f37b355533..dfac9ec803 100644 --- a/google-beta/services/dialogflowcx/resource_dialogflow_cx_flow.go +++ b/google-beta/services/dialogflowcx/resource_dialogflow_cx_flow.go @@ -687,6 +687,15 @@ You may set this, for example: Description: `The unique identifier of the flow. Format: projects//locations//agents//flows/.`, }, + "is_default_start_flow": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + Description: `Marks this as the [Default Start Flow](https://cloud.google.com/dialogflow/cx/docs/concept/flow#start) for an agent. When you create an agent, the Default Start Flow is created automatically. +The Default Start Flow cannot be deleted; deleting the 'google_dialogflow_cx_flow' resource does nothing to the underlying GCP resources. + +~> Avoid having multiple 'google_dialogflow_cx_flow' resources linked to the same agent with 'is_default_start_flow = true' because they will compete to control a single Default Start Flow resource in GCP.`, + }, }, UseJSONNumber: true, } @@ -776,6 +785,34 @@ func resourceDialogflowCXFlowCreate(d *schema.ResourceData, meta interface{}) er } url = strings.Replace(url, "-dialogflow", fmt.Sprintf("%s-dialogflow", location), 1) + + // if it's a default object Dialogflow creates for you, "Update" instead of "Create" + // Note: below we try to access fields that aren't present in the resource, because this custom code is reused across multiple Dialogflow resources that contain different fields. When the field isn't present, we deliberately ignore the error and the boolean is false. + isDefaultStartFlow, _ := d.Get("is_default_start_flow").(bool) + isDefaultWelcomeIntent, _ := d.Get("is_default_welcome_intent").(bool) + isDefaultNegativeIntent, _ := d.Get("is_default_negative_intent").(bool) + if isDefaultStartFlow || isDefaultWelcomeIntent || isDefaultNegativeIntent { + // hardcode the default object ID: + var defaultObjName string + if isDefaultStartFlow || isDefaultWelcomeIntent { + defaultObjName = "00000000-0000-0000-0000-000000000000" + } + if isDefaultNegativeIntent { + defaultObjName = "00000000-0000-0000-0000-000000000001" + } + + // Store the ID + d.Set("name", defaultObjName) + id, err := tpgresource.ReplaceVars(d, config, "{{parent}}/flows/{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + // and defer to the Update method: + log.Printf("[DEBUG] Updating default DialogflowCXFlow") + return resourceDialogflowCXFlowUpdate(d, meta) + } res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ Config: config, Method: "POST", @@ -848,6 +885,8 @@ func resourceDialogflowCXFlowRead(d *schema.ResourceData, meta interface{}) erro return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("DialogflowCXFlow %q", d.Id())) } + // Explicitly set virtual fields to default values if unset + if err := d.Set("name", flattenDialogflowCXFlowName(res["name"], d, config)); err != nil { return fmt.Errorf("Error reading Flow: %s", err) } @@ -1047,6 +1086,17 @@ func resourceDialogflowCXFlowDelete(d *schema.ResourceData, meta interface{}) er } url = strings.Replace(url, "-dialogflow", fmt.Sprintf("%s-dialogflow", location), 1) + + // if it's a default object Dialogflow creates for you, skip deletion + // Note: below we try to access fields that aren't present in the resource, because this custom code is reused across multiple Dialogflow resources that contain different fields. When the field isn't present, we deliberately ignore the error and the boolean is false. + isDefaultStartFlow, _ := d.Get("is_default_start_flow").(bool) + isDefaultWelcomeIntent, _ := d.Get("is_default_welcome_intent").(bool) + isDefaultNegativeIntent, _ := d.Get("is_default_negative_intent").(bool) + if isDefaultStartFlow || isDefaultWelcomeIntent || isDefaultNegativeIntent { + // we can't delete these resources so do nothing + log.Printf("[DEBUG] Not deleting default DialogflowCXFlow") + return nil + } log.Printf("[DEBUG] Deleting Flow %q", d.Id()) // err == nil indicates that the billing_project value was found @@ -1089,6 +1139,11 @@ func resourceDialogflowCXFlowImport(d *schema.ResourceData, meta interface{}) ([ } d.SetId(id) + // Set is_default_start_flow if the resource is actually the Default Start Flow + if d.Get("name").(string) == "00000000-0000-0000-0000-000000000000" { + d.Set("is_default_start_flow", true) + } + return []*schema.ResourceData{d}, nil } diff --git a/google-beta/services/dialogflowcx/resource_dialogflow_cx_flow_generated_test.go b/google-beta/services/dialogflowcx/resource_dialogflow_cx_flow_generated_test.go index 3bd675f1bf..8fbda28aca 100644 --- a/google-beta/services/dialogflowcx/resource_dialogflow_cx_flow_generated_test.go +++ b/google-beta/services/dialogflowcx/resource_dialogflow_cx_flow_generated_test.go @@ -30,6 +30,98 @@ import ( transport_tpg "github.com/hashicorp/terraform-provider-google-beta/google-beta/transport" ) +func TestAccDialogflowCXFlow_dialogflowcxFlowBasicExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckDialogflowCXFlowDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccDialogflowCXFlow_dialogflowcxFlowBasicExample(context), + }, + { + ResourceName: "google_dialogflow_cx_flow.basic_flow", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"parent"}, + }, + }, + }) +} + +func testAccDialogflowCXFlow_dialogflowcxFlowBasicExample(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_dialogflow_cx_agent" "agent" { + display_name = "tf-test-dialogflowcx-agent%{random_suffix}" + location = "global" + default_language_code = "en" + supported_language_codes = ["fr", "de", "es"] + time_zone = "America/New_York" + description = "Example description." + avatar_uri = "https://cloud.google.com/_static/images/cloud/icons/favicons/onecloud/super_cloud.png" + enable_stackdriver_logging = true + enable_spell_correction = true + speech_to_text_settings { + enable_speech_adaptation = true + } +} + + +resource "google_dialogflow_cx_flow" "basic_flow" { + parent = google_dialogflow_cx_agent.agent.id + display_name = "MyFlow" + description = "Test Flow" + + nlu_settings { + classification_threshold = 0.3 + model_type = "MODEL_TYPE_STANDARD" + } + + event_handlers { + event = "custom-event" + trigger_fulfillment { + return_partial_responses = false + messages { + text { + text = ["I didn't get that. Can you say it again?"] + } + } + } + } + + event_handlers { + event = "sys.no-match-default" + trigger_fulfillment { + return_partial_responses = false + messages { + text { + text = ["Sorry, could you say that again?"] + } + } + } + } + + event_handlers { + event = "sys.no-input-default" + trigger_fulfillment { + return_partial_responses = false + messages { + text { + text = ["One more time?"] + } + } + } + } +} +`, context) +} + func TestAccDialogflowCXFlow_dialogflowcxFlowFullExample(t *testing.T) { t.Parallel() @@ -342,6 +434,112 @@ resource "google_dialogflow_cx_flow" "basic_flow" { `, context) } +func TestAccDialogflowCXFlow_dialogflowcxFlowDefaultStartFlowExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckDialogflowCXFlowDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccDialogflowCXFlow_dialogflowcxFlowDefaultStartFlowExample(context), + }, + { + ResourceName: "google_dialogflow_cx_flow.default_start_flow", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"parent"}, + }, + }, + }) +} + +func testAccDialogflowCXFlow_dialogflowcxFlowDefaultStartFlowExample(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_dialogflow_cx_agent" "agent" { + display_name = "tf-test-dialogflowcx-agent%{random_suffix}" + location = "global" + default_language_code = "en" + time_zone = "America/New_York" +} + +resource "google_dialogflow_cx_intent" "default_welcome_intent" { + parent = google_dialogflow_cx_agent.agent.id + is_default_welcome_intent = true + display_name = "Default Welcome Intent" + priority = 1 + training_phrases { + parts { + text = "Hello" + } + repeat_count = 1 + } +} + + +resource "google_dialogflow_cx_flow" "default_start_flow" { + parent = google_dialogflow_cx_agent.agent.id + is_default_start_flow = true + display_name = "Default Start Flow" + description = "A start flow created along with the agent" + + nlu_settings { + classification_threshold = 0.3 + model_type = "MODEL_TYPE_STANDARD" + } + + transition_routes { + intent = google_dialogflow_cx_intent.default_welcome_intent.id + trigger_fulfillment { + messages { + text { + text = ["Response to default welcome intent."] + } + } + } + } + + event_handlers { + event = "custom-event" + trigger_fulfillment { + messages { + text { + text = ["This is a default flow."] + } + } + } + } + + event_handlers { + event = "sys.no-match-default" + trigger_fulfillment { + messages { + text { + text = ["We've updated the default flow no-match response!"] + } + } + } + } + + event_handlers { + event = "sys.no-input-default" + trigger_fulfillment { + messages { + text { + text = ["We've updated the default flow no-input response!"] + } + } + } + } +} +`, context) +} + func testAccCheckDialogflowCXFlowDestroyProducer(t *testing.T) func(s *terraform.State) error { return func(s *terraform.State) error { for name, rs := range s.RootModule().Resources { diff --git a/google-beta/services/dialogflowcx/resource_dialogflow_cx_intent.go b/google-beta/services/dialogflowcx/resource_dialogflow_cx_intent.go index 046c0ccfff..56eb282830 100644 --- a/google-beta/services/dialogflowcx/resource_dialogflow_cx_intent.go +++ b/google-beta/services/dialogflowcx/resource_dialogflow_cx_intent.go @@ -71,7 +71,8 @@ func ResourceDialogflowCXIntent() *schema.Resource { Type: schema.TypeBool, Optional: true, Description: `Indicates whether this is a fallback intent. Currently only default fallback intent is allowed in the agent, which is added upon agent creation. -Adding training phrases to fallback intent is useful in the case of requests that are mistakenly matched, since training phrases assigned to fallback intents act as negative examples that triggers no-match event.`, +Adding training phrases to fallback intent is useful in the case of requests that are mistakenly matched, since training phrases assigned to fallback intents act as negative examples that triggers no-match event. +To manage the fallback intent, set 'is_default_negative_intent = true'`, }, "labels": { Type: schema.TypeMap, @@ -201,6 +202,24 @@ Format: projects//locations//agents//intents/ and default labels configured on the provider.`, Elem: &schema.Schema{Type: schema.TypeString}, }, + "is_default_welcome_intent": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + Description: `Marks this as the [Default Welcome Intent](https://cloud.google.com/dialogflow/cx/docs/concept/intent#welcome) for an agent. When you create an agent, a Default Welcome Intent is created automatically. +The Default Welcome Intent cannot be deleted; deleting the 'google_dialogflow_cx_intent' resource does nothing to the underlying GCP resources. + +~> Avoid having multiple 'google_dialogflow_cx_intent' resources linked to the same agent with 'is_default_welcome_intent = true' because they will compete to control a single Default Welcome Intent resource in GCP.`, + }, + "is_default_negative_intent": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + Description: `Marks this as the [Default Negative Intent](https://cloud.google.com/dialogflow/cx/docs/concept/intent#negative) for an agent. When you create an agent, a Default Negative Intent is created automatically. +The Default Negative Intent cannot be deleted; deleting the 'google_dialogflow_cx_intent' resource does nothing to the underlying GCP resources. + +~> Avoid having multiple 'google_dialogflow_cx_intent' resources linked to the same agent with 'is_default_negative_intent = true' because they will compete to control a single Default Negative Intent resource in GCP.`, + }, }, UseJSONNumber: true, } @@ -290,6 +309,34 @@ func resourceDialogflowCXIntentCreate(d *schema.ResourceData, meta interface{}) } url = strings.Replace(url, "-dialogflow", fmt.Sprintf("%s-dialogflow", location), 1) + + // if it's a default object Dialogflow creates for you, "Update" instead of "Create" + // Note: below we try to access fields that aren't present in the resource, because this custom code is reused across multiple Dialogflow resources that contain different fields. When the field isn't present, we deliberately ignore the error and the boolean is false. + isDefaultStartFlow, _ := d.Get("is_default_start_flow").(bool) + isDefaultWelcomeIntent, _ := d.Get("is_default_welcome_intent").(bool) + isDefaultNegativeIntent, _ := d.Get("is_default_negative_intent").(bool) + if isDefaultStartFlow || isDefaultWelcomeIntent || isDefaultNegativeIntent { + // hardcode the default object ID: + var defaultObjName string + if isDefaultStartFlow || isDefaultWelcomeIntent { + defaultObjName = "00000000-0000-0000-0000-000000000000" + } + if isDefaultNegativeIntent { + defaultObjName = "00000000-0000-0000-0000-000000000001" + } + + // Store the ID + d.Set("name", defaultObjName) + id, err := tpgresource.ReplaceVars(d, config, "{{parent}}/intents/{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + // and defer to the Update method: + log.Printf("[DEBUG] Updating default DialogflowCXIntent") + return resourceDialogflowCXIntentUpdate(d, meta) + } res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ Config: config, Method: "POST", @@ -362,6 +409,8 @@ func resourceDialogflowCXIntentRead(d *schema.ResourceData, meta interface{}) er return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("DialogflowCXIntent %q", d.Id())) } + // Explicitly set virtual fields to default values if unset + if err := d.Set("name", flattenDialogflowCXIntentName(res["name"], d, config)); err != nil { return fmt.Errorf("Error reading Intent: %s", err) } @@ -567,6 +616,17 @@ func resourceDialogflowCXIntentDelete(d *schema.ResourceData, meta interface{}) } url = strings.Replace(url, "-dialogflow", fmt.Sprintf("%s-dialogflow", location), 1) + + // if it's a default object Dialogflow creates for you, skip deletion + // Note: below we try to access fields that aren't present in the resource, because this custom code is reused across multiple Dialogflow resources that contain different fields. When the field isn't present, we deliberately ignore the error and the boolean is false. + isDefaultStartFlow, _ := d.Get("is_default_start_flow").(bool) + isDefaultWelcomeIntent, _ := d.Get("is_default_welcome_intent").(bool) + isDefaultNegativeIntent, _ := d.Get("is_default_negative_intent").(bool) + if isDefaultStartFlow || isDefaultWelcomeIntent || isDefaultNegativeIntent { + // we can't delete these resources so do nothing + log.Printf("[DEBUG] Not deleting default DialogflowCXIntent") + return nil + } log.Printf("[DEBUG] Deleting Intent %q", d.Id()) // err == nil indicates that the billing_project value was found @@ -609,6 +669,14 @@ func resourceDialogflowCXIntentImport(d *schema.ResourceData, meta interface{}) } d.SetId(id) + // Set is_default_X if the resource is actually a Default Intent + if d.Get("name").(string) == "00000000-0000-0000-0000-000000000000" { + d.Set("is_default_welcome_intent", true) + } + if d.Get("name").(string) == "00000000-0000-0000-0000-000000000001" { + d.Set("is_default_negative_intent", true) + } + return []*schema.ResourceData{d}, nil } diff --git a/google-beta/services/dialogflowcx/resource_dialogflow_cx_intent_generated_test.go b/google-beta/services/dialogflowcx/resource_dialogflow_cx_intent_generated_test.go index 0f4b4b271f..f00af47ada 100644 --- a/google-beta/services/dialogflowcx/resource_dialogflow_cx_intent_generated_test.go +++ b/google-beta/services/dialogflowcx/resource_dialogflow_cx_intent_generated_test.go @@ -107,6 +107,107 @@ resource "google_dialogflow_cx_intent" "basic_intent" { `, context) } +func TestAccDialogflowCXIntent_dialogflowcxIntentDefaultNegativeIntentExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckDialogflowCXIntentDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccDialogflowCXIntent_dialogflowcxIntentDefaultNegativeIntentExample(context), + }, + { + ResourceName: "google_dialogflow_cx_intent.default_negative_intent", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"parent", "labels", "terraform_labels"}, + }, + }, + }) +} + +func testAccDialogflowCXIntent_dialogflowcxIntentDefaultNegativeIntentExample(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_dialogflow_cx_agent" "agent" { + display_name = "tf-test-dialogflowcx-agent%{random_suffix}" + location = "global" + default_language_code = "en" + time_zone = "America/New_York" +} + + +resource "google_dialogflow_cx_intent" "default_negative_intent" { + parent = google_dialogflow_cx_agent.agent.id + is_default_negative_intent = true + display_name = "Default Negative Intent" + priority = 1 + is_fallback = true + training_phrases { + parts { + text = "Never match this phrase" + } + repeat_count = 1 + } +} +`, context) +} + +func TestAccDialogflowCXIntent_dialogflowcxIntentDefaultWelcomeIntentExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckDialogflowCXIntentDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccDialogflowCXIntent_dialogflowcxIntentDefaultWelcomeIntentExample(context), + }, + { + ResourceName: "google_dialogflow_cx_intent.default_welcome_intent", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"parent", "labels", "terraform_labels"}, + }, + }, + }) +} + +func testAccDialogflowCXIntent_dialogflowcxIntentDefaultWelcomeIntentExample(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_dialogflow_cx_agent" "agent" { + display_name = "tf-test-dialogflowcx-agent%{random_suffix}" + location = "global" + default_language_code = "en" + time_zone = "America/New_York" +} + + +resource "google_dialogflow_cx_intent" "default_welcome_intent" { + parent = google_dialogflow_cx_agent.agent.id + is_default_welcome_intent = true + display_name = "Default Welcome Intent" + priority = 1 + training_phrases { + parts { + text = "Hello" + } + repeat_count = 1 + } +} +`, context) +} + func testAccCheckDialogflowCXIntentDestroyProducer(t *testing.T) func(s *terraform.State) error { return func(s *terraform.State) error { for name, rs := range s.RootModule().Resources { diff --git a/google-beta/services/dialogflowcx/resource_dialogflowcx_flow_test.go b/google-beta/services/dialogflowcx/resource_dialogflowcx_flow_test.go index 63ebfec55f..d82e2e1429 100644 --- a/google-beta/services/dialogflowcx/resource_dialogflowcx_flow_test.go +++ b/google-beta/services/dialogflowcx/resource_dialogflowcx_flow_test.go @@ -352,3 +352,203 @@ func testAccDialogflowCXFlow_full(context map[string]interface{}) string { } `, context) } + +func TestAccDialogflowCXFlow_defaultStartFlow(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + // Note: this isn't actually a "create" test; it creates a resource in the TF state, but is actually importing the default object GCP has created, then updating it. + Config: testAccDialogflowCXFlow_defaultStartFlow_create(context), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_dialogflow_cx_flow.default_start_flow", "name", "00000000-0000-0000-0000-000000000000"), + resource.TestCheckResourceAttrPair( + "google_dialogflow_cx_flow.default_start_flow", "id", + "google_dialogflow_cx_agent.agent", "start_flow", + ), + ), + }, + { + ResourceName: "google_dialogflow_cx_flow.default_start_flow", + ImportState: true, + ImportStateVerify: true, + }, + { + // This is testing updating the default object without having to create it in the TF state first. + Config: testAccDialogflowCXFlow_defaultStartFlow_update(context), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_dialogflow_cx_flow.default_start_flow", "name", "00000000-0000-0000-0000-000000000000"), + resource.TestCheckResourceAttrPair( + "google_dialogflow_cx_flow.default_start_flow", "id", + "google_dialogflow_cx_agent.agent", "start_flow", + ), + ), + }, + { + ResourceName: "google_dialogflow_cx_flow.default_start_flow", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccDialogflowCXFlow_defaultStartFlow_create(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_dialogflow_cx_agent" "agent" { + display_name = "tf-test-dialogflowcx-agent%{random_suffix}" + location = "global" + default_language_code = "en" + time_zone = "America/New_York" +} + +resource "google_dialogflow_cx_intent" "default_welcome_intent" { + parent = google_dialogflow_cx_agent.agent.id + is_default_welcome_intent = true + display_name = "Default Welcome Intent" + priority = 1 + training_phrases { + parts { + text = "Hello" + } + repeat_count = 1 + } +} + + +resource "google_dialogflow_cx_flow" "default_start_flow" { + parent = google_dialogflow_cx_agent.agent.id + is_default_start_flow = true + display_name = "Default Start Flow" + description = "A start flow created along with the agent" + + nlu_settings { + classification_threshold = 0.3 + model_type = "MODEL_TYPE_STANDARD" + } + + transition_routes { + intent = google_dialogflow_cx_intent.default_welcome_intent.id + trigger_fulfillment { + messages { + text { + text = ["Response to default welcome intent."] + } + } + } + } + + event_handlers { + event = "custom-event" + trigger_fulfillment { + messages { + text { + text = ["Handle a custom event!"] + } + } + } + } + + event_handlers { + event = "sys.no-match-default" + trigger_fulfillment { + messages { + text { + text = ["This is the flow no-match response."] + } + } + } + } + + event_handlers { + event = "sys.no-input-default" + trigger_fulfillment { + messages { + text { + text = ["This is the flow no-input response."] + } + } + } + } +} +`, context) +} + +func testAccDialogflowCXFlow_defaultStartFlow_update(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_dialogflow_cx_agent" "agent" { + display_name = "tf-test-dialogflowcx-agent%{random_suffix}" + location = "global" + default_language_code = "en" + time_zone = "America/New_York" +} + +resource "google_dialogflow_cx_intent" "default_welcome_intent" { + parent = google_dialogflow_cx_agent.agent.id + is_default_welcome_intent = true + display_name = "Default Welcome Intent" + priority = 1 + training_phrases { + parts { + text = "Hello" + } + repeat_count = 1 + } +} + + +resource "google_dialogflow_cx_flow" "default_start_flow" { + parent = google_dialogflow_cx_agent.agent.id + is_default_start_flow = true + display_name = "Default Start Flow" + description = "A start flow created along with the agent" + + nlu_settings { + classification_threshold = 0.5 + model_type = "MODEL_TYPE_STANDARD" + } + + transition_routes { + intent = google_dialogflow_cx_intent.default_welcome_intent.id + trigger_fulfillment { + messages { + text { + text = ["We can update the default welcome intent response!"] + } + } + } + } + + // delete the custom-event handler to show we can + + event_handlers { + event = "sys.no-match-default" + trigger_fulfillment { + messages { + text { + text = ["We an also update the no-match response!"] + } + } + } + } + + event_handlers { + event = "sys.no-input-default" + trigger_fulfillment { + messages { + text { + text = ["The no-input response has been updated too!"] + } + } + } + } +} +`, context) +} diff --git a/google-beta/services/dialogflowcx/resource_dialogflowcx_intent_test.go b/google-beta/services/dialogflowcx/resource_dialogflowcx_intent_test.go index ba9beee8d4..f97f3a6ed2 100644 --- a/google-beta/services/dialogflowcx/resource_dialogflowcx_intent_test.go +++ b/google-beta/services/dialogflowcx/resource_dialogflowcx_intent_test.go @@ -141,3 +141,134 @@ func testAccDialogflowCXIntent_full(context map[string]interface{}) string { } `, context) } + +func TestAccDialogflowCXIntent_defaultIntents(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + // Note: this isn't actually a "create" test; it creates resources in the TF state, but is actually importing the default objects GCP has created, then updating them. + Config: testAccDialogflowCXIntent_defaultIntents_create(context), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_dialogflow_cx_intent.default_negative_intent", "name", "00000000-0000-0000-0000-000000000001"), + resource.TestCheckResourceAttr("google_dialogflow_cx_intent.default_welcome_intent", "name", "00000000-0000-0000-0000-000000000000"), + ), + }, + { + ResourceName: "google_dialogflow_cx_intent.default_negative_intent", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"labels", "terraform_labels"}, + }, + { + ResourceName: "google_dialogflow_cx_intent.default_welcome_intent", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"labels", "terraform_labels"}, + }, + { + // This is testing updating the default objects without having to create them in the TF state first. + Config: testAccDialogflowCXIntent_defaultIntents_update(context), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_dialogflow_cx_intent.default_negative_intent", "name", "00000000-0000-0000-0000-000000000001"), + resource.TestCheckResourceAttr("google_dialogflow_cx_intent.default_welcome_intent", "name", "00000000-0000-0000-0000-000000000000"), + ), + }, + { + ResourceName: "google_dialogflow_cx_intent.default_negative_intent", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"labels", "terraform_labels"}, + }, + { + ResourceName: "google_dialogflow_cx_intent.default_welcome_intent", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"labels", "terraform_labels"}, + }, + }, + }) +} + +func testAccDialogflowCXIntent_defaultIntents_create(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_dialogflow_cx_agent" "agent" { + display_name = "tf-test-dialogflowcx-agent%{random_suffix}" + location = "global" + default_language_code = "en" + time_zone = "America/New_York" +} + +resource "google_dialogflow_cx_intent" "default_negative_intent" { + parent = google_dialogflow_cx_agent.agent.id + is_default_negative_intent = true + display_name = "Default Negative Intent" + priority = 1 + is_fallback = true + training_phrases { + parts { + text = "Never match this phrase" + } + repeat_count = 1 + } +} + +resource "google_dialogflow_cx_intent" "default_welcome_intent" { + parent = google_dialogflow_cx_agent.agent.id + is_default_welcome_intent = true + display_name = "Default Welcome Intent" + priority = 1 + training_phrases { + parts { + text = "Hello" + } + repeat_count = 1 + } +} +`, context) +} + +func testAccDialogflowCXIntent_defaultIntents_update(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_dialogflow_cx_agent" "agent" { + display_name = "tf-test-dialogflowcx-agent%{random_suffix}" + location = "global" + default_language_code = "en" + time_zone = "America/New_York" +} + +resource "google_dialogflow_cx_intent" "default_negative_intent" { + parent = google_dialogflow_cx_agent.agent.id + is_default_negative_intent = true + display_name = "Default Negative Intent" + priority = 1 + is_fallback = true + training_phrases { + parts { + text = "An updated phrase to never match." + } + repeat_count = 2 + } +} + +resource "google_dialogflow_cx_intent" "default_welcome_intent" { + parent = google_dialogflow_cx_agent.agent.id + is_default_welcome_intent = true + display_name = "Default Welcome Intent" + priority = 1 + training_phrases { + parts { + text = "An updated hello." + } + repeat_count = 2 + } +} +`, context) +} diff --git a/google-beta/services/firebasehosting/resource_firebase_hosting_custom_domain.go b/google-beta/services/firebasehosting/resource_firebase_hosting_custom_domain.go index d5d832cddb..796624892b 100644 --- a/google-beta/services/firebasehosting/resource_firebase_hosting_custom_domain.go +++ b/google-beta/services/firebasehosting/resource_firebase_hosting_custom_domain.go @@ -528,6 +528,7 @@ let Hosting serve secure content on its behalf.`, "wait_dns_verification": { Type: schema.TypeBool, Optional: true, + ForceNew: true, Default: false, Description: `If true, Terraform will wait for DNS records to be fully resolved on the 'CustomDomain'. If false, Terraform will not wait for DNS records on the 'CustomDomain'. Any issues in diff --git a/website/docs/r/dialogflow_cx_flow.html.markdown b/website/docs/r/dialogflow_cx_flow.html.markdown index c142e67fd1..8120974f1e 100644 --- a/website/docs/r/dialogflow_cx_flow.html.markdown +++ b/website/docs/r/dialogflow_cx_flow.html.markdown @@ -28,6 +28,78 @@ To get more information about Flow, see: * How-to Guides * [Official Documentation](https://cloud.google.com/dialogflow/cx/docs) + +## Example Usage - Dialogflowcx Flow Basic + + +```hcl +resource "google_dialogflow_cx_agent" "agent" { + display_name = "dialogflowcx-agent" + location = "global" + default_language_code = "en" + supported_language_codes = ["fr", "de", "es"] + time_zone = "America/New_York" + description = "Example description." + avatar_uri = "https://cloud.google.com/_static/images/cloud/icons/favicons/onecloud/super_cloud.png" + enable_stackdriver_logging = true + enable_spell_correction = true + speech_to_text_settings { + enable_speech_adaptation = true + } +} + + +resource "google_dialogflow_cx_flow" "basic_flow" { + parent = google_dialogflow_cx_agent.agent.id + display_name = "MyFlow" + description = "Test Flow" + + nlu_settings { + classification_threshold = 0.3 + model_type = "MODEL_TYPE_STANDARD" + } + + event_handlers { + event = "custom-event" + trigger_fulfillment { + return_partial_responses = false + messages { + text { + text = ["I didn't get that. Can you say it again?"] + } + } + } + } + + event_handlers { + event = "sys.no-match-default" + trigger_fulfillment { + return_partial_responses = false + messages { + text { + text = ["Sorry, could you say that again?"] + } + } + } + } + + event_handlers { + event = "sys.no-input-default" + trigger_fulfillment { + return_partial_responses = false + messages { + text { + text = ["One more time?"] + } + } + } + } +} +```
Open in Cloud Shell @@ -389,6 +461,11 @@ The following arguments are supported: Flow.transition_routes.trigger_fulfillment.conditional_cases If not specified, the agent's default language is used. Many languages are supported. Note: languages must be enabled in the agent before they can be used. +* `is_default_start_flow` - (Optional) Marks this as the [Default Start Flow](https://cloud.google.com/dialogflow/cx/docs/concept/flow#start) for an agent. When you create an agent, the Default Start Flow is created automatically. +The Default Start Flow cannot be deleted; deleting the `google_dialogflow_cx_flow` resource does nothing to the underlying GCP resources. + +~> Avoid having multiple `google_dialogflow_cx_flow` resources linked to the same agent with `is_default_start_flow = true` because they will compete to control a single Default Start Flow resource in GCP. + The `transition_routes` block supports: diff --git a/website/docs/r/dialogflow_cx_intent.html.markdown b/website/docs/r/dialogflow_cx_intent.html.markdown index 7ab0185690..cb81c12c37 100644 --- a/website/docs/r/dialogflow_cx_intent.html.markdown +++ b/website/docs/r/dialogflow_cx_intent.html.markdown @@ -119,6 +119,7 @@ The following arguments are supported: (Optional) Indicates whether this is a fallback intent. Currently only default fallback intent is allowed in the agent, which is added upon agent creation. Adding training phrases to fallback intent is useful in the case of requests that are mistakenly matched, since training phrases assigned to fallback intents act as negative examples that triggers no-match event. + To manage the fallback intent, set `is_default_negative_intent = true` * `labels` - (Optional) @@ -144,6 +145,16 @@ The following arguments are supported: Intent.training_phrases.parts.text If not specified, the agent's default language is used. Many languages are supported. Note: languages must be enabled in the agent before they can be used. +* `is_default_welcome_intent` - (Optional) Marks this as the [Default Welcome Intent](https://cloud.google.com/dialogflow/cx/docs/concept/intent#welcome) for an agent. When you create an agent, a Default Welcome Intent is created automatically. +The Default Welcome Intent cannot be deleted; deleting the `google_dialogflow_cx_intent` resource does nothing to the underlying GCP resources. + +~> Avoid having multiple `google_dialogflow_cx_intent` resources linked to the same agent with `is_default_welcome_intent = true` because they will compete to control a single Default Welcome Intent resource in GCP. + +* `is_default_negative_intent` - (Optional) Marks this as the [Default Negative Intent](https://cloud.google.com/dialogflow/cx/docs/concept/intent#negative) for an agent. When you create an agent, a Default Negative Intent is created automatically. +The Default Negative Intent cannot be deleted; deleting the `google_dialogflow_cx_intent` resource does nothing to the underlying GCP resources. + +~> Avoid having multiple `google_dialogflow_cx_intent` resources linked to the same agent with `is_default_negative_intent = true` because they will compete to control a single Default Negative Intent resource in GCP. + The `training_phrases` block supports: