Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @IndicoDataSolutions/pr-be-indicodata-ai
8 changes: 8 additions & 0 deletions IndicoV2.Abstractions/Submissions/ISubmissionsClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,14 @@ public interface ISubmissionsClient
/// <returns>Id of the submission updated.</returns>
Task<ISubmission> MarkSubmissionAsRetrieved(int submissionId, bool retrieved = true, CancellationToken cancellationToken = default);

/// <summary>
/// Retries failed submissions. Submissions must be in a failed state and cannot be requested before the cool-off period (typically 180ms).
/// </summary>
/// <param name="submissionIds">The submission ids to retry.</param>
/// <param name="cancellationToken"><c><see cref="CancellationToken"/></c> for handling cancellation of asynchronous operations.</param>
/// <returns><c><see cref="IEnumerable{T}"/></c> of <c><see cref="ISubmission"/></c></returns>
Task<IEnumerable<ISubmission>> RetrySubmissionsAsync(IEnumerable<int> submissionIds, CancellationToken cancellationToken = default);

/// <summary>
/// Uses the legacy C# client for older frameworks.
/// </summary>
Expand Down
90 changes: 90 additions & 0 deletions IndicoV2.IntegrationTests/Submissions/SubmissionClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -412,5 +412,95 @@ public async Task MarkSubmissionAsRetrieved_ShouldUpdateSubmission()
updated_sub.Retrieved.Should().BeTrue();

}

[Test]
public async Task RetrySubmissionsAsync_ShouldThrowArgumentException_WhenSubmissionIdsIsNull()
{
// Arrange
IEnumerable<int> submissionIds = null;

// Act & Assert
Func<Task> act = async () => await _submissionsClient.RetrySubmissionsAsync(submissionIds);
var exceptionAssertions = await act.Should().ThrowAsync<ArgumentException>();
exceptionAssertions.Which.Message.Should().Contain("You must specify submission ids");
exceptionAssertions.Which.ParamName.Should().Be("submissionIds");
}

[Test]
public async Task RetrySubmissionsAsync_ShouldThrowArgumentException_WhenSubmissionIdsIsEmpty()
{
// Arrange
var submissionIds = new List<int>();

// Act & Assert
Func<Task> act = async () => await _submissionsClient.RetrySubmissionsAsync(submissionIds);
var exceptionAssertions = await act.Should().ThrowAsync<ArgumentException>();
exceptionAssertions.Which.Message.Should().Contain("You must specify submission ids");
exceptionAssertions.Which.ParamName.Should().Be("submissionIds");
}

[Test]
public async Task RetrySubmissionsAsync_ShouldReturnSubmissions_WithRetryInformation()
{
// Arrange
var filters = new SubmissionFilter
{
Status = SubmissionStatus.FAILED
};
var failedSubmissions = await _submissionsClient.ListAsync(null, new List<int> { _workflowId }, filters, 0, 10);

if (!failedSubmissions.Data.Any())
{
Assert.Inconclusive("No failed submissions available for retry test. This test requires a submission in FAILED status.");
return;
}

var failedSubmissionId = failedSubmissions.Data.First().Id;

// Act
var retriedSubmissions = await _submissionsClient.RetrySubmissionsAsync(new List<int> { failedSubmissionId });

// Assert
retriedSubmissions.Should().NotBeNull();
retriedSubmissions.Should().HaveCount(1);
var retriedSubmission = retriedSubmissions.First();
retriedSubmission.Should().NotBeNull();
retriedSubmission.Id.Should().BeGreaterThan(0);
retriedSubmission.Status.Should().BeOfType<SubmissionStatus>();
retriedSubmission.Retries.Should().NotBeNull();
}

[Test]
public async Task RetrySubmissionsAsync_ShouldHandleMultipleSubmissionIds()
{
// Arrange
var filters = new SubmissionFilter
{
Status = SubmissionStatus.FAILED
};
var failedSubmissions = await _submissionsClient.ListAsync(null, new List<int> { _workflowId }, filters, 0, 10);

if (failedSubmissions.Data.Count() < 2)
{
Assert.Inconclusive("Less than 2 failed submissions available for retry test. This test requires at least 2 submissions in FAILED status.");
return;
}

var failedSubmissionIds = failedSubmissions.Data.Take(2).Select(s => s.Id).ToList();

// Act
var retriedSubmissions = await _submissionsClient.RetrySubmissionsAsync(failedSubmissionIds);

// Assert
retriedSubmissions.Should().NotBeNull();
retriedSubmissions.Should().HaveCount(2);
foreach (var retriedSubmission in retriedSubmissions)
{
retriedSubmission.Should().NotBeNull();
retriedSubmission.Id.Should().BeGreaterThan(0);
retriedSubmission.Status.Should().BeOfType<SubmissionStatus>();
retriedSubmission.Retries.Should().NotBeNull();
}
}
}
}
3 changes: 3 additions & 0 deletions IndicoV2.StrawberryShake/Submissions/SubmissionSsClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
.SubmissionIds
.Select(id => id.Value);

public async Task<IListSubmissions_Submissions> List(IReadOnlyList<int?> ids, IReadOnlyList<int?> workflowIds, SubmissionFilter? filter, int? limit, int? after, CancellationToken cancellationToken) => (

Check warning on line 42 in IndicoV2.StrawberryShake/Submissions/SubmissionSsClient.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 42 in IndicoV2.StrawberryShake/Submissions/SubmissionSsClient.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
await ExecuteAsync(async () => await _services.GetRequiredService<ListSubmissionsQuery>().ExecuteAsync(ids, workflowIds, filter, limit, null, null, after, cancellationToken))).Submissions;

public async Task<int?> MarkRetrieved(int submissionId, bool retrieved = true, CancellationToken cancellationToken = default) => (await ExecuteAsync(async () => await _services.GetRequiredService<UpdateSubmissionMutation>().ExecuteAsync(submissionId, retrieved, cancellationToken))).UpdateSubmission.Id;
Expand All @@ -50,6 +50,9 @@
public async Task<string> GenerateSubmissionResult(int submissionId, CancellationToken cancellationToken) => (
await ExecuteAsync(async () => await _services.GetRequiredService<CreateSubmissionResultsMutation>().ExecuteAsync(submissionId, cancellationToken))).SubmissionResults.JobId;

public async Task<IReadOnlyList<IRetrySubmissions_RetrySubmissions>> Retry(IReadOnlyList<int> submissionIds, CancellationToken cancellationToken = default) => (
await ExecuteAsync(async () => await _services.GetRequiredService<RetrySubmissionsMutation>().ExecuteAsync(submissionIds.Select(id => (int?)id).ToList(), cancellationToken))).RetrySubmissions;

private string RemovePropsCausingErrors(string metaString)
{
var acceptableMetaProps = new[] { "name", "path", "upload_type" };
Expand Down
15 changes: 15 additions & 0 deletions IndicoV2.StrawberryShake/Submissions/Submissions.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -204,3 +204,18 @@ mutation CreateSubmissionResults($submissionId: Int!) {
jobId
}
}

mutation RetrySubmissions($submissionIds: [Int]!) {
retrySubmissions(submissionIds: $submissionIds) {
id
status
errors
retries {
id
submissionId
previousErrors
previousStatus
retryErrors
}
}
}
39 changes: 39 additions & 0 deletions IndicoV2/Submissions/SubmissionsClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,45 @@
return GetSubmissionToSubmission(result);
}

public async Task<IEnumerable<ISubmission>> RetrySubmissionsAsync(IEnumerable<int> submissionIds, CancellationToken cancellationToken = default)
{
var submissionIdsList = submissionIds?.ToList() ?? throw new ArgumentException("You must specify submission ids", nameof(submissionIds));
if (submissionIdsList.Count == 0)
throw new ArgumentException("You must specify submission ids", nameof(submissionIds));

var result = await _strawberryShakeClient.Submissions().Retry(submissionIdsList, cancellationToken);
return result?.Select(r =>

Check failure on line 150 in IndicoV2/Submissions/SubmissionsClient.cs

View workflow job for this annotation

GitHub Actions / build

Operator '??' cannot be applied to operands of type 'List<Submission>' and 'List<ISubmission>'

Check failure on line 150 in IndicoV2/Submissions/SubmissionsClient.cs

View workflow job for this annotation

GitHub Actions / build

Operator '??' cannot be applied to operands of type 'List<Submission>' and 'List<ISubmission>'
{
if (!Enum.IsDefined(typeof(StrawberryShake.SubmissionStatus), r.Status))
{
throw new NotSupportedException($"Cannot read submission status: {r.Status}");
}

return new Submission
{
Id = r.Id ?? 0,
Status = (Models.SubmissionStatus)r.Status,
Errors = r.Errors ?? null,
Retries = r.Retries?.Select(retry =>
{
if (!Enum.IsDefined(typeof(StrawberryShake.SubmissionStatus), retry.PreviousStatus))
{
throw new NotSupportedException($"Cannot read submission retry previous status: {retry.PreviousStatus}");
}

return new SubmissionRetry
{
Id = retry.Id ?? 0,
SubmissionId = retry.SubmissionId ?? 0,
PreviousErrors = retry.PreviousErrors,
PreviousStatus = (Models.SubmissionStatus)retry.PreviousStatus,
RetryErrors = retry.RetryErrors
};
}).ToArray() ?? Array.Empty<SubmissionRetry>()
};
}).ToList() ?? new List<ISubmission>();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing null checks for collection elements in Select

Medium Severity

The RetrySubmissionsAsync method iterates over result using Select without checking if individual elements are null. If the GraphQL API returns null elements in the retrySubmissions array or in the nested retries array, accessing properties like r.Status, r.Id, or retry.PreviousStatus will throw a NullReferenceException. This was flagged by the reviewer as "null reference issues" that need to be fixed.

Additional Locations (1)

Fix in Cursor Fix in Web

}

private ISubmission ToSubmissionFromSs(IListSubmissions_Submissions_Submissions submission) => new SubmissionSs(submission);

private ISubmission GetSubmissionToSubmission(IGetSubmission_Submission result) => new Submission
Expand Down
Loading