Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve support to AWS SQS/SNS #3437

Open
wants to merge 26 commits into
base: master
Choose a base branch
from

Conversation

lillo42
Copy link
Contributor

@lillo42 lillo42 commented Dec 22, 2024

Hohohoho 🎅

  • Add support to SNS/SQS FIFO
  • Improve support for LocalStack
  • Rename some classes from Sqs to Sns (breaking changes)
  • Add support to SQS publication
  • Add Sample for SQS publication & FIFO

How should I handle the tests? My current approach is to duplicate them. Should I continue on this path?
Duplicate the test

Copy link

@codescene-delta-analysis codescene-delta-analysis bot left a comment

Choose a reason for hiding this comment

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

Code Health Quality Gates: FAILED

Change in average Code Health of affected files: -0.12 (9.65 -> 9.53)

  • Declining Code Health: 4 findings(s) 🚩

View detailed results in CodeScene

Copy link

@codescene-delta-analysis codescene-delta-analysis bot left a comment

Choose a reason for hiding this comment

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

Code Health Quality Gates: FAILED

Change in average Code Health of affected files: -0.10 (9.46 -> 9.36)

  • Declining Code Health: 8 findings(s) 🚩

View detailed results in CodeScene

@iancooper
Copy link
Member

Hi @lillo42 - thanks for this. To lift out of inline above, the usual strategy would be:

Folders for each queue type:

  • Standard
  • Fifo

Put the existing tests in Standard and duplicate the tests in Fifo, but with the changed semantics.

I tend to prefer being explicit in tests, over worrying about duplication there.

@iancooper
Copy link
Member

PS @lillo42 if you have time. One weakness in Brighter is that you have to produce to SNS and can't target a queue. It would be good to allow the queue to be the target. We now do something similar for ASB. Probably pop that in a separate PR though?

Copy link

@codescene-delta-analysis codescene-delta-analysis bot left a comment

Choose a reason for hiding this comment

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

Code Health Quality Gates: FAILED

Change in average Code Health of affected files: +0.05 (9.61 -> 9.66)

  • Declining Code Health: 6 findings(s) 🚩

View detailed results in CodeScene

Copy link
Member

@iancooper iancooper left a comment

Choose a reason for hiding this comment

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

This looks great. You may also need to support async approaches, you'll note the increased footprint when you merge in master. It is a little crude, as we just tend to duplicate in tests, but I sort of prefer tests to be explicit

Copy link

@codescene-delta-analysis codescene-delta-analysis bot left a comment

Choose a reason for hiding this comment

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

Code Health Quality Gates: FAILED

Change in average Code Health of affected files: +0.10 (9.50 -> 9.60)

  • Declining Code Health: 5 findings(s) 🚩

View detailed results in CodeScene

@lillo42
Copy link
Contributor Author

lillo42 commented Dec 30, 2024

PS @lillo42 if you have time. One weakness in Brighter is that you have to produce to SNS and can't target a queue. It would be good to allow the queue to be the target. We now do something similar for ASB. Probably pop that in a separate PR though?

Sure no problem, I can add it to this PR.

@iancooper
Copy link
Member

Thanks @lillo42. Apologies in advance for the merge issues caused by the Reactor(Blocking)/Proactor(Non-Blocking) enhancement

Copy link

@codescene-delta-analysis codescene-delta-analysis bot left a comment

Choose a reason for hiding this comment

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

Code Health Quality Gates: FAILED

Change in average Code Health of affected files: +0.05 (9.60 -> 9.65)

  • Declining Code Health: 5 findings(s) 🚩

View detailed results in CodeScene

@iancooper
Copy link
Member

We still have a credentials issue on the AWS tests, that means they don't show up here. I need to fix that too. Let me see if I can fix that and merge to main, so that we can at least see what is passing

RoutingKey topic,
TopicFindBy topicFindBy,
SnsAttributes? attributes,
OnMissingChannel makeTopic = OnMissingChannel.Create,
Copy link
Member

Choose a reason for hiding this comment

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

Can we make makeTopic the last parameter? That keeps it in sync with other producers

namespace System.Diagnostics.CodeAnalysis;

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
public sealed class MemberNotNullWhenAttribute : Attribute
Copy link
Member

Choose a reason for hiding this comment

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

Out of interest, we have an open conversation on using polyfills vs dropping netstandard20

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tend to prefer drop support for netstandard20, but depending on how many downloads we have today on .NET standard maybe we still have people using Brighter with .NET Framework 4

Maybe on V11 we can remove the support for .NET Standard completed, and put a warning on V10 saying that is the last version of supporting .NET Standard/Framework

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, you may be right that it would be 0-warning if we dropped it with V10 and we should declare that will happen with V11.

Copy link

@codescene-delta-analysis codescene-delta-analysis bot left a comment

Choose a reason for hiding this comment

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

Code Health Quality Gates: FAILED

Change in average Code Health of affected files: +0.13 (9.61 -> 9.74)

  • Declining Code Health: 9 findings(s) 🚩
  • Improving Code Health: 1 findings(s) ✅

View detailed results in CodeScene

Comment on lines +41 to +47
public Message CreateMessage(Amazon.SQS.Model.Message sqsMessage)
{
var topic = HeaderResult<RoutingKey>.Empty();
var messageId = HeaderResult<string?>.Empty();

Message message;
try

Choose a reason for hiding this comment

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

❌ Getting worse: Complex Method
CreateMessage increases in cyclomatic complexity from 10 to 12, threshold = 9

Suppress

Copy link

@codescene-delta-analysis codescene-delta-analysis bot left a comment

Choose a reason for hiding this comment

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

Code Health Quality Gates: FAILED

Change in average Code Health of affected files: +0.43 (9.42 -> 9.85)

  • Declining Code Health: 10 findings(s) 🚩
  • Improving Code Health: 5 findings(s) ✅

View detailed results in CodeScene


public Message CreateMessage(Amazon.SQS.Model.Message sqsMessage)

Choose a reason for hiding this comment

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

❌ Getting worse: Complex Method
CreateMessage increases in cyclomatic complexity from 10 to 13, threshold = 9

Suppress

Comment on lines +72 to +75
RoutingKey topic,
TopicFindBy topicFindBy,
SnsAttributes? attributes,
OnMissingChannel makeTopic = OnMissingChannel.Create,

Choose a reason for hiding this comment

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

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: EnsureQueueAsync,EnsureTopicAsync

Suppress

Comment on lines +184 to +188
private async Task<string> CreateQueueAsync(
string queueName,
SqsAttributes? sqsAttributes,
OnMissingChannel makeChannel,
CancellationToken cancellationToken)

Choose a reason for hiding this comment

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

❌ New issue: Complex Method
CreateQueueAsync has a cyclomatic complexity of 17, threshold = 9

Suppress

Comment on lines +282 to +342
private async Task<string> CreateDeadLetterQueueAsync(
SqsAttributes sqsAttributes,
CancellationToken cancellationToken)
{
using var sqsClient = _awsClientFactory.CreateSqsClient();

var queueName = sqsAttributes.RedrivePolicy!.DeadlLetterQueueName;

var tags = new Dictionary<string, string> { { "Source", "Brighter" } };
var attributes = new Dictionary<string, string?>();
if (sqsAttributes.Type == SnsSqsType.Fifo)
{
queueName = queueName.ToValidSQSQueueName(true);

attributes.Add(QueueAttributeName.FifoQueue, "true");
if (sqsAttributes.ContentBasedDeduplication)
{
attributes.Add(QueueAttributeName.ContentBasedDeduplication, "true");
}

if (sqsAttributes is { DeduplicationScope: not null, FifoThroughputLimit: not null })
{
attributes.Add(QueueAttributeName.FifoThroughputLimit,
sqsAttributes.FifoThroughputLimit.Value.ToString());
attributes.Add(QueueAttributeName.DeduplicationScope, sqsAttributes.DeduplicationScope switch
{
DeduplicationScope.MessageGroup => "messageGroup",
_ => "queue"
});
}
}

string queueUrl;

try
{
var request = new CreateQueueRequest(queueName) { Attributes = attributes, Tags = tags };
// create queue is idempotent, so safe to call even if queue already exists
var response = await sqsClient.CreateQueueAsync(request, cancellationToken);

queueUrl = response.QueueUrl ?? throw new InvalidOperationException(
$"Could not find create DLQ, status: {response.HttpStatusCode}");
}
catch (QueueNameExistsException)
{
var response = await sqsClient.GetQueueUrlAsync(queueName, cancellationToken);
queueUrl = response.QueueUrl;
}

var attributesResponse = await sqsClient.GetQueueAttributesAsync(
new GetQueueAttributesRequest { QueueUrl = queueUrl, AttributeNames = [QueueAttributeName.QueueArn] },
cancellationToken);

if (attributesResponse.HttpStatusCode != HttpStatusCode.OK)
{
throw new InvalidOperationException(
$"Could not find ARN of DLQ, status: {attributesResponse.HttpStatusCode}");
}

return attributesResponse.QueueARN;
}

Choose a reason for hiding this comment

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

❌ New issue: Complex Method
CreateDeadLetterQueueAsync has a cyclomatic complexity of 10, threshold = 9

Suppress

Comment on lines +184 to +188
private async Task<string> CreateQueueAsync(
string queueName,
SqsAttributes? sqsAttributes,
OnMissingChannel makeChannel,
CancellationToken cancellationToken)

Choose a reason for hiding this comment

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

❌ New issue: Bumpy Road Ahead
CreateQueueAsync has 3 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is one single, nested block per function

Suppress

Comment on lines +402 to +426
private async Task SubscribeToTopicAsync(
string topicArn,
string queueUrl,
SqsAttributes? sqsAttributes,
AmazonSQSClient sqsClient,
AmazonSimpleNotificationServiceClient snsClient)
{
var arn = await snsClient.SubscribeQueueAsync(topicArn, sqsClient, queueUrl);
if (string.IsNullOrEmpty(arn))
{
throw new InvalidOperationException(
$"Could not subscribe to topic: {topicArn} from queue: {queueUrl} in region {AwsConnection.Region}");
}

var response = await snsClient.SetSubscriptionAttributesAsync(
new SetSubscriptionAttributesRequest(arn,
"RawMessageDelivery",
sqsAttributes?.RawMessageDelivery.ToString())
);

if (response.HttpStatusCode != HttpStatusCode.OK)
{
throw new InvalidOperationException("Unable to set subscription attribute for raw message delivery");
}
}

Choose a reason for hiding this comment

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

ℹ New issue: Excess Number of Function Arguments
SubscribeToTopicAsync has 5 arguments, threshold = 4

Copy link
Member

@iancooper iancooper left a comment

Choose a reason for hiding this comment

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

There is a lot here, and it is a great addition. A couple of minor tweaks, which are more o do with style over anything else.


var arnElements = s!.Split(':');
var topic = arnElements[(int)ARNAmazonSNS.TopicName];
var topic = topicArn.GetValueInString() ?? string.Empty;
Copy link
Member

Choose a reason for hiding this comment

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

The earlier version reduces nesting by returning early, which is generally preferable

}
}

var messageAttributes = new Dictionary<string, MessageAttributeValue>
Copy link
Member

Choose a reason for hiding this comment

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

Typically we factor this into a publisher, to keep the producer clean of all the header manipulation

Copy link

@codescene-delta-analysis codescene-delta-analysis bot left a comment

Choose a reason for hiding this comment

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

Code Health Quality Gates: FAILED

Change in average Code Health of affected files: +0.38 (9.48 -> 9.86)

  • Declining Code Health: 8 findings(s) 🚩
  • Improving Code Health: 5 findings(s) ✅

View detailed results in CodeScene

@lillo42 lillo42 changed the title GH-1294 Add support to FIFO Improve support to AWS SQS/SNS Jan 6, 2025
Copy link

@codescene-delta-analysis codescene-delta-analysis bot left a comment

Choose a reason for hiding this comment

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

Code Health Quality Gates: FAILED

Change in average Code Health of affected files: +0.38 (9.48 -> 9.86)

  • Declining Code Health: 8 findings(s) 🚩
  • Improving Code Health: 5 findings(s) ✅

View detailed results in CodeScene

Comment on lines +253 to +290
public SqsSubscription(
SubscriptionName? name = null,
ChannelName? channelName = null,
RoutingKey? routingKey = null,
int bufferSize = 1,
int noOfPerformers = 1,
TimeSpan? timeOut = null,
int requeueCount = -1,
TimeSpan? requeueDelay = null,
int unacceptableMessageLimit = 0,
MessagePumpType messagePumpType = MessagePumpType.Proactor,
IAmAChannelFactory? channelFactory = null,
int lockTimeout = 10,
int delaySeconds = 0,
int messageRetentionPeriod = 345600,
TopicFindBy findTopicBy = TopicFindBy.Name,
string? iAmPolicy = null,
RedrivePolicy? redrivePolicy = null,
SnsAttributes? snsAttributes = null,
Dictionary<string, string>? tags = null,
OnMissingChannel makeChannels = OnMissingChannel.Create,
bool rawMessageDelivery = true,
TimeSpan? emptyChannelDelay = null,
TimeSpan? channelFailureDelay = null,
SnsSqsType sqsType = SnsSqsType.Standard,
bool contentBasedDeduplication = true,
DeduplicationScope? deduplicationScope = null,
int? fifoThroughputLimit = null,
ChannelType channelType = ChannelType.PubSub,
QueueFindBy findQueueBy = QueueFindBy.Name
)
: base(typeof(T), name, channelName, routingKey, bufferSize, noOfPerformers, timeOut, requeueCount,
requeueDelay,
unacceptableMessageLimit, messagePumpType, channelFactory, lockTimeout, delaySeconds,
messageRetentionPeriod, findTopicBy,
iAmPolicy, redrivePolicy, snsAttributes, tags, makeChannels, rawMessageDelivery, emptyChannelDelay,
channelFailureDelay, sqsType, contentBasedDeduplication, deduplicationScope, fifoThroughputLimit,
channelType, findQueueBy)

Choose a reason for hiding this comment

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

ℹ Getting worse: Constructor Over-Injection
SqsSubscription increases from 23 to 29 arguments, threshold = 5

Comment on lines 38 to 42
if (delay != null)
{
request.DelaySeconds = (int)delay.Value.TotalSeconds;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@iancooper question about DelaySeconds AWS SQS supports max of 15min, should I add an extra logic to support longer delay? Or let it throw?

Copy link
Member

Choose a reason for hiding this comment

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

Just to confirm our conversation, reduce to max, and log that it was reduced.

Copy link

@codescene-delta-analysis codescene-delta-analysis bot left a comment

Choose a reason for hiding this comment

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

Code Health Quality Gates: FAILED

Change in average Code Health of affected files: +0.38 (9.48 -> 9.86)

  • Declining Code Health: 10 findings(s) 🚩
  • Improving Code Health: 5 findings(s) ✅

View detailed results in CodeScene

Comment on lines +158 to +192
public SqsSubscription(
Type dataType,
SubscriptionName? name = null,
ChannelName? channelName = null,
RoutingKey? routingKey = null,
int bufferSize = 1,
int noOfPerformers = 1,
TimeSpan? timeOut = null,
int requeueCount = -1,
TimeSpan? requeueDelay = null,
int unacceptableMessageLimit = 0,
MessagePumpType messagePumpType = MessagePumpType.Unknown,
IAmAChannelFactory? channelFactory = null,
int lockTimeout = 10,
int delaySeconds = 0,
int messageRetentionPeriod = 345600,
TopicFindBy findTopicBy = TopicFindBy.Name,
string? iAmPolicy = null,
RedrivePolicy? redrivePolicy = null,
SnsAttributes? snsAttributes = null,
Dictionary<string, string>? tags = null,
OnMissingChannel makeChannels = OnMissingChannel.Create,
bool rawMessageDelivery = true,
TimeSpan? emptyChannelDelay = null,
TimeSpan? channelFailureDelay = null,
SnsSqsType sqsType = SnsSqsType.Standard,
bool contentBasedDeduplication = true,
DeduplicationScope? deduplicationScope = null,
int? fifoThroughputLimit = null,
ChannelType channelType = ChannelType.PubSub,
QueueFindBy findQueueBy = QueueFindBy.Name
)
: base(dataType, name, channelName, routingKey, bufferSize, noOfPerformers, timeOut, requeueCount,
requeueDelay, unacceptableMessageLimit, messagePumpType, channelFactory, makeChannels, emptyChannelDelay,
channelFailureDelay)

Choose a reason for hiding this comment

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

ℹ Getting worse: Constructor Over-Injection
SqsSubscription increases from 24 to 30 arguments, threshold = 5

Comment on lines +46 to +119
public async Task<string?> SendAsync(Message message, TimeSpan? delay, CancellationToken cancellationToken)
{
var request = new SendMessageRequest
{
QueueUrl = _queueUrl,
MessageBody = message.Body.Value
};

delay ??= TimeSpan.Zero;
if (delay > TimeSpan.Zero)
{
// SQS has a hard limit of 15min for Delay in Seconds
if (delay.Value > s_maxDelay)
{
delay = s_maxDelay;
s_logger.LogWarning("Set delay from {CurrentDelay} to 15min (SQS support up to 15min)", delay);
}

request.DelaySeconds = (int)delay.Value.TotalSeconds;
}

if (_queueType == SnsSqsType.Fifo)
{
request.MessageGroupId = message.Header.PartitionKey;
if (message.Header.Bag.TryGetValue(HeaderNames.DeduplicationId, out var deduplicationId))
{
request.MessageDeduplicationId = (string)deduplicationId;
}
}

var messageAttributes = new Dictionary<string, MessageAttributeValue>
{
[HeaderNames.Id] =
new() { StringValue = message.Header.MessageId, DataType = "String" },
[HeaderNames.Topic] = new() { StringValue = _queueUrl, DataType = "String" },
[HeaderNames.ContentType] = new() { StringValue = message.Header.ContentType, DataType = "String" },
[HeaderNames.CorrelationId] =
new() { StringValue = message.Header.CorrelationId, DataType = "String" },
[HeaderNames.HandledCount] =
new() { StringValue = Convert.ToString(message.Header.HandledCount), DataType = "String" },
[HeaderNames.MessageType] =
new() { StringValue = message.Header.MessageType.ToString(), DataType = "String" },
[HeaderNames.Timestamp] = new()
{
StringValue = Convert.ToString(message.Header.TimeStamp), DataType = "String"
}
};

if (!string.IsNullOrEmpty(message.Header.ReplyTo))
{
messageAttributes.Add(HeaderNames.ReplyTo,
new MessageAttributeValue { StringValue = message.Header.ReplyTo, DataType = "String" });
}

if (!string.IsNullOrEmpty(message.Header.Subject))
{
messageAttributes.Add(HeaderNames.Subject,
new MessageAttributeValue { StringValue = message.Header.Subject, DataType = "String" });
}

// we can set up to 10 attributes; we have set 6 above, so use a single JSON object as the bag
var bagJson = JsonSerializer.Serialize(message.Header.Bag, JsonSerialisationOptions.Options);
messageAttributes[HeaderNames.Bag] = new() { StringValue = bagJson, DataType = "String" };
request.MessageAttributes = messageAttributes;

var response = await _client.SendMessageAsync(request, cancellationToken);
if (response.HttpStatusCode is HttpStatusCode.OK or HttpStatusCode.Created
or HttpStatusCode.Accepted)
{
return response.MessageId;
}

return null;
}

Choose a reason for hiding this comment

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

❌ New issue: Complex Method
SendAsync has a cyclomatic complexity of 9, threshold = 9

Suppress

Comment on lines +46 to +119
public async Task<string?> SendAsync(Message message, TimeSpan? delay, CancellationToken cancellationToken)
{
var request = new SendMessageRequest
{
QueueUrl = _queueUrl,
MessageBody = message.Body.Value
};

delay ??= TimeSpan.Zero;
if (delay > TimeSpan.Zero)
{
// SQS has a hard limit of 15min for Delay in Seconds
if (delay.Value > s_maxDelay)
{
delay = s_maxDelay;
s_logger.LogWarning("Set delay from {CurrentDelay} to 15min (SQS support up to 15min)", delay);
}

request.DelaySeconds = (int)delay.Value.TotalSeconds;
}

if (_queueType == SnsSqsType.Fifo)
{
request.MessageGroupId = message.Header.PartitionKey;
if (message.Header.Bag.TryGetValue(HeaderNames.DeduplicationId, out var deduplicationId))
{
request.MessageDeduplicationId = (string)deduplicationId;
}
}

var messageAttributes = new Dictionary<string, MessageAttributeValue>
{
[HeaderNames.Id] =
new() { StringValue = message.Header.MessageId, DataType = "String" },
[HeaderNames.Topic] = new() { StringValue = _queueUrl, DataType = "String" },
[HeaderNames.ContentType] = new() { StringValue = message.Header.ContentType, DataType = "String" },
[HeaderNames.CorrelationId] =
new() { StringValue = message.Header.CorrelationId, DataType = "String" },
[HeaderNames.HandledCount] =
new() { StringValue = Convert.ToString(message.Header.HandledCount), DataType = "String" },
[HeaderNames.MessageType] =
new() { StringValue = message.Header.MessageType.ToString(), DataType = "String" },
[HeaderNames.Timestamp] = new()
{
StringValue = Convert.ToString(message.Header.TimeStamp), DataType = "String"
}
};

if (!string.IsNullOrEmpty(message.Header.ReplyTo))
{
messageAttributes.Add(HeaderNames.ReplyTo,
new MessageAttributeValue { StringValue = message.Header.ReplyTo, DataType = "String" });
}

if (!string.IsNullOrEmpty(message.Header.Subject))
{
messageAttributes.Add(HeaderNames.Subject,
new MessageAttributeValue { StringValue = message.Header.Subject, DataType = "String" });
}

// we can set up to 10 attributes; we have set 6 above, so use a single JSON object as the bag
var bagJson = JsonSerializer.Serialize(message.Header.Bag, JsonSerialisationOptions.Options);
messageAttributes[HeaderNames.Bag] = new() { StringValue = bagJson, DataType = "String" };
request.MessageAttributes = messageAttributes;

var response = await _client.SendMessageAsync(request, cancellationToken);
if (response.HttpStatusCode is HttpStatusCode.OK or HttpStatusCode.Created
or HttpStatusCode.Accepted)
{
return response.MessageId;
}

return null;
}

Choose a reason for hiding this comment

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

❌ New issue: Bumpy Road Ahead
SendAsync has 2 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is one single, nested block per function

Suppress

Copy link

@codescene-delta-analysis codescene-delta-analysis bot left a comment

Choose a reason for hiding this comment

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

Code Health Quality Gates: FAILED

Change in average Code Health of affected files: +0.38 (9.48 -> 9.86)

  • Declining Code Health: 10 findings(s) 🚩
  • Improving Code Health: 5 findings(s) ✅

View detailed results in CodeScene

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants