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

Implement Bulk Operations in WebAPI #2455

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

Conversation

Sreejithpin
Copy link
Contributor

Issues

Implements Bulk Operation in webAPI

Description

  • Implements Bulk Operation
  • Deserialization
  • Patching
  • Nested Patching
  • DataModification Exception

Briefly describe the changes of this pull request.

Checklist (Uncheck if it is not completed)

  • Test cases added
  • Build and test with one-click build and test script passed

Additional work necessary

If documentation update is needed, please add "Docs Needed" label to the issue and provide details about the required document change in the issue.

<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\sln\packages\System.Buffers.4.5.0\lib\netstandard2.0\System.Buffers.dll</HintPath>
</Reference>
<Reference Include="Microsoft.OData.Edm, Version=7.7.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
Copy link
Member

@xuzhg xuzhg Mar 31, 2021

Choose a reason for hiding this comment

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

Microsoft.OData.Edm [](start = 24, length = 19)

We have Microsoft.OData package 7.8.2 installed at line 91~-97 , why do you install the 7.7.3 again? #Resolved

<dependentAssembly>
<assemblyIdentity name="System.Collections.Concurrent" publicKeyToken="B03F5F7F11D50A3A" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.11.0" newVersion="4.0.11.0" />
</dependentAssembly>
<dependentAssembly>
Copy link
Member

@xuzhg xuzhg Mar 31, 2021

Choose a reason for hiding this comment

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

why do you update this web.config? #Resolved

@@ -39,6 +39,15 @@
<Reference Include="Microsoft.Spatial, Version=7.8.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\sln\packages\Microsoft.Spatial.7.8.2\lib\net45\Microsoft.Spatial.dll</HintPath>
</Reference>
<Reference Include="Microsoft.OData.Core, Version=7.8.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
Copy link
Member

@xuzhg xuzhg Mar 31, 2021

Choose a reason for hiding this comment

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

Microsoft.OData.Cor

why do you install 7.8.1, since we have 7.8.2 installed? #Resolved

Copy link
Member

Choose a reason for hiding this comment

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

What was the rationale here?


In reply to: 604609447 [](ancestors = 604609447)

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 guess it was accidental during some rebase or so !!

ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntityType<DataModificationExceptionType>();

builder.Namespace = typeof(DataModificationExceptionType).Namespace;
Copy link
Member

@xuzhg xuzhg Mar 31, 2021

Choose a reason for hiding this comment

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

You have a model builder here ..... ‼️

What's it for? what's the usage of buider?
#Resolved

/// Represents king of <see cref="DataModificationOperationKind"/> type of operation
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "failed")]
public DataModificationOperationKind failedOperation { get; set; }
Copy link
Member

@xuzhg xuzhg Mar 31, 2021

Choose a reason for hiding this comment

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

failedOperation [](start = 45, length = 15)

Are you sure use the camelcase for the public property name?
It's same as for "responseCode"?

#Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the vocabs its like that


In reply to: 604610652 [](ancestors = 604610652)

Copy link
Member

@mikepizzo mikepizzo Apr 8, 2021

Choose a reason for hiding this comment

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

The casing of the term definition reflects the casing in a json error payload, which is lower camel-case (unlike other term properties).

However, the public API for ODataError still uses upper-camel case for consistency.

We should do the same thing here -- the public properties should be upper camel case, even though we serialize in lower camel case.


In reply to: 604691364 [](ancestors = 604691364,604610652)

/// <summary>
/// Initializes a new instance of the <see cref="DataModificationExceptionType"/> class.
/// </summary>
public DataModificationExceptionType(DataModificationOperationKind failedOperation)
Copy link
Member

@xuzhg xuzhg Mar 31, 2021

Choose a reason for hiding this comment

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

DataModificationOperationKind failedOperation [](start = 45, length = 45)

If you have a construct to set the "FailedOperation",

  1. remove the "set" for the property, it means it's read only.

Or

  1. remove the constructor, #Resolved

using System;
using Microsoft.AspNet.OData.Builder;

namespace Org.OData.Core.V1
Copy link
Member

@xuzhg xuzhg Mar 31, 2021

Choose a reason for hiding this comment

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

The namespace is by design here? #ByDesign

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, we need to have the same namespace name in order to consider it as vocabs in the core lib @mikepizzo


In reply to: 604611580 [](ancestors = 604611580)

{
_id = value;
}
}
Copy link
Member

@xuzhg xuzhg Mar 31, 2021

Choose a reason for hiding this comment

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

use Auto-property :

public Uri Id {get;set;} #Resolved

{
_reason = (DeltaDeletedEntryReason)value;
}
}
Copy link
Member

@xuzhg xuzhg Mar 31, 2021

Choose a reason for hiding this comment

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

use Auto-property :

public DeltaDeletedEntryReason Reason {get;set;} #Resolved

}

/// <inheritdoc />
public DeltaDeletedEntryReason Reason
Copy link
Member

@xuzhg xuzhg Mar 31, 2021

Choose a reason for hiding this comment

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

DeltaDeletedEntryReason [](start = 15, length = 23)

Is it a nullable?

public DeltaDeletedEntryReason? Reason {get;set;} #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Doesnt looked so as per the structure


In reply to: 604612233 [](ancestors = 604612233)

Copy link
Member

Choose a reason for hiding this comment

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

Reason is optional -- we should be able to omit it from the response. See http://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4.01.html#sec_DeletedEntity


In reply to: 604692550 [](ancestors = 604692550,604612233)

}

/// <inheritdoc />
public IEdmNavigationSource NavigationSource
Copy link
Member

@xuzhg xuzhg Mar 31, 2021

Choose a reason for hiding this comment

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

public IEdmNavigationSource NavigationSource [](start = 8, length = 44)

auto property #Resolved

///
/// </summary>
/// <param name="keys"></param>
/// <returns></returns>
Copy link
Member

@xuzhg xuzhg Mar 31, 2021

Choose a reason for hiding this comment

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

finish it #Resolved

/// Used to hold the Entry object in the Delta Feed Payload.
/// </summary>
[NonValidatingParameterBinding]
public class EdmDeltaEntityObject<TStructuralType> : EdmDeltaEntityObject, IEdmChangedObject<TStructuralType>
Copy link
Member

@xuzhg xuzhg Mar 31, 2021

Choose a reason for hiding this comment

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

EdmDeltaEntityObject [](start = 17, length = 37)

why do you still have this class?

Edm* for no-clr-type scenario, we don't have the , how can we use it?

If we have thte TStructuralType, we are in typed scenario. #Resolved

namespace Microsoft.AspNet.OData
{
/// <summary>
/// Basic interface to represented a typed deleted entity object
Copy link
Member

@xuzhg xuzhg Mar 31, 2021

Choose a reason for hiding this comment

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

Basic => Base? #Resolved

/// Base interface to be implemented by any Delta object required to be part of the DeltaFeed Payload.
/// </summary>
/// <typeparam name="TStructuralType">Generic Type for changed object</typeparam>
public interface IEdmChangedObject<TStructuralType> : IEdmChangedObject
Copy link
Member

@xuzhg xuzhg Mar 31, 2021

Choose a reason for hiding this comment

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

IEdmChangedObject : IEdmChangedObject [](start = 21, length = 54)

we don't need this interface anymore, right? #Resolved

/// Represents an instance of an <see cref="IEdmDeltaDeletedEntityObject"/>.
/// Holds the properties necessary to create the ODataDeltaDeletedEntry.
/// </summary>
public interface IEdmDeltaDeletedEntityObject<TStructuralType> : IEdmChangedObject<TStructuralType>, IEdmDeltaDeletedEntityObject
Copy link
Member

@xuzhg xuzhg Mar 31, 2021

Choose a reason for hiding this comment

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

we don't need this interface any more? #Resolved

/// <summary>
/// Handler Class to handle users methods for create, delete and update
/// </summary>
public abstract class TypelessPatchMethodHandler
Copy link
Member

@xuzhg xuzhg Mar 31, 2021

Choose a reason for hiding this comment

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

TypelessPatchMethodHandler

the nameing is weird.

You can just use PatchMethodHandler (Be noted, without the ), #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just wanted to call it out seperately for typeless, to distinguish


In reply to: 604618607 [](ancestors = 604618607)

Copy link
Member

Choose a reason for hiding this comment

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

Maybe IEdmPathMethodHandler, to follow the pattern for non clr-backed classes?


In reply to: 604693905 [](ancestors = 604693905,604618607)

@@ -203,6 +203,7 @@ internal static bool TryGetCharSet(MediaTypeHeaderValue mediaType, IEnumerable<s
writeContext.Path = path;
writeContext.MetadataLevel = metadataLevel;
writeContext.QueryOptions = internalRequest.Context.QueryOptions;
writeContext.IsUntyped = typeof(IEdmObject).IsAssignableFrom(type);
Copy link
Member

@xuzhg xuzhg Mar 31, 2021

Choose a reason for hiding this comment

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

writeContext.IsUntyped = typeof(IEdmObject).IsAssignableFrom(type); [](start = 16, length = 67)

why do you have such line code?

IsUntyped is calculated in the ODataSerializerContext #Resolved

@@ -88,6 +88,7 @@ internal ODataDeserializer GetODataDeserializerImpl(Type type, Func<IEdmModel> m
return _rootContainer.GetRequiredService<ODataActionPayloadDeserializer>();
}


// Get the model. Using a Func<IEdmModel> to delay evaluation of the model
// until after the above checks have passed.
Copy link
Member

@xuzhg xuzhg Mar 31, 2021

Choose a reason for hiding this comment

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

revert #Resolved

{
return Item as ODataResourceSetBase;
}
}
Copy link
Member

@xuzhg xuzhg Mar 31, 2021

Choose a reason for hiding this comment

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

public ODataResourceSetBase ResourceSetBase {get;}

in constructor, just do:

ResourceSetBase = item; #Resolved

/// <summary>
/// Gets the wrapped <see cref="ResourceSetBase"/>.
/// </summary>
public ODataResourceSetBase ResourceSetBase
Copy link
Member

@xuzhg xuzhg Mar 31, 2021

Choose a reason for hiding this comment

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

public ODataResourceSetBase ResourceSetBase [](start = 8, length = 43)

ODataDeltaResourceSet #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Base is for DeltaResourceSet and ResourceSet right


In reply to: 604620220 [](ancestors = 604620220)

Copy link
Member

@mikepizzo mikepizzo Apr 8, 2021

Choose a reason for hiding this comment

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

@Sreejithpin - Is this resolved?


In reply to: 604694595 [](ancestors = 604694595,604620220)

{
Type clrType = EdmLibHelpers.GetClrType(elementType, readContext.Model);

foreach (ODataItemBase odataItemBase in resourceSet.Resources)
Copy link
Member

@xuzhg xuzhg Mar 31, 2021

Choose a reason for hiding this comment

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

ODataItemBase [](start = 25, length = 13)

the type of resoureSet.Resources is ODataResourceWrapper, why don't you use ODataResourceWrapper in foreach.

Then we can get rid of line 211 #Resolved


if (readContext.IsUntyped)
{
readContext.ResourceType = resourceWrapper.ResourceBase is ODataDeletedResource ? typeof(EdmDeltaDeletedEntityObject) : typeof(EdmDeltaEntityObject);
Copy link
Member

@xuzhg xuzhg Mar 31, 2021

Choose a reason for hiding this comment

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

should just use EdmEntityObject, not use EdmDeltaEntityObject? #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

EdmDeltaEntityObject inherits from IEdmChangedObject (edmchangedobjcoll is coll if idmchangedobject) , but EdmEntity doesnt , @mikepizzo - pls look


In reply to: 604621960 [](ancestors = 604621960)

Copy link
Member

Choose a reason for hiding this comment

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

@sreejith -- can you resolve this issue if it's been addressed (I think it has, by implementing IEdmChangedObject on EdmEntityObject and using EdmEntityObject here?)


In reply to: 604695489 [](ancestors = 604695489,604621960)

if (readContext.IsDeltaEntity)
{
return new EdmDeltaEntityObject(structuredType.AsEntity());
}
Copy link
Member

@xuzhg xuzhg Mar 31, 2021

Choose a reason for hiding this comment

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

we should not distiguish DeltaEntity and Normal Entity.
DeltaEntity is a normal entity. @mikepizzo
#Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

EdmDeltaEntityObject inherits from IEdmChangedObject (edmchangedobjcoll is coll if idmchangedobject) , dut EdmEntity doesnt


In reply to: 604622500 [](ancestors = 604622500)

@Sreejithpin
Copy link
Contributor Author

Sreejithpin commented Mar 31, 2021 via email

using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
Copy link
Member

@xuzhg xuzhg Mar 31, 2021

Choose a reason for hiding this comment

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

copyright, sorting
#Resolved

@@ -44,6 +44,15 @@
<Reference Include="Microsoft.OData.Edm, Version=7.8.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\..\sln\packages\Microsoft.OData.Edm.7.8.2\lib\net45\Microsoft.OData.Edm.dll</HintPath>
</Reference>
<Reference Include="Microsoft.OData.Client, Version=7.8.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
Copy link
Member

@xuzhg xuzhg Mar 31, 2021

Choose a reason for hiding this comment

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

Microsoft.OData.Client, Version=7.8.1.0, [](start = 24, length = 41)

why have 7.8.1? We alredy have 7.8.2 #Resolved

/// <summary>
/// Enum to determine the type of Resource Set
/// </summary>
internal enum ResourceSetType
Copy link
Member

Choose a reason for hiding this comment

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

ResourceSetType

since there are only (ever likely to be) two values, should we just use a boolean isDeltaResourceSet property?

{
IEdmStructuredType structuredType = resourceContext.StructuredType;
IEdmStructuredObject structuredObject = resourceContext.EdmObject;

//For appending transient and persistent instance annotations for both enity object and normal resources
//var entityObject = structuredObject;
Copy link
Member

@mikepizzo mikepizzo Apr 28, 2021

Choose a reason for hiding this comment

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

this looks like leftover code - cleanup? #Resolved

return;
}
EdmEntityObject edmEntityObject = null;
object value = null;
Copy link
Member

@mikepizzo mikepizzo Apr 28, 2021

Choose a reason for hiding this comment

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

value

can we use something more meaningful than "value" -- i.e., "instanceAnnotations"? #Resolved

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1053:StaticHolderTypesShouldNotHaveConstructors")]
public class ODataSerializerHelper
{
internal static void AppendInstanceAnnotations(ODataResourceBase resource, ResourceContext resourceContext, object value, ODataSerializerProvider SerializerProvider)
Copy link
Member

Choose a reason for hiding this comment

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

object

You can save a cast within the method for by making this an IODataInstanceAnnotationContainer, and requiring that the caller pass as an IODataInstanceAnnotationContainer (since, in many cases, the caller already knows it is an IODataInstanceAnnotationContainer). Keep the null check on line 22, though, so if the caller does pass in their object cast to an IODataInstanceAnnotationContainer, and the cast fails, it will be a no-op.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was kept so because from the calling method it sends an object(got from trygetvalue outs object) . So a cast would be inevitable, may be can move that cast to the calling method

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1053:StaticHolderTypesShouldNotHaveConstructors")]
public class ODataSerializerHelper
{
internal static void AppendInstanceAnnotations(ODataResourceBase resource, ResourceContext resourceContext, object value, ODataSerializerProvider SerializerProvider)
Copy link
Member

Choose a reason for hiding this comment

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

value

We are expecting this to be an IODataInstanceAnnotationContainer, so please name it something like instanceAnnotationContainer rather than the generic (and unhelpful) "value".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was kept so because from the calling method it sends an object(got from trygetvalue outs object) . So a cast would be inevitable, may be can move that cast to the calling method

Copy link
Member

@mikepizzo mikepizzo left a comment

Choose a reason for hiding this comment

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

🕐

@Sreejithpin
Copy link
Contributor Author

    internal IEdmTypeReference GetEdmType(object instance, Type type)

We can throw an exception if its null, which would change the current behaviour, but i guess thats fine right


In reply to: 828778014


Refers to: src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataSerializerContext.cs:290 in ab656df. [](commit_id = ab656df, deletion_comment = False)

@Sreejithpin
Copy link
Contributor Author

    internal IEdmTypeReference GetEdmType(object instance, Type type)

we have a type mapping cache already right


In reply to: 828777381


Refers to: src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataSerializerContext.cs:290 in ab656df. [](commit_id = ab656df, deletion_comment = False)

Sreejithpin and others added 18 commits June 2, 2021 08:11
* BulkInsert1 draft

* Bulk insert changes

* deleted old test

* updates

* Update ODataResourceSetWrapper.cs

* updates

* Updated addressing  comments

* updates

* updates

updates

* Updates

* Updates

* Minor updates

* comments

* updates

* update publicapi for core

* Address comments

* Cleanup and additional tests

Cleanup and additional tests

* Updated code

* BulkInsert1 draft

* Bulk insert changes

* deleted old test

* updates

* updates

* Updated addressing  comments

* updates

* updates

updates

* Updates

* Updates

* Minor updates

* updates

* Address comments

* Cleanup and additional tests

Cleanup and additional tests

* DataAnnotationException updates

* comments

* small updates

* updates

* small update

* updates

* Updates

* Update DeltaSetOfT.cs

* Updates with Patch

* updates

* updates

* Update WebHostTestFixture.cs

* updates

* Update DeltaOfTStructuralType.cs

* Updates

* Updates for serializer etc

* Update WebHostTestFixture.cs

* updates

* updates

* updates

* Bulk Operations Updates
@pull-request-quantifier-deprecated

This PR has 3935 quantified lines of changes. In general, a change size of upto 200 lines is ideal for the best PR experience!


Quantification details

Label      : Extra Large
Size       : +3582 -353
Percentile : 100%

Total files changed: 86

Change summary by file extension:
.csproj : +48 -4
.config : +95 -84
.cs : +3111 -241
.resx : +18 -0
.projitems : +16 -0
.bsl : +294 -24

Change counts above are quantified counts, based on the PullRequestQuantifier customizations.

Why proper sizing of changes matters

Optimal pull request sizes drive a better predictable PR flow as they strike a
balance between between PR complexity and PR review overhead. PRs within the
optimal size (typical small, or medium sized PRs) mean:

  • Fast and predictable releases to production:
    • Optimal size changes are more likely to be reviewed faster with fewer
      iterations.
    • Similarity in low PR complexity drives similar review times.
  • Review quality is likely higher as complexity is lower:
    • Bugs are more likely to be detected.
    • Code inconsistencies are more likely to be detetcted.
  • Knowledge sharing is improved within the participants:
    • Small portions can be assimilated better.
  • Better engineering practices are exercised:
    • Solving big problems by dividing them in well contained, smaller problems.
    • Exercising separation of concerns within the code changes.

What can I do to optimize my changes

  • Use the PullRequestQuantifier to quantify your PR accurately
    • Create a context profile for your repo using the context generator
    • Exclude files that are not necessary to be reviewed or do not increase the review complexity. Example: Autogenerated code, docs, project IDE setting files, binaries, etc. Check out the Excluded section from your prquantifier.yaml context profile.
    • Understand your typical change complexity, drive towards the desired complexity by adjusting the label mapping in your prquantifier.yaml context profile.
    • Only use the labels that matter to you, see context specification to customize your prquantifier.yaml context profile.
  • Change your engineering behaviors
    • For PRs that fall outside of the desired spectrum, review the details and check if:
      • Your PR could be split in smaller, self-contained PRs instead
      • Your PR only solves one particular issue. (For example, don't refactor and code new features in the same PR).

How to interpret the change counts in git diff output

  • One line was added: +1 -0
  • One line was deleted: +0 -1
  • One line was modified: +1 -1 (git diff doesn't know about modified, it will
    interpret that line like one addition plus one deletion)
  • Change percentiles: Change characteristics (addition, deletion, modification)
    of this PR in relation to all other PRs within the repository.


Was this comment helpful? 👍  :ok_hand:  :thumbsdown: (Email)
Customize PullRequestQuantifier for this repository.

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

Successfully merging this pull request may close these issues.

None yet

4 participants