This commit is contained in:
2024-02-07 16:03:57 -07:00
parent c48046a97e
commit 7b764a599f
8 changed files with 370 additions and 264 deletions

View File

@@ -1,6 +1,9 @@
# Top-most EditorConfig file # Top-most EditorConfig file
root = true root = true
# use unix newline characters
end_of_line = lf
[*.cs] [*.cs]
dotnet_naming_rule.methods_must_be_camel_case.severity = warning dotnet_naming_rule.methods_must_be_camel_case.severity = warning
dotnet_naming_rule.methods_must_be_camel_case.symbols = private_methods dotnet_naming_rule.methods_must_be_camel_case.symbols = private_methods
@@ -199,5 +202,3 @@ indent_size = 2
# Shell scripts # Shell scripts
[*.sh] [*.sh]
end_of_line = lf end_of_line = lf
[*.{cmd,bat}]
end_of_line = crlf

View File

@@ -0,0 +1,219 @@
using System.Text;
using LocalModels;
public class QuizDeterministicChecks
{
[Test]
public void SerializationIsDeterministic_EmptyQuiz()
{
var quiz = new LocalQuiz()
{
Name = "Test Quiz",
Description = "quiz description",
LockAt = new DateTime(2022, 10, 3, 12, 5, 0),
DueAt = new DateTime(2022, 10, 3, 12, 5, 0),
ShuffleAnswers = true,
OneQuestionAtATime = true,
LocalAssignmentGroupName = "Assignments"
};
var quizMarkdown = quiz.ToMarkdown();
var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown);
parsedQuiz.Should().BeEquivalentTo(quiz);
}
[Test]
public void SerializationIsDeterministic_ShowCorrectAnswers()
{
var quiz = new LocalQuiz()
{
Name = "Test Quiz",
Description = "quiz description",
LockAt = new DateTime(2022, 10, 3, 12, 5, 0),
DueAt = new DateTime(2022, 10, 3, 12, 5, 0),
showCorrectAnswers = false,
ShuffleAnswers = true,
OneQuestionAtATime = true,
LocalAssignmentGroupName = "Assignments"
};
var quizMarkdown = quiz.ToMarkdown();
var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown);
parsedQuiz.Should().BeEquivalentTo(quiz);
}
[Test]
public void SerializationIsDeterministic_ShortAnswer()
{
var quiz = new LocalQuiz()
{
Name = "Test Quiz",
Description = "quiz description",
LockAt = new DateTime(2022, 10, 3, 12, 5, 0),
DueAt = new DateTime(2022, 10, 3, 12, 5, 0),
ShuffleAnswers = true,
OneQuestionAtATime = true,
LocalAssignmentGroupName = "Assignments",
Questions = new LocalQuizQuestion[]
{
new ()
{
Text = "test short answer",
QuestionType = QuestionType.SHORT_ANSWER,
Points = 1
}
}
};
var quizMarkdown = quiz.ToMarkdown();
var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown);
parsedQuiz.Should().BeEquivalentTo(quiz);
}
[Test]
public void SerializationIsDeterministic_Essay()
{
var quiz = new LocalQuiz()
{
Name = "Test Quiz",
Description = "quiz description",
LockAt = new DateTime(2022, 10, 3, 12, 5, 0),
DueAt = new DateTime(2022, 10, 3, 12, 5, 0),
ShuffleAnswers = true,
OneQuestionAtATime = true,
LocalAssignmentGroupName = "Assignments",
Questions = new LocalQuizQuestion[]
{
new ()
{
Text = "test essay",
QuestionType = QuestionType.ESSAY,
Points = 1
}
}
};
var quizMarkdown = quiz.ToMarkdown();
var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown);
parsedQuiz.Should().BeEquivalentTo(quiz);
}
[Test]
public void SerializationIsDeterministic_MultipleAnswer()
{
var quiz = new LocalQuiz()
{
Name = "Test Quiz",
Description = "quiz description",
LockAt = new DateTime(2022, 10, 3, 12, 5, 0),
DueAt = new DateTime(2022, 10, 3, 12, 5, 0),
ShuffleAnswers = true,
OneQuestionAtATime = true,
LocalAssignmentGroupName = "Assignments",
Questions = new LocalQuizQuestion[]
{
new ()
{
Text = "test multiple answer",
QuestionType = QuestionType.MULTIPLE_ANSWERS,
Points = 1,
Answers = new LocalQuizQuestionAnswer[]
{
new() {
Correct = true,
Text="yes",
},
new() {
Correct = true,
Text="no",
}
}
}
}
};
var quizMarkdown = quiz.ToMarkdown();
var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown);
parsedQuiz.Should().BeEquivalentTo(quiz);
}
[Test]
public void SerializationIsDeterministic_MultipleChoice()
{
var quiz = new LocalQuiz()
{
Name = "Test Quiz",
Description = "quiz description",
LockAt = new DateTime(2022, 10, 3, 12, 5, 0),
DueAt = new DateTime(2022, 10, 3, 12, 5, 0),
ShuffleAnswers = true,
OneQuestionAtATime = true,
LocalAssignmentGroupName = "Assignments",
Questions = new LocalQuizQuestion[]
{
new ()
{
Text = "test multiple choice",
QuestionType = QuestionType.MULTIPLE_CHOICE,
Points = 1,
Answers = new LocalQuizQuestionAnswer[]
{
new() {
Correct = true,
Text="yes",
},
new() {
Correct = false,
Text="no",
}
}
}
}
};
var quizMarkdown = quiz.ToMarkdown();
var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown);
parsedQuiz.Should().BeEquivalentTo(quiz);
}
[Test]
public void SerializationIsDeterministic_Matching()
{
var quiz = new LocalQuiz()
{
Name = "Test Quiz",
Description = "quiz description",
LockAt = new DateTime(2022, 10, 3, 12, 5, 0),
DueAt = new DateTime(2022, 10, 3, 12, 5, 0),
ShuffleAnswers = true,
OneQuestionAtATime = true,
LocalAssignmentGroupName = "Assignments",
Questions = new LocalQuizQuestion[]
{
new ()
{
Text = "test matching",
QuestionType = QuestionType.MATCHING,
Points = 1,
Answers = new LocalQuizQuestionAnswer[]
{
new() {
Correct = true,
Text="yes",
MatchedText = "testing yes"
},
new() {
Correct = true,
Text="no",
MatchedText = "testing no"
}
}
}
}
};
var quizMarkdown = quiz.ToMarkdown();
var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown);
parsedQuiz.Should().BeEquivalentTo(quiz);
}
}

View File

@@ -1,3 +1,4 @@
using System.Text;
using LocalModels; using LocalModels;
// try to follow syntax from https://github.com/gpoore/text2qti // try to follow syntax from https://github.com/gpoore/text2qti
@@ -22,7 +23,7 @@ this is my description in markdown
OneQuestionAtATime = false, OneQuestionAtATime = false,
LocalAssignmentGroupName = "someId", LocalAssignmentGroupName = "someId",
AllowedAttempts = -1, AllowedAttempts = -1,
Questions = new LocalQuizQuestion[] { } Questions = []
}; };
var markdown = quiz.ToMarkdown(); var markdown = quiz.ToMarkdown();
@@ -39,28 +40,82 @@ this is my description in markdown
[Test] [Test]
public void TestCanParseMarkdownQuizWithNoQuestions() public void TestCanParseMarkdownQuizWithNoQuestions()
{ {
var rawMarkdownQuiz = @" var rawMarkdownQuiz = new StringBuilder();
Name: Test Quiz rawMarkdownQuiz.Append("Name: Test Quiz\n");
ShuffleAnswers: true rawMarkdownQuiz.Append("ShuffleAnswers: true\n");
OneQuestionAtATime: false rawMarkdownQuiz.Append("OneQuestionAtATime: false\n");
DueAt: 2023-08-21T23:59:00 rawMarkdownQuiz.Append("DueAt: 2023-08-21T23:59:00\n");
LockAt: 2023-08-21T23:59:00 rawMarkdownQuiz.Append("LockAt: 2023-08-21T23:59:00\n");
AssignmentGroup: Assignments rawMarkdownQuiz.Append("AssignmentGroup: Assignments\n");
AllowedAttempts: -1 rawMarkdownQuiz.Append("AllowedAttempts: -1\n");
Description: this is the rawMarkdownQuiz.Append("Description: this is the\n");
multi line rawMarkdownQuiz.Append("multi line\n");
description rawMarkdownQuiz.Append("description\n");
--- rawMarkdownQuiz.Append("---\n");
"; rawMarkdownQuiz.Append('\n');
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz);
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz.ToString());
var expectedDescription = new StringBuilder();
expectedDescription.Append("this is the\n");
expectedDescription.Append("multi line\n");
expectedDescription.Append("description");
quiz.Name.Should().Be("Test Quiz"); quiz.Name.Should().Be("Test Quiz");
quiz.ShuffleAnswers.Should().Be(true); quiz.ShuffleAnswers.Should().Be(true);
quiz.OneQuestionAtATime.Should().BeFalse(); quiz.OneQuestionAtATime.Should().BeFalse();
quiz.AllowedAttempts.Should().Be(-1); quiz.AllowedAttempts.Should().Be(-1);
quiz.Description.Should().Be(@"this is the quiz.Description.Should().Be(expectedDescription.ToString());
multi line }
description"); [Test]
public void TestCanParseMarkdownQuizPassword()
{
var password = "this-is-the-password";
var rawMarkdownQuiz = new StringBuilder();
rawMarkdownQuiz.Append("Name: Test Quiz\n");
rawMarkdownQuiz.Append($"Password: {password}\n");
rawMarkdownQuiz.Append("ShuffleAnswers: true\n");
rawMarkdownQuiz.Append("OneQuestionAtATime: false\n");
rawMarkdownQuiz.Append("DueAt: 2023-08-21T23:59:00\n");
rawMarkdownQuiz.Append("LockAt: 2023-08-21T23:59:00\n");
rawMarkdownQuiz.Append("AssignmentGroup: Assignments\n");
rawMarkdownQuiz.Append("AllowedAttempts: -1\n");
rawMarkdownQuiz.Append("Description: this is the\n");
rawMarkdownQuiz.Append("multi line\n");
rawMarkdownQuiz.Append("description\n");
rawMarkdownQuiz.Append("---\n");
rawMarkdownQuiz.Append('\n');
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz.ToString());
quiz.Password.Should().Be(password);
}
[Test]
public void TestCanParseMarkdownQuiz_CanConfigureToShowCorrectAnswers()
{
var rawMarkdownQuiz = new StringBuilder();
rawMarkdownQuiz.Append("Name: Test Quiz\n");
rawMarkdownQuiz.Append("ShuffleAnswers: true\n");
rawMarkdownQuiz.Append("OneQuestionAtATime: false\n");
rawMarkdownQuiz.Append("ShowCorrectAnswers: false\n");
rawMarkdownQuiz.Append("DueAt: 2023-08-21T23:59:00\n");
rawMarkdownQuiz.Append("LockAt: 2023-08-21T23:59:00\n");
rawMarkdownQuiz.Append("AssignmentGroup: Assignments\n");
rawMarkdownQuiz.Append("AllowedAttempts: -1\n");
rawMarkdownQuiz.Append("Description: this is the\n");
rawMarkdownQuiz.Append("multi line\n");
rawMarkdownQuiz.Append("description\n");
rawMarkdownQuiz.Append("---\n");
rawMarkdownQuiz.Append('\n');
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz.ToString());
quiz.showCorrectAnswers.Should().BeFalse();
} }
[Test] [Test]
@@ -165,196 +220,4 @@ Which events are triggered when the user clicks on an input field?
short_answer"; short_answer";
questionMarkdown.Should().Contain(expectedMarkdown); questionMarkdown.Should().Contain(expectedMarkdown);
} }
[Test]
public void SerializationIsDeterministic_EmptyQuiz()
{
var quiz = new LocalQuiz()
{
Name = "Test Quiz",
Description = "quiz description",
LockAt = new DateTime(2022, 10, 3, 12, 5, 0),
DueAt = new DateTime(2022, 10, 3, 12, 5, 0),
ShuffleAnswers = true,
OneQuestionAtATime = true,
LocalAssignmentGroupName = "Assignments"
};
var quizMarkdown = quiz.ToMarkdown();
var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown);
parsedQuiz.Should().BeEquivalentTo(quiz);
}
[Test]
public void SerializationIsDeterministic_ShortAnswer()
{
var quiz = new LocalQuiz()
{
Name = "Test Quiz",
Description = "quiz description",
LockAt = new DateTime(2022, 10, 3, 12, 5, 0),
DueAt = new DateTime(2022, 10, 3, 12, 5, 0),
ShuffleAnswers = true,
OneQuestionAtATime = true,
LocalAssignmentGroupName = "Assignments",
Questions = new LocalQuizQuestion[]
{
new ()
{
Text = "test short answer",
QuestionType = QuestionType.SHORT_ANSWER,
Points = 1
}
}
};
var quizMarkdown = quiz.ToMarkdown();
var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown);
parsedQuiz.Should().BeEquivalentTo(quiz);
}
[Test]
public void SerializationIsDeterministic_Essay()
{
var quiz = new LocalQuiz()
{
Name = "Test Quiz",
Description = "quiz description",
LockAt = new DateTime(2022, 10, 3, 12, 5, 0),
DueAt = new DateTime(2022, 10, 3, 12, 5, 0),
ShuffleAnswers = true,
OneQuestionAtATime = true,
LocalAssignmentGroupName = "Assignments",
Questions = new LocalQuizQuestion[]
{
new ()
{
Text = "test essay",
QuestionType = QuestionType.ESSAY,
Points = 1
}
}
};
var quizMarkdown = quiz.ToMarkdown();
var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown);
parsedQuiz.Should().BeEquivalentTo(quiz);
}
[Test]
public void SerializationIsDeterministic_MultipleAnswer()
{
var quiz = new LocalQuiz()
{
Name = "Test Quiz",
Description = "quiz description",
LockAt = new DateTime(2022, 10, 3, 12, 5, 0),
DueAt = new DateTime(2022, 10, 3, 12, 5, 0),
ShuffleAnswers = true,
OneQuestionAtATime = true,
LocalAssignmentGroupName = "Assignments",
Questions = new LocalQuizQuestion[]
{
new ()
{
Text = "test multiple answer",
QuestionType = QuestionType.MULTIPLE_ANSWERS,
Points = 1,
Answers = new LocalQuizQuestionAnswer[]
{
new() {
Correct = true,
Text="yes",
},
new() {
Correct = true,
Text="no",
}
}
}
}
};
var quizMarkdown = quiz.ToMarkdown();
var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown);
parsedQuiz.Should().BeEquivalentTo(quiz);
}
[Test]
public void SerializationIsDeterministic_MultipleChoice()
{
var quiz = new LocalQuiz()
{
Name = "Test Quiz",
Description = "quiz description",
LockAt = new DateTime(2022, 10, 3, 12, 5, 0),
DueAt = new DateTime(2022, 10, 3, 12, 5, 0),
ShuffleAnswers = true,
OneQuestionAtATime = true,
LocalAssignmentGroupName = "Assignments",
Questions = new LocalQuizQuestion[]
{
new ()
{
Text = "test multiple choice",
QuestionType = QuestionType.MULTIPLE_CHOICE,
Points = 1,
Answers = new LocalQuizQuestionAnswer[]
{
new() {
Correct = true,
Text="yes",
},
new() {
Correct = false,
Text="no",
}
}
}
}
};
var quizMarkdown = quiz.ToMarkdown();
var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown);
parsedQuiz.Should().BeEquivalentTo(quiz);
}
[Test]
public void SerializationIsDeterministic_Matching()
{
var quiz = new LocalQuiz()
{
Name = "Test Quiz",
Description = "quiz description",
LockAt = new DateTime(2022, 10, 3, 12, 5, 0),
DueAt = new DateTime(2022, 10, 3, 12, 5, 0),
ShuffleAnswers = true,
OneQuestionAtATime = true,
LocalAssignmentGroupName = "Assignments",
Questions = new LocalQuizQuestion[]
{
new ()
{
Text = "test matching",
QuestionType = QuestionType.MATCHING,
Points = 1,
Answers = new LocalQuizQuestionAnswer[]
{
new() {
Correct = true,
Text="yes",
MatchedText = "testing yes"
},
new() {
Correct = true,
Text="no",
MatchedText = "testing no"
}
}
}
}
};
var quizMarkdown = quiz.ToMarkdown();
var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown);
parsedQuiz.Should().BeEquivalentTo(quiz);
}
} }

View File

@@ -5,11 +5,14 @@ namespace LocalModels;
public record LocalQuiz: IModuleItem public record LocalQuiz: IModuleItem
{ {
public required string Name { get; init; } public required string Name { get; init; }
public required string Description { get; init; } public required string Description { get; init; }
public string? Password { get; init; } = null;
public DateTime? LockAt { get; init; } public DateTime? LockAt { get; init; }
public DateTime DueAt { get; init; } public DateTime DueAt { get; init; }
public bool ShuffleAnswers { get; init; } = true; public bool ShuffleAnswers { get; init; } = true;
public bool showCorrectAnswers { get; init; } = true;
public bool OneQuestionAtATime { get; init; } = false; public bool OneQuestionAtATime { get; init; } = false;
public string? LocalAssignmentGroupName { get; init; } public string? LocalAssignmentGroupName { get; init; }
public int AllowedAttempts { get; init; } = -1; // -1 is infinite public int AllowedAttempts { get; init; } = -1; // -1 is infinite
@@ -21,6 +24,7 @@ public record LocalQuiz: IModuleItem
// If “until_after_last_attempt”, students can only see results after their last attempt. (Only valid if allowed_attempts > 1). Defaults to null. // If “until_after_last_attempt”, students can only see results after their last attempt. (Only valid if allowed_attempts > 1). Defaults to null.
public IEnumerable<LocalQuizQuestion> Questions { get; init; } = public IEnumerable<LocalQuizQuestion> Questions { get; init; } =
Enumerable.Empty<LocalQuizQuestion>(); Enumerable.Empty<LocalQuizQuestion>();
public ulong? GetCanvasAssignmentGroupId(IEnumerable<LocalAssignmentGroup> assignmentGroups) => public ulong? GetCanvasAssignmentGroupId(IEnumerable<LocalAssignmentGroup> assignmentGroups) =>
assignmentGroups assignmentGroups
.FirstOrDefault(g => g.Name == LocalAssignmentGroupName)? .FirstOrDefault(g => g.Name == LocalAssignmentGroupName)?
@@ -44,7 +48,9 @@ public record LocalQuiz: IModuleItem
return $@"Name: {Name} return $@"Name: {Name}
LockAt: {LockAt} LockAt: {LockAt}
DueAt: {DueAt} DueAt: {DueAt}
Password: {Password}
ShuffleAnswers: {ShuffleAnswers.ToString().ToLower()} ShuffleAnswers: {ShuffleAnswers.ToString().ToLower()}
ShowCorrectAnswers: {showCorrectAnswers.ToString().ToLower()}
OneQuestionAtATime: {OneQuestionAtATime.ToString().ToLower()} OneQuestionAtATime: {OneQuestionAtATime.ToString().ToLower()}
AssignmentGroup: {LocalAssignmentGroupName} AssignmentGroup: {LocalAssignmentGroupName}
AllowedAttempts: {AllowedAttempts} AllowedAttempts: {AllowedAttempts}
@@ -82,6 +88,18 @@ Description: {Description}
: throw new QuizMarkdownParseException($"Error with ShuffleAnswers: {rawShuffleAnswers}"); : throw new QuizMarkdownParseException($"Error with ShuffleAnswers: {rawShuffleAnswers}");
var rawPassword = extractLabelValue(settings, "Password");
var password = rawPassword == null || rawPassword.Trim() == string.Empty
? null
: rawPassword;
var rawShowCorrectAnswers = extractLabelValue(settings, "ShowCorrectAnswers");
var showCorrectAnswers = bool.TryParse(rawShowCorrectAnswers, out bool parsedShowCorrectAnswers)
? parsedShowCorrectAnswers
: true; //default to true
var rawOneQuestionAtATime = extractLabelValue(settings, "OneQuestionAtATime"); var rawOneQuestionAtATime = extractLabelValue(settings, "OneQuestionAtATime");
var oneQuestionAtATime = bool.TryParse(rawOneQuestionAtATime, out bool parsedOneQuestion) var oneQuestionAtATime = bool.TryParse(rawOneQuestionAtATime, out bool parsedOneQuestion)
? parsedOneQuestion ? parsedOneQuestion
@@ -112,9 +130,11 @@ Description: {Description}
{ {
Name = name, Name = name,
Description = description, Description = description,
Password = password,
LockAt = lockAt, LockAt = lockAt,
DueAt = dueAt, DueAt = dueAt,
ShuffleAnswers = shuffleAnswers, ShuffleAnswers = shuffleAnswers,
showCorrectAnswers = showCorrectAnswers,
OneQuestionAtATime = oneQuestionAtATime, OneQuestionAtATime = oneQuestionAtATime,
LocalAssignmentGroupName = assignmentGroup, LocalAssignmentGroupName = assignmentGroup,
AllowedAttempts = allowedAttempts, AllowedAttempts = allowedAttempts,

View File

@@ -50,6 +50,8 @@ public class CanvasQuizService(
// assignment_group_id = "quiz", // TODO: support specific assignment groups // assignment_group_id = "quiz", // TODO: support specific assignment groups
// time_limit = localQuiz.TimeLimit, // time_limit = localQuiz.TimeLimit,
shuffle_answers = localQuiz.ShuffleAnswers, shuffle_answers = localQuiz.ShuffleAnswers,
access_code = localQuiz.Password,
show_correct_answers = localQuiz.showCorrectAnswers,
// hide_results = localQuiz.HideResults, // hide_results = localQuiz.HideResults,
allowed_attempts = localQuiz.AllowedAttempts, allowed_attempts = localQuiz.AllowedAttempts,
one_question_at_a_time = false, one_question_at_a_time = false,
@@ -91,11 +93,9 @@ public class CanvasQuizService(
private async Task hackFixRedundantAssignments(ulong canvasCourseId) private async Task hackFixRedundantAssignments(ulong canvasCourseId)
{ {
using var activity = DiagnosticsConfig.Source.StartActivity("hack fixing redundant quiz assignments that are auto-created"); using var activity = DiagnosticsConfig.Source.StartActivity("hack fixing redundant quiz assignments that are auto-created");
activity?.SetTag("canvas syncronization", true); activity?.SetTag("canvas syncronization", true);
var canvasAssignments = await assignments.GetAll(canvasCourseId); var canvasAssignments = await assignments.GetAll(canvasCourseId);
var assignmentsToDelete = canvasAssignments var assignmentsToDelete = canvasAssignments
.Where( .Where(
@@ -113,7 +113,7 @@ public class CanvasQuizService(
a.Name a.Name
); );
} }
); ).ToArray();
await Task.WhenAll(tasks); await Task.WhenAll(tasks);
} }

View File

@@ -9,6 +9,8 @@ public class WebRequestor : IWebRequestor
private RestClient client; private RestClient client;
private readonly IConfiguration _config; private readonly IConfiguration _config;
private readonly ILogger<WebRequestor> logger; private readonly ILogger<WebRequestor> logger;
private static int rateLimitRetryCount = 6;
private static int rateLimitSleepInterval = 1000;
public WebRequestor(IConfiguration config, ILogger<WebRequestor> logger) public WebRequestor(IConfiguration config, ILogger<WebRequestor> logger)
{ {
@@ -34,39 +36,38 @@ public class WebRequestor : IWebRequestor
return (deserialize<T>(response), response); return (deserialize<T>(response), response);
} }
public async Task<RestResponse> PostAsync(RestRequest request) public async Task<RestResponse> PostAsync(RestRequest request) => await rateLimitAwarePostAsync(request, 0);
{
using var activity = DiagnosticsConfig.Source.StartActivity("sending post");
activity?.AddTag("success", false);
private async Task<RestResponse> rateLimitAwarePostAsync(RestRequest request, int retryCount = 0)
{
request.AddHeader("Content-Type", "application/json"); request.AddHeader("Content-Type", "application/json");
try var response = await client.ExecutePostAsync(request);
{
var response = await client.ExecutePostAsync(request);
activity?.AddTag("url", response.ResponseUri);
if (isRateLimited(response)) if (isRateLimited(response)) {
logger.LogInformation("hit rate limit"); if(retryCount < rateLimitRetryCount){
logger.LogInformation($"hit rate limit on post, retry count is {retryCount} / {rateLimitRetryCount}, retrying");
if (!response.IsSuccessful) Console.WriteLine($"hit rate limit on post, retry count is {retryCount} / {rateLimitRetryCount}, retrying");
{ Thread.Sleep(rateLimitSleepInterval);
logger.LogError($"Error with response, response content: {response.Content}", response); return await rateLimitAwarePostAsync(request, retryCount + 1);}
throw new Exception("error with response");
}
activity?.AddTag("success", true);
return response;
} }
catch (Exception e)
{
Console.WriteLine("inside post catch block");
throw e;
if (!response.IsSuccessful)
{
logger.LogError($"Error with response, response content: {response.Content}");
throw new Exception($"error post response, retrycount: {retryCount}, ratelimited: {isRateLimited(response)}, code: {response.StatusCode}, response content: {response.Content}");
} }
return response;
} }
private static bool isRateLimited(RestResponse response) => private static bool isRateLimited(RestResponse response)
response.StatusCode == HttpStatusCode.Forbidden && response.Content?.Contains("403 Forbidden (Rate Limit Exceeded)") != null; {
if(response.Content == null)
return false;
return response.StatusCode == HttpStatusCode.Forbidden
&& response.Content.Contains("403 Forbidden (Rate Limit Exceeded)");
}
public async Task<(T?, RestResponse)> PostAsync<T>(RestRequest request) public async Task<(T?, RestResponse)> PostAsync<T>(RestRequest request)
{ {
@@ -95,32 +96,33 @@ public class WebRequestor : IWebRequestor
return (deserialize<T>(response), response); return (deserialize<T>(response), response);
} }
public async Task<RestResponse> DeleteAsync(RestRequest request) public Task<RestResponse> DeleteAsync(RestRequest request) => recursiveDeleteAsync(request, 0);
private async Task<RestResponse> recursiveDeleteAsync(RestRequest request, int retryCount)
{ {
// using var activity = DiagnosticsConfig.Source.StartActivity($"sending delete web request");
// activity?.AddTag("success", false);
try try
{ {
var response = await client.DeleteAsync(request); var response = await client.DeleteAsync(request);
if (isRateLimited(response)) if (isRateLimited(response))
Console.WriteLine("after delete response in rate limited"); Console.WriteLine("after delete response in rate limited");
// Console.WriteLine(response.Content);
// activity?.AddTag("url", response.ResponseUri);
// activity?.AddTag("success", true);
return response; return response;
} }
catch (HttpRequestException e) catch (HttpRequestException e)
{ {
if (e.StatusCode == HttpStatusCode.Forbidden) // && response.Content == "403 Forbidden (Rate Limit Exceeded)" if (e.StatusCode == HttpStatusCode.Forbidden) // && response.Content == "403 Forbidden (Rate Limit Exceeded)"
logger.LogInformation("hit rate limit in delete"); {
if(retryCount < rateLimitRetryCount)
Console.WriteLine(e.StatusCode); {
// Console.WriteLine(); logger.LogInformation($"hit rate limit in delete, retry count is {retryCount} / {rateLimitRetryCount}, retrying");
throw e; Console.WriteLine($"hit rate limit in delete, retry count is {retryCount} / {rateLimitRetryCount}, retrying");
Thread.Sleep(rateLimitSleepInterval);
return await recursiveDeleteAsync(request, retryCount + 1);
}
else
{
logger.LogInformation($"hit rate limit in delete, {rateLimitRetryCount} retries did not fix it");
}
}
throw;
} }
} }
@@ -164,4 +166,5 @@ public class WebRequestor : IWebRequestor
throw; throw;
} }
} }
} }

View File

@@ -22,7 +22,7 @@ services:
collector: collector:
image: otel/opentelemetry-collector-contrib image: otel/opentelemetry-collector-contrib
volumes: volumes:
- ./ops/otel-collector-config.yml:/etc/otelcol-contrib/config.yaml - ./canvas-development/otel-collector-config.yml:/etc/otelcol-contrib/config.yaml
ports: ports:
- 1888:1888 # pprof extension - 1888:1888 # pprof extension
- 8888:8888 # Prometheus metrics exposed by the Collector - 8888:8888 # Prometheus metrics exposed by the Collector