Пример #1
0
    def form_segment(self, node_oid):
        """
        for given drip campaign node
        get the set of applicable members for this node
        and create a segment based on it
        there are two cases:
        1. node is initial node - then the segment is the whole list
        2. node is not initial node - gather the set based on segments of
           previous nodes by applying the trigger filters
        """
        # init empty segment and stuff
        new_segment = Segment()
        new_segment.save()
        name = "%s_seg_%s" % (self.PREFIX, new_segment.id)
        node = Node.objects(id=node_oid)[0]
        list_id = DripCampaign.objects(id=node["drip_campaign_id"])[0]["list_id"]
        node.update(set__segment_oid=new_segment.id, set__updated_at=datetime.utcnow())

        # gather all users that apply for this node after triggers on previous nodes
        all_euids = set()
        if node["initial"]:
            all_euids = set(List.objects(list_id=list_id)[0]["members_euid"])
        else:
            for trg in Trigger.objects(node_to=node_oid):
                for euids, to_node_oid in self.segment_by_triggers(trg["node_from"]):
                    if to_node_oid == node_oid:
                        all_euids.update(set(euids))

        # # intersect euids with current state of the list
        # # it might be the case that some people are removed from the list since previous email
        self.fetch_members_for_list(list_id)
        all_euids = all_euids & set(List.objects(list_id=list_id)[0]["members_euid"])

        all_euids = list(all_euids)

        # apply the user list to segment n stuff
        # if user list is empty, save only meta info and don't actually work with mailchimp
        if all_euids:
            segment_id = self.mw.create_segment(list_id, name)
            self.mw.update_segment_members(list_id, segment_id, all_euids)
        else:
            segment_id = None
        new_segment.update(set__segment_id=segment_id, set__name=name, members_euid=all_euids,
                           set__updated_at=datetime.utcnow())
Пример #2
0
    def load_entire_campaign(self, drip_campaign_id):
        """
        helps frontend with loading entire campaigns
        takes drip campaign id and loads all of its components
        in a drip superstructure as frontend wants
        """
        # load campaign
        drip_campaign = DripCampaign.objects(id=drip_campaign_id)[0]
        drip_campaign_frontend = {
            "id": drip_campaign["id"],
            "name": drip_campaign["name"],
            "userListId": drip_campaign["list_id"],
        }
        # load blocks
        blocks = Block.objects(drip_campaign_id=drip_campaign_id)
        blocks_frontend = [
            {
                "id": block["id"],
                "datetime": block["start_time"],
                "nodeIds": block["nodes_id"],
            }
            for block in blocks
        ]
        # load nodes
        nodes_frontend = []
        for node in Node.objects(drip_campaign_id=drip_campaign_id):
            def get_trigger_action_id(trigger):
                if trigger.opened:
                    return self.get_frontend_action_id("open")
                if trigger.any_click:
                    return self.get_frontend_action_id("any click")
                if trigger.default:
                    return self.get_frontend_action_id("default")
                return self.get_frontend_action_id(trigger.clicked)
            triggers = [
                {
                    "id": trigger["id"],
                    "actionId":  get_trigger_action_id(trigger),
                    "nodeId": trigger["node_to"],
                }
                for trigger in Trigger.objects(node_from=node["id"])
            ]
            nodes_frontend.append({
                "id": node["id"],
                "name": node["title"],
                "description": node["description"],
                "templateId": node["content"]["template_id"],
                "triggers": triggers,
            })
        # update and load lists and templates
        lists = self.update_lists()
        lists_frontend = [
            {
                "id": lst["id"],
                "name": lst["name"],
            }
            for lst in lists
        ]
        templates = self.update_templates()
        templates_frontend = [
            {
                "id": tmplt["id"],
                "name": tmplt["name"],
            }
            for tmplt in templates
        ]

        # create actions for frontend
        # set default actions that apply to all templates
        actions = {
            self.get_frontend_action_id(action_type): {
                "id": self.get_frontend_action_id(action_type),
                "name": self.get_frontend_action_name(action_type),
                "templates": [],
            }
            for action_type in self.DEFAULT_ACTIONS
        }
        # iterate over all tempaltes and update actions
        for tmplt in templates:
            # first, add the template to all default actions
            for action_type in self.DEFAULT_ACTIONS:
                action_frontend_id = self.get_frontend_action_id(action_type)
                actions[action_frontend_id]["templates"].append(tmplt["id"])
            # second, add template to all its link click actions
            for link in self.get_links(tmplt["template_id"]):
                action_frontend_id = self.get_frontend_action_id(link)
                # if this link is new, add a new action
                if action_frontend_id not in actions:
                    actions[action_frontend_id] = {
                        "id": action_frontend_id,
                        "name": self.get_frontend_action_name(link),
                        "templates": [],
                    }
                # add the template to this link's click action
                actions[action_frontend_id]["templates"].append(tmplt["id"])
        # ditch the mapping
        actions_frontend = actions.values()

        # form the resulting frontend superstructure
        return {
            "campaign": drip_campaign_frontend,
            "userLists": lists_frontend,
            "templates": templates_frontend,
            "actions": actions_frontend,
            "blocks": blocks_frontend,
            "nodes": nodes_frontend,
        }
Пример #3
0
    def segment_by_triggers(self, node_oid):
        """
        segments users of given node by triggers they have set
        deals with trigger priorities correctly
        each euid will be assigned to 0 or 1 succeeding node

        we assume triggers are not broken (e.g., a node has triggers for both specific links
        and any_click at the same time)

        returns a list of (euids, node_oid) pairs that describe that
        the set of euids are assigned to node node_oid after segmentation by triggers
        (there might be multiple sets of euids assigned to the same node)
        """
        # fetch member activity for all members of given node
        node = Node.objects(id=node_oid)[0]
        all_euids = Segment.objects(id=node.segment_oid)[0]["members_euid"]
        member_activity = self.mw.get_member_activity(node["campaign_id"], all_euids)

        # find which actions we are actually interested in here according to triggers
        check_opened = False
        check_clicked = set()
        for trg in Trigger.objects(node_from=node.id):
            if trg["opened"]:
                check_opened = True
            elif trg["clicked"]:
                check_clicked.add(trg["clicked"])
            elif trg["any_click"]:
                # if any click, add all links to interesting actions
                for link in trg["any_click"]:
                    check_clicked.add(link)

        # check if specific action is interesting to us
        def is_interesting(action):
            if action["action"] == "open":
                return check_opened
            if action["action"] == "click":
                return action["url"] in check_clicked
            return False

        # go through all member activities
        # and decide which segment it belongs to according to priorities
        # priorities are roughly like this: open < click < latest click
        # where click is a click on a link with a trigger assigned to it
        # everything not raising any of defined triggers is assigned to default segment
        # EXCEPT when there is an open trigger, and user has opened and clicked a non-trigger link
        # that's an open, not default
        segments = defaultdict(list)
        for ma in member_activity:
            interesting_actions = [action for action in ma["activity"] if is_interesting(action)]
            if not interesting_actions:
                segments["default"].append(ma["euid"])
            else:
                last_open = None
                last_click = None
                for action in sorted(interesting_actions, key=lambda a: a["timestamp"]):
                    if action["action"] == "open":
                        last_open = action
                    elif action["action"] == "click":
                        last_click = action
                if last_click is None and last_open is None:
                    segments["default"].append(ma["euid"])
                elif last_click is None and last_open is not None:
                    segments["opened"].append(ma["euid"])
                elif last_click is not None:
                    segments[last_click["url"]].append(ma["euid"])
                    segments["any_click"].append(ma["euid"])

        # go through defined triggers again and assign sets of users
        split = []
        for trg in Trigger.objects(node_from=node.id):
            if trg["opened"]:
                split.append((segments["opened"], trg.node_to))
            elif trg["clicked"]:
                split.append((segments[trg["clicked"]], trg.node_to))
            elif trg["default"]:
                split.append((segments["default"], trg.node_to))
            elif trg["any_click"]:
                split.append((segments["any_click"], trg.node_to))

        return split