Skip to content

Commit

Permalink
Merge branch 'staging' into 'master'
Browse files Browse the repository at this point in the history
Staging -> Master

See merge request bullet-train/bullet-train-api!235
  • Loading branch information
matthewelwell committed Dec 5, 2020
2 parents 575d6f2 + f8998e9 commit 555e4fc
Show file tree
Hide file tree
Showing 14 changed files with 135 additions and 58 deletions.
7 changes: 7 additions & 0 deletions .gitlab/merge_request_templates/general.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## Overview of the Merge Request

This Merge Request works by...

## Have you written tests?

If not, explain here!
19 changes: 16 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,19 @@ The application relies on the following environment variables to run:
* `ALLOWED_ADMIN_IP_ADDRESSES`: restrict access to the django admin console to a comma separated list of IP addresses (e.g. `127.0.0.1,127.0.0.2`)
* `USER_CREATE_PERMISSIONS`: set the permissions for creating new users, using a comma separated list of djoser or rest_framework permissions. Use this to turn off public user creation for self hosting. e.g. `'djoser.permissions.CurrentUserOrAdmin'` Defaults to `'rest_framework.permissions.AllowAny'`.
* `ENABLE_EMAIL_ACTIVATION`: new user registration will go via email activation flow, default False
* `SENTRY_SDK_DSN`: If using Sentry, set the project DSN here.
* `SENTRY_TRACE_SAMPLE_RATE`: Float. If using Sentry, sets the trace sample rate. Defaults to 1.0.

## Pre commit

The application uses pre-commit configuration ( `.pre-commit-config.yaml` ) to run black formatting before commits.

To install pre-commit:

```bash
pip install pre-commit
pre-commit install
```

### Creating a secret key

Expand Down Expand Up @@ -191,9 +204,9 @@ given project. The number of seconds this is cached for is configurable using th

## Stack

- Python 2.7.14
- Django 1.11.13
- DjangoRestFramework 3.8.2
- Python 3.8
- Django 2.2.17
- DjangoRestFramework 3.12.1

## Static Files

Expand Down
5 changes: 3 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ chargebee==2.7.7
python-http-client<3.2.0 # 3.2.0 is the latest but throws an error on installation saying that it's not found
django-health-check==3.14.3
django-storages==1.10.1
django-environ==0.4.5
django-trench==0.2.3
djoser==2.0.5
influxdb-client==1.11.0
Expand All @@ -31,4 +30,6 @@ django-ses==1.0.3
django-axes==5.8.0
django-admin-sso==3.0.0
drf-yasg2==1.19.3
django-debug-toolbar==3.1.1
django-debug-toolbar==3.1.1
sentry-sdk==0.19.4
environs==9.2.0
Empty file added src/.ebignore
Empty file.
11 changes: 6 additions & 5 deletions src/analytics/influxdb_wrapper.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from collections import defaultdict

from django.conf import settings
from influxdb_client import InfluxDBClient, Point
from influxdb_client.client.write_api import SYNCHRONOUS
Expand Down Expand Up @@ -88,14 +90,13 @@ def get_event_list_for_organisation(organisation_id: int):
drop_columns='"organisation", "organisation_id", "type", "project", "project_id"',
extra="|> aggregateWindow(every: 24h, fn: sum)",
)
dataset = []
dataset = defaultdict(list)
labels = []
for result in results:
for record in result.records:
dataset.append(
{"t": record.values["_time"].isoformat(), "y": record.values["_value"]}
)
labels.append(record.values["_time"].strftime("%Y-%m-%d"))
dataset[record["resource"]].append(record["_value"])
if len(labels) != 31:
labels.append(record.values["_time"].strftime("%Y-%m-%d"))
return dataset, labels


Expand Down
13 changes: 8 additions & 5 deletions src/app/settings/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@
from importlib import reload

import dj_database_url
import environ
from environs import Env
import requests
from corsheaders.defaults import default_headers
from django.core.management.utils import get_random_secret_key

env = environ.Env()
env = Env()

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
Expand Down Expand Up @@ -118,6 +118,7 @@
# Third party integrations
"integrations.datadog",
"integrations.amplitude",
"integrations.sentry",
# Rate limiting admin endpoints
"axes",
]
Expand All @@ -127,9 +128,7 @@

SITE_ID = 1

DATABASES = {
"default": dj_database_url.parse(os.environ["DATABASE_URL"], conn_max_age=60)
}
DATABASES = {"default": dj_database_url.parse(env("DATABASE_URL"), conn_max_age=60)}

REST_FRAMEWORK = {
"DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated"],
Expand Down Expand Up @@ -403,3 +402,7 @@
"/admin/login/?next=/admin",
"/admin/",
]

# Sentry tracking
SENTRY_SDK_DSN = env("SENTRY_SDK_DSN", default=None)
SENTRY_TRACE_SAMPLE_RATE = env.float("SENTRY_TRACE_SAMPLE_RATE", default=1.0)
3 changes: 2 additions & 1 deletion src/features/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ def save(self, *args, **kwargs):

super(Feature, self).save(*args, **kwargs)

# create feature states for all environments in the project
# create / update feature states for all environments in the project
# todo: is update necessary here
environments = self.project.environments.all()
for env in environments:
FeatureState.objects.update_or_create(
Expand Down
9 changes: 9 additions & 0 deletions src/features/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ def validate(self, attrs):
return attrs


class UpdateFeatureSerializer(CreateFeatureSerializer):
""" prevent users from changing the value of default enabled after creation """

class Meta(CreateFeatureSerializer.Meta):
read_only_fields = CreateFeatureSerializer.Meta.read_only_fields + (
"default_enabled",
)


class FeatureSegmentCreateSerializer(serializers.ModelSerializer):
value = FeatureSegmentValueField(required=False)

Expand Down
33 changes: 25 additions & 8 deletions src/features/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from unittest import TestCase, mock

import pytest
from django.forms import model_to_dict
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APIClient, APITestCase
Expand Down Expand Up @@ -494,6 +495,28 @@ def test_list_features_return_tags(self):
feature = response_json["results"][0]
assert "tags" in feature

def test_put_feature_does_not_update_feature_states(self):
# Given
feature = Feature.objects.create(
name="test_feature", project=self.project, default_enabled=False
)
url = reverse(
"api-v1:projects:project-features-detail",
args=[self.project.id, feature.id],
)
data = model_to_dict(feature)
data["default_enabled"] = True

# When
response = self.client.put(
url, data=json.dumps(data), content_type="application/json"
)

# Then
assert response.status_code == status.HTTP_200_OK

assert all(fs.enabled is False for fs in feature.feature_states.all())


@pytest.mark.django_db
class FeatureSegmentViewTest(TestCase):
Expand Down Expand Up @@ -731,14 +754,8 @@ def test_priority_of_multiple_feature_segments(self):
assert feature_segment_1.priority == 0
assert feature_segment_2.priority == 1
data = [
{
"id": feature_segment_1.id,
"priority": 1,
},
{
"id": feature_segment_2.id,
"priority": 0,
},
{"id": feature_segment_1.id, "priority": 1},
{"id": feature_segment_2.id, "priority": 0},
]

# When
Expand Down
16 changes: 8 additions & 8 deletions src/features/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
FeatureStateSerializerWithIdentity,
FeatureStateValueSerializer,
FeatureWithTagsSerializer,
UpdateFeatureSerializer,
)

logger = logging.getLogger()
Expand All @@ -54,12 +55,12 @@ class FeatureViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated, FeaturePermissions]

def get_serializer_class(self):
if self.action == "list":
return FeatureWithTagsSerializer
elif self.action in ["create", "update"]:
return CreateFeatureSerializer
else:
return FeatureSerializer
return {
"list": FeatureWithTagsSerializer,
"create": CreateFeatureSerializer,
"update": UpdateFeatureSerializer,
"partial_update": UpdateFeatureSerializer,
}.get(self.action, FeatureSerializer)

def get_queryset(self):
user_projects = self.request.user.get_permitted_projects(["VIEW_PROJECT"])
Expand Down Expand Up @@ -387,8 +388,7 @@ def _get_flags_from_cache(self, filter_args, environment):

def _get_flags_response_with_identifier(self, request, identifier):
identity, _ = Identity.objects.get_or_create(
identifier=identifier,
environment=request.environment,
identifier=identifier, environment=request.environment
)

kwargs = {
Expand Down
1 change: 1 addition & 0 deletions src/integrations/sentry/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
default_app_config = "integrations.sentry.apps.SentryConfig"
20 changes: 20 additions & 0 deletions src/integrations/sentry/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import sentry_sdk
from django.apps import AppConfig
from django.conf import settings
from sentry_sdk.integrations.django import DjangoIntegration


class SentryConfig(AppConfig):
name = "integrations.sentry"

def ready(self):
if settings.SENTRY_SDK_DSN:
sentry_sdk.init(
dsn=settings.SENTRY_SDK_DSN,
integrations=[DjangoIntegration()],
traces_sample_rate=settings.SENTRY_TRACE_SAMPLE_RATE,
environment=settings.ENV,
# If you wish to associate users to errors (assuming you are using
# django.contrib.auth) you may enable sending PII data.
send_default_pii=True,
)
51 changes: 26 additions & 25 deletions src/sales_dashboard/templates/sales_dashboard/organisation.html
Original file line number Diff line number Diff line change
Expand Up @@ -94,32 +94,33 @@ <h2>Users</h2>
<script>
var ctx = document.getElementById("examChart").getContext("2d");
var myChart = new Chart(ctx, {
type: 'line',
type: 'bar',
data: {
labels: {{labels}},
datasets: [{
label: 'Api Requests',
data: {{ event_list }},
backgroundColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(255, 206, 86, 0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(153, 102, 255, 0.2)',
'rgba(255, 159, 64, 0.2)'
],
borderColor: [
'rgba(255,99,132,1)',
'rgba(54, 162, 235, 1)',
'rgba(255, 206, 86, 1)',
'rgba(75, 192, 192, 1)',
'rgba(153, 102, 255, 1)',
'rgba(255, 159, 64, 1)'
],
borderWidth: 1
}]
},
options: {}
});
datasets: [
{
label: 'Flags',
data: {{flags}},
backgroundColor: '#D6E9C6',
},
{
label: 'Identities',
data: {{identities}},
backgroundColor: '#FAEBCC',
},
{
label: 'Traits',
data: {{traits}},
backgroundColor: '#EBCCD1',
}
]
},
options: {
scales: {
xAxes: [{ stacked: true }],
yAxes: [{ stacked: true }]
}
}
});
</script>
{% endblock %}
5 changes: 4 additions & 1 deletion src/sales_dashboard/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,10 @@ def organisation_info(request, organisation_id):
template = loader.get_template("sales_dashboard/organisation.html")
context = {
"organisation": organisation,
"event_list": mark_safe(json.dumps(event_list)),
"event_list": event_list,
"traits": mark_safe(json.dumps(event_list["traits"])),
"identities": mark_safe(json.dumps(event_list["identities"])),
"flags": mark_safe(json.dumps(event_list["flags"])),
"labels": mark_safe(json.dumps(labels)),
}

Expand Down

0 comments on commit 555e4fc

Please sign in to comment.