diff --git a/src/pkg/services/m365/api/lists.go b/src/pkg/services/m365/api/lists.go index 3e62970e5..c8a3f7470 100644 --- a/src/pkg/services/m365/api/lists.go +++ b/src/pkg/services/m365/api/lists.go @@ -2,12 +2,15 @@ package api import ( "context" + "fmt" + "strings" "github.com/alcionai/clues" "github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/alcionai/corso/src/internal/common/keys" "github.com/alcionai/corso/src/internal/common/ptr" + "github.com/alcionai/corso/src/internal/common/str" "github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/services/m365/api/graph" @@ -28,22 +31,32 @@ const ( ContentTypeColumnDisplayName = "Content Type" - AddressFieldName = "address" - CoordinatesFieldName = "coordinates" - DisplayNameFieldName = "displayName" - LocationURIFieldName = "locationUri" - UniqueIDFieldName = "uniqueId" + AddressKey = "address" + CoordinatesKey = "coordinates" + DisplayNameKey = "displayName" + LocationURIKey = "locationUri" + UniqueIDKey = "uniqueId" - CountryOrRegionFieldName = "CountryOrRegion" - StateFieldName = "State" - CityFieldName = "City" - PostalCodeFieldName = "PostalCode" - StreetFieldName = "Street" - GeoLocFieldName = "GeoLoc" - DispNameFieldName = "DispName" - LinkTitleFieldNamePart = "LinkTitle" - ChildCountFieldNamePart = "ChildCount" - LookupIDFieldNamePart = "LookupId" + // entries that are nested within a second layer + CityKey = "city" + CountryKey = "countryOrRegion" + PostalCodeKey = "postalCode" + StateKey = "state" + StreetKey = "street" + LatitudeKey = "latitude" + LongitudeKey = "longitude" + + CountryOrRegionFN = "CountryOrRegion" + StateFN = "State" + CityFN = "City" + PostalCodeFN = "PostalCode" + StreetFN = "Street" + GeoLocFN = "GeoLoc" + DispNameFN = "DispName" + + LinkTitleFieldNamePart = "LinkTitle" + ChildCountFieldNamePart = "ChildCount" + LookupIDFieldNamePart = "LookupId" ReadOnlyOrHiddenFieldNamePrefix = "_" DescoratorFieldNamePrefix = "@" @@ -56,6 +69,14 @@ const ( AccessRequestsListTemplate = "accessRequest" ) +var addressFieldNames = []string{ + AddressKey, + CoordinatesKey, + DisplayNameKey, + LocationURIKey, + UniqueIDKey, +} + var legacyColumns = keys.Set{ AttachmentsColumnName: {}, EditColumnName: {}, @@ -481,6 +502,11 @@ func retrieveFieldData(orig models.FieldValueSetable, columnNames map[string]any fields := models.NewFieldValueSet() additionalData := setAdditionalDataByColumnNames(orig, columnNames) + if addressField, fieldName, ok := hasAddressFields(additionalData); ok { + concatenatedAddress := concatenateAddressFields(addressField) + additionalData[fieldName] = concatenatedAddress + } + fields.SetAdditionalData(additionalData) return fields @@ -506,6 +532,58 @@ func setAdditionalDataByColumnNames( return filteredData } +func hasAddressFields(additionalData map[string]any) (map[string]any, string, bool) { + for k, v := range additionalData { + nestedFields, ok := v.(map[string]any) + if !ok || keys.HasKeys(nestedFields, GeoLocFN) { + continue + } + + if keys.HasKeys(nestedFields, addressFieldNames...) { + return nestedFields, k, true + } + } + + return nil, "", false +} + +func concatenateAddressFields(addressFields map[string]any) string { + parts := make([]string, 0) + + if dispName, ok := addressFields[DisplayNameKey].(*string); ok { + parts = append(parts, ptr.Val(dispName)) + } + + if fields, ok := addressFields[AddressKey].(map[string]any); ok { + parts = append(parts, addressKeyToVal(fields, StreetKey)) + parts = append(parts, addressKeyToVal(fields, CityKey)) + parts = append(parts, addressKeyToVal(fields, StateKey)) + parts = append(parts, addressKeyToVal(fields, CountryKey)) + parts = append(parts, addressKeyToVal(fields, PostalCodeKey)) + } + + if coords, ok := addressFields[CoordinatesKey].(map[string]any); ok { + parts = append(parts, addressKeyToVal(coords, LatitudeKey)) + parts = append(parts, addressKeyToVal(coords, LongitudeKey)) + } + + if len(parts) > 0 { + return strings.Join(parts, ",") + } + + return "" +} + +func addressKeyToVal(fields map[string]any, key string) string { + if v, err := str.AnyValueToString(key, fields); err == nil { + return v + } else if v, ok := fields[key].(*float64); ok { + return fmt.Sprintf("%v", ptr.Val(v)) + } + + return "" +} + func (c Lists) getListItemFields( ctx context.Context, siteID, listID, itemID string, diff --git a/src/pkg/services/m365/api/lists_test.go b/src/pkg/services/m365/api/lists_test.go index 6f8c6c71b..18968f9d6 100644 --- a/src/pkg/services/m365/api/lists_test.go +++ b/src/pkg/services/m365/api/lists_test.go @@ -1,6 +1,7 @@ package api import ( + "fmt" "testing" "time" @@ -455,52 +456,54 @@ func (suite *ListsUnitSuite) TestFieldValueSetable() { func (suite *ListsUnitSuite) TestFieldValueSetable_Location() { t := suite.T() + displayName := "B123 Unit 1852 Prime Residences Tagaytay" + street := "Prime Residences CityLand 1852" + state := "Calabarzon" + postal := "4120" + country := "Philippines" + city := "Tagaytay" + lat := 14.1153 + lon := 120.962 + additionalData := map[string]any{ "MyAddress": map[string]any{ - AddressFieldName: map[string]any{ - "city": "Tagaytay", - "countryOrRegion": "Philippines", - "postalCode": "4120", - "state": "Calabarzon", - "street": "Prime Residences CityLand 1852", + AddressKey: map[string]any{ + CityKey: ptr.To(city), + CountryKey: ptr.To(country), + PostalCodeKey: ptr.To(postal), + StateKey: ptr.To(state), + StreetKey: ptr.To(street), }, - CoordinatesFieldName: map[string]any{ - "latitude": "14.1153", - "longitude": "120.962", + CoordinatesKey: map[string]any{ + LatitudeKey: ptr.To(lat), + LongitudeKey: ptr.To(lon), }, - DisplayNameFieldName: "B123 Unit 1852 Prime Residences Tagaytay", - LocationURIFieldName: "https://www.bingapis.com/api/v6/localbusinesses/YN8144x496766267081923032", - UniqueIDFieldName: "https://www.bingapis.com/api/v6/localbusinesses/YN8144x496766267081923032", + DisplayNameKey: ptr.To(displayName), + LocationURIKey: ptr.To("https://www.bingapis.com/api/v6/localbusinesses/YN8144x496766267081923032"), + UniqueIDKey: ptr.To("https://www.bingapis.com/api/v6/localbusinesses/YN8144x496766267081923032"), }, - CountryOrRegionFieldName: "Philippines", - StateFieldName: "Calabarzon", - CityFieldName: "Tagaytay", - PostalCodeFieldName: "4120", - StreetFieldName: "Prime Residences CityLand 1852", - GeoLocFieldName: map[string]any{ - "latitude": 14.1153, - "longitude": 120.962, + CountryOrRegionFN: ptr.To(country), + StateFN: ptr.To(state), + CityFN: ptr.To(city), + PostalCodeFN: ptr.To(postal), + StreetFN: ptr.To(street), + GeoLocFN: map[string]any{ + "latitude": ptr.To(lat), + "longitude": ptr.To(lon), }, - DispNameFieldName: "B123 Unit 1852 Prime Residences Tagaytay", + DispNameFN: ptr.To(displayName), } expectedData := map[string]any{ - "MyAddress": map[string]any{ - AddressFieldName: map[string]any{ - "city": "Tagaytay", - "countryOrRegion": "Philippines", - "postalCode": "4120", - "state": "Calabarzon", - "street": "Prime Residences CityLand 1852", - }, - CoordinatesFieldName: map[string]any{ - "latitude": "14.1153", - "longitude": "120.962", - }, - DisplayNameFieldName: "B123 Unit 1852 Prime Residences Tagaytay", - LocationURIFieldName: "https://www.bingapis.com/api/v6/localbusinesses/YN8144x496766267081923032", - UniqueIDFieldName: "https://www.bingapis.com/api/v6/localbusinesses/YN8144x496766267081923032", - }, + "MyAddress": fmt.Sprintf("%s,%s,%s,%s,%s,%s,%v,%v", + displayName, + street, + city, + state, + country, + postal, + lat, + lon), } origFs := models.NewFieldValueSet() @@ -515,6 +518,138 @@ func (suite *ListsUnitSuite) TestFieldValueSetable_Location() { assert.Equal(t, expectedData, fsAdditionalData) } +func (suite *ListsUnitSuite) TestConcatenateAddressFields() { + t := suite.T() + + tests := []struct { + name string + addressFields map[string]any + expectedResult string + }{ + { + name: "Valid Address", + addressFields: map[string]any{ + DisplayNameKey: ptr.To("John Doe"), + AddressKey: map[string]any{ + StreetKey: ptr.To("123 Main St"), + CityKey: ptr.To("Cityville"), + StateKey: ptr.To("State"), + CountryKey: ptr.To("Country"), + PostalCodeKey: ptr.To("12345"), + }, + CoordinatesKey: map[string]any{ + LatitudeKey: ptr.To(40.7128), + LongitudeKey: ptr.To(-74.0060), + }, + }, + expectedResult: "John Doe,123 Main St,Cityville,State,Country,12345,40.7128,-74.006", + }, + { + name: "Empty Address Fields", + addressFields: map[string]any{ + DisplayNameKey: ptr.To("John Doe"), + }, + expectedResult: "John Doe", + }, + { + name: "Empty Input", + addressFields: map[string]any{}, + expectedResult: "", + }, + } + + for _, test := range tests { + suite.Run(test.name, func() { + result := concatenateAddressFields(test.addressFields) + assert.Equal(t, test.expectedResult, result, "address should match") + }) + } +} + +func (suite *ListsUnitSuite) TestHasAddressFields() { + t := suite.T() + + tests := []struct { + name string + additionalData map[string]any + expectedFields map[string]any + expectedName string + expectedFound bool + }{ + { + name: "Address Fields Found", + additionalData: map[string]any{ + "person1": map[string]any{ + AddressKey: map[string]any{ + StreetKey: "123 Main St", + CityKey: "Cityville", + StateKey: "State", + CountryKey: "Country", + PostalCodeKey: "12345", + }, + CoordinatesKey: map[string]any{ + LatitudeKey: "40.7128", + LongitudeKey: "-74.0060", + }, + DisplayNameKey: "John Doe", + LocationURIKey: "some loc", + UniqueIDKey: "some id", + }, + }, + expectedFields: map[string]any{ + AddressKey: map[string]any{ + StreetKey: "123 Main St", + CityKey: "Cityville", + StateKey: "State", + CountryKey: "Country", + PostalCodeKey: "12345", + }, + CoordinatesKey: map[string]any{ + LatitudeKey: "40.7128", + LongitudeKey: "-74.0060", + }, + DisplayNameKey: "John Doe", + LocationURIKey: "some loc", + UniqueIDKey: "some id", + }, + expectedName: "person1", + expectedFound: true, + }, + { + name: "No Address Fields", + additionalData: map[string]any{ + "person1": map[string]any{ + "name": "John Doe", + "age": 30, + }, + "person2": map[string]any{ + "name": "Jane Doe", + "age": 25, + }, + }, + expectedFields: nil, + expectedName: "", + expectedFound: false, + }, + { + name: "Empty Input", + additionalData: map[string]any{}, + expectedFields: nil, + expectedName: "", + expectedFound: false, + }, + } + + for _, test := range tests { + suite.Run(test.name, func() { + fields, fieldName, found := hasAddressFields(test.additionalData) + require.Equal(t, test.expectedFound, found, "address fields identification should match") + assert.Equal(t, test.expectedName, fieldName, "address field name should match") + assert.Equal(t, test.expectedFields, fields, "address fields should match") + }) + } +} + type ListsAPIIntgSuite struct { tester.Suite its intgTesterSetup