Skip to content

Commit

Permalink
Release 1.12.0 (#49)
Browse files Browse the repository at this point in the history
* Add sonarqube scan worflow

* Pd 177323 cdv filter (#13)

* Implemented CDV filter for reviews.json

* Pd 177925 seccondary ratings filter (#14)

* Implemented Secondaryratings filters for reviews.json

* Implemented Additional field filter for reviews.json (#15)

* Implemented tag dimensions filter for reviews.json

* Pd 178797 add value label cdv response (#18)

Mapped "ValueLabel" response object for context data values

* Pd 178686 tag statistics (#19)

Implemented tagstats reuest param for reviews.json and products.json

* Mapped secondary averages response objects

* Implemented Secondary Rating Distribution

* Refactoring keyword filter classes to maintain the parity

* Allow dynamic array or variadic parameters to be passed to filter function

* Release 1.10.0

* Implemented Custom Sort, Relavancy Sort, Xcode 14 fix

* Update Sonarscan workflow

* Q&A statistics changes for statistics.json

* Release 1.11.0

* Fix for configuration errors with test case changes

* Release 1.11.1

* 🐛 fixed transaction monitoring issue by adding clientId in BVAnalyticsEventTransaction (#35)

* Revert ":bug: fixed transaction monitoring issue by adding clientId in BVAnalyticsEventTransaction (#35)" (#37)

This reverts commit ba52da0e2d2209afe443df00ace8230bdbe8b498.

* Pd 215869 bv pixel transaction event not recorded on monitoring tool (#38)

* 🐛 fixed transaction monitoring issue by adding clientId in BVAnalyticsEventTransaction

* 🐛 fix transaction monitoring issue by checking client key in whitelist and blacklist dictionary
✅ add separate test cases for nonPII and PII transactions event

* ⚰️ removing unused code

* Release 1.11.2

* Pd 217873 upgrade ios minimum deployment target (#42)

* 🐛 fixed transaction monitoring issue by adding clientId in BVAnalyticsEventTransaction

* 🐛 fix transaction monitoring issue by checking client key in whitelist and blacklist dictionary
✅ add separate test cases for nonPII and PII transactions event

* ⚰️ removing unused code

* ⚡ update minimum iOS deployment target

* 🔖 Release 1.11.3

* 🐛 remove escaping for unsafe parameters value (#45)

* 🔖 Release 1.11.4

* ✨ add originalProductName to BVReview.swift and originalProductName success and failure test cases in BVReviewQueryTest (#48)

* Pd 219419 remove hardcoded credentials (#47)

* 🎨 removed hardcoded credentials

* 🎨 added valid and invalid userId as json file

* 🎨 handling user id json using BVTestUsers class

* 🎨 applied gitguardian changes

* 🔖 Release 1.12.0

---------

Co-authored-by: Abishekh S Kamath <[email protected]>
Co-authored-by: chetan shanbag <[email protected]>
  • Loading branch information
3 people authored Oct 23, 2023
1 parent 259abf9 commit 4ccc4a0
Show file tree
Hide file tree
Showing 11 changed files with 162 additions and 34 deletions.
2 changes: 1 addition & 1 deletion BVSwift.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

Pod::Spec.new do |s|
s.name = 'BVSwift'
s.version = '1.11.4'
s.version = '1.12.0'
s.summary = 'Simple Swift based iOS SDK to interact with the Bazaarvoice platform API.'
s.description = 'The Bazaarvoice Software Development Kit (SDK) is a Swift iOS library that provides an easy way to generate REST calls to the Bazaarvoice Developer API. Using this SDK, mobile developers can quickly integrate Bazaarvoice content into their native iOS apps for iPhone and iPad on iOS 8.0 or newer.'

Expand Down
33 changes: 22 additions & 11 deletions BVSwift.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,9 @@
9F51CA50240CD1150043AED5 /* BVReviewHighlightsConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F51CA44240CD1150043AED5 /* BVReviewHighlightsConfiguration.swift */; };
9F51CA52240CD8A80043AED5 /* BVReviewHighlightsQueryTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F51CA51240CD8A80043AED5 /* BVReviewHighlightsQueryTest.swift */; };
B5B31E1D20B36ABD002DBEC8 /* BVSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5B31E1320B36ABD002DBEC8 /* BVSwift.framework */; };
B736C2022ABAF76600299F42 /* userIdJSON.json in Resources */ = {isa = PBXBuildFile; fileRef = B736C2012ABAF76600299F42 /* userIdJSON.json */; };
B76D94E22ADE5E5800B0BF9D /* sessionTokenJSON.json in Resources */ = {isa = PBXBuildFile; fileRef = B76D94E12ADE5E5800B0BF9D /* sessionTokenJSON.json */; };
B7CBDC532ABC3BF100CBE042 /* BVTestKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7CBDC522ABC3BF100CBE042 /* BVTestKeys.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -562,7 +565,10 @@
B5B31E1320B36ABD002DBEC8 /* BVSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = BVSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; };
B5B31E1720B36ABD002DBEC8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
B5B31E1C20B36ABD002DBEC8 /* BVSwiftTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BVSwiftTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
B5B31E2320B36ABD002DBEC8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; name = Info.plist; path = BVSwiftTests/Info.plist; sourceTree = "<group>"; };
B736C2012ABAF76600299F42 /* userIdJSON.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = userIdJSON.json; sourceTree = "<group>"; };
B76D94E12ADE5E5800B0BF9D /* sessionTokenJSON.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = sessionTokenJSON.json; sourceTree = "<group>"; };
B7CBDC512ABC3BCB00CBE042 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; name = Info.plist; path = ../../../Xcode/BVSwiftTests/BVSwiftTests/Info.plist; sourceTree = "<group>"; };
B7CBDC522ABC3BF100CBE042 /* BVTestKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BVTestKeys.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -1061,7 +1067,7 @@
B549EEA920D04E9400ED5190 /* BVCurations */,
B53984B420D94F5F00EF77DA /* BVRecommendations */,
B5B31F2820B373D8002DBEC8 /* MockData */,
B5B31F6920B3756E002DBEC8 /* Support */,
B7CBDC502ABC3BC400CBE042 /* Support */,
);
path = BVSwiftTests;
sourceTree = "<group>";
Expand All @@ -1074,6 +1080,8 @@
98686D722407159300D67889 /* testCurationsFeedTest.json */,
98686D752407159400D67889 /* testJPEGPhotoResourceDecode.json */,
98686D762407159400D67889 /* testSyndicationSource.json */,
B736C2012ABAF76600299F42 /* userIdJSON.json */,
B76D94E12ADE5E5800B0BF9D /* sessionTokenJSON.json */,
);
path = MockData;
sourceTree = "<group>";
Expand Down Expand Up @@ -1142,15 +1150,6 @@
path = BVAnalytics;
sourceTree = "<group>";
};
B5B31F6920B3756E002DBEC8 /* Support */ = {
isa = PBXGroup;
children = (
B5B31E2320B36ABD002DBEC8 /* Info.plist */,
);
name = Support;
path = ../../Xcode/BVSwiftTests;
sourceTree = "<group>";
};
B5B31F6A20B375DC002DBEC8 /* Support */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -1265,6 +1264,15 @@
path = Submission;
sourceTree = "<group>";
};
B7CBDC502ABC3BC400CBE042 /* Support */ = {
isa = PBXGroup;
children = (
B7CBDC512ABC3BCB00CBE042 /* Info.plist */,
B7CBDC522ABC3BF100CBE042 /* BVTestKeys.swift */,
);
path = Support;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXHeadersBuildPhase section */
Expand Down Expand Up @@ -1375,7 +1383,9 @@
files = (
98686D772407159400D67889 /* testCurationsFeedTest.json in Resources */,
98686D7A2407159400D67889 /* testJPEGPhotoResourceDecode.json in Resources */,
B76D94E22ADE5E5800B0BF9D /* sessionTokenJSON.json in Resources */,
98686D7B2407159400D67889 /* testSyndicationSource.json in Resources */,
B736C2022ABAF76600299F42 /* userIdJSON.json in Resources */,
98686D782407159400D67889 /* ph.png in Resources */,
98686D792407159400D67889 /* skelly_android.jpg in Resources */,
);
Expand Down Expand Up @@ -1667,6 +1677,7 @@
98686D9124080D0900D67889 /* BVMultiProductQueryTest.swift in Sources */,
98686D6F2407153C00D67889 /* BVCurationsSubmissionTest.swift in Sources */,
422C3B212538524900080544 /* BVCommentsQueryTest.swift in Sources */,
B7CBDC532ABC3BF100CBE042 /* BVTestKeys.swift in Sources */,
98686D4B240714A000D67889 /* BVURLParameterTest.swift in Sources */,
98686D53240714D100D67889 /* BVReviewSearchQueryTest.swift in Sources */,
98686D672407151600D67889 /* BVQuestionSubmissionTest.swift in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion BVSwift/BVCommon/Configuration/BVConstants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ internal struct BVConstants {
static let appVersionField: String = "_appVersion"
static let buildNumberField: String = "_buildNumber"
static let sdkVersionField: String = "_bvIosSwiftSdkVersion"
static let bvSwiftSDKVersion: String = "1.11.4"
static let bvSwiftSDKVersion: String = "1.12.0"
}

internal extension Bundle {
Expand Down
6 changes: 5 additions & 1 deletion BVSwift/BVConversations/Model/BVReview.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ public struct BVReview: BVQueryable, BVSubmissionable {
public let userLocation: String?
public let userNickname: String?
public let videos: [BVVideo]?

public let originalProductName: String?

private enum CodingKeys: String, CodingKey {
case additionalFieldsDictionary = "AdditionalFields"
case authorId = "AuthorId"
Expand Down Expand Up @@ -132,6 +133,7 @@ public struct BVReview: BVQueryable, BVSubmissionable {
case userLocation = "UserLocation"
case userNickname = "UserNickname"
case videos = "Videos"
case originalProductName = "OriginalProductName"
}
}

Expand Down Expand Up @@ -183,6 +185,7 @@ extension BVReview {
self.userLocation = nil
self.userNickname = nil
self.videos = nil
self.originalProductName = nil
}

public init(productId: String) {
Expand Down Expand Up @@ -228,6 +231,7 @@ extension BVReview {
self.userLocation = nil
self.userNickname = nil
self.videos = nil
self.originalProductName = nil
}
}

Expand Down
2 changes: 1 addition & 1 deletion BVSwift/Support/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.11.4</string>
<string>1.12.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSPrincipalClass</key>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,7 @@ class BVMultiProductQueryTest: XCTestCase {
func testMultiProductQueryWithUserId() {
let expectation =
self.expectation(description: "testMultiProductQueryWithUserId")

let multiProduct: BVMultiProduct = BVMultiProduct(productIds: ["product4", "product2", "product3"], locale: "en_US", userId: "test109")
let multiProduct: BVMultiProduct = BVMultiProduct(productIds: ["product4", "product2", "product3"], locale: "en_US", userId: BVTestKeys().loadKeyForId(fromJSON: .userIdJSON, forId: BVTestKeys.userIdKeys.validUserId.rawValue))

guard let multiProductSubmission = BVMultiProductQuery(multiProduct) else {
XCTFail()
Expand Down Expand Up @@ -155,8 +154,7 @@ class BVMultiProductQueryTest: XCTestCase {
func testMultiProductQueryMissingProductsError() {
let expectation =
self.expectation(description: "testMultiProductQueryMissingProductsError")

let multiProduct: BVMultiProduct = BVMultiProduct(productIds: [], locale: "en_US", userId: "test109")
let multiProduct: BVMultiProduct = BVMultiProduct(productIds: [], locale: "en_US", userId: BVTestKeys().loadKeyForId(fromJSON: .userIdJSON, forId: BVTestKeys.userIdKeys.validUserId.rawValue))

guard let multiProductSubmission = BVMultiProductQuery(multiProduct) else {
XCTFail()
Expand Down Expand Up @@ -191,8 +189,7 @@ class BVMultiProductQueryTest: XCTestCase {
func testMultiProductQueryMissingLocaleError() {
let expectation =
self.expectation(description: "testMultiProductQueryMissingLocaleError")

let multiProduct: BVMultiProduct = BVMultiProduct(productIds: ["product4", "product2", "product3"], locale: "", userId: "test109")
let multiProduct: BVMultiProduct = BVMultiProduct(productIds: ["product4", "product2", "product3"], locale: "", userId: BVTestKeys().loadKeyForId(fromJSON: .userIdJSON, forId: BVTestKeys.userIdKeys.validUserId.rawValue))

guard let multiProductSubmission = BVMultiProductQuery(multiProduct) else {
XCTFail()
Expand Down
65 changes: 65 additions & 0 deletions BVSwiftTests/BVConversations/Display/BVReviewQueryTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ class BVReviewQueryTest: XCTestCase {
XCTAssertEqual(review.authorId, "endersgame")
XCTAssertEqual(review.userNickname, "endersgame")
XCTAssertEqual(review.userLocation, "Charlottesville, VA")
XCTAssertEqual(review.originalProductName, nil)

guard let proDimension: BVDimensionElement =
tagDimensions.filter({ (elem: BVDimensionElement) -> Bool in
Expand Down Expand Up @@ -1479,4 +1480,68 @@ class BVReviewQueryTest: XCTestCase {
error, "Something went horribly wrong, request took too long.")
}
}

func testReviewQueryDisplayOriginalProductName() {

let expectation = self.expectation(description: "testReviewQueryDisplayOriginalProductName")

let reviewQuery = BVReviewQuery(productId: "data-gen-moppq9ekthfzbc6qff3bqokie", limit: 10, offset: 0)
.sort(.rating, order: .descending)
.configure(BVReviewQueryTest.incentivizedStatsConfig)
.handler { (response: BVConversationsQueryResponse<BVReview>) in

if case .failure(let error) = response {
print(error)
XCTFail()
expectation.fulfill()
return
}

guard case let .success(_, reviews) = response else {
XCTFail()
expectation.fulfill()
return
}


guard let review: BVReview = reviews.first(where: {$0.reviewId == "45570991"}),
let originalProductName = review.originalProductName else {
XCTFail()
expectation.fulfill()
return
}

XCTAssertEqual(reviews.count, 10)
XCTAssertEqual(review.rating, 5)
XCTAssertEqual(review.title, "Test test")
XCTAssertEqual(review.reviewText, "test test test test test test test test tes ttesttest")
XCTAssertEqual(review.moderationStatus, "APPROVED")
XCTAssertEqual(review.reviewId, "45570991")
XCTAssertNotNil(review.productId)
XCTAssertEqual(review.isRatingsOnly, false)
XCTAssertEqual(review.isFeatured, false)
XCTAssertEqual(review.productId, "data-gen-moppq9ekthfzbc6qff3bqokie")
XCTAssertEqual(review.authorId, "jhan23")
XCTAssertEqual(review.userNickname, "jhcontentent")
XCTAssertEqual(review.userLocation, nil)
XCTAssertEqual(originalProductName, "14K White Gold Diamond Teardrop Necklace")

expectation.fulfill()
}

guard let req = reviewQuery.request else {
XCTFail()
expectation.fulfill()
return
}

print(req)

reviewQuery.async()

self.waitForExpectations(timeout: 20) { (error) in
XCTAssertNil(
error, "Something went horribly wrong, request took too long.")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -188,10 +188,9 @@ class BVProgressiveSubmissionTest: XCTestCase {
func testProgressiveSubmissionMissingEmailError() {
let expectation =
self.expectation(description: "testProgressiveSubmissionMissingEmailError")

var progressiveReview: BVProgressiveReview = self.buildRequest()
progressiveReview.userToken = nil
progressiveReview.userId = "tets109"
progressiveReview.userId = BVTestKeys().loadKeyForId(fromJSON: .userIdJSON, forId: BVTestKeys.userIdKeys.validUserId.rawValue)

guard let progressiveReviewSubmission = BVProgressiveReviewSubmission(progressiveReview) else {
XCTFail()
Expand Down Expand Up @@ -466,7 +465,7 @@ class BVProgressiveSubmissionTest: XCTestCase {
submissionFields.isRecommended = true

var submission = BVProgressiveReview(productId:"product10", submissionFields: submissionFields)
submission.submissionSessionToken = "VcticiyaPKqkXxKeZbKRoq0a3ArcQqAObHunbMNdjOiDSSYElouJV7wHkb6nZaSLw5q8OtGFyRFyPyZAChej/RAYtPmVCleQuFiwuKub0ac="
submission.submissionSessionToken = BVTestKeys().loadKeyForId(fromJSON: .sessionTokenJSON, forId: BVTestKeys.sessionTokenKeys.buildRequest.rawValue)
submission.locale = "en_US"
submission.userToken = "6b1549daa5df7eb481d8cf95c0d3e4d2646174653d3230323130363134267573657269643d746573743039383826456d61696c416464726573733d646576656c6f70657225343062617a616172766f6963652e636f6d26557365724e616d653d3039383874657374266d61786167653d333635"
return submission
Expand All @@ -478,7 +477,6 @@ class BVProgressiveSubmissionTest: XCTestCase {
}

func buildRequestHostedAuthSuccess() -> BVProgressiveReview {

self.submissionFields = BVProgressiveReviewFields()
submissionFields.rating = 4
submissionFields.title = "my favorite product ever!"
Expand All @@ -489,15 +487,14 @@ class BVProgressiveSubmissionTest: XCTestCase {
submissionFields.hostedAuthenticationCallbackurl = "https://bazaarvoice.com"

var submission = BVProgressiveReview(productId:"Product1", submissionFields: submissionFields)
submission.submissionSessionToken = "9y8kITDlqeFpCLH634OJJr7ErGP13y3oZ8ePKsYT/jOAITIj2GQ31eD2NX4oz74SujG3c4PTgkiMHBdF2FGvRI7qDUSM2SP8sGT3maxErGudQPMh9TGx8kc/8Dgn6Ik1"
submission.submissionSessionToken = BVTestKeys().loadKeyForId(fromJSON: .sessionTokenJSON, forId: BVTestKeys.sessionTokenKeys.buildRequestHostedAuthSuccess.rawValue)
submission.locale = "en_US"
submission.userId = "z6amcrde52q0hppiaym3h5flpf"
submission.userId = BVTestKeys().loadKeyForId(fromJSON: .userIdJSON, forId: BVTestKeys.userIdKeys.validUserId.rawValue)
submission.hostedAuth = true
return submission
}

func buildRequestHostedAuthFailure() -> BVProgressiveReview {

self.submissionFields = BVProgressiveReviewFields()
submissionFields.rating = 4
submissionFields.title = "my favorite product ever!"
Expand All @@ -508,14 +505,10 @@ class BVProgressiveSubmissionTest: XCTestCase {
submissionFields.hostedAuthenticationCallbackurl = "https://bazaarvoice.com"

var submission = BVProgressiveReview(productId:"Product1", submissionFields: submissionFields)
submission.submissionSessionToken = "DueNHADxCKCHad5AvUHlz6Md3kxA9aVmrVEYiJ1ln3KD9E0yC+kNJ/rAjukeSdiS9h73xtnxerVFyafTPT53nSloTr6GvV5hDaelalQ+cPB8ajya2zak2AfHwBEv5R8v"
submission.submissionSessionToken = BVTestKeys().loadKeyForId(fromJSON: .sessionTokenJSON, forId: BVTestKeys.sessionTokenKeys.buildRequestHostedAuthFailure.rawValue)
submission.locale = "en_US"
submission.userId = "invalid"
submission.userId = BVTestKeys().loadKeyForId(fromJSON: .userIdJSON, forId: BVTestKeys.userIdKeys.invalidUserId.rawValue)
submission.hostedAuth = true
return submission
}




}
6 changes: 6 additions & 0 deletions BVSwiftTests/MockData/sessionTokenJSON.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"buildRequest": "VcticiyaPKqkXxKeZbKRoq0a3ArcQqAObHunbMNdjOiDSSYElouJV7wHkb6nZaSLw5q8OtGFyRFyPyZAChej/RAYtPmVCleQuFiwuKub0ac=",
"buildRequestHostedAuthFailure": "DueNHADxCKCHad5AvUHlz6Md3kxA9aVmrVEYiJ1ln3KD9E0yC+kNJ/rAjukeSdiS9h73xtnxerVFyafTPT53nSloTr6GvV5hDaelalQ+cPB8ajya2zak2AfHwBEv5R8v",
"buildRequestHostedAuthSuccess": "9y8kITDlqeFpCLH634OJJr7ErGP13y3oZ8ePKsYT/jOAITIj2GQ31eD2NX4oz74SujG3c4PTgkiMHBdF2FGvRI7qDUSM2SP8sGT3maxErGudQPMh9TGx8kc/8Dgn6Ik1"
}

4 changes: 4 additions & 0 deletions BVSwiftTests/MockData/userIdJSON.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"validUserId": "test109",
"invalidUserId": "invalid"
}
48 changes: 48 additions & 0 deletions BVSwiftTests/Support/BVTestKeys.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
//
// BVTestKeys.swift
// BVSwift
//
// Copyright © 2023 Bazaarvoice. All rights reserved.
//

import Foundation

class BVTestKeys {
enum idJson: String {
case userIdJSON = "userIdJSON"
case sessionTokenJSON = "sessionIdJSON"
}

enum userIdKeys: String {
case validUserId = "validUserId"
case invalidUserId = "invalidUserId"
}

enum sessionTokenKeys: String {
case buildRequest = "buildRequest"
case buildRequestHostedAuthFailure = "buildRequestHostedAuthFailure"
case buildRequestHostedAuthSuccess = "buildRequestHostedAuthSuccess"
}

func loadKeyForId(fromJSON: idJson, forId: String) -> String {
guard let resourceURL =
Bundle(
for: BVTestKeys.self)
.url(
forResource: fromJSON.rawValue,
withExtension: ".json") else {
return ""
}
do {
let data = try Data(contentsOf: resourceURL, options: [])
if let json = try JSONSerialization.jsonObject(with: data) as? [String : Any] {
return json[forId] as? String ?? ""
} else {
return ""
}
} catch {
return ""
}
}
}

0 comments on commit 4ccc4a0

Please sign in to comment.