diff --git a/frontend/src/ActionEdit.js b/frontend/src/ActionEdit.js
index dea6dba94f042..98ac92a8be8cc 100644
--- a/frontend/src/ActionEdit.js
+++ b/frontend/src/ActionEdit.js
@@ -61,6 +61,7 @@ class ActionStep extends Component {
this.sendStep = this.sendStep.bind(this);
this.AutocaptureFields = this.AutocaptureFields.bind(this);
this.TypeSwitcher = this.TypeSwitcher.bind(this);
+ this.URLMatching = this.URLMatching.bind(this);
this.stop = this.stop.bind(this);
this.box = document.createElement('div');
@@ -154,7 +155,7 @@ class ActionStep extends Component {
}
this.setState({selection: this.state.selection}, () => this.sendStep(this.props.step))
}}
- /> {props.label}
+ /> {props.label} {props.extra_options}
{props.item == 'selector' ?
:
}
@@ -217,6 +218,22 @@ class ActionStep extends Component {
/>
}
+ URLMatching(step) {
+ return
@@ -224,15 +241,15 @@ class ActionStep extends Component {
×
}
{!isEditor &&
}
-
+
{this.props.isEditor && }
{step.event == '$autocapture' && }
{(step.event == '$autocapture' || step.event == '$pageview') && }
+ extra_options={}
+ label='URL' />}
}
@@ -272,7 +289,7 @@ export class ActionEdit extends Component {
if(detail.detail == 'action-exists') this.setState({saved: false, error: 'action-exists', error_id: detail.id})
}
let steps = this.state.action.steps.map((step) => {
- if(step.event == '$pageview') step.selection = ['url'];
+ if(step.event == '$pageview') step.selection = ['url', 'url_matching'];
if(step.event != '$pageview' && step.event != '$autocapture') step.selection = [];
if(!step.selection) return step;
let data = {};
diff --git a/frontend/src/ActionEvents.js b/frontend/src/ActionEvents.js
index 7fab015896302..ca0063d63b3e4 100644
--- a/frontend/src/ActionEvents.js
+++ b/frontend/src/ActionEvents.js
@@ -2,7 +2,7 @@ import React, { Component } from 'react';
import api from './Api';
import moment from 'moment';
import { Link } from 'react-router-dom';
-import { toParams, fromParams, colors } from './utils';
+import { toParams, fromParams, colors, Loading } from './utils';
import PropTypes from 'prop-types';
import { EventDetails } from './Events';
import PropertyFilters from './PropertyFilter';
@@ -14,7 +14,8 @@ export class ActionEventsTable extends Component {
this.state = {
propertyFilters: fromParams(),
- newEvents: []
+ newEvents: [],
+ loading: true
}
this.fetchEvents = this.fetchEvents.bind(this);
this.FilterLink = this.FilterLink.bind(this);
@@ -29,7 +30,7 @@ export class ActionEventsTable extends Component {
})
clearTimeout(this.poller)
api.get('api/event/actions/?' + params).then((events) => {
- this.setState({events: events.results});
+ this.setState({events: events.results, loading: false});
this.poller = setTimeout(this.pollEvents, this.pollTimeout);
})
}
@@ -61,7 +62,7 @@ export class ActionEventsTable extends Component {
}
render() {
let params = ['$current_url']
- let { propertyFilters, events } = this.state;
+ let { loading, propertyFilters, events } = this.state;
return (
this.setState({propertyFilters}, this.fetchEvents)} />
@@ -73,9 +74,8 @@ export class ActionEventsTable extends Component {
User |
Date |
Browser |
- City |
- Country |
+ {loading && }
{events && events.length == 0 && We didn't find any events matching any actions. You can either set up some actions or integrate PostHog in your app. |
}
{events && events.map((action, index) => [
index > 0
diff --git a/posthog/api/action.py b/posthog/api/action.py
index f0f1e840d10e8..f6638197290ac 100644
--- a/posthog/api/action.py
+++ b/posthog/api/action.py
@@ -16,7 +16,7 @@
class ActionStepSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ActionStep
- fields = ['id', 'event', 'tag_name', 'text', 'href', 'selector', 'url', 'name']
+ fields = ['id', 'event', 'tag_name', 'text', 'href', 'selector', 'url', 'name', 'url_matching']
class ActionSerializer(serializers.HyperlinkedModelSerializer):
diff --git a/posthog/api/event.py b/posthog/api/event.py
index 4bd91fc5a4474..8b62a79fa5782 100644
--- a/posthog/api/event.py
+++ b/posthog/api/event.py
@@ -1,8 +1,8 @@
-from posthog.models import Event, Team, Person, Element, Action, ActionStep, PersonDistinctId, ElementGroup
+from posthog.models import Event, Team, Person, Element, Action, PersonDistinctId, ElementGroup
from rest_framework import request, response, serializers, viewsets # type: ignore
from rest_framework.decorators import action # type: ignore
from django.http import HttpResponse, JsonResponse
-from django.db.models import Q, Count, QuerySet, query, Prefetch, F, Func, TextField, functions
+from django.db.models import Q, Count, QuerySet, query, F, Func, functions
from django.forms.models import model_to_dict
from typing import Any, Union, Tuple, Dict, List
import re
@@ -93,7 +93,7 @@ def actions(self, request: request.Request) -> response.Response:
actions = Action.objects.filter(
deleted=False,
team=request.user.team_set.get()
- ).prefetch_related(Prefetch('steps', queryset=ActionStep.objects.all()))
+ )
matches = []
for action in actions:
events = Event.objects.filter_by_action(action)
diff --git a/posthog/migrations/0028_actionstep_url_matching.py b/posthog/migrations/0028_actionstep_url_matching.py
new file mode 100644
index 0000000000000..5fc66d993ce21
--- /dev/null
+++ b/posthog/migrations/0028_actionstep_url_matching.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.0.3 on 2020-03-04 01:20
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('posthog', '0027_move_elements_to_group'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='actionstep',
+ name='url_matching',
+ field=models.CharField(choices=[('exact', 'exact'), ('contains', 'contains')], default='contains', max_length=400),
+ ),
+ ]
diff --git a/posthog/models.py b/posthog/models.py
index 25f5b1a3e7dee..36ba280541493 100644
--- a/posthog/models.py
+++ b/posthog/models.py
@@ -176,7 +176,9 @@ def filter_by_element(self, action_step):
def filter_by_url(self, action_step):
if not action_step.url:
return {}
- return {'properties__$current_url': action_step.url}
+ if action_step.url_matching == ActionStep.EXACT:
+ return {'properties__$current_url': action_step.url}
+ return {'properties__$current_url__icontains': action_step.url}
def filter_by_event(self, action_step):
if not action_step.event:
@@ -323,12 +325,19 @@ def __str__(self):
return self.name
class ActionStep(models.Model):
+ EXACT = 'exact'
+ CONTAINS = 'contains'
+ URL_MATCHING = [
+ (EXACT, EXACT),
+ (CONTAINS, CONTAINS),
+ ]
action: models.ForeignKey = models.ForeignKey(Action, related_name='steps', on_delete=models.CASCADE)
tag_name: models.CharField = models.CharField(max_length=400, null=True, blank=True)
text: models.CharField = models.CharField(max_length=400, null=True, blank=True)
href: models.CharField = models.CharField(max_length=400, null=True, blank=True)
selector: models.CharField = models.CharField(max_length=400, null=True, blank=True)
url: models.CharField = models.CharField(max_length=400, null=True, blank=True)
+ url_matching: models.CharField = models.CharField(max_length=400, choices=URL_MATCHING, default=CONTAINS)
name: models.CharField = models.CharField(max_length=400, null=True, blank=True)
event: models.CharField = models.CharField(max_length=400, null=True, blank=True)
diff --git a/posthog/test/test_event_model.py b/posthog/test/test_event_model.py
index 2ef5d1d1aca5e..b982193d2cec4 100644
--- a/posthog/test/test_event_model.py
+++ b/posthog/test/test_event_model.py
@@ -103,22 +103,27 @@ def test_attributes(self):
self.assertEqual(len(events), 1)
self.assertEqual(events[0], event1)
- def test_filter_events_by_url(self):
+ def test_filter_events_by_url_exact(self):
Person.objects.create(distinct_ids=['whatever'], team=self.team)
event1 = Event.objects.create(team=self.team, distinct_id='whatever')
-
event2 = Event.objects.create(team=self.team, distinct_id='whatever', properties={'$current_url': 'https://posthog.com/feedback/123'}, elements=[
Element(tag_name='div', text='some_other_text', nth_child=0, nth_of_type=0, order=1)
])
-
action1 = Action.objects.create(team=self.team)
- ActionStep.objects.create(action=action1, url='https://posthog.com/feedback/123')
+ ActionStep.objects.create(action=action1, url='https://posthog.com/feedback/123', url_matching=ActionStep.EXACT)
ActionStep.objects.create(action=action1, href='/a-url-2')
+ action2 = Action.objects.create(team=self.team)
+ ActionStep.objects.create(action=action2, url='123', url_matching=ActionStep.CONTAINS)
+
events = Event.objects.filter_by_action(action1)
self.assertEqual(events[0], event2)
self.assertEqual(len(events), 1)
+ events = Event.objects.filter_by_action(action2)
+ self.assertEqual(events[0], event2)
+ self.assertEqual(len(events), 1)
+
def test_person_with_different_distinct_id(self):
action_watch_movie = Action.objects.create(team=self.team, name='watched movie')
ActionStep.objects.create(action=action_watch_movie, tag_name='a', href='/movie')