Skip to content

Commit

Permalink
handles multiple persons list items (#5111)
Browse files Browse the repository at this point in the history
handles multiple persons list items.

**Original `person` list with single value**:
![Person-List](https://github.com/alcionai/corso/assets/48874082/a4a87cde-f907-4fc7-94da-f9ddda0f5a18)
 
**Restored `person` list with single value**:
![Restored-Person-List](https://github.com/alcionai/corso/assets/48874082/6b5c2a8b-743c-4020-9393-356d28948bf0)

**Original `person` list with multi value**:
![Person-List-Multi](https://github.com/alcionai/corso/assets/48874082/18d2c536-67ac-4b28-87be-2352764f2c95)

**Restored `person` list with multi value**:
![Restored-Person-List-Multi](https://github.com/alcionai/corso/assets/48874082/f9694e0d-d2cc-48d9-94f2-16b61c5b7cdb)

#### Does this PR need a docs update or release note?

- [ ] ✅ Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [x] ⛔ No

#### Type of change

<!--- Please check the type of change your PR introduces: --->
- [x] 🌻 Feature
- [ ] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [ ] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

#### Issue(s)
#5108 
#5084 

#### Test Plan

<!-- How will this be tested prior to merging.-->
- [x] 💪 Manual
- [x] ⚡ Unit test
- [x] 💚 E2E
  • Loading branch information
HiteshRepo authored Jan 30, 2024
1 parent d2f1bbb commit 734fd72
Show file tree
Hide file tree
Showing 3 changed files with 217 additions and 53 deletions.
6 changes: 6 additions & 0 deletions src/pkg/services/m365/api/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,18 @@ const (
HyperlinkDescriptionKey = "Description"
HyperlinkURLKey = "Url"

LookupIDKey = "LookupId"
LookupValueKey = "LookupValue"

PersonEmailKey = "Email"

LinkTitleFieldNamePart = "LinkTitle"
ChildCountFieldNamePart = "ChildCount"
LookupIDFieldNamePart = "LookupId"

ODataTypeFieldNamePart = "@odata.type"
ODataTypeFieldNameStringVal = "Collection(Edm.String)"
ODataTypeFieldNameIntVal = "Collection(Edm.Int32)"

ReadOnlyOrHiddenFieldNamePrefix = "_"
DescoratorFieldNamePrefix = "@"
Expand Down
85 changes: 77 additions & 8 deletions src/pkg/services/m365/api/lists.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ import (
var ErrSkippableListTemplate = clues.New("unable to create lists with skippable templates")

type columnDetails struct {
createFieldName string
getFieldName string
isPersonColumn bool
isMultipleEnabled bool
hasDefaultedToText bool
}
Expand Down Expand Up @@ -300,7 +303,10 @@ func ToListable(orig models.Listable, listName string) (models.Listable, map[str
newList.SetParentReference(orig.GetParentReference())

columns := make([]models.ColumnDefinitionable, 0)
columnNames := map[string]*columnDetails{TitleColumnName: {}}
columnNames := map[string]*columnDetails{TitleColumnName: {
getFieldName: TitleColumnName,
createFieldName: TitleColumnName,
}}

for _, cd := range orig.GetColumns() {
var (
Expand Down Expand Up @@ -380,8 +386,18 @@ func setColumnType(
orig models.ColumnDefinitionable,
columnNames map[string]*columnDetails,
) {
colName := ptr.Val(newColumn.GetName())
colDetails := &columnDetails{}

// for certain columns like 'person', the column name is say 'personName'.
// if the list item for that column holds single value,
// the field data is fetched as '{"personNameLookupId": "10"}'
// if the list item for that column holds multiple values,
// the field data is fetched as '{"personName": [{"lookupId": 10}, {"lookupId": 11}]}'.
// Hence this function helps us to determine which name to use while accessing stored data
colDetails.getFieldName = colName
colDetails.createFieldName = colName

switch {
case orig.GetText() != nil:
newColumn.SetText(orig.GetText())
Expand Down Expand Up @@ -410,14 +426,24 @@ func setColumnType(
case orig.GetTerm() != nil:
newColumn.SetTerm(orig.GetTerm())
case orig.GetPersonOrGroup() != nil:
colDetails.isPersonColumn = true
isMultipleEnabled := ptr.Val(orig.GetPersonOrGroup().GetAllowMultipleSelection())
colDetails.isMultipleEnabled = isMultipleEnabled
updatedName := colName + LookupIDFieldNamePart
colDetails.createFieldName = updatedName

if !isMultipleEnabled {
colDetails.getFieldName = updatedName
}

newColumn.SetPersonOrGroup(orig.GetPersonOrGroup())
default:
colDetails.hasDefaultedToText = true

newColumn.SetText(models.NewTextColumn())
}

columnNames[ptr.Val(newColumn.GetName())] = colDetails
columnNames[colName] = colDetails
}

// CloneListItem creates a new `SharePoint.ListItem` and stores the original item's
Expand Down Expand Up @@ -490,24 +516,59 @@ func setAdditionalDataByColumnNames(
fieldData := orig.GetAdditionalData()
filteredData := make(map[string]any)

for colName, colDetails := range columnNames {
if val, ok := fieldData[colName]; ok {
for _, colDetails := range columnNames {
if val, ok := fieldData[colDetails.getFieldName]; ok {
// for columns like 'choice', even though it has an option to hold single/multiple values,
// the columnDefinition property 'allowMultipleValues' is not available.
// Hence we determine single/multiple from the actual field data.
if isSlice(val) {
if !colDetails.isMultipleEnabled && isSlice(val) {
colDetails.isMultipleEnabled = true
}

filteredData[colName] = fieldData[colName]
filteredData[colDetails.createFieldName] = val
populateMultipleValues(val, filteredData, colDetails)
}

specifyODataType(filteredData, colDetails, colName)
specifyODataType(filteredData, colDetails, colDetails.createFieldName)
}

return filteredData
}

func populateMultipleValues(val any, filteredData map[string]any, colDetails *columnDetails) {
if !colDetails.isMultipleEnabled {
return
}

if !colDetails.isPersonColumn {
return
}

multiNestedFields, ok := val.([]any)
if !ok || len(multiNestedFields) == 0 {
return
}

lookupIDs := make([]float64, 0)
lookupKeys := []string{LookupIDKey, LookupValueKey, PersonEmailKey}

for _, nestedFields := range multiNestedFields {
md, ok := nestedFields.(map[string]any)
if !ok || !keys.HasKeys(md, lookupKeys...) {
continue
}

v, ok := md[LookupIDKey].(*float64)
if !ok {
continue
}

lookupIDs = append(lookupIDs, ptr.Val(v))
}

filteredData[colDetails.createFieldName] = lookupIDs
}

// when creating list items with multiple values for a single column
// we let the API know that we are sending a collection.
// Hence this adds an additional field '<columnName>@@odata.type'
Expand All @@ -520,7 +581,15 @@ func specifyODataType(filteredData map[string]any, colDetails *columnDetails, co
return
}

if colDetails.isMultipleEnabled {
// only specify odata.type for columns holding multiple data
if !colDetails.isMultipleEnabled {
return
}

switch {
case colDetails.isPersonColumn:
filteredData[colName+ODataTypeFieldNamePart] = ODataTypeFieldNameIntVal
default:
filteredData[colName+ODataTypeFieldNamePart] = ODataTypeFieldNameStringVal
}
}
Expand Down
Loading

0 comments on commit 734fd72

Please sign in to comment.