Skip to content

Commit

Permalink
Add batches of responses to Google Sheets (#51)
Browse files Browse the repository at this point in the history
* Implement adding batches of responses

* Implement clearing batches of responses
  • Loading branch information
benjimarshall authored Nov 20, 2020
1 parent 53fc724 commit 592eeb5
Show file tree
Hide file tree
Showing 3 changed files with 354 additions and 67 deletions.
46 changes: 42 additions & 4 deletions DiscordBot.DataAccess/SheetsServiceParsingHelper.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using DiscordBot.DataAccess.Exceptions;
using DiscordBot.DataAccess.Models;
using Google.Apis.Sheets.v4.Data;

namespace DiscordBot.DataAccess
{
Expand All @@ -27,10 +30,15 @@ internal static class SheetsServiceParsingHelper
return ulong.Parse(cellContents);
}

public static IEnumerable<EventResponse> ParseResponseHeaders(IList<IList<object>> sheetsResponse)
public static IEnumerable<EventResponse> ParseResponseHeaders(ValueRange sheetsResponse, int eventKey)
{
return sheetsResponse[0]
.Zip(sheetsResponse[1])
if (sheetsResponse == null || sheetsResponse.Values.Count < 2)
{
throw new EventsSheetsInitialisationException($"Event sheet {eventKey} is empty");
}

return sheetsResponse.Values[0]
.Zip(sheetsResponse.Values[1])
.Skip(1) // Skip titles column
.Select<(object emoji, object name), EventResponse>(response =>
new EventResponse((string)response.emoji, (string)response.name)
Expand All @@ -51,5 +59,35 @@ IList<object> row
)
.Select(pair => (pair.response, userId));
}

public static int? FindRowNumberOfKey(
ValueRange response,
SheetsColumn keyColumn,
int numberOfHeaderRows,
ulong key
)
{
if (response == null || response.Values.Count < numberOfHeaderRows)
{
throw new EventNotFoundException($"Event key {key} not recognised");
}

try
{
var rowNumber = response.Values
.Skip(numberOfHeaderRows)
.Select((values, index) => (values, index))
.First(row => ulong.Parse((string)row.values[keyColumn.Index]) == key);

// Extract row number, plus a correction factor:
// Correct for skipping the header
// These lists are 0 indexed, but Sheets index from 1
return rowNumber.index + numberOfHeaderRows + 1;
}
catch (InvalidOperationException)
{
return null;
}
}
}
}
120 changes: 120 additions & 0 deletions DiscordBot.DataAccess/SheetsServiceRequestsHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,101 @@ internal static ValueRange MakeCellUpdate(string range, object value)
};
}

internal static IEnumerable<Request> AddResponseRow(
int responseSheetId,
IList<EventResponse> responsesOptions,
ulong userId,
IEnumerable<string> responses
)
{
var newRowValues = responsesOptions
.Select(response => responses.Contains(response.Emoji) ? 1.0 : 0)
.ToList();

// If none of the user's emoji are recognised, don't add a user response row
if (newRowValues.All(responseBit => responseBit == 0))
{
return Enumerable.Empty<Request>();
}

var newRow = newRowValues
.Select(MakeCellData)
.Prepend(MakeCellData(userId.ToString()));

var appendCellsRequest = new AppendCellsRequest()
{
Rows = new[]
{
new RowData()
{
Values = newRow.ToList()
}
},
SheetId = responseSheetId,
Fields = "*"
};

return new[] { new Request() { AppendCells = appendCellsRequest } };
}

internal static IEnumerable<Request> UpdateResponseRow(
int responseSheetId,
IList<EventResponse> responsesOptions,
int responseRow,
IEnumerable<string> responses
)
{
var responseColumnList = responsesOptions.ToList();
var indices = responses
.Select(emoji =>
responseColumnList.FindIndex(eventResponse => eventResponse.Emoji == emoji)
)
.Where(index => index != -1); // If the emoji wasn't found, drop this update

var cellData = MakeCellData(1);
return indices.Select(index => UpdateCell(responseSheetId, responseRow - 1, index + 1, cellData));
}

internal static IEnumerable<Request> AddUserResponsesRequests(
int sheetId,
IList<EventResponse> responsesOptions,
ValueRange userIdColumn,
ulong userId,
IEnumerable<string> responses
)
{
var userRow = SheetsServiceParsingHelper.FindRowNumberOfKey(
userIdColumn,
UnsafeEventsSheetsService.UserIdColumn,
2,
userId
);

if (userRow == null)
{
return AddResponseRow(sheetId, responsesOptions, userId, responses);
}

return UpdateResponseRow(sheetId, responsesOptions, userRow.Value, responses);
}

internal static IEnumerable<Request> ClearUserResponsesRequests(
int sheetId,
ValueRange userIdColumn,
IEnumerable<ulong> users
)
{
return users
.Select(user => SheetsServiceParsingHelper.FindRowNumberOfKey(
userIdColumn,
UnsafeEventsSheetsService.UserIdColumn,
2,
user
))
.Where(userRow => userRow != null)
.Select(userRow => RemoveRow(sheetId, userRow!.Value));
}

internal static T ExecuteRequestsWithRetries<T>(ClientServiceRequest<T> request) =>
ExecuteRequestsWithRetriesAsync(request).Result;

Expand Down Expand Up @@ -170,5 +265,30 @@ private static CellData MakeCellData(string stringValue)
UserEnteredValue = new ExtendedValue() { StringValue = stringValue }
};
}

private static Request UpdateCell(int sheetId, int row, int col, CellData value)
{
var updateCellsRequest = new UpdateCellsRequest()
{
Range = new GridRange()
{
StartRowIndex = row,
EndRowIndex = row + 1,
StartColumnIndex = col,
EndColumnIndex = col + 1,
SheetId = sheetId
},
Rows = new[]
{
new RowData()
{
Values = new[] { value }
}
},
Fields = "*"
};

return new Request() { UpdateCells = updateCellsRequest };
}
}
}
Loading

0 comments on commit 592eeb5

Please sign in to comment.