From a85ea81c89d725f4e296e18408b2227b93c2f99b Mon Sep 17 00:00:00 2001 From: Dean Huynh Date: Thu, 26 Oct 2023 15:03:05 -0700 Subject: [PATCH 1/3] Transformation resource --- internal/provider/models/transformation.go | 163 +++++++++ internal/provider/provider.go | 1 + internal/provider/transformation_resource.go | 331 ++++++++++++++++++ .../provider/transformation_resource_test.go | 258 ++++++++++++++ 4 files changed, 753 insertions(+) create mode 100644 internal/provider/models/transformation.go create mode 100644 internal/provider/transformation_resource.go create mode 100644 internal/provider/transformation_resource_test.go diff --git a/internal/provider/models/transformation.go b/internal/provider/models/transformation.go new file mode 100644 index 0000000..924ba6c --- /dev/null +++ b/internal/provider/models/transformation.go @@ -0,0 +1,163 @@ +package models + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/segmentio/public-api-sdk-go/api" +) + +type TransformationPlan struct { + ID types.String `tfsdk:"id"` + SourceID types.String `tfsdk:"source_id"` + DestinationMetadataID types.String `tfsdk:"destination_metadata_id"` + Name types.String `tfsdk:"name"` + Enabled types.Bool `tfsdk:"enabled"` + If types.String `tfsdk:"if"` + NewEventName types.String `tfsdk:"new_event_name"` + PropertyRenames types.Set `tfsdk:"property_renames"` + PropertyValueTransformations types.Set `tfsdk:"property_value_transformations"` + FQLDefinedProperties types.Set `tfsdk:"fql_defined_properties"` +} + +type TransformationState struct { + ID types.String `tfsdk:"id"` + SourceID types.String `tfsdk:"source_id"` + DestinationMetadataID types.String `tfsdk:"destination_metadata_id"` + Name types.String `tfsdk:"name"` + Enabled types.Bool `tfsdk:"enabled"` + If types.String `tfsdk:"if"` + NewEventName types.String `tfsdk:"new_event_name"` + PropertyRenames []PropertyRename `tfsdk:"property_renames"` + PropertyValueTransformations []PropertyValueTransform `tfsdk:"property_value_transformations"` + FQLDefinedProperties []FQLDefinedProperty `tfsdk:"fql_defined_properties"` +} + +type PropertyRename struct { + OldName types.String `tfsdk:"old_name"` + NewName types.String `tfsdk:"new_name"` +} + +type PropertyValueTransform struct { + PropertyPaths []types.String `tfsdk:"property_paths"` + PropertyValue types.String `tfsdk:"property_value"` +} + +type PropertyValueTransformPlan struct { + PropertyPaths types.Set `tfsdk:"property_paths"` + PropertyValue types.String `tfsdk:"property_value"` +} + +type FQLDefinedProperty struct { + FQL types.String `tfsdk:"fql"` + PropertyName types.String `tfsdk:"property_name"` +} + +func (t *TransformationState) Fill(transformation api.Transformation5) { + t.ID = types.StringValue(transformation.Id) + t.SourceID = types.StringValue(transformation.SourceId) + t.DestinationMetadataID = types.StringPointerValue(transformation.DestinationMetadataId) + t.Name = types.StringValue(transformation.Name) + t.Enabled = types.BoolValue(transformation.Enabled) + t.If = types.StringValue(transformation.If) + t.NewEventName = types.StringPointerValue(transformation.NewEventName) + + // Fill PropertyRenames + t.PropertyRenames = make([]PropertyRename, len(transformation.PropertyRenames)) + for i, pr := range transformation.PropertyRenames { + t.PropertyRenames[i] = PropertyRename{ + OldName: types.StringValue(pr.OldName), + NewName: types.StringValue(pr.NewName), + } + } + + // Fill PropertyValueTransformations + t.PropertyValueTransformations = make([]PropertyValueTransform, len(transformation.PropertyValueTransformations)) + for i, pvt := range transformation.PropertyValueTransformations { + var paths []types.String + for _, path := range pvt.PropertyPaths { + paths = append(paths, types.StringValue(path)) + } + + t.PropertyValueTransformations[i] = PropertyValueTransform{ + PropertyPaths: paths, + PropertyValue: types.StringValue(pvt.PropertyValue), + } + } + + // Fill FQLDefinedProperties + t.FQLDefinedProperties = make([]FQLDefinedProperty, len(transformation.FqlDefinedProperties)) + for i, fdp := range transformation.FqlDefinedProperties { + t.FQLDefinedProperties[i] = FQLDefinedProperty{ + FQL: types.StringValue(fdp.Fql), + PropertyName: types.StringValue(fdp.PropertyName), + } + } +} + +func PropertyRenamesPlanToAPIValue(ctx context.Context, renames types.Set) ([]api.PropertyRenameV1, diag.Diagnostics) { + apiRenames := []api.PropertyRenameV1{} + + if !renames.IsNull() && !renames.IsUnknown() { + stateRenames := []PropertyRename{} + diags := renames.ElementsAs(ctx, &stateRenames, false) + if diags.HasError() { + return apiRenames, diags + } + for _, rename := range stateRenames { + apiRenames = append(apiRenames, api.PropertyRenameV1{ + OldName: rename.OldName.ValueString(), + NewName: rename.NewName.ValueString(), + }) + } + } + + return apiRenames, diag.Diagnostics{} +} + +func PropertyValueTransformationsPlanToAPIValue(ctx context.Context, transforms types.Set) ([]api.PropertyValueTransformationV1, diag.Diagnostics) { + apiTransforms := []api.PropertyValueTransformationV1{} + + if !transforms.IsNull() && !transforms.IsUnknown() { + stateTransforms := []PropertyValueTransformPlan{} + diags := transforms.ElementsAs(ctx, &stateTransforms, false) + if diags.HasError() { + return apiTransforms, diags + } + for _, transform := range stateTransforms { + paths := []string{} + diags := transform.PropertyPaths.ElementsAs(ctx, &paths, false) + if diags.HasError() { + return apiTransforms, diags + } + + apiTransforms = append(apiTransforms, api.PropertyValueTransformationV1{ + PropertyPaths: paths, + PropertyValue: transform.PropertyValue.ValueString(), + }) + } + } + + return apiTransforms, diag.Diagnostics{} +} + +func FQLDefinedPropertiesPlanToAPIValue(ctx context.Context, properties types.Set) ([]api.FQLDefinedPropertyV1, diag.Diagnostics) { + apiPreperties := []api.FQLDefinedPropertyV1{} + + if !properties.IsNull() && !properties.IsUnknown() { + stateProperties := []FQLDefinedProperty{} + diags := properties.ElementsAs(ctx, &stateProperties, false) + if diags.HasError() { + return apiPreperties, diags + } + for _, property := range stateProperties { + apiPreperties = append(apiPreperties, api.FQLDefinedPropertyV1{ + Fql: property.FQL.ValueString(), + PropertyName: property.PropertyName.ValueString(), + }) + } + } + + return apiPreperties, diag.Diagnostics{} +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 63b8634..f5fb83f 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -156,6 +156,7 @@ func (p *segmentProvider) Resources(_ context.Context) []func() resource.Resourc NewDestinationSubscriptionResource, NewSourceTrackingPlanConnectionResource, NewReverseETLModelResource, + NewTransformationResource, } } diff --git a/internal/provider/transformation_resource.go b/internal/provider/transformation_resource.go new file mode 100644 index 0000000..9516915 --- /dev/null +++ b/internal/provider/transformation_resource.go @@ -0,0 +1,331 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/segmentio/terraform-provider-segment/internal/provider/models" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/segmentio/public-api-sdk-go/api" +) + +var ( + _ resource.Resource = &transformationResource{} + _ resource.ResourceWithConfigure = &transformationResource{} + _ resource.ResourceWithImportState = &transformationResource{} +) + +func NewTransformationResource() resource.Resource { + return &transformationResource{} +} + +type transformationResource struct { + client *api.APIClient + authContext context.Context +} + +func (r *transformationResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_transformation" +} + +func (r *transformationResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "The id of the Transformation.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "source_id": schema.StringAttribute{ + Required: true, + Description: "The Source associated with the Transformation.", + }, + "destination_metadata_id": schema.StringAttribute{ + Optional: true, + Description: "The optional Destination metadata associated with the Transformation.", + }, + "name": schema.StringAttribute{ + Required: true, + Description: "The name of the Transformation.", + }, + "enabled": schema.BoolAttribute{ + Required: true, + Description: "If the Transformation is enabled.", + }, + "if": schema.StringAttribute{ + Required: true, + Description: `If statement (FQL) to match events. + + For standard event matchers, use the following: Track -> "event='EVENT_NAME'" Identify -> "type='identify'" Group -> "type='group'"`, + }, + "new_event_name": schema.StringAttribute{ + Optional: true, + Description: "Optional new event name for renaming events. Works only for 'track' event type.", + }, + "property_renames": schema.SetNestedAttribute{ + Required: true, + Description: "Optional array for renaming properties collected by your events.", + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "old_name": schema.StringAttribute{ + Required: true, + Description: "The old name of the property.", + }, + "new_name": schema.StringAttribute{ + Required: true, + Description: "The new name to rename the property.", + }, + }, + }, + }, + "property_value_transformations": schema.SetNestedAttribute{ + Required: true, + Description: "Optional array for transforming properties and values collected by your events. Limited to 10 properties.", + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "property_paths": schema.SetAttribute{ + Required: true, + Description: "The property paths. The maximum number of paths is 10.", + ElementType: types.StringType, + }, + "property_value": schema.StringAttribute{ + Required: true, + Description: "The new value of the property paths.", + }, + }, + }, + }, + "fql_defined_properties": schema.SetNestedAttribute{ + Required: true, + Description: "Optional array for defining new properties in FQL. Currently limited to 1 property.", + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "fql": schema.StringAttribute{ + Required: true, + Description: "The FQL expression used to compute the property.", + }, + "property_name": schema.StringAttribute{ + Required: true, + Description: "The new property name.", + }, + }, + }, + }, + }, + } +} + +func (r *transformationResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan models.TransformationPlan + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + propertyRenames, diags := models.PropertyRenamesPlanToAPIValue(ctx, plan.PropertyRenames) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + propertyValueTransformations, diags := models.PropertyValueTransformationsPlanToAPIValue(ctx, plan.PropertyValueTransformations) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + fqlDefinedProperties, diags := models.FQLDefinedPropertiesPlanToAPIValue(ctx, plan.FQLDefinedProperties) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + out, body, err := r.client.TransformationsApi.CreateTransformation(r.authContext).CreateTransformationV1Input(api.CreateTransformationV1Input{ + Name: plan.Name.ValueString(), + SourceId: plan.SourceID.ValueString(), + DestinationMetadataId: plan.DestinationMetadataID.ValueStringPointer(), + Enabled: plan.Enabled.ValueBool(), + If: plan.If.ValueString(), + NewEventName: plan.NewEventName.ValueStringPointer(), + PropertyRenames: propertyRenames, + PropertyValueTransformations: propertyValueTransformations, + FqlDefinedProperties: fqlDefinedProperties, + }).Execute() + if body != nil { + defer body.Body.Close() + } + if err != nil { + resp.Diagnostics.AddError( + "Unable to create Transformation", + getError(err, body), + ) + + return + } + + transformation := out.Data.GetTransformation() + + var state models.TransformationState + state.Fill(transformation) + + // Set state to fully populated data + diags = resp.State.Set(ctx, state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *transformationResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var previousState models.TransformationState + + diags := req.State.Get(ctx, &previousState) + + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + out, body, err := r.client.TransformationsApi.GetTransformation(r.authContext, previousState.ID.ValueString()).Execute() + if body != nil { + defer body.Body.Close() + } + if err != nil { + resp.Diagnostics.AddError( + "Unable to read Transformation", + getError(err, body), + ) + + return + } + + var state models.TransformationState + + state.Fill(api.Transformation5(out.Data.Transformation)) + + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *transformationResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan models.TransformationPlan + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + var state models.TransformationState + diags = req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + propertyRenames, diags := models.PropertyRenamesPlanToAPIValue(ctx, plan.PropertyRenames) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + propertyValueTransformations, diags := models.PropertyValueTransformationsPlanToAPIValue(ctx, plan.PropertyValueTransformations) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + fqlDefinedProperties, diags := models.FQLDefinedPropertiesPlanToAPIValue(ctx, plan.FQLDefinedProperties) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + out, body, err := r.client.TransformationsApi.UpdateTransformation(r.authContext, state.ID.ValueString()).UpdateTransformationV1Input(api.UpdateTransformationV1Input{ + Name: plan.Name.ValueStringPointer(), + Enabled: plan.Enabled.ValueBoolPointer(), + If: plan.If.ValueStringPointer(), + NewEventName: plan.NewEventName.ValueStringPointer(), + SourceId: plan.SourceID.ValueStringPointer(), + DestinationMetadataId: plan.DestinationMetadataID.ValueStringPointer(), + PropertyRenames: propertyRenames, + PropertyValueTransformations: propertyValueTransformations, + FqlDefinedProperties: fqlDefinedProperties, + }).Execute() + if body != nil { + defer body.Body.Close() + } + if err != nil { + resp.Diagnostics.AddError( + "Unable to update Transformation", + getError(err, body), + ) + + return + } + + state.Fill(api.Transformation5(out.Data.Transformation)) + + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *transformationResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var config models.TransformationState + diags := req.State.Get(ctx, &config) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + _, body, err := r.client.TransformationsApi.DeleteTransformation(r.authContext, config.ID.ValueString()).Execute() + if body != nil { + defer body.Body.Close() + } + if err != nil { + resp.Diagnostics.AddError( + "Unable to delete Transformation", + getError(err, body), + ) + + return + } +} + +func (r *transformationResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} + +func (r *transformationResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + config, ok := req.ProviderData.(*ClientInfo) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected ClientInfo, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.client = config.client + r.authContext = config.authContext +} diff --git a/internal/provider/transformation_resource_test.go b/internal/provider/transformation_resource_test.go new file mode 100644 index 0000000..2f0b2ff --- /dev/null +++ b/internal/provider/transformation_resource_test.go @@ -0,0 +1,258 @@ +package provider + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccTransformationResource(t *testing.T) { + t.Parallel() + + updated := 0 + fakeServer := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + w.Header().Set("content-type", "application/json") + + payload := "" + if req.URL.Path == "/transformations" && req.Method == http.MethodPost { + payload = ` + { + "data": { + "transformation": { + "id": "my-transformation-id", + "name": "My transformation name", + "workspaceId": "my-workspace-id", + "sourceId": "my-source-id", + "enabled": true, + "if": "event = 'Bad Event'", + "newEventName": "Good Event", + "propertyRenames": [ + { + "oldName": "old-name", + "newName": "new-name" + } + ], + "propertyValueTransformations": [ + { + "propertyPaths": [ + "properties.some-property", + "context.some-property" + ], + "propertyValue": "some property value" + } + ], + "fqlDefinedProperties": [] + } + } + } + ` + } else if req.URL.Path == "/transformations/my-transformation-id" && req.Method == http.MethodPatch { + payload = ` + { + "data": { + "transformation": { + "id": "my-transformation-id", + "name": "My new transformation name", + "workspaceId": "my-workspace-id", + "sourceId": "my-other-source-id", + "enabled": false, + "if": "event = 'Good Event'", + "newEventName": "Bad Event", + "propertyRenames": [], + "propertyValueTransformations": [], + "fqlDefinedProperties": [ + { + "fql": "event = 'Good Event'", + "propertyName": "some-property" + } + ] + } + } + } + ` + updated++ + } else if req.URL.Path == "/transformations/my-transformation-id" && req.Method == http.MethodGet { + if updated == 0 { + payload = ` + { + "data": { + "transformation": { + "id": "my-transformation-id", + "name": "My transformation name", + "workspaceId": "my-workspace-id", + "sourceId": "my-source-id", + "enabled": true, + "if": "event = 'Bad Event'", + "newEventName": "Good Event", + "propertyRenames": [ + { + "oldName": "old-name", + "newName": "new-name" + } + ], + "propertyValueTransformations": [ + { + "propertyPaths": [ + "properties.some-property", + "context.some-property" + ], + "propertyValue": "some property value" + } + ], + "fqlDefinedProperties": [] + } + } + } + ` + } else { + payload = ` + { + "data": { + "transformation": { + "id": "my-transformation-id", + "name": "My new transformation name", + "workspaceId": "my-workspace-id", + "sourceId": "my-other-source-id", + "enabled": false, + "if": "event = 'Good Event'", + "newEventName": "Bad Event", + "propertyRenames": [], + "propertyValueTransformations": [], + "fqlDefinedProperties": [ + { + "fql": "event = 'Good Event'", + "propertyName": "some-property" + } + ] + } + } + } + ` + } + } + + _, _ = w.Write([]byte(payload)) + }), + ) + defer fakeServer.Close() + + providerConfig := ` + provider "segment" { + url = "` + fakeServer.URL + `" + token = "abc123" + } + ` + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: providerConfig + ` + resource "segment_transformation" "test" { + source_id = "my-source-id" + name = "My transformation name" + enabled = true + if = "event = 'Bad Event'" + new_event_name = "Good Event" + property_renames = [ + { + old_name = "old-name" + new_name = "new-name" + } + ] + property_value_transformations = [ + { + property_paths = ["properties.some-property", "context.some-property"], + property_value = "some property value" + }, + ] + fql_defined_properties = [] + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("segment_transformation.test", "id", "my-transformation-id"), + resource.TestCheckResourceAttr("segment_transformation.test", "source_id", "my-source-id"), + resource.TestCheckResourceAttr("segment_transformation.test", "name", "My transformation name"), + resource.TestCheckResourceAttr("segment_transformation.test", "enabled", "true"), + resource.TestCheckResourceAttr("segment_transformation.test", "if", "event = 'Bad Event'"), + resource.TestCheckResourceAttr("segment_transformation.test", "new_event_name", "Good Event"), + resource.TestCheckResourceAttr("segment_transformation.test", "property_renames.#", "1"), + resource.TestCheckResourceAttr("segment_transformation.test", "property_renames.0.old_name", "old-name"), + resource.TestCheckResourceAttr("segment_transformation.test", "property_renames.0.new_name", "new-name"), + resource.TestCheckResourceAttr("segment_transformation.test", "property_value_transformations.#", "1"), + resource.TestCheckResourceAttr("segment_transformation.test", "property_value_transformations.0.property_paths.#", "2"), + resource.TestCheckResourceAttr("segment_transformation.test", "property_value_transformations.0.property_paths.1", "properties.some-property"), + resource.TestCheckResourceAttr("segment_transformation.test", "property_value_transformations.0.property_paths.0", "context.some-property"), + resource.TestCheckResourceAttr("segment_transformation.test", "property_value_transformations.0.property_value", "some property value"), + resource.TestCheckResourceAttr("segment_transformation.test", "fql_defined_properties.#", "0"), + ), + }, + // ImportState testing + { + ResourceName: "segment_transformation.test", + Config: providerConfig + ` + resource "segment_transformation" "test" { + source_id = "my-source-id" + name = "My transformation name" + enabled = true + if = "event = 'Bad Event'" + new_event_name = "Good Event" + property_renames = [ + { + old_name = "old-name" + new_name = "new-name" + } + ] + property_value_transformations = [ + { + property_paths = ["properties.some-property", "context.some-property"], + property_value = "some property value" + }, + ] + fql_defined_properties = [] + } + `, + ImportState: true, + ImportStateVerify: true, + }, + // Update and Read testing + { + Config: providerConfig + ` + resource "segment_transformation" "test" { + source_id = "my-other-source-id" + name = "My new transformation name" + enabled = false + if = "event = 'Good Event'" + new_event_name = "Bad Event" + property_renames = [] + property_value_transformations = [] + fql_defined_properties = [ + { + fql = "event = 'Good Event'" + property_name = "some-property" + } + ] + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("segment_transformation.test", "id", "my-transformation-id"), + resource.TestCheckResourceAttr("segment_transformation.test", "source_id", "my-other-source-id"), + resource.TestCheckResourceAttr("segment_transformation.test", "name", "My new transformation name"), + resource.TestCheckResourceAttr("segment_transformation.test", "enabled", "false"), + resource.TestCheckResourceAttr("segment_transformation.test", "if", "event = 'Good Event'"), + resource.TestCheckResourceAttr("segment_transformation.test", "new_event_name", "Bad Event"), + resource.TestCheckResourceAttr("segment_transformation.test", "property_renames.#", "0"), + resource.TestCheckResourceAttr("segment_transformation.test", "property_value_transformations.#", "0"), + resource.TestCheckResourceAttr("segment_transformation.test", "fql_defined_properties.#", "1"), + resource.TestCheckResourceAttr("segment_transformation.test", "fql_defined_properties.0.fql", "event = 'Good Event'"), + resource.TestCheckResourceAttr("segment_transformation.test", "fql_defined_properties.0.property_name", "some-property"), + ), + }, + // Delete testing automatically occurs in TestCase + }, + }) +} From 7130e784bbeed88f77d761f49969b3cb6c2a3d54 Mon Sep 17 00:00:00 2001 From: Dean Huynh Date: Thu, 26 Oct 2023 15:05:03 -0700 Subject: [PATCH 2/3] Add example and go generate --- docs/resources/transformation.md | 87 +++++++++++++++++++ .../segment_transformation/resource.tf | 21 +++++ 2 files changed, 108 insertions(+) create mode 100644 docs/resources/transformation.md create mode 100644 examples/resources/segment_transformation/resource.tf diff --git a/docs/resources/transformation.md b/docs/resources/transformation.md new file mode 100644 index 0000000..7e81bdb --- /dev/null +++ b/docs/resources/transformation.md @@ -0,0 +1,87 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "segment_transformation Resource - terraform-provider-segment" +subcategory: "" +description: |- + +--- + +# segment_transformation (Resource) + + + +## Example Usage + +```terraform +# Configures a specific transformation +resource "segment_transformation" "example" { + source_id = segment_source.example.id + name = "My transformation name" + enabled = true + if = "event = 'Bad Event'" + new_event_name = "Good Event" + property_renames = [ + { + old_name = "old-name" + new_name = "new-name" + } + ] + property_value_transformations = [ + { + property_paths = ["properties.some-property", "context.some-property"], + property_value = "some property value" + }, + ] + fql_defined_properties = [] +} +``` + + +## Schema + +### Required + +- `enabled` (Boolean) If the Transformation is enabled. +- `fql_defined_properties` (Attributes Set) Optional array for defining new properties in FQL. Currently limited to 1 property. (see [below for nested schema](#nestedatt--fql_defined_properties)) +- `if` (String) If statement (FQL) to match events. + + For standard event matchers, use the following: Track -> "event='EVENT_NAME'" Identify -> "type='identify'" Group -> "type='group'" +- `name` (String) The name of the Transformation. +- `property_renames` (Attributes Set) Optional array for renaming properties collected by your events. (see [below for nested schema](#nestedatt--property_renames)) +- `property_value_transformations` (Attributes Set) Optional array for transforming properties and values collected by your events. Limited to 10 properties. (see [below for nested schema](#nestedatt--property_value_transformations)) +- `source_id` (String) The Source associated with the Transformation. + +### Optional + +- `destination_metadata_id` (String) The optional Destination metadata associated with the Transformation. +- `new_event_name` (String) Optional new event name for renaming events. Works only for 'track' event type. + +### Read-Only + +- `id` (String) The id of the Transformation. + + +### Nested Schema for `fql_defined_properties` + +Required: + +- `fql` (String) The FQL expression used to compute the property. +- `property_name` (String) The new property name. + + + +### Nested Schema for `property_renames` + +Required: + +- `new_name` (String) The new name to rename the property. +- `old_name` (String) The old name of the property. + + + +### Nested Schema for `property_value_transformations` + +Required: + +- `property_paths` (Set of String) The property paths. The maximum number of paths is 10. +- `property_value` (String) The new value of the property paths. diff --git a/examples/resources/segment_transformation/resource.tf b/examples/resources/segment_transformation/resource.tf new file mode 100644 index 0000000..6bb77da --- /dev/null +++ b/examples/resources/segment_transformation/resource.tf @@ -0,0 +1,21 @@ +# Configures a specific transformation +resource "segment_transformation" "example" { + source_id = segment_source.example.id + name = "My transformation name" + enabled = true + if = "event = 'Bad Event'" + new_event_name = "Good Event" + property_renames = [ + { + old_name = "old-name" + new_name = "new-name" + } + ] + property_value_transformations = [ + { + property_paths = ["properties.some-property", "context.some-property"], + property_value = "some property value" + }, + ] + fql_defined_properties = [] +} From 2de0a8bb787cac007bf94b843d865bea382156e1 Mon Sep 17 00:00:00 2001 From: Dean Huynh Date: Fri, 5 Jan 2024 16:53:53 -0800 Subject: [PATCH 3/3] Update with new SDK --- internal/provider/models/transformation.go | 2 +- internal/provider/transformation_resource.go | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/provider/models/transformation.go b/internal/provider/models/transformation.go index 924ba6c..7d16565 100644 --- a/internal/provider/models/transformation.go +++ b/internal/provider/models/transformation.go @@ -54,7 +54,7 @@ type FQLDefinedProperty struct { PropertyName types.String `tfsdk:"property_name"` } -func (t *TransformationState) Fill(transformation api.Transformation5) { +func (t *TransformationState) Fill(transformation api.TransformationV1) { t.ID = types.StringValue(transformation.Id) t.SourceID = types.StringValue(transformation.SourceId) t.DestinationMetadataID = types.StringPointerValue(transformation.DestinationMetadataId) diff --git a/internal/provider/transformation_resource.go b/internal/provider/transformation_resource.go index 9516915..4b51a5b 100644 --- a/internal/provider/transformation_resource.go +++ b/internal/provider/transformation_resource.go @@ -150,7 +150,7 @@ func (r *transformationResource) Create(ctx context.Context, req resource.Create return } - out, body, err := r.client.TransformationsApi.CreateTransformation(r.authContext).CreateTransformationV1Input(api.CreateTransformationV1Input{ + out, body, err := r.client.TransformationsAPI.CreateTransformation(r.authContext).CreateTransformationV1Input(api.CreateTransformationV1Input{ Name: plan.Name.ValueString(), SourceId: plan.SourceID.ValueString(), DestinationMetadataId: plan.DestinationMetadataID.ValueStringPointer(), @@ -196,7 +196,7 @@ func (r *transformationResource) Read(ctx context.Context, req resource.ReadRequ return } - out, body, err := r.client.TransformationsApi.GetTransformation(r.authContext, previousState.ID.ValueString()).Execute() + out, body, err := r.client.TransformationsAPI.GetTransformation(r.authContext, previousState.ID.ValueString()).Execute() if body != nil { defer body.Body.Close() } @@ -211,7 +211,7 @@ func (r *transformationResource) Read(ctx context.Context, req resource.ReadRequ var state models.TransformationState - state.Fill(api.Transformation5(out.Data.Transformation)) + state.Fill(out.Data.Transformation) diags = resp.State.Set(ctx, &state) resp.Diagnostics.Append(diags...) @@ -253,7 +253,7 @@ func (r *transformationResource) Update(ctx context.Context, req resource.Update return } - out, body, err := r.client.TransformationsApi.UpdateTransformation(r.authContext, state.ID.ValueString()).UpdateTransformationV1Input(api.UpdateTransformationV1Input{ + out, body, err := r.client.TransformationsAPI.UpdateTransformation(r.authContext, state.ID.ValueString()).UpdateTransformationV1Input(api.UpdateTransformationV1Input{ Name: plan.Name.ValueStringPointer(), Enabled: plan.Enabled.ValueBoolPointer(), If: plan.If.ValueStringPointer(), @@ -276,7 +276,7 @@ func (r *transformationResource) Update(ctx context.Context, req resource.Update return } - state.Fill(api.Transformation5(out.Data.Transformation)) + state.Fill(out.Data.Transformation) diags = resp.State.Set(ctx, &state) resp.Diagnostics.Append(diags...) @@ -293,7 +293,7 @@ func (r *transformationResource) Delete(ctx context.Context, req resource.Delete return } - _, body, err := r.client.TransformationsApi.DeleteTransformation(r.authContext, config.ID.ValueString()).Execute() + _, body, err := r.client.TransformationsAPI.DeleteTransformation(r.authContext, config.ID.ValueString()).Execute() if body != nil { defer body.Body.Close() }