Change Firefox Versions and Channels to single values fixes #198

This commit is contained in:
Jared Kerim 2017-08-24 17:44:42 -04:00 коммит произвёл Jared Kerim
Родитель aa4ba3d4c1
Коммит 1f74a7292a
23 изменённых файлов: 92 добавлений и 490 удалений

Просмотреть файл

@ -0,0 +1,85 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-08-24 21:42
from __future__ import unicode_literals
from django.conf import settings
import django.contrib.postgres.fields.jsonb
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('projects', '0003_auto_20170630_1924'),
('experiments', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Experiment',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('status', models.CharField(choices=[('Created', 'Created'), ('Pending', 'Pending'), ('Accepted', 'Accepted'), ('Launched', 'Launched'), ('Complete', 'Complete'), ('Rejected', 'Rejected')], default='Created', max_length=255)),
('pref_key', models.CharField(blank=True, max_length=255, null=True)),
('pref_type', models.CharField(choices=[('bool', 'bool'), ('int', 'int'), ('str', 'str')], default='bool', max_length=255)),
('firefox_version', models.CharField(max_length=255)),
('firefox_channel', models.CharField(choices=[('Nightly', 'Nightly'), ('Beta', 'Beta'), ('Release', 'Release')], default='Nightly', max_length=255)),
('name', models.CharField(max_length=255, unique=True)),
('slug', models.SlugField(max_length=255, unique=True)),
('objectives', models.TextField(default='')),
('analysis', models.TextField(default='')),
('created_date', models.DateTimeField(auto_now_add=True)),
('start_date', models.DateTimeField(blank=True, null=True)),
('end_date', models.DateTimeField(blank=True, null=True)),
('dashboard_url', models.URLField(blank=True, null=True)),
('dashboard_image_url', models.URLField(blank=True, null=True)),
('population_percent', models.DecimalField(decimal_places=4, default='0', max_digits=6)),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='experiments', to='projects.Project')),
],
options={
'verbose_name': 'Experiment',
'verbose_name_plural': 'Experiments',
},
),
migrations.CreateModel(
name='ExperimentChangeLog',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('changed_on', models.DateTimeField(auto_now_add=True)),
('old_status', models.CharField(blank=True, choices=[('Created', 'Created'), ('Pending', 'Pending'), ('Accepted', 'Accepted'), ('Launched', 'Launched'), ('Complete', 'Complete'), ('Rejected', 'Rejected')], max_length=255, null=True)),
('new_status', models.CharField(choices=[('Created', 'Created'), ('Pending', 'Pending'), ('Accepted', 'Accepted'), ('Launched', 'Launched'), ('Complete', 'Complete'), ('Rejected', 'Rejected')], max_length=255)),
('message', models.TextField()),
('changed_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('experiment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='changes', to='experiments.Experiment')),
],
options={
'verbose_name': 'Experiment Change Log',
'verbose_name_plural': 'Experiment Change Logs',
},
),
migrations.CreateModel(
name='ExperimentVariant',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('slug', models.SlugField(max_length=255)),
('is_control', models.BooleanField(default=False)),
('description', models.TextField(default='')),
('ratio', models.PositiveIntegerField(default=1)),
('value', django.contrib.postgres.fields.jsonb.JSONField(default=False)),
('experiment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='variants', to='experiments.Experiment')),
],
options={
'verbose_name': 'Experiment Variant',
'verbose_name_plural': 'Experiment Variants',
},
),
migrations.AlterUniqueTogether(
name='experimentvariant',
unique_together=set([('is_control', 'experiment'), ('slug', 'experiment')]),
),
]

Просмотреть файл

@ -1,55 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.3 on 2016-11-25 19:50
from __future__ import unicode_literals
import django.contrib.postgres.fields.jsonb
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('projects', '0002_project'),
('experiments', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Experiment',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('active', models.BooleanField(default=False)),
('name', models.CharField(max_length=255, unique=True)),
('slug', models.SlugField(max_length=255, unique=True)),
('objectives', models.TextField(default='')),
('success_criteria', models.TextField(default='')),
('start_date', models.DateTimeField(blank=True, null=True)),
('end_date', models.DateTimeField(blank=True, null=True)),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='projects.Project')),
],
options={
'verbose_name_plural': 'Experiments',
'verbose_name': 'Experiment',
},
),
migrations.CreateModel(
name='ExperimentVariant',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, unique=True)),
('slug', models.SlugField(max_length=255, unique=True)),
('is_control', models.BooleanField(default=False)),
('description', models.TextField(default='')),
('threshold', models.PositiveIntegerField(default=0)),
('value', django.contrib.postgres.fields.jsonb.JSONField(default=False)),
('experiment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='experiments.Experiment')),
],
options={
'verbose_name_plural': 'Experiment Variants',
'verbose_name': 'Experiment Variant',
},
),
]

Просмотреть файл

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-04-27 21:06
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('experiments', '0002_experiment_experimentvariant'),
]
operations = [
migrations.AddField(
model_name='experiment',
name='analysis',
field=models.TextField(default=''),
),
]

Просмотреть файл

@ -1,29 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-05-03 18:33
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('experiments', '0003_experiment_analysis'),
]
operations = [
migrations.AlterField(
model_name='experimentvariant',
name='name',
field=models.CharField(max_length=255),
),
migrations.AlterField(
model_name='experimentvariant',
name='slug',
field=models.SlugField(max_length=255),
),
migrations.AlterUniqueTogether(
name='experimentvariant',
unique_together=set([('slug', 'experiment')]),
),
]

Просмотреть файл

@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-05-05 19:52
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('experiments', '0004_auto_20170503_1833'),
]
operations = [
migrations.AlterUniqueTogether(
name='experimentvariant',
unique_together=set([('is_control', 'experiment'), ('slug', 'experiment')]),
),
]

Просмотреть файл

@ -1,22 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-05-04 15:29
from __future__ import unicode_literals
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('experiments', '0004_auto_20170503_1833'),
]
operations = [
migrations.AddField(
model_name='experiment',
name='created_date',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
]

Просмотреть файл

@ -1,16 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-05-05 20:45
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('experiments', '0005_auto_20170505_1952'),
('experiments', '0005_experiment_created_date'),
]
operations = [
]

Просмотреть файл

@ -1,26 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-05-16 18:15
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('experiments', '0006_merge_20170505_2045'),
]
operations = [
migrations.AlterField(
model_name='experimentvariant',
name='experiment',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='variants', to='experiments.Experiment'),
),
migrations.AlterField(
model_name='experimentvariant',
name='threshold',
field=models.DecimalField(decimal_places=4, max_digits=6),
),
]

Просмотреть файл

@ -1,29 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-05-16 19:28
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('experiments', '0007_auto_20170516_1815'),
]
operations = [
migrations.RemoveField(
model_name='experiment',
name='active',
),
migrations.AddField(
model_name='experiment',
name='status',
field=models.CharField(choices=[('Not Started', 'Not Started'), ('Started', 'Started'), ('Complete', 'Complete')], default='Not Started', max_length=255),
),
migrations.AlterField(
model_name='experimentvariant',
name='threshold',
field=models.DecimalField(decimal_places=4, default='0', max_digits=6),
),
]

Просмотреть файл

@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-05-18 19:40
from __future__ import unicode_literals
import django.contrib.postgres.fields.jsonb
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('experiments', '0008_auto_20170516_1928'),
]
operations = [
migrations.AddField(
model_name='experiment',
name='addon_versions',
field=django.contrib.postgres.fields.jsonb.JSONField(default=[]),
),
]

Просмотреть файл

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-06-27 18:10
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('experiments', '0009_experiment_addon_versions'),
]
operations = [
migrations.AddField(
model_name='experiment',
name='dashboard_url',
field=models.URLField(blank=True, null=True),
),
]

Просмотреть файл

@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-06-28 20:09
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('experiments', '0010_experiment_dashboard_url'),
]
operations = [
migrations.RemoveField(
model_name='experiment',
name='success_criteria',
),
]

Просмотреть файл

@ -1,25 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-06-29 19:04
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('experiments', '0011_remove_experiment_success_criteria'),
]
operations = [
migrations.AddField(
model_name='experiment',
name='dashboard_image_url',
field=models.URLField(blank=True, null=True),
),
migrations.AlterField(
model_name='experiment',
name='status',
field=models.CharField(choices=[('Not Started', 'Not Started'), ('Started', 'Started'), ('Complete', 'Complete'), ('Rejected', 'Rejected'), ('Invalid', 'Invalid')], default='Not Started', max_length=255),
),
]

Просмотреть файл

@ -1,26 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-07-11 17:45
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('experiments', '0012_auto_20170629_1904'),
]
operations = [
migrations.AddField(
model_name='experiment',
name='pref_key',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AlterField(
model_name='experiment',
name='project',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='experiments', to='projects.Project'),
),
]

Просмотреть файл

@ -1,39 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-08-15 15:44
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('experiments', '0013_auto_20170711_1745'),
]
operations = [
migrations.CreateModel(
name='ExperimentChangeLog',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('changed_on', models.DateTimeField(auto_now_add=True)),
('changed_by', models.CharField(max_length=255)),
('message', models.TextField()),
],
options={
'verbose_name': 'Experiment Change Log',
'verbose_name_plural': 'Experiment Change Logs',
},
),
migrations.AlterField(
model_name='experiment',
name='status',
field=models.CharField(choices=[('Created', 'Created'), ('Pending Review', 'Pending Review'), ('Accepted', 'Accepted'), ('Launched', 'Launched'), ('Complete', 'Complete'), ('Rejected', 'Rejected')], default='Created', max_length=255),
),
migrations.AddField(
model_name='experimentchangelog',
name='experiment',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='changes', to='experiments.Experiment'),
),
]

Просмотреть файл

@ -1,36 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-08-15 18:29
from __future__ import unicode_literals
import django.contrib.postgres.fields.jsonb
from django.db import migrations, models
import multiselectfield.db.fields
class Migration(migrations.Migration):
dependencies = [
('experiments', '0014_auto_20170815_1544'),
]
operations = [
migrations.RemoveField(
model_name='experiment',
name='addon_versions',
),
migrations.AddField(
model_name='experiment',
name='firefox_channels',
field=multiselectfield.db.fields.MultiSelectField(choices=[('Nightly', 'Nightly'), ('Beta', 'Beta'), ('Release', 'Release')], default='Nightly', max_length=20),
),
migrations.AddField(
model_name='experiment',
name='firefox_versions',
field=django.contrib.postgres.fields.jsonb.JSONField(default=[]),
),
migrations.AddField(
model_name='experiment',
name='pref_type',
field=models.CharField(choices=[('bool', 'bool'), ('int', 'int'), ('str', 'str')], default='bool', max_length=255),
),
]

Просмотреть файл

@ -1,29 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-08-15 21:25
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('experiments', '0015_auto_20170815_1829'),
]
operations = [
migrations.RemoveField(
model_name='experimentvariant',
name='threshold',
),
migrations.AddField(
model_name='experiment',
name='population_percent',
field=models.DecimalField(decimal_places=4, default='0', max_digits=6),
),
migrations.AddField(
model_name='experimentvariant',
name='ratio',
field=models.PositiveIntegerField(default=1),
),
]

Просмотреть файл

@ -1,33 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-08-18 19:18
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('experiments', '0016_auto_20170815_2125'),
]
operations = [
migrations.AddField(
model_name='experimentchangelog',
name='new_status',
field=models.CharField(choices=[('Created', 'Created'), ('Pending Review', 'Pending Review'), ('Accepted', 'Accepted'), ('Launched', 'Launched'), ('Complete', 'Complete'), ('Rejected', 'Rejected')], default='Created', max_length=255),
preserve_default=False,
),
migrations.AddField(
model_name='experimentchangelog',
name='old_status',
field=models.CharField(blank=True, choices=[('Created', 'Created'), ('Pending Review', 'Pending Review'), ('Accepted', 'Accepted'), ('Launched', 'Launched'), ('Complete', 'Complete'), ('Rejected', 'Rejected')], max_length=255, null=True),
),
migrations.AlterField(
model_name='experimentchangelog',
name='changed_by',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
]

Просмотреть файл

@ -2,7 +2,6 @@ from django.contrib.auth import get_user_model
from django.contrib.postgres.fields import JSONField
from django.core.exceptions import ValidationError
from django.db import models
from multiselectfield import MultiSelectField
class Experiment(models.Model):
@ -81,9 +80,9 @@ class Experiment(models.Model):
choices=PREF_TYPE_CHOICES,
default=PREF_TYPE_BOOL,
)
firefox_versions = JSONField(default=[])
firefox_channels = MultiSelectField(
choices=CHANNEL_CHOICES, default=CHANNEL_NIGHTLY)
firefox_version = models.CharField(max_length=255)
firefox_channel = models.CharField(
max_length=255, choices=CHANNEL_CHOICES, default=CHANNEL_NIGHTLY)
name = models.CharField(
max_length=255, unique=True, blank=False, null=False)
slug = models.SlugField(
@ -105,18 +104,6 @@ class Experiment(models.Model):
verbose_name = 'Experiment'
verbose_name_plural = 'Experiments'
def clean_firefox_versions(self):
if not (
type(self.firefox_versions) is list and
all([type(version) is str for version in self.firefox_versions])
):
raise ValidationError({
'firefox_versions': (
'firefox_versions must be a list of '
'strings, ex: ["1.0.0", "1.0.1"]'
),
})
def clean_status(self):
if not self.pk:
return
@ -133,7 +120,6 @@ class Experiment(models.Model):
old_status=old_status, new_status=new_status)})
def clean(self):
self.clean_firefox_versions()
self.clean_status()
def save(self, *args, **kwargs):

Просмотреть файл

@ -44,8 +44,8 @@ class ExperimentSerializer(serializers.ModelSerializer):
fields = (
'name',
'slug',
'firefox_versions',
'firefox_channels',
'firefox_version',
'firefox_channel',
'pref_key',
'pref_type',
'start_date',

Просмотреть файл

@ -9,10 +9,6 @@ from experimenter.experiments.tests.factories import ExperimentFactory
class TestExperimentModel(TestCase):
def test_invalid_firefox_versions_raises_validation_error(self):
with self.assertRaises(ValidationError):
ExperimentFactory.create_with_variants(firefox_versions='invalid')
def test_control_property_returns_experiment_control(self):
experiment = ExperimentFactory.create_with_variants()
control = ExperimentVariant.objects.get(

Просмотреть файл

@ -47,8 +47,8 @@ class TestExperimentSerializer(TestCase):
self.assertEqual(serialized.data, {
'name': experiment.name,
'slug': experiment.slug,
'firefox_versions': experiment.firefox_versions,
'firefox_channels': experiment.firefox_channels,
'firefox_version': experiment.firefox_version,
'firefox_channel': experiment.firefox_channel,
'pref_key': experiment.pref_key,
'pref_type': experiment.pref_type,
'start_date': JSTimestampField().to_representation(

Просмотреть файл

@ -2,7 +2,6 @@ Django==1.11
coverage==4.3.4
django-cors-headers==2.1.0
django-filter==1.0.4
django-multiselectfield==0.1.8
djangorestframework==3.6.2
dockerflow==2017.4.0
factory-boy==2.8.1