Skip to content

Commit

Permalink
Enabling management of default Dialogflow resources without terraform…
Browse files Browse the repository at this point in the history
… import: `is_default_x` fields (#9336)

* Enabling management of Default Start Flow + Default Welcome Intent + Default Negative Intent, without terraform import

Fixes hashicorp/terraform-provider-google#16308

* Basic tests for default flow + intents

* Tests asserting resource IDs

* Making Flow + Intent import respect is_default_X; fixing description on examples

* Bah, making isFallback output-only might break users that'd imported the Default Negative Intent :(

* Update mmv1/templates/terraform/pre_create/dialogflowcx_set_location_skip_default_obj.go.erb

Co-authored-by: Sarah French <[email protected]>

* Adding callout notes about multiple is_default_x resources

* More words around the pre-create + pre-delete code

* Adding more tests around Default Start Flow

* Adding more tests around Default Welcome Intent and Default Negative Intent

---------

Co-authored-by: Sarah French <[email protected]>
[upstream:58d0735cfa46a3668a0c5af3181ce6443ab22d91]

Signed-off-by: Modular Magician <[email protected]>
  • Loading branch information
modular-magician committed Nov 3, 2023
1 parent c991825 commit 8e80f12
Show file tree
Hide file tree
Showing 10 changed files with 849 additions and 1 deletion.
6 changes: 6 additions & 0 deletions .changelog/9336.txt
Original file line number Diff line number Diff line change
@@ -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
```
55 changes: 55 additions & 0 deletions google-beta/services/dialogflowcx/resource_dialogflow_cx_flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,15 @@ You may set this, for example:
Description: `The unique identifier of the flow.
Format: projects/<Project ID>/locations/<Location ID>/agents/<Agent ID>/flows/<Flow ID>.`,
},
"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,
}
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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 {
Expand Down
Loading

0 comments on commit 8e80f12

Please sign in to comment.