/// <summary>
        /// Creates a new <see cref="AdGroupCriterion"/> configured for an
        /// <code>ADD</code> operation.
        /// </summary>
        /// <param name="node">The node whose criterion should be added.</param>
        /// <param name="adGroupId">The ad group ID of the criterion.</param>
        /// <param name="idGenerator">The temporary ID generator for new nodes.</param>
        /// <returns>An <see cref="AdGroupCriterion"/> object for <code>ADD</code>
        /// operation.</returns>
        internal static AdGroupCriterion CreateCriterionForAdd(ProductPartitionNode node,
        long adGroupId, TemporaryIdGenerator idGenerator)
        {
            PreconditionUtilities.CheckNotNull(node, ShoppingMessages.NodeCannotBeNull);

              AdGroupCriterion adGroupCriterion;

              if (node.IsExcludedUnit) {
            adGroupCriterion = new NegativeAdGroupCriterion();
              } else {
            adGroupCriterion = new BiddableAdGroupCriterion() {
              biddingStrategyConfiguration = node.GetBiddingConfig()
            };
              }
              adGroupCriterion.adGroupId = adGroupId;
              adGroupCriterion.criterion = node.GetCriterion();

              adGroupCriterion.criterion.id = node.ProductPartitionId;
              if (node.Parent != null) {
            (adGroupCriterion.criterion as ProductPartition).parentCriterionId =
               node.Parent.ProductPartitionId;
              }

              return adGroupCriterion;
        }
        /// <summary>
        /// Creates a new AdGroupCriterion configured for a REMOVE operation.
        /// </summary>
        /// <param name="node">The node whose criterion should be removed.</param>
        /// <param name="adGroupId">The ad group ID of the criterion.</param>
        /// <returns>The AdGroupCriterion to be removed.</returns>
        internal static AdGroupCriterion CreateCriterionForRemove(ProductPartitionNode node,
        long adGroupId)
        {
            PreconditionUtilities.CheckNotNull(node, ShoppingMessages.NodeCannotBeNull);

              return new AdGroupCriterion() {
            adGroupId = adGroupId,
            criterion = new ProductPartition() {
              id = node.ProductPartitionId
            }
              };
        }
        public void TestAddChildThatExistsFails()
        {
            rootNode = rootNode.AsSubdivision();
              rootNode.AddChild(ProductDimensions.CreateBrand("google"));

              // Add the same child again. The call should fail.
              Assert.Throws<ArgumentException>(delegate() {
            rootNode.AddChild(ProductDimensions.CreateBrand("google"));
              });

              // Add the same child again, this time with a different case.
              // The call should fail.
              Assert.Throws<ArgumentException>(delegate() {
            rootNode.AddChild(ProductDimensions.CreateBrand("GOOGLE"));
              });
        }
        /// <summary>
        /// Creates ADD operations for the node and ALL of its children.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="idGenerator">The temporary ID generator for new nodes.</param>
        /// <returns>A list of operation pair for the specified operation and nodes.
        /// </returns>
        private List<OperationPair> CreateAddOperations(ProductPartitionNode node,
        TemporaryIdGenerator idGenerator)
        {
            AdGroupCriterionOperation addOp = new AdGroupCriterionOperation();
              addOp.@operator = Operator.ADD;

              // Overwrite the ID set by the user when doing ADD operations. This
              // minimizes the chances of a malformed tree.
              node.ProductPartitionId = idGenerator.Next;

              addOp.operand = ProductPartitionNodeAdapter.CreateCriterionForAdd(node, adGroupId,
              idGenerator);

              List<OperationPair> operationsList = new List<OperationPair>();
              operationsList.Add(new OperationPair(node, addOp));

              // Recursively add all of this node's children to the operations list.
              foreach (ProductPartitionNode child in node.Children) {
            operationsList.AddRange(CreateAddOperations(child, idGenerator));
              }
              return operationsList;
        }
        public void TestCreateMultiNodeTreeFromScratch()
        {
            ProductPartitionTree tree = ProductPartitionTree.CreateAdGroupTree(
              new List<AdGroupCriterion>());

              ProductPartitionNode rootNode = tree.Root.AsSubdivision();
              ProductPartitionNode brand1 =
              rootNode.AddChild(ProductDimensions.CreateBrand("google")).AsSubdivision();
              ProductPartitionNode brand1Offer1 =
              brand1.AddChild(ProductDimensions.CreateOfferId("A"));
              brand1Offer1.AsBiddableUnit().CpcBid = 1000000L;
              ProductPartitionNode brand1Offer2 =
              brand1.AddChild(ProductDimensions.CreateOfferId()).AsExcludedUnit();
              ProductPartitionNode brand2 =
              rootNode.AddChild(ProductDimensions.CreateBrand()).AsExcludedUnit();

              ProductPartitionNode[] nodes = new ProductPartitionNode[] { rootNode, brand1, brand1Offer1,
            brand1Offer2, brand2 };

              AdGroupCriterionOperation[] mutateOperations = tree.GetMutateOperations();

              for (int i = 0; i < nodes.Length; i++) {
            List<AdGroupCriterionOperation> nodeOperations =
            shoppingTestUtils.GetOperationsForNode(nodes[i], mutateOperations);
            Assert.That(nodeOperations.Count == 1);
            Assert.That(nodeOperations[0].@operator == Operator.ADD);
            ProductPartition partition = (ProductPartition) nodeOperations[0].operand.criterion;
            Assert.That(partition.id == nodes[i].ProductPartitionId);
            if (nodes[i].Parent == null) {
              Assert.That(partition.parentCriterionIdSpecified == false);
            } else {
              Assert.That(partition.parentCriterionId == nodes[i].Parent.ProductPartitionId);
            }
            Assert.That(partition.caseValue == nodes[i].Dimension);
              }
        }
Пример #6
0
        public void TestNavigation()
        {
            rootNode = rootNode.AsSubdivision();
            ProductBrand brandGoogle = ProductDimensions.CreateBrand("google");
            ProductBrand brandOther  = ProductDimensions.CreateBrand(null);
            ProductCanonicalCondition conditionNew =
                ProductDimensions.CreateCanonicalCondition(ProductCanonicalConditionCondition.NEW);
            ProductCanonicalCondition conditionUsed =
                ProductDimensions.CreateCanonicalCondition(ProductCanonicalConditionCondition.USED);
            ProductCanonicalCondition conditionOther = ProductDimensions.CreateCanonicalCondition();

            // Build up the brand = Google node under the root.
            ProductPartitionNode brandGoogleNode = rootNode.AddChild(brandGoogle).AsSubdivision();

            brandGoogleNode.AddChild(conditionNew);
            brandGoogleNode.AddChild(conditionUsed);
            brandGoogleNode.AddChild(conditionOther);

            Assert.True(brandGoogleNode.HasChild(conditionNew),
                        "HasChild should return true for existing child dimension.");
            Assert.AreSame(brandGoogleNode, brandGoogleNode.GetChild(conditionNew).Parent,
                           "parent->GetChild->getParent should return parent.");
            Assert.True(brandGoogleNode.HasChild(conditionUsed),
                        "HasChild should return true for existing child dimension.");
            Assert.AreSame(brandGoogleNode, brandGoogleNode.GetChild(conditionUsed).Parent,
                           "parent->GetChild->getParent should return parent.");
            Assert.True(brandGoogleNode.HasChild(conditionOther),
                        "HasChild should return true for existing child dimension.");
            Assert.AreSame(brandGoogleNode, brandGoogleNode.GetChild(conditionOther).Parent,
                           "parent->GetChild->getParent should return parent.");

            // Build up the brand = null (other) node under the root.
            ProductPartitionNode brandOtherNode = rootNode.AddChild(brandOther).AsSubdivision();

            brandOtherNode.AddChild(conditionNew);
            Assert.True(brandOtherNode.HasChild(conditionNew),
                        "HasChild should return true for existing child dimension.");
            Assert.AreSame(brandOtherNode, brandOtherNode.GetChild(conditionNew).Parent,
                           "parent->GetChild->getParent should return parent.");
            Assert.False(brandOtherNode.HasChild(conditionUsed),
                         "HasChild should return false for nonexistent child dimension.");
            Assert.False(brandOtherNode.HasChild(conditionOther),
                         "HasChild should return false for nonexistent child dimension.");
            brandOtherNode.AddChild(conditionOther);
            Assert.True(brandOtherNode.HasChild(conditionOther),
                        "HasChild should return true for existing child dimension.");
            Assert.AreSame(brandOtherNode, brandOtherNode.GetChild(conditionOther).Parent,
                           "parent->GetChild->getParent should return parent.");

            // Remove one of the children of brand = null.
            brandOtherNode.RemoveChild(conditionOther);
            Assert.False(brandOtherNode.HasChild(conditionOther),
                         "HasChild should return false for a removed child dimension.");

            // Remove the rest of the children of brand = null.
            brandOtherNode.RemoveAllChildren();
            Assert.False(brandOtherNode.HasChild(conditionNew),
                         "HasChild should return false for any removed child dimension.");
            Assert.False(brandOtherNode.HasChild(conditionUsed),
                         "HasChild should return false for any removed child dimension.");
        }
        public void TestMutateMultiNodeTree()
        {
            ProductPartitionTree tree =
                shoppingTestUtils.CreateTestTreeForTransformation(ADGROUP_ID);

            Assert.AreEqual(ADGROUP_ID, tree.AdGroupId, "ad group ID is incorrect");

            // Change the bids on leaf nodes.
            ProductPartitionNode brandGoogleNode =
                tree.Root.GetChild(shoppingTestUtils.BRAND_GOOGLE);
            ProductPartitionNode offerANode = brandGoogleNode.GetChild(shoppingTestUtils.OFFER_A);

            // This should produce 1 SET operation.
            offerANode.CpcBid = offerANode.CpcBid * 10;

            // Offer B is changed from Exclude to Biddable. This should produce 1
            // REMOVE operation + 1 ADD operation.
            ProductPartitionNode offerBNode = brandGoogleNode.GetChild(shoppingTestUtils.OFFER_B);

            offerBNode.AsBiddableUnit().CpcBid = 5000000L;

            // Other Brand node is changed from Exclude to Biddable. This should
            // produce 1 REMOVE operation + 1 ADD operation.
            ProductPartitionNode brandOtherNode = tree.Root.GetChild(shoppingTestUtils.BRAND_OTHER);

            brandOtherNode = brandOtherNode.AsBiddableUnit();

            // Add an offer C node. This should produce 1 ADD operation.
            ProductPartitionNode offerCNode = brandGoogleNode.AddChild(shoppingTestUtils.OFFER_C);

            offerCNode.AsBiddableUnit().CpcBid = 1500000L;

            // Remove the brand Motorola node. This should produce 1 REMOVE operation.
            ProductPartitionNode brandMotorolaNode =
                tree.Root.GetChild(shoppingTestUtils.BRAND_MOTOROLA);

            tree.Root.RemoveChild(shoppingTestUtils.BRAND_MOTOROLA);

            // Get the mutate operations generated by the modifications made to the tree.
            AdGroupCriterionOperation[] mutateOperations = tree.GetMutateOperations();
            Assert.AreEqual(7, mutateOperations.Length);

            List <AdGroupCriterionOperation> operations = null;

            // Since Offer A node only has modified attributes, there should only be
            // one SET operation.
            operations = shoppingTestUtils.GetOperationsForNode(offerANode, mutateOperations);
            Assert.That(operations.Count == 1);
            Assert.That(operations[0].@operator == Operator.SET);

            // Since Offer B node is being converted from Exclude to Biddable node,
            // there should be one REMOVE operation, and another ADD operation.
            operations = shoppingTestUtils.GetOperationsForNode(offerBNode, mutateOperations);
            Assert.That(operations.Count == 2);
            Assert.That(operations[0].@operator == Operator.REMOVE);
            Assert.That(operations[1].@operator == Operator.ADD);

            // Since Offer C node is being added, there should be one ADD operation.
            operations = shoppingTestUtils.GetOperationsForNode(offerCNode, mutateOperations);
            Assert.That(operations.Count == 1);
            Assert.That(operations[0].@operator == Operator.ADD);

            // Since Other Brand node is being converted from Exclude to Biddable node,
            // there should be one REMOVE operation, and another ADD operation.
            operations = shoppingTestUtils.GetOperationsForNode(brandOtherNode, mutateOperations);
            Assert.That(operations.Count == 2);
            Assert.That(operations[0].@operator == Operator.REMOVE);
            Assert.That(operations[1].@operator == Operator.ADD);

            // Since Offer B node is being removed, there should be one REMOVE
            // operation.
            operations =
                shoppingTestUtils.GetOperationsForNode(brandMotorolaNode, mutateOperations);
            Assert.That(operations.Count == 1);
            Assert.That(operations[0].@operator == Operator.REMOVE);
        }
        /// <summary>
        /// Runs the code example.
        /// </summary>
        /// <param name="user">The AdWords user.</param>
        /// <param name="adGroupId">The ad group to which product partition is
        /// added.</param>
        public void Run(AdWordsUser user, long adGroupId)
        {
            // Get the AdGroupCriterionService.
            AdGroupCriterionService adGroupCriterionService =
                (AdGroupCriterionService)user.GetService(
                    AdWordsService.v201509.AdGroupCriterionService);

            // Build a new ProductPartitionTree using the ad group's current set of criteria.
            ProductPartitionTree partitionTree =
                ProductPartitionTree.DownloadAdGroupTree(user, adGroupId);

            Console.WriteLine("Original tree: {0}", partitionTree);

            // Clear out any existing criteria.
            ProductPartitionNode rootNode = partitionTree.Root.RemoveAllChildren();

            // Make the root node a subdivision.
            rootNode = rootNode.AsSubdivision();

            // Add a unit node for condition = NEW.
            ProductPartitionNode newConditionNode = rootNode.AddChild(
                ProductDimensions.CreateCanonicalCondition(ProductCanonicalConditionCondition.NEW));

            newConditionNode.AsBiddableUnit().CpcBid = 200000;

            ProductPartitionNode usedConditionNode = rootNode.AddChild(
                ProductDimensions.CreateCanonicalCondition(ProductCanonicalConditionCondition.USED));

            usedConditionNode.AsBiddableUnit().CpcBid = 100000;

            // Add a subdivision node for condition = null (everything else).
            ProductPartitionNode otherConditionNode =
                rootNode.AddChild(ProductDimensions.CreateCanonicalCondition()).AsSubdivision();

            // Add a unit node under condition = null for brand = "CoolBrand".
            ProductPartitionNode coolBrandNode = otherConditionNode.AddChild(
                ProductDimensions.CreateBrand("CoolBrand"));

            coolBrandNode.AsBiddableUnit().CpcBid = 900000L;

            // Add a unit node under condition = null for brand = "CheapBrand".
            ProductPartitionNode cheapBrandNode = otherConditionNode.AddChild(
                ProductDimensions.CreateBrand("CheapBrand"));

            cheapBrandNode.AsBiddableUnit().CpcBid = 10000L;

            // Add a subdivision node under condition = null for brand = null (everything else).
            ProductPartitionNode otherBrandNode = otherConditionNode.AddChild(
                ProductDimensions.CreateBrand(null)).AsSubdivision();

            // Add unit nodes under condition = null/brand = null.
            // The value for each bidding category is a fixed ID for a specific
            // category. You can retrieve IDs for categories from the ConstantDataService.
            // See the 'GetProductCategoryTaxonomy' example for more details.

            // Add a unit node under condition = null/brand = null for product type
            // level 1 = 'Luggage & Bags'.
            ProductPartitionNode luggageAndBagNode = otherBrandNode.AddChild(
                ProductDimensions.CreateBiddingCategory(ProductDimensionType.BIDDING_CATEGORY_L1,
                                                        -5914235892932915235L));

            luggageAndBagNode.AsBiddableUnit().CpcBid = 750000L;

            // Add a unit node under condition = null/brand = null for product type
            // level 1 = null (everything else).
            ProductPartitionNode everythingElseNode = otherBrandNode.AddChild(
                ProductDimensions.CreateBiddingCategory(ProductDimensionType.BIDDING_CATEGORY_L1));

            everythingElseNode.AsBiddableUnit().CpcBid = 110000L;

            try {
                // Make the mutate request, using the operations returned by the ProductPartitionTree.
                AdGroupCriterionOperation[] mutateOperations = partitionTree.GetMutateOperations();

                if (mutateOperations.Length == 0)
                {
                    Console.WriteLine("Skipping the mutate call because the original tree and the current " +
                                      "tree are logically identical.");
                }
                else
                {
                    adGroupCriterionService.mutate(mutateOperations);
                }

                // The request was successful, so create a new ProductPartitionTree based on the updated
                // state of the ad group.
                partitionTree = ProductPartitionTree.DownloadAdGroupTree(user, adGroupId);

                Console.WriteLine("Final tree: {0}", partitionTree);
            } catch (Exception e) {
                throw new System.ApplicationException("Failed to set shopping product partition.", e);
            }
        }
        /// <summary>
        /// Creates a new AdGroupCriterion configured for a SET operation that will
        /// set the criterion's bid.
        /// </summary>
        /// <param name="node">The node whose criterion should be updated.</param>
        /// <param name="adGroupId">The ad group ID of the criterion.</param>
        /// <param name="biddingConfig">The bidding strategy configuration of the
        /// criterion.</param>
        /// <returns>The AdGroupCriterion for SET operation.</returns>
        internal static AdGroupCriterion CreateCriterionForSetBid(ProductPartitionNode node,
        long adGroupId)
        {
            PreconditionUtilities.CheckNotNull(node, ShoppingMessages.NodeCannotBeNull);
              PreconditionUtilities.CheckArgument(node.IsBiddableUnit,
              string.Format(ShoppingMessages.NodeForBidUpdateIsNotBiddable, node.ProductPartitionId));

              return new BiddableAdGroupCriterion() {
            adGroupId = adGroupId,
            criterion = node.GetCriterion(),
            biddingStrategyConfiguration = node.GetBiddingConfig()
              };
        }
 public void Init()
 {
     rootNode = new ProductPartitionNode(null, null, ROOT_NODE_ID);
 }
        public void TestSetBidOnUnit()
        {
            rootNode = rootNode.AsSubdivision();
              ProductBrand childDimension = ProductDimensions.CreateBrand("google");
              ProductPartitionNode childNode = rootNode.AddChild(childDimension);

              Assert.That(childNode.CpcBidSpecified == false, "Bid should be null by default.");

              childNode.CpcBid = 1L;

              Assert.AreEqual(1L, childNode.CpcBid, "Bid does not reflect setBid.");
              Assert.True(childNode.IsBiddableUnit, "Node should be a biddable unit.");

              childNode = childNode.AsExcludedUnit();
              Assert.True(childNode.IsExcludedUnit, "Node should be an excluded unit.");
              Assert.False(childNode.IsBiddableUnit, "Node should not be a biddable unit.");
              Assert.False(childNode.CpcBidSpecified, "Excluded unit should have a null bid");

              // Set back to biddable.
              childNode = childNode.AsBiddableUnit();
              Assert.True(childNode.IsBiddableUnit, "Node should be a biddable unit.");
        }
 public void TestSetBidOnSubdivisionFails()
 {
     rootNode = rootNode.AsSubdivision();
       Assert.Throws<InvalidOperationException>(delegate() {
     rootNode.CpcBid = 1;
       });
 }
 public void TestRemoveChildThatDoesNotExistFails()
 {
     rootNode = rootNode.AsSubdivision();
       Assert.Throws<ArgumentException>(delegate() {
     rootNode.RemoveChild(ProductDimensions.CreateBrand("google"));
       });
 }
        public void TestNavigation()
        {
            rootNode = rootNode.AsSubdivision();
              ProductBrand brandGoogle = ProductDimensions.CreateBrand("google");
              ProductBrand brandOther = ProductDimensions.CreateBrand(null);
              ProductCanonicalCondition conditionNew =
              ProductDimensions.CreateCanonicalCondition(ProductCanonicalConditionCondition.NEW);
              ProductCanonicalCondition conditionUsed =
              ProductDimensions.CreateCanonicalCondition(ProductCanonicalConditionCondition.USED);
              ProductCanonicalCondition conditionOther = ProductDimensions.CreateCanonicalCondition();

              // Build up the brand = Google node under the root.
              ProductPartitionNode brandGoogleNode = rootNode.AddChild(brandGoogle).AsSubdivision();
              brandGoogleNode.AddChild(conditionNew);
              brandGoogleNode.AddChild(conditionUsed);
              brandGoogleNode.AddChild(conditionOther);

              Assert.True(brandGoogleNode.HasChild(conditionNew),
              "HasChild should return true for existing child dimension.");
              Assert.AreSame(brandGoogleNode, brandGoogleNode.GetChild(conditionNew).Parent,
              "parent->GetChild->getParent should return parent.");
              Assert.True(brandGoogleNode.HasChild(conditionUsed),
              "HasChild should return true for existing child dimension.");
              Assert.AreSame(brandGoogleNode, brandGoogleNode.GetChild(conditionUsed).Parent,
              "parent->GetChild->getParent should return parent.");
              Assert.True(brandGoogleNode.HasChild(conditionOther),
              "HasChild should return true for existing child dimension.");
              Assert.AreSame(brandGoogleNode, brandGoogleNode.GetChild(conditionOther).Parent,
              "parent->GetChild->getParent should return parent.");

              // Build up the brand = null (other) node under the root.
              ProductPartitionNode brandOtherNode = rootNode.AddChild(brandOther).AsSubdivision();
              brandOtherNode.AddChild(conditionNew);
              Assert.True(brandOtherNode.HasChild(conditionNew),
              "HasChild should return true for existing child dimension.");
              Assert.AreSame(brandOtherNode, brandOtherNode.GetChild(conditionNew).Parent,
              "parent->GetChild->getParent should return parent.");
              Assert.False(brandOtherNode.HasChild(conditionUsed),
              "HasChild should return false for nonexistent child dimension.");
              Assert.False(brandOtherNode.HasChild(conditionOther),
              "HasChild should return false for nonexistent child dimension.");
              brandOtherNode.AddChild(conditionOther);
              Assert.True(brandOtherNode.HasChild(conditionOther),
              "HasChild should return true for existing child dimension.");
              Assert.AreSame(brandOtherNode, brandOtherNode.GetChild(conditionOther).Parent,
              "parent->GetChild->getParent should return parent.");

              // Remove one of the children of brand = null.
              brandOtherNode.RemoveChild(conditionOther);
              Assert.False(brandOtherNode.HasChild(conditionOther),
              "HasChild should return false for a removed child dimension.");

              // Remove the rest of the children of brand = null.
              brandOtherNode.RemoveAllChildren();
              Assert.False(brandOtherNode.HasChild(conditionNew),
              "HasChild should return false for any removed child dimension.");
              Assert.False(brandOtherNode.HasChild(conditionUsed),
              "HasChild should return false for any removed child dimension.");
        }
        /// <summary>
        /// Gets the operations for node.
        /// </summary>
        /// <param name="partitionNode">The partition node.</param>
        /// <param name="mutateOperations">The list of all mutate operations.</param>
        /// <returns>The list of operations that apply to partitionNode.</returns>
        internal List<AdGroupCriterionOperation> GetOperationsForNode(
        ProductPartitionNode partitionNode, AdGroupCriterionOperation[] mutateOperations)
        {
            ProductDimensionEqualityComparer comparer = new ProductDimensionEqualityComparer();
              List<AdGroupCriterionOperation> retval = new List<AdGroupCriterionOperation>();

              foreach (AdGroupCriterionOperation operation in mutateOperations) {
            switch (operation.@operator) {
              case Operator.SET:
              case Operator.REMOVE:
            if (operation.operand.criterion.id == partitionNode.ProductPartitionId) {
              retval.Add(operation);
            }
            break;

              case Operator.ADD:
            if (comparer.Equals((operation.operand.criterion as ProductPartition).caseValue,
                partitionNode.Dimension)) {
              retval.Add(operation);
            }
            break;
            }
              }
              return retval;
        }
        /// <summary>
        /// Creates a REMOVE operation for the specified node.
        /// </summary>
        /// <param name="node">The node to be removed.</param>
        /// <returns>An operation pair for the node and the REMOVE operation.
        /// </returns>
        private OperationPair CreateRemoveOperation(ProductPartitionNode node)
        {
            PreconditionUtilities.CheckArgument(node.ProductPartitionId >= NEW_ROOT_ID,
              string.Format(ShoppingMessages.NodeForRemoveCannotHaveNegativeId,
              node.ProductPartitionId));

              AdGroupCriterionOperation removeOp = new AdGroupCriterionOperation();
              removeOp.@operator = Operator.REMOVE;
              removeOp.operand = ProductPartitionNodeAdapter.CreateCriterionForRemove(node, adGroupId);

              return new OperationPair(node, removeOp);
        }
        /// <summary>
        /// Creates a SET operation for the specified node.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <returns>An operation pair for the specified operation and node.
        /// </returns>
        private OperationPair CreateSetBidOperation(ProductPartitionNode node)
        {
            // TODO(Anash): This check is dangerous, since we should only depend on parent-child
              // relationships, not ID relationships.
              PreconditionUtilities.CheckArgument(node.ProductPartitionId >= NEW_ROOT_ID,
              string.Format(ShoppingMessages.NodeForSetCannotHaveNegativeId, node));
              AdGroupCriterionOperation setOp = new AdGroupCriterionOperation();
              setOp.@operator = Operator.SET;
              setOp.operand = ProductPartitionNodeAdapter.CreateCriterionForSetBid(node, adGroupId);

              return new OperationPair(node, setOp);
        }
        public void TestChildNodeBasicFunctionality()
        {
            rootNode = rootNode.AsSubdivision();
              Assert.False(rootNode.IsUnit, "Parent should not be a unit.");
              Assert.True(rootNode.IsSubdivision, "Parent should be a subdivision.");
              ProductBrand childDimension = ProductDimensions.CreateBrand("google");
              ProductPartitionNode childNode = rootNode.AddChild(childDimension);

              Assert.AreSame(childDimension, childNode.Dimension, "Child node merely wraps the " +
              "underlying dimension node.");
              Assert.AreSame(rootNode, childNode.Parent, "child.GetParent should return parentNode.");
              Assert.That(childNode.ProductPartitionId == 0, "Partition ID is incorrect.");

              Assert.That(childNode.Children.Count() == 0, "ChildNode should not have any children.");
              Assert.True(childNode.IsUnit, "New node should be a unit node by default.");
              Assert.True(childNode.IsBiddableUnit, "New node should be a biddable unit node by default.");

              Assert.That(rootNode.HasChild(childDimension), "rootNode.HasChild should return true when " +
              "passed the dimension of the added child");
              Assert.False(rootNode.HasChild(ProductDimensions.CreateBrand("xyz")), "rootNode.HasChild " +
              "should return false when passed a dimension for a nonexistent child");
              Assert.False(rootNode.HasChild(null), "rootNode.HasChild should return false when passed " +
              "a dimension for a nonexistent child");
        }
        /// <summary>
        /// Constructor that initializes the temp ID generator based on the ID of
        /// the root node.
        /// </summary>
        /// <param name="adGroupId">the ID of the ad group.</param>
        /// <param name="root">The root node of the tree.</param>
        private ProductPartitionTree(long adGroupId, ProductPartitionNode root)
        {
            PreconditionUtilities.CheckNotNull(root, ShoppingMessages.RootNodeCannotBeNull);

              this.adGroupId = adGroupId;
              this.root = root;
              this.dimensionComparator = new ProductDimensionEqualityComparer();

              // If this is an existing node in an ad group, then the tree should be
              // cloned so that we can keep track of the original state of the tree.
              if (this.Root.ProductPartitionId > 0) {
            originalRoot = this.Root.Clone();
              }
        }
        /// <summary>
        /// Using the criteria in <paramref name="parentIdMap"/>, recursively adds
        /// all children under the partition ID of <paramref name="parentNode"/> to
        /// <paramref name="parentNode"/>.
        /// </summary>
        /// <param name="parentNode">The parent node.</param>
        /// <param name="parentIdMap">The multimap from parent partition ID to list
        /// of child criteria</param>
        private static void AddChildNodes(ProductPartitionNode parentNode,
        Dictionary<long, List<AdGroupCriterion>> parentIdMap)
        {
            List<AdGroupCriterion> childCriteria = null;
              if (parentIdMap.ContainsKey(parentNode.ProductPartitionId)) {
            childCriteria = parentIdMap[parentNode.ProductPartitionId];
              }

              // no children, return.
              if (childCriteria == null || childCriteria.Count == 0) {
            return;
              }

              // Ensure that the parent is a subdivision.
              parentNode.AsSubdivision();

              foreach (AdGroupCriterion childCriterion in childCriteria) {
            ProductPartition partition = (ProductPartition) childCriterion.criterion;
            ProductPartitionNode childNode = parentNode.AddChild(partition.caseValue);
            childNode.ProductPartitionId = partition.id;

            if (childCriterion is BiddableAdGroupCriterion) {
              childNode = childNode.AsBiddableUnit();
              Money cpcBidAmount = GetBid((BiddableAdGroupCriterion) childCriterion);
              if (cpcBidAmount != null) {
            childNode.CpcBid = cpcBidAmount.microAmount;
              }
            } else {
              childNode = childNode.AsExcludedUnit();
            }

            AddChildNodes(childNode, parentIdMap);
              }
        }
 public void TestRemoveChildThatDoesNotExistFails() {
   rootNode = rootNode.AsSubdivision();
   Assert.Throws<ArgumentException>(delegate() {
     rootNode.RemoveChild(ProductDimensions.CreateBrand("google"));
   });
 }
        /// <summary>
        /// Returns a new tree based on a non-empty collection of ad group criteria.
        /// </summary>
        /// <param name="adGroupId">The ad group ID.</param>
        /// <param name="parentIdMap">The multimap from parent product partition ID
        /// to child criteria.</param>
        /// <returns>a new product partition tree.</returns>
        private static ProductPartitionTree CreateAdGroupTree(long adGroupId,
        Dictionary<long, List<AdGroupCriterion>> parentIdMap)
        {
            ProductPartitionNode rootNode = null;

              if (parentIdMap.Count == 0) {
            rootNode = new ProductPartitionNode(null, null, NEW_ROOT_ID);
              } else {
            PreconditionUtilities.CheckState(parentIdMap.ContainsKey(ROOT_PARENT_ID),
            string.Format(ShoppingMessages.RootCriteriaNotFoundInCriteriaList, adGroupId));

            PreconditionUtilities.CheckState(parentIdMap[ROOT_PARENT_ID].Count == 1,
            string.Format(ShoppingMessages.MoreThanOneRootFound, adGroupId));

            AdGroupCriterion rootCriterion = parentIdMap[ROOT_PARENT_ID][0];

            PreconditionUtilities.CheckState(rootCriterion is BiddableAdGroupCriterion,
            string.Format(ShoppingMessages.RootCriterionIsNotBiddable, adGroupId));

            BiddableAdGroupCriterion biddableRootCriterion = (BiddableAdGroupCriterion) rootCriterion;

            rootNode = new ProductPartitionNode(null, null, rootCriterion.criterion.id,
            new ProductDimensionEqualityComparer());

            // Set the root's bid if a bid exists on the BiddableAdGroupCriterion.
            Money rootNodeBid = GetBid(biddableRootCriterion);

            if (rootNodeBid != null) {
              rootNode.AsBiddableUnit().CpcBid = rootNodeBid.microAmount;
            }

            AddChildNodes(rootNode, parentIdMap);
              }
              return new ProductPartitionTree(adGroupId, rootNode);
        }
        /// <summary>
        /// Creates a tree of the form:
        ///
        /// <pre>
        /// ROOT
        ///   ProductType Level1 shoes
        ///     ProductType Level2 athletic shoes
        ///       Condition new $2.50
        ///       Condition used $1.00
        ///       Other - exclude from bidding
        ///     ProductType Level2 walking shoes
        ///       Condition new $3.50
        ///       Condition used $1.25
        ///       Other $1.00
        ///     ProductType Level2 null (everything else) - exclude from bidding
        ///   ProductType Level1 clothing
        ///     ProductType Level2 winter clothing
        ///       Condition new $1.00
        ///       Condition used $1.25
        ///       Other $1.50
        ///     ProductType Level2 summer clothing
        ///       Condition new $1.10
        ///       Condition used $1.00
        ///       Other $1.25
        ///     ProductType Level2 null (everything else)
        ///       Condition new $0.90
        ///       Condition used $0.85
        ///       Other $0.75
        ///   ProductType Level1 null (everything else) - exclude from bidding
        /// </pre>
        /// </summary>
        private void RebuildComplexTree()
        {
            // Clear out the tree.
            ProductPartitionNode rootNode = tree.Root.RemoveAllChildren().AsSubdivision();

            ProductPartitionNode shoesLevel1 = rootNode.AddChild(ProductDimensions.CreateType(
                                                                     ProductDimensionType.PRODUCT_TYPE_L1, "shoes")).AsSubdivision();

            ProductPartitionNode athleticShoesLevel2 = shoesLevel1.AddChild(
                ProductDimensions.CreateType(ProductDimensionType.PRODUCT_TYPE_L2, "athletic shoes"))
                                                       .AsSubdivision();

            athleticShoesLevel2.AddChild(ProductDimensions.CreateCanonicalCondition(
                                             ProductCanonicalConditionCondition.NEW)).AsBiddableUnit().CpcBid = 2500000L;
            athleticShoesLevel2.AddChild(ProductDimensions.CreateCanonicalCondition(
                                             ProductCanonicalConditionCondition.USED)).AsBiddableUnit().CpcBid = 1000000L;
            athleticShoesLevel2.AddChild(ProductDimensions.CreateCanonicalCondition()).AsExcludedUnit();

            ProductPartitionNode walkingShoesLevel2 = shoesLevel1.AddChild(
                ProductDimensions.CreateType(ProductDimensionType.PRODUCT_TYPE_L2, "walking shoes"))
                                                      .AsSubdivision();

            walkingShoesLevel2.AddChild(ProductDimensions.CreateCanonicalCondition(
                                            ProductCanonicalConditionCondition.NEW)).AsBiddableUnit().CpcBid = 3500000L;
            walkingShoesLevel2.AddChild(ProductDimensions.CreateCanonicalCondition(
                                            ProductCanonicalConditionCondition.USED)).AsBiddableUnit().CpcBid = 1250000L;
            walkingShoesLevel2.AddChild(ProductDimensions.CreateCanonicalCondition()).AsBiddableUnit()
            .CpcBid = 1000000L;

            shoesLevel1.AddChild(ProductDimensions.CreateType(ProductDimensionType.PRODUCT_TYPE_L2))
            .AsExcludedUnit();

            ProductPartitionNode clothingLevel1 = rootNode.AddChild(ProductDimensions.CreateType(
                                                                        ProductDimensionType.PRODUCT_TYPE_L1, "clothing")).AsSubdivision();

            ProductPartitionNode winterClothingLevel2 = clothingLevel1.AddChild(
                ProductDimensions.CreateType(ProductDimensionType.PRODUCT_TYPE_L2, "winter clothing"))
                                                        .AsSubdivision();

            winterClothingLevel2.AddChild(ProductDimensions.CreateCanonicalCondition(
                                              ProductCanonicalConditionCondition.NEW)).AsBiddableUnit().CpcBid = 1000000L;
            winterClothingLevel2.AddChild(ProductDimensions.CreateCanonicalCondition(
                                              ProductCanonicalConditionCondition.USED)).AsBiddableUnit().CpcBid = 1250000L;
            winterClothingLevel2.AddChild(ProductDimensions.CreateCanonicalCondition()).AsBiddableUnit()
            .CpcBid = 1500000L;

            ProductPartitionNode summerClothingLevel2 = clothingLevel1.AddChild(
                ProductDimensions.CreateType(ProductDimensionType.PRODUCT_TYPE_L2, "summer clothing"))
                                                        .AsSubdivision();

            summerClothingLevel2.AddChild(ProductDimensions.CreateCanonicalCondition(
                                              ProductCanonicalConditionCondition.NEW)).AsBiddableUnit().CpcBid = 1100000L;
            summerClothingLevel2.AddChild(ProductDimensions.CreateCanonicalCondition(
                                              ProductCanonicalConditionCondition.USED)).AsBiddableUnit().CpcBid = 1000000L;
            summerClothingLevel2.AddChild(ProductDimensions.CreateCanonicalCondition()).AsBiddableUnit()
            .CpcBid = 1250000L;

            ProductPartitionNode otherClothingLevel2 = clothingLevel1.AddChild(
                ProductDimensions.CreateType(ProductDimensionType.PRODUCT_TYPE_L2, null))
                                                       .AsSubdivision();

            otherClothingLevel2.AddChild(ProductDimensions.CreateCanonicalCondition(
                                             ProductCanonicalConditionCondition.NEW)).AsBiddableUnit().CpcBid = 900000L;
            otherClothingLevel2.AddChild(ProductDimensions.CreateCanonicalCondition(
                                             ProductCanonicalConditionCondition.USED)).AsBiddableUnit().CpcBid = 850000L;
            otherClothingLevel2.AddChild(ProductDimensions.CreateCanonicalCondition()).AsBiddableUnit()
            .CpcBid = 750000L;

            rootNode.AddChild(ProductDimensions.CreateType(ProductDimensionType.PRODUCT_TYPE_L1, null))
            .AsExcludedUnit();

            Assert.DoesNotThrow(delegate() {
                tree = ExecuteTreeOperations();
            });
        }
 /// <summary>
 /// Returns the <see cref="NodeDifference"/> between the original node and
 /// the new node.
 /// </summary>
 /// <param name="originalNode">The original node.</param>
 /// <param name="newNode">The new node.</param>
 /// <param name="dimensionComparator">The dimension comparator.</param>
 private static NodeDifference Diff(ProductPartitionNode originalNode,
 ProductPartitionNode newNode, IEqualityComparer<ProductDimension> dimensionComparator)
 {
     NodeDifference nodeDifference;
       if (originalNode == null && newNode == null) {
     nodeDifference = NodeDifference.NONE;
       } else if (originalNode == null) {
     nodeDifference = NodeDifference.NEW_NODE;
       } else if (newNode == null) {
     nodeDifference = NodeDifference.REMOVED_NODE;
       } else if (!dimensionComparator.Equals(originalNode.Dimension, newNode.Dimension)) {
     throw new InvalidOperationException(string.Format(
     ShoppingMessages.ProductDimensionMismatch, originalNode.Dimension, newNode.Dimension));
       } else if (originalNode.IsUnit != newNode.IsUnit) {
     nodeDifference = NodeDifference.PARTITION_TYPE_CHANGE;
       } else if (originalNode.IsExcludedUnit != newNode.IsExcludedUnit) {
     nodeDifference = NodeDifference.EXCLUDED_UNIT_CHANGE;
       } else if (!originalNode.IsExcludedUnit && originalNode.IsUnit && newNode.IsUnit) {
     // Both nodes are non-excluded units - the only possible difference
     // left is the bid.
     nodeDifference = (originalNode.CpcBid != newNode.CpcBid) ? NodeDifference.BID_CHANGE :
     NodeDifference.NONE;
       } else {
     nodeDifference = NodeDifference.NONE;
       }
       return nodeDifference;
 }
Пример #25
0
 public void TestSetBidOnSubdivisionFails()
 {
     rootNode = rootNode.AsSubdivision();
     Assert.Throws <InvalidOperationException>(delegate() { rootNode.CpcBid = 1; });
 }
        /// <summary>
        /// Adds to the operations list all operations required to mutate
        /// <paramref name="originalNode"/> to the state* of
        /// <paramref name="newNode"/>.
        /// </summary>
        /// <param name="originalNode">The original node.</param>
        /// <param name="newNode">The new node.</param>
        /// <param name="ops">The operations list to add to.</param>
        /// <param name="idGenerator">The temporary ID generator for ADD operations.</param>
        /// <returns>The set of child product dimensions that require further
        /// processing.</returns>
        private void AddMutateOperations(ProductPartitionNode originalNode,
        ProductPartitionNode newNode, List<OperationPair> ops, TemporaryIdGenerator idGenerator)
        {
            NodeDifference nodeDifference = Diff(originalNode, newNode, dimensionComparator);
              bool isProcessChildren;

              switch (nodeDifference) {
            case NodeDifference.NEW_NODE:
              ops.AddRange(CreateAddOperations(newNode, idGenerator));
              // No need to further process children. The ADD operations above will include operations
              // for all children of newNode.
              isProcessChildren = false;
              break;

            case NodeDifference.REMOVED_NODE:
              ops.Add(CreateRemoveOperation(originalNode));
              // No need to further process children. The REMOVE operation above will perform a
              // cascading delete of all children of newNode.
              isProcessChildren = false;
              break;

            case NodeDifference.PARTITION_TYPE_CHANGE:
            case NodeDifference.EXCLUDED_UNIT_CHANGE:
              ops.Add(CreateRemoveOperation(originalNode));
              ops.AddRange(CreateAddOperations(newNode, idGenerator));
              // No need to further process children. The ADD operations above will include operations
              // for all children of newNode.
              isProcessChildren = false;
              break;

            case NodeDifference.BID_CHANGE:
              // Ensure that the new node has the proper ID (this may have been lost if the node
              // was removed and then re-added).
              newNode.ProductPartitionId = originalNode.ProductPartitionId;
              ops.Add(CreateSetBidOperation(newNode));
              // Process the children of newNode. The SET operation above will only handle changes
              // made to newNode, not its children.
              isProcessChildren = true;
              break;

            case NodeDifference.NONE:
              // Ensure that the new node has the proper ID (this may have been lost if the node
              // was removed and then re-added).
              newNode.ProductPartitionId = originalNode.ProductPartitionId;
              // This node does not have changes, but its children may.
              isProcessChildren = true;
              break;

            default:
              throw new InvalidOperationException("Unrecognized difference: " + nodeDifference);
              }

              if (isProcessChildren) {
            // Try to match the children in new and original trees to identify the
            // matching dimensions.

            foreach (ProductPartitionNode newChild in newNode.Children) {
              if (originalNode.HasChild(newChild.Dimension)) {
            // this is probably an edit.
            AddMutateOperations(originalNode.GetChild(newChild.Dimension), newChild, ops,
                idGenerator);
              } else {
            // this is a new node.
            AddMutateOperations(null, newChild, ops, idGenerator);
              }
            }

            foreach (ProductPartitionNode originalChild in originalNode.Children) {
              if (newNode.HasChild(originalChild.Dimension)) {
            // this is probably an edit. We dealt with it before
            continue;
              } else {
            // this is a removed node.
            AddMutateOperations(originalChild, null, ops, idGenerator);
              }
            }
              }
        }
Пример #27
0
 public void Init()
 {
     rootNode = new ProductPartitionNode(null, null, ROOT_NODE_ID);
 }
        /// <summary>
        /// Compares two product partition nodes.
        /// </summary>
        /// <param name="leftNode">The left node.</param>
        /// <param name="rightNode">The right node.</param>
        private static void CompareTree(ProductPartitionNode leftNode,
        ProductPartitionNode rightNode)
        {
            ProductDimensionEqualityComparer comparer = new ProductDimensionEqualityComparer();
              Assert.True(comparer.Equals(leftNode.Dimension, rightNode.Dimension));

              Assert.AreEqual(leftNode.Children.Count(), rightNode.Children.Count());

              for (int i = 0; i < leftNode.Children.Count(); i++) {
            ProductPartitionNode leftChildNode = leftNode.Children.ElementAt(i);
            bool foundMatch = false;
            for (int j = 0; j < rightNode.Children.Count(); j++) {
              ProductPartitionNode rightChildNode = rightNode.Children.ElementAt(j);

              if (comparer.Equals(leftChildNode.Dimension, rightChildNode.Dimension)) {
            CompareTree(leftChildNode, rightChildNode);
            foundMatch = true;
            break;
              }
            }
            Assert.True(foundMatch, "Matching node not found.");
              }
        }