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())
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, }
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