-
Notifications
You must be signed in to change notification settings - Fork 720
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
New Adapter: operaads #1916
New Adapter: operaads #1916
Changes from 5 commits
f698001
e8c48b1
0b82ac6
a8ef18a
c0dd8b2
b495d96
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,233 @@ | ||
package operaads | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"github.com/prebid/prebid-server/macros" | ||
"net/http" | ||
"text/template" | ||
|
||
"github.com/mxmCherry/openrtb/v15/openrtb2" | ||
"github.com/prebid/prebid-server/adapters" | ||
"github.com/prebid/prebid-server/config" | ||
"github.com/prebid/prebid-server/errortypes" | ||
"github.com/prebid/prebid-server/openrtb_ext" | ||
) | ||
|
||
type adapter struct { | ||
epTemplate *template.Template | ||
} | ||
|
||
// Builder builds a new instance of the operaads adapter for the given bidder with the given config. | ||
func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { | ||
epTemplate, err := template.New("endpoint").Parse(config.Endpoint) | ||
if err != nil { | ||
return nil, err | ||
} | ||
bidder := &adapter{ | ||
epTemplate: epTemplate, | ||
} | ||
return bidder, nil | ||
} | ||
|
||
func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { | ||
impCount := len(request.Imp) | ||
requestData := make([]*adapters.RequestData, 0, impCount) | ||
errs := []error{} | ||
request, err := deepCopyRequest(request) | ||
if err != nil { | ||
errs = append(errs, &errortypes.BadInput{ | ||
Message: err.Error(), | ||
}) | ||
return nil, errs | ||
} | ||
headers := http.Header{} | ||
headers.Add("Content-Type", "application/json;charset=utf-8") | ||
headers.Add("Accept", "application/json") | ||
|
||
err = checkRequest(request) | ||
if err != nil { | ||
errs = append(errs, err) | ||
return nil, errs | ||
} | ||
|
||
for _, imp := range request.Imp { | ||
requestCopy := *request | ||
var bidderExt adapters.ExtImpBidder | ||
if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { | ||
errs = append(errs, &errortypes.BadInput{ | ||
Message: err.Error(), | ||
}) | ||
continue | ||
} | ||
|
||
var operaadsExt openrtb_ext.ImpExtOperaads | ||
if err := json.Unmarshal(bidderExt.Bidder, &operaadsExt); err != nil { | ||
errs = append(errs, &errortypes.BadInput{ | ||
Message: err.Error(), | ||
}) | ||
continue | ||
} | ||
jizeyopera marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
err := convertImpression(&imp) | ||
if err != nil { | ||
errs = append(errs, &errortypes.BadInput{ | ||
Message: err.Error(), | ||
}) | ||
continue | ||
} | ||
|
||
imp.TagID = operaadsExt.PlacementID | ||
|
||
requestCopy.Imp = []openrtb2.Imp{imp} | ||
reqJSON, err := json.Marshal(&requestCopy) | ||
if err != nil { | ||
errs = append(errs, err) | ||
return nil, errs | ||
} | ||
|
||
macro := macros.EndpointTemplateParams{PublisherID: operaadsExt.PublisherID, AccountID: operaadsExt.EndpointID} | ||
endpoint, err := macros.ResolveMacros(*a.epTemplate, ¯o) | ||
if err != nil { | ||
errs = append(errs, err) | ||
continue | ||
} | ||
reqData := &adapters.RequestData{ | ||
Method: http.MethodPost, | ||
Uri: endpoint, | ||
Body: reqJSON, | ||
Headers: headers, | ||
} | ||
requestData = append(requestData, reqData) | ||
} | ||
return requestData, errs | ||
} | ||
|
||
func checkRequest(request *openrtb2.BidRequest) error { | ||
if request.Device == nil || len(request.Device.OS) == 0 { | ||
return &errortypes.BadInput{ | ||
Message: "Impression is missing device OS information", | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func convertImpression(imp *openrtb2.Imp) error { | ||
if imp.Banner != nil { | ||
bannerCopy, err := convertBanner(imp.Banner) | ||
if err != nil { | ||
return err | ||
} | ||
imp.Banner = bannerCopy | ||
} | ||
if imp.Native != nil && imp.Native.Request != "" { | ||
v := make(map[string]interface{}) | ||
err := json.Unmarshal([]byte(imp.Native.Request), &v) | ||
if err != nil { | ||
return err | ||
} | ||
_, ok := v["native"] | ||
if !ok { | ||
body, err := json.Marshal(struct { | ||
Native interface{} `json:"native"` | ||
}{ | ||
Native: v, | ||
}) | ||
Comment on lines
+124
to
+129
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you please add a test case for when |
||
if err != nil { | ||
return err | ||
} | ||
imp.Native.Request = string(body) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The race detected by the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you can solve the race issue by simply replacing
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any other thread that will use imp.Native when I write it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There shouldn't be. Your adapter is working with shallow copies of imps. Since There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes,thanks. |
||
} | ||
} | ||
return nil | ||
} | ||
|
||
// make sure that banner has openrtb 2.3-compatible size information | ||
func convertBanner(banner *openrtb2.Banner) (*openrtb2.Banner, error) { | ||
if banner.W == nil || banner.H == nil || *banner.W == 0 || *banner.H == 0 { | ||
if len(banner.Format) > 0 { | ||
f := banner.Format[0] | ||
|
||
bannerCopy := *banner | ||
|
||
bannerCopy.W = openrtb2.Int64Ptr(f.W) | ||
bannerCopy.H = openrtb2.Int64Ptr(f.H) | ||
|
||
return &bannerCopy, nil | ||
} else { | ||
return nil, &errortypes.BadInput{ | ||
Message: "Size information missing for banner", | ||
} | ||
} | ||
Comment on lines
+153
to
+157
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add test coverage for this |
||
} | ||
return banner, nil | ||
} | ||
|
||
func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { | ||
if response.StatusCode == http.StatusNoContent { | ||
return nil, nil | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you please add test coverage here for when the status code is |
||
} | ||
|
||
if response.StatusCode == http.StatusBadRequest { | ||
return nil, []error{&errortypes.BadInput{ | ||
Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), | ||
}} | ||
} | ||
Comment on lines
+168
to
+171
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add test coverage for |
||
|
||
if response.StatusCode != http.StatusOK { | ||
return nil, []error{&errortypes.BadServerResponse{ | ||
Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), | ||
}} | ||
} | ||
Comment on lines
+174
to
+177
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add test coverage here as well. |
||
|
||
var parsedResponse openrtb2.BidResponse | ||
if err := json.Unmarshal(response.Body, &parsedResponse); err != nil { | ||
return nil, []error{&errortypes.BadServerResponse{ | ||
Message: err.Error(), | ||
}} | ||
} | ||
|
||
bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) | ||
|
||
for _, sb := range parsedResponse.SeatBid { | ||
for i := 0; i < len(sb.Bid); i++ { | ||
bid := sb.Bid[i] | ||
if bid.Price != 0 { | ||
bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ | ||
Bid: &bid, | ||
BidType: getMediaTypeForImp(bid.ImpID, internalRequest.Imp), | ||
}) | ||
} | ||
} | ||
} | ||
return bidResponse, nil | ||
} | ||
|
||
func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { | ||
mediaType := openrtb_ext.BidTypeBanner | ||
for _, imp := range imps { | ||
if imp.ID == impId { | ||
if imp.Video != nil { | ||
mediaType = openrtb_ext.BidTypeVideo | ||
} else if imp.Native != nil { | ||
mediaType = openrtb_ext.BidTypeNative | ||
} | ||
return mediaType | ||
} | ||
} | ||
return mediaType | ||
} | ||
|
||
func deepCopyRequest(request *openrtb2.BidRequest) (*openrtb2.BidRequest, error) { | ||
reqJson, err := json.Marshal(request) | ||
if err != nil { | ||
return nil, err | ||
} | ||
var reqCopy openrtb2.BidRequest | ||
err = json.Unmarshal(reqJson, &reqCopy) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &reqCopy, err | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package operaads | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/prebid/prebid-server/adapters/adapterstest" | ||
"github.com/prebid/prebid-server/config" | ||
"github.com/prebid/prebid-server/openrtb_ext" | ||
) | ||
|
||
func TestJsonSamples(t *testing.T) { | ||
bidder, buildErr := Builder(openrtb_ext.BidderOperaads, config.Adapter{ | ||
Endpoint: "http://example.com/operaads/ortb/v2/{{.PublisherID}}?ep={{.AccountID}}"}) | ||
|
||
if buildErr != nil { | ||
t.Fatalf("Builder returned unexpected error %v", buildErr) | ||
} | ||
|
||
adapterstest.RunJSONBidderTest(t, "operaadstest", bidder) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
{ | ||
"mockBidRequest":{ | ||
"id":"some-req-id", | ||
"imp":[ | ||
{ | ||
"id":"some-imp-id", | ||
"native":{ | ||
"request":"{\"native\":{\"ver\":\"1.1\",\"layout\":3,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":344,\"hmin\":194}},{\"id\":3,\"required\":1,\"img\":{\"type\":1,\"w\":128,\"wmin\":80,\"h\":128,\"hmin\":80}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":6,\"data\":{\"type\":12,\"len\":15}}]}}", | ||
"ver":"1.1" | ||
}, | ||
"ext":{ | ||
"bidder":{ | ||
"placementId":"s123456", | ||
"endpointId":"ep19978", | ||
"publisherId":"pub123" | ||
} | ||
} | ||
} | ||
], | ||
"site":{ | ||
"domain":"example.com", | ||
"page":"example.com" | ||
}, | ||
"device":{ | ||
"ip":"152.193.6.74", | ||
"os":"android" | ||
}, | ||
"user":{ | ||
"id":"db089de9-a62e-4861-a881-0ff15e052516", | ||
"buyeruid":"8299345306627569435" | ||
}, | ||
"tmax":500 | ||
}, | ||
"httpcalls":[ | ||
{ | ||
"expectedRequest":{ | ||
"uri":"http://example.com/operaads/ortb/v2/pub123?ep=ep19978", | ||
"body":{ | ||
"id":"some-req-id", | ||
"imp":[ | ||
{ | ||
"id":"some-imp-id", | ||
"tagid":"s123456", | ||
"ext":{ | ||
"bidder":{ | ||
"placementId":"s123456", | ||
"endpointId":"ep19978", | ||
"publisherId":"pub123" | ||
} | ||
}, | ||
"native":{ | ||
"request":"{\"native\":{\"ver\":\"1.1\",\"layout\":3,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":344,\"hmin\":194}},{\"id\":3,\"required\":1,\"img\":{\"type\":1,\"w\":128,\"wmin\":80,\"h\":128,\"hmin\":80}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":6,\"data\":{\"type\":12,\"len\":15}}]}}", | ||
"ver":"1.1" | ||
} | ||
} | ||
], | ||
"site":{ | ||
"domain":"example.com", | ||
"page":"example.com" | ||
}, | ||
"device":{ | ||
"ip":"152.193.6.74", | ||
"os":"android" | ||
}, | ||
"user":{ | ||
"id":"db089de9-a62e-4861-a881-0ff15e052516", | ||
"buyeruid":"8299345306627569435" | ||
}, | ||
"tmax":500 | ||
} | ||
}, | ||
"mockResponse":{ | ||
"status":200, | ||
"body":{ | ||
"id":"some-req-id", | ||
"seatbid":[ | ||
{ | ||
"bid":[ | ||
{ | ||
"id":"928185755156387460", | ||
"impid":"some-imp-id", | ||
"price":1, | ||
"adid":"69595837", | ||
"adm":"{\"assets\":[{\"id\": 2,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 344,\"h\": 194}},{\"id\": 3,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 100,\"h\": 100}} ,{\"id\": 1,\"title\":{\"text\":\"This is an example Prebid Native creative\"}},{\"id\": 4,\"data\":{\"value\":\"This is a Prebid Native Creative. There are many like it, but this one is mine.\"}},{\"id\": 6,\"data\":{\"value\":\"Please.\"}} ],\"link\":{\"url\":\"http://example.com/click?AAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwhdYz3ZyNFNG3fXpZUyLXNZ0o5aAAAAACrElgC-AwAAvgMAAAIAAAC98iUEeP4QAAAAAABVU0QAVVNEAAEAAQARIAAAAAABAgQCAAAAAAEAhBaSXgAAAAA./pp=${AUCTION_PRICE}/cnd=%21OwwGAQiGmooHEL3llyEY-PxDIAQoADoRZGVmYXVsdCNOWU0yOjQwMjM./bn=75922/test=1/referrer=prebid.org/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html\"},\"imptrackers\":[\"http://example.com/openrtb_win?e=wqT_3QLFBqBFAwAAAwDWAAUBCNmku9QFEIi6jeuTm_LoTRib7t2u2tLMlnMqNgkAAAECCPA_EQEHEAAA8D8ZCQkIAAAhCQkI8D8pEQkAMQkJqAAAMKqI2wQ4vgdAvgdIAlC95ZchWPj8Q2AAaJFAeJLRBIABAYoBA1VTRJIFBvBQmAEBoAEBqAEBsAEAuAECwAEEyAEC0AEJ2AEA4AEB8AEAigI7dWYoJ2EnLCAxMzc2ODYwLCAxNTE5MzA5NDAxKTt1ZigncicsIDY5NTk1ODM3Nh4A8IqSAvUBIXRETkdfUWlHbW9vSEVMM2xseUVZQUNENF9FTXdBRGdBUUFSSXZnZFFxb2piQkZnQVlMTURhQUJ3QUhnQWdBRUFpQUVBa0FFQm1BRUJvQUVCcUFFRHNBRUF1UUVwaTRpREFBRHdQOEVCS1l1SWd3QUE4RF9KQVhfelYzek1zXzBfMlFFQUFBAQMkRHdQLUFCQVBVQgEOLEFKZ0NBS0FDQUxVQwUQBEwwCQjwTE1BQ0FNZ0NBT0FDQU9nQ0FQZ0NBSUFEQVpBREFKZ0RBYWdEaHBxS0I3b0RFV1JsWm1GMWJIUWpUbGxOTWpvME1ESXqaAjkhT3d3R0FRNvgA8E4tUHhESUFRb0FEb1JaR1ZtWVhWc2RDTk9XVTB5T2pRd01qTS7YAugH4ALH0wHqAgpwcmViaWQub3Jn8gIRCgZBRFZfSUQSBzEzNzY4NjDyARQMQ1BHXwEUNDM1MDMwOTjyAhEKBUNQARPwmQgxNDg0NzIzOIADAYgDAZADAJgDFKADAaoDAMADkBzIAwDYAwDgAwDoAwD4AwOABACSBAkvb3BlbnJ0YjKYBACiBAwxNTIuMTkzLjYuNzSoBJrMI7IEDAgAEAAYACAAMAA4ALgEAMAEAMgEANIEEWRlZmF1bHQjTllNMjo0MDIz2gQCCADgBADwBL3llyGIBQGYBQCgBf____8FA1ABqgULc29tZS1yZXEtaWTABQDJBQAFARTwP9IFCQkFC2QAAADYBQHgBQHwBd4C-gUECAAQAJAGAZgGAA..&s=08b1535744639c904684afe46e3c6c0e4786089f&test=1&referrer=prebid.org&pp=${AUCTION_PRICE}\"]}", | ||
"adomain":[ | ||
"example.com" | ||
], | ||
"iurl":"http://example.com/cr?id=69595837", | ||
"cid":"958", | ||
"crid":"69595837", | ||
"cat":[ | ||
"IAB3-1" | ||
], | ||
"ext":{ | ||
|
||
} | ||
} | ||
], | ||
"seat":"958" | ||
} | ||
], | ||
"bidid":"8141327771600527856", | ||
"cur":"USD" | ||
} | ||
} | ||
} | ||
], | ||
"expectedBidResponses":[ | ||
{ | ||
"currency":"USD", | ||
"bids":[ | ||
{ | ||
"bid":{ | ||
"id":"928185755156387460", | ||
"impid":"some-imp-id", | ||
"price":1, | ||
"adm":"{\"assets\":[{\"id\": 2,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 344,\"h\": 194}},{\"id\": 3,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 100,\"h\": 100}} ,{\"id\": 1,\"title\":{\"text\":\"This is an example Prebid Native creative\"}},{\"id\": 4,\"data\":{\"value\":\"This is a Prebid Native Creative. There are many like it, but this one is mine.\"}},{\"id\": 6,\"data\":{\"value\":\"Please.\"}} ],\"link\":{\"url\":\"http://example.com/click?AAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwhdYz3ZyNFNG3fXpZUyLXNZ0o5aAAAAACrElgC-AwAAvgMAAAIAAAC98iUEeP4QAAAAAABVU0QAVVNEAAEAAQARIAAAAAABAgQCAAAAAAEAhBaSXgAAAAA./pp=${AUCTION_PRICE}/cnd=%21OwwGAQiGmooHEL3llyEY-PxDIAQoADoRZGVmYXVsdCNOWU0yOjQwMjM./bn=75922/test=1/referrer=prebid.org/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html\"},\"imptrackers\":[\"http://example.com/openrtb_win?e=wqT_3QLFBqBFAwAAAwDWAAUBCNmku9QFEIi6jeuTm_LoTRib7t2u2tLMlnMqNgkAAAECCPA_EQEHEAAA8D8ZCQkIAAAhCQkI8D8pEQkAMQkJqAAAMKqI2wQ4vgdAvgdIAlC95ZchWPj8Q2AAaJFAeJLRBIABAYoBA1VTRJIFBvBQmAEBoAEBqAEBsAEAuAECwAEEyAEC0AEJ2AEA4AEB8AEAigI7dWYoJ2EnLCAxMzc2ODYwLCAxNTE5MzA5NDAxKTt1ZigncicsIDY5NTk1ODM3Nh4A8IqSAvUBIXRETkdfUWlHbW9vSEVMM2xseUVZQUNENF9FTXdBRGdBUUFSSXZnZFFxb2piQkZnQVlMTURhQUJ3QUhnQWdBRUFpQUVBa0FFQm1BRUJvQUVCcUFFRHNBRUF1UUVwaTRpREFBRHdQOEVCS1l1SWd3QUE4RF9KQVhfelYzek1zXzBfMlFFQUFBAQMkRHdQLUFCQVBVQgEOLEFKZ0NBS0FDQUxVQwUQBEwwCQjwTE1BQ0FNZ0NBT0FDQU9nQ0FQZ0NBSUFEQVpBREFKZ0RBYWdEaHBxS0I3b0RFV1JsWm1GMWJIUWpUbGxOTWpvME1ESXqaAjkhT3d3R0FRNvgA8E4tUHhESUFRb0FEb1JaR1ZtWVhWc2RDTk9XVTB5T2pRd01qTS7YAugH4ALH0wHqAgpwcmViaWQub3Jn8gIRCgZBRFZfSUQSBzEzNzY4NjDyARQMQ1BHXwEUNDM1MDMwOTjyAhEKBUNQARPwmQgxNDg0NzIzOIADAYgDAZADAJgDFKADAaoDAMADkBzIAwDYAwDgAwDoAwD4AwOABACSBAkvb3BlbnJ0YjKYBACiBAwxNTIuMTkzLjYuNzSoBJrMI7IEDAgAEAAYACAAMAA4ALgEAMAEAMgEANIEEWRlZmF1bHQjTllNMjo0MDIz2gQCCADgBADwBL3llyGIBQGYBQCgBf____8FA1ABqgULc29tZS1yZXEtaWTABQDJBQAFARTwP9IFCQkFC2QAAADYBQHgBQHwBd4C-gUECAAQAJAGAZgGAA..&s=08b1535744639c904684afe46e3c6c0e4786089f&test=1&referrer=prebid.org&pp=${AUCTION_PRICE}\"]}", | ||
"adid":"69595837", | ||
"adomain":[ | ||
"example.com" | ||
], | ||
"iurl":"http://example.com/cr?id=69595837", | ||
"cid":"958", | ||
"crid":"69595837", | ||
"cat":[ | ||
"IAB3-1" | ||
], | ||
"ext":{ | ||
|
||
} | ||
}, | ||
"type":"native" | ||
} | ||
] | ||
} | ||
] | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@yuanjize I see what you're trying to do here but I think this is a bit of overkill. I recommend removing the deep copy logic and instead considering my suggestion in
convertImpression
below.